ssr-0.4.2/000077500000000000000000000000001236416011200123205ustar00rootroot00000000000000ssr-0.4.2/.gitignore000066400000000000000000000000271236416011200143070ustar00rootroot00000000000000ssr_scene_autosave.asd ssr-0.4.2/AUTHORS000066400000000000000000000004431236416011200133710ustar00rootroot00000000000000Written by: Matthias Geier, Jens Ahrens Scientific supervision: Sascha Spors Contributions by: Peter Bartz, Florian Hinterleitner, Torben Hohn, Lukas Kaser, André Möhl, Till Rettberg, Fiete Winter GUI design: Katharina Bredies, Jonas Loh, Jens Ahrens Logo design: Fabian Hemmert ssr-0.4.2/COPYING000066400000000000000000001045131236416011200133570ustar00rootroot00000000000000 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 . ssr-0.4.2/INSTALL000066400000000000000000000363321236416011200133600ustar00rootroot00000000000000Installation Instructions ************************* Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, Inc. Copying and distribution of this file, with or without modification, are permitted in any medium without royalty provided the copyright notice and this notice are preserved. This file is offered as-is, without warranty of any kind. Basic Installation ================== Briefly, the shell commands `./configure; make; make install' should configure, build, and install this package. The following more-detailed instructions are generic; see the `README' file for instructions specific to this package. Some packages provide this `INSTALL' file but do not implement all of the features documented below. The lack of an optional feature in a given package is not necessarily a bug. More recommendations for GNU packages can be found in *note Makefile Conventions: (standards)Makefile Conventions. The `configure' shell script attempts to guess correct values for various system-dependent variables used during compilation. It uses those values to create a `Makefile' in each directory of the package. It may also create one or more `.h' files containing system-dependent definitions. Finally, it creates a shell script `config.status' that you can run in the future to recreate the current configuration, and a file `config.log' containing compiler output (useful mainly for debugging `configure'). It can also use an optional file (typically called `config.cache' and enabled with `--cache-file=config.cache' or simply `-C') that saves the results of its tests to speed up reconfiguring. Caching is disabled by default to prevent problems with accidental use of stale cache files. If you need to do unusual things to compile the package, please try to figure out how `configure' could check whether to do them, and mail diffs or instructions to the address given in the `README' so they can be considered for the next release. If you are using the cache, and at some point `config.cache' contains results you don't want to keep, you may remove or edit it. The file `configure.ac' (or `configure.in') is used to create `configure' by a program called `autoconf'. You need `configure.ac' if you want to change it or regenerate `configure' using a newer version of `autoconf'. The simplest way to compile this package is: 1. `cd' to the directory containing the package's source code and type `./configure' to configure the package for your system. Running `configure' might take a while. While running, it prints some messages telling which features it is checking for. 2. Type `make' to compile the package. 3. Optionally, type `make check' to run any self-tests that come with the package, generally using the just-built uninstalled binaries. 4. Type `make install' to install the programs and any data files and documentation. When installing into a prefix owned by root, it is recommended that the package be configured and built as a regular user, and only the `make install' phase executed with root privileges. 5. Optionally, type `make installcheck' to repeat any self-tests, but this time using the binaries in their final installed location. This target does not install anything. Running this target as a regular user, particularly if the prior `make install' required root privileges, verifies that the installation completed correctly. 6. You can remove the program binaries and object files from the source code directory by typing `make clean'. To also remove the files that `configure' created (so you can compile the package for a different kind of computer), type `make distclean'. There is also a `make maintainer-clean' target, but that is intended mainly for the package's developers. If you use it, you may have to get all sorts of other programs in order to regenerate files that came with the distribution. 7. Often, you can also type `make uninstall' to remove the installed files again. In practice, not all packages have tested that uninstallation works correctly, even though it is required by the GNU Coding Standards. 8. Some packages, particularly those that use Automake, provide `make distcheck', which can by used by developers to test that all other targets like `make install' and `make uninstall' work correctly. This target is generally not run by end users. Compilers and Options ===================== Some systems require unusual options for compilation or linking that the `configure' script does not know about. Run `./configure --help' for details on some of the pertinent environment variables. You can give `configure' initial values for configuration parameters by setting variables in the command line or in the environment. Here is an example: ./configure CC=c99 CFLAGS=-g LIBS=-lposix *Note Defining Variables::, for more details. Compiling For Multiple Architectures ==================================== You can compile the package for more than one kind of computer at the same time, by placing the object files for each architecture in their own directory. To do this, you can use GNU `make'. `cd' to the directory where you want the object files and executables to go and run the `configure' script. `configure' automatically checks for the source code in the directory that `configure' is in and in `..'. This is known as a "VPATH" build. With a non-GNU `make', it is safer to compile the package for one architecture at a time in the source code directory. After you have installed the package for one architecture, use `make distclean' before reconfiguring for another architecture. On MacOS X 10.5 and later systems, you can create libraries and executables that work on multiple system types--known as "fat" or "universal" binaries--by specifying multiple `-arch' options to the compiler but only a single `-arch' option to the preprocessor. Like this: ./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ CPP="gcc -E" CXXCPP="g++ -E" This is not guaranteed to produce working output in all cases, you may have to build one architecture at a time and combine the results using the `lipo' tool if you have problems. Installation Names ================== By default, `make install' installs the package's commands under `/usr/local/bin', include files under `/usr/local/include', etc. You can specify an installation prefix other than `/usr/local' by giving `configure' the option `--prefix=PREFIX', where PREFIX must be an absolute file name. You can specify separate installation prefixes for architecture-specific files and architecture-independent files. If you pass the option `--exec-prefix=PREFIX' to `configure', the package uses PREFIX as the prefix for installing programs and libraries. Documentation and other data files still use the regular prefix. In addition, if you use an unusual directory layout you can give options like `--bindir=DIR' to specify different values for particular kinds of files. Run `configure --help' for a list of the directories you can set and what kinds of files go in them. In general, the default for these options is expressed in terms of `${prefix}', so that specifying just `--prefix' will affect all of the other directory specifications that were not explicitly provided. The most portable way to affect installation locations is to pass the correct locations to `configure'; however, many packages provide one or both of the following shortcuts of passing variable assignments to the `make install' command line to change installation locations without having to reconfigure or recompile. The first method involves providing an override variable for each affected directory. For example, `make install prefix=/alternate/directory' will choose an alternate location for all directory configuration variables that were expressed in terms of `${prefix}'. Any directories that were specified during `configure', but not in terms of `${prefix}', must each be overridden at install time for the entire installation to be relocated. The approach of makefile variable overrides for each directory variable is required by the GNU Coding Standards, and ideally causes no recompilation. However, some platforms have known limitations with the semantics of shared libraries that end up requiring recompilation when using this method, particularly noticeable in packages that use GNU Libtool. The second method involves providing the `DESTDIR' variable. For example, `make install DESTDIR=/alternate/directory' will prepend `/alternate/directory' before all installation names. The approach of `DESTDIR' overrides is not required by the GNU Coding Standards, and does not work on platforms that have drive letters. On the other hand, it does better at avoiding recompilation issues, and works well even when some directory options were not specified in terms of `${prefix}' at `configure' time. Optional Features ================= If the package supports it, you can cause programs to be installed with an extra prefix or suffix on their names by giving `configure' the option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. Some packages pay attention to `--enable-FEATURE' options to `configure', where FEATURE indicates an optional part of the package. They may also pay attention to `--with-PACKAGE' options, where PACKAGE is something like `gnu-as' or `x' (for the X Window System). The `README' should mention any `--enable-' and `--with-' options that the package recognizes. For packages that use the X Window System, `configure' can usually find the X include and library files automatically, but if it doesn't, you can use the `configure' options `--x-includes=DIR' and `--x-libraries=DIR' to specify their locations. Some packages offer the ability to configure how verbose the execution of `make' will be. For these packages, running `./configure --enable-silent-rules' sets the default to minimal output, which can be overridden with `make V=1'; while running `./configure --disable-silent-rules' sets the default to verbose, which can be overridden with `make V=0'. Particular systems ================== On HP-UX, the default C compiler is not ANSI C compatible. If GNU CC is not installed, it is recommended to use the following options in order to use an ANSI C compiler: ./configure CC="cc -Ae -D_XOPEN_SOURCE=500" and if that doesn't work, install pre-built binaries of GCC for HP-UX. On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot parse its `' header file. The option `-nodtk' can be used as a workaround. If GNU CC is not installed, it is therefore recommended to try ./configure CC="cc" and if that doesn't work, try ./configure CC="cc -nodtk" On Solaris, don't put `/usr/ucb' early in your `PATH'. This directory contains several dysfunctional programs; working variants of these programs are available in `/usr/bin'. So, if you need `/usr/ucb' in your `PATH', put it _after_ `/usr/bin'. On Haiku, software installed for all users goes in `/boot/common', not `/usr/local'. It is recommended to use the following options: ./configure --prefix=/boot/common Specifying the System Type ========================== There may be some features `configure' cannot figure out automatically, but needs to determine by the type of machine the package will run on. Usually, assuming the package is built to be run on the _same_ architectures, `configure' can figure that out, but if it prints a message saying it cannot guess the machine type, give it the `--build=TYPE' option. TYPE can either be a short name for the system type, such as `sun4', or a canonical name which has the form: CPU-COMPANY-SYSTEM where SYSTEM can have one of these forms: OS KERNEL-OS See the file `config.sub' for the possible values of each field. If `config.sub' isn't included in this package, then this package doesn't need to know the machine type. If you are _building_ compiler tools for cross-compiling, you should use the option `--target=TYPE' to select the type of system they will produce code for. If you want to _use_ a cross compiler, that generates code for a platform different from the build platform, you should specify the "host" platform (i.e., that on which the generated programs will eventually be run) with `--host=TYPE'. Sharing Defaults ================ If you want to set default values for `configure' scripts to share, you can create a site shell script called `config.site' that gives default values for variables like `CC', `cache_file', and `prefix'. `configure' looks for `PREFIX/share/config.site' if it exists, then `PREFIX/etc/config.site' if it exists. Or, you can set the `CONFIG_SITE' environment variable to the location of the site script. A warning: not all `configure' scripts look for a site script. Defining Variables ================== Variables not defined in a site shell script can be set in the environment passed to `configure'. However, some packages may run configure again during the build, and the customized values of these variables may be lost. In order to avoid this problem, you should set them in the `configure' command line, using `VAR=value'. For example: ./configure CC=/usr/local2/bin/gcc causes the specified `gcc' to be used as the C compiler (unless it is overridden in the site shell script). Unfortunately, this technique does not work for `CONFIG_SHELL' due to an Autoconf bug. Until the bug is fixed you can use this workaround: CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash `configure' Invocation ====================== `configure' recognizes the following options to control how it operates. `--help' `-h' Print a summary of all of the options to `configure', and exit. `--help=short' `--help=recursive' Print a summary of the options unique to this package's `configure', and exit. The `short' variant lists options used only in the top level, while the `recursive' variant lists options also present in any nested packages. `--version' `-V' Print the version of Autoconf used to generate the `configure' script, and exit. `--cache-file=FILE' Enable the cache: use and save the results of the tests in FILE, traditionally `config.cache'. FILE defaults to `/dev/null' to disable caching. `--config-cache' `-C' Alias for `--cache-file=config.cache'. `--quiet' `--silent' `-q' Do not print messages saying which checks are being made. To suppress all normal output, redirect it to `/dev/null' (any error messages will still be shown). `--srcdir=DIR' Look for the package's source code in directory DIR. Usually `configure' can determine that directory automatically. `--prefix=DIR' Use DIR as the installation prefix. *note Installation Names:: for more details, including other options available for fine-tuning the installation locations. `--no-create' `-n' Run the configure checks, but stop before creating any output files. `configure' also accepts some other, not widely useful, options. Run `configure --help' for more details. ssr-0.4.2/Makefile.am000066400000000000000000000064251236416011200143630ustar00rootroot00000000000000## This file will be processed by automake (which is called by autogen.sh) to ## generate Makefile.in, which in turn will be processed by configure to ## generate Makefile. ## comments starting with a single # are copied to Makefile.in (and afterwards ## to Makefile), comments with ## are dropped. SUBDIRS = src man data ## only entered for "make dist", "make distclean" and "make maintainer-clean": ##DIST_SUBDIRS = $(SUBDIRS) another_dir # user manual from SVN (not distributed) SSR_MANUAL_ORIG = $(srcdir)/doc/manual/SoundScapeRenderer.pdf # distributed version of the user manual SSR_MANUAL_DIST = doc/SoundScapeRenderer-@PACKAGE_VERSION@-manual.pdf dist_doc_DATA = AUTHORS COPYING $(SSR_MANUAL_DIST) NEWS # files which should be distributed but not installed dist_noinst_DATA = \ INSTALL README \ mex/README.md \ mex/COPYING \ mex/DESCRIPTION \ mex/Makefile \ mex/ssr_mex.h \ mex/ssr_aap.cpp \ mex/ssr_binaural.cpp \ mex/ssr_nfc_hoa.cpp \ mex/ssr_vbap.cpp \ mex/ssr_wfs.cpp \ mex/ssr_helper.m \ mex/test_ssr.m \ flext/Makefile \ flext/package.txt \ flext/ssr_flext.h \ flext/ssr_aap.cpp \ flext/ssr_binaural.cpp \ flext/ssr_nfc_hoa.cpp \ flext/ssr_vbap.cpp \ flext/ssr_wfs.cpp \ flext/ssr_messages.pd \ flext/ssr_aap~-help.pd \ flext/ssr_binaural~-help.pd \ flext/ssr_nfc_hoa~-help.pd \ flext/ssr_vbap~-help.pd \ flext/ssr_wfs~-help.pd \ flext/virtual_vbap.pd \ flext/virtual_wfs.pd \ flext/8channels.asd \ flext/circle.asd \ flext/hrirs_fabian.wav \ flext/wfs_prefilter_120_1500_44100.wav dist_noinst_SCRIPTS = flext/startpd.sh # these files are deleted on "make maintainer-clean": MAINTAINERCLEANFILES = aclocal.m4 stamp-h.in \ config.log config.cache config.status ## TODO: find a way to actually use this location (see autogen.sh) ## # where we keep local rules for automake ## ACLOCAL_M4 = autotools/aclocal.m4 # checks for m4 macros in the directory "autotools/m4/" ## see AC_CONFIG_MACRO_DIR in configure.ac ACLOCAL_AMFLAGS = -I autotools/m4 # just a quick reminder: # # $@ stands for the target, # $* the part wich is identical on the target and on the prerequisite, # $< the prerequisite which caused the rule to apply, # $^ all prerequisites (duplicates removed), # $+ all prerequisites (including duplicates), # $? gives the names of the prerequisites which are newer than the target # $(@D) only the directory part, $(@F) only the file part # copy and rename (include version) the user manual on "make dist". # $(SSR_MANUAL_DIST) has to be in dist_doc_DATA! # If the PDF file doesn't exist, an empty dummy file is created! $(SSR_MANUAL_DIST): $(SSR_MANUAL_ORIG) @test -f "$<" && cp "$<" "$@" \ || $(MKDIR_P) "$$(dirname "$@")" && touch "$@" # show warning message (if missing), the file must be created by the maintainer: $(SSR_MANUAL_ORIG): @test -f "$@" || echo "Warning: $@ is not generated by the Makefile!" doc: $(MAKE) -C src $@ .PHONY: doc if ENABLE_APP_BUNDLE dmg: install $(MAKE) -C data/MacOSX $@ else dmg: @echo "You have to run './configure --enable-app-bundle' first!"; false endif .PHONY: dmg .NOTPARALLEL: install # remove pkgdatadir and docdir (if empty) uninstall-hook: -rmdir $(DESTDIR)$(pkgdatadir) -rmdir $(DESTDIR)$(docdir) ## Settings for Vim (http://www.vim.org/), please do not remove: ## vim:textwidth=80:comments+=bO\:## ssr-0.4.2/NEWS000066400000000000000000000056171236416011200130300ustar00rootroot00000000000000User-visible changes in the SoundScape Renderer. Recent changes on top. 0.4.2 (24 July 2014) - the default number of threads is now obtained automatically (but can still be overwritten) - configure options to en-/disable certain renderers - certain renderers are now available as Puredata externals (via flext), still experimental (a.k.a. buggy) - minor GUI changes (no more "pie slices" on sources, larger fonts) - man pages (generated with help2man) - several bugfixes, improvements to the MEX files 0.4.1 (28 January 2014) - all renderers (except BRS and generic) are now available as MEX files - SSR can now be compiled with clang++ and libc++ on OSX 10.9 (Mavericks) - several bugfixes 0.4.0 (20 December 2013) - the signal processing core of the SSR is now a separate thing and part of the "Audio Processing Framework" (APF, http://audioprocessingframework.github.io) - multi-threading support and other performance improvements - brand new Near-Field-Corrected Higher-Order-Ambisonics (NFC-HOA) renderer (which is still experimental and might have quite a few bugs) - WAV, FLAC and OGG playback is now possible via ecasound & libsndfile - binaural and BRS renderers now support IR files with arbitrary number of channels (as long as they are divisible by 2) - new option "--name" to set the JACK client name - compatibility with the "Clang" compiler - experimental draft for Matlab MEX files (using the NFC-HOA renderer) - VRPN tracker support (thanks to Rouven von der Burg, Daniel Schwingenheuer and Johannes Arend!) - many bugfixes and improvements (but most probably also new bugs) 0.3.4 a.k.a. "Pianoforte" (13 November 2012) - several bugfixes and improvements - fixed Polhemus tracker support on MacOSX - introduced "reference offset" for tracking people within loudspeaker arrays 0.3.3 a.k.a. "Harpsichord" (1 February 2012) - several bugfixes, including a nasty bug in the delay line - support for the Razor AHRS headtracker: http://dev.qu.tu-berlin.de/projects/sf-razor-9dof-ahrs/wiki - support for MacOSX 10.7 (Lion) 0.3.2 a.k.a. "Spinet" (6 September 2011) - several bugfixes, mainly in the build system - source code for "Android SSR Remote" is now included 0.3.1 a.k.a. "Harmonium" (3 May 2011) - several bugfixes - the SSR can now be compiled for MacOSX, an App-Bundle is also provided - the "Binaural Playback Renderer" is now available - improvements in new renderer architecture, still to be activated with ./configure --enable-newrenderer - improvements in the build system - current audio scene can now be saved (have a look in the "file" menu) - improvements to the "source properties" context menu - Pure Data (Pd) patches for remote-controlling the SSR are now included - Android SSR remote control is provided for download as .apk file 0.3.0 a.k.a. "Keytar" (23 May 2010) - first public release (under the GPLv3+ license) ssr-0.4.2/README000066400000000000000000000012541236416011200132020ustar00rootroot00000000000000This is the source distribution of SoundScape Renderer (SSR) licensed under the GPLv3+. Please consult the file COPYING for more information about this license. The user manual in the doc/ directory contains relevant informations about the SSR, including installation instructions. Additional (very detailed) installation instructions can be found in the file INSTALL. For questions, bug reports and feature requests: Contact: ssr@spatialaudio.net Website: http://spatialaudio.net/ssr/ Copyright (c) 2012-2014 Institut für Nachrichtentechnik, Universität Rostock Copyright (c) 2006-2012 Quality & Usability Lab Deutsche Telekom Laboratories, TU Berlin ssr-0.4.2/apf/000077500000000000000000000000001236416011200130665ustar00rootroot00000000000000ssr-0.4.2/apf/AUTHORS000066400000000000000000000001541236416011200141360ustar00rootroot00000000000000Author: Matthias Geier Contributions by: Sascha Spors Jens Ahrens Torben Hohn Till Rettberg Moritz Heppner ssr-0.4.2/apf/COPYING000066400000000000000000001045131236416011200141250ustar00rootroot00000000000000 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 . ssr-0.4.2/apf/NEWS000066400000000000000000000020721236416011200135660ustar00rootroot00000000000000User-visible changes in the Audio Processing Framework. Recent changes on top. 0.2.1 (16 July 2014) - new: default_number_of_threads() in thread policies - new: default_thread_policy.h for selecting thread policy based on OS - new: some helper functions for implementing MEX files - extend delay_is_valid() to provide "corrected" delay value - several bug-fixes, more unit tests, improved examples 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 ssr-0.4.2/apf/README000066400000000000000000000006451236416011200137530ustar00rootroot00000000000000This 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-2014 Institut für Nachrichtentechnik, Universität Rostock Copyright (c) 2006-2012 Quality & Usability Lab Deutsche Telekom Laboratories, TU Berlin ssr-0.4.2/apf/apf/000077500000000000000000000000001236416011200136345ustar00rootroot00000000000000ssr-0.4.2/apf/apf/biquad.h000066400000000000000000000246551236416011200152660ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 ssr-0.4.2/apf/apf/blockdelayline.h000066400000000000000000000267031236416011200167760ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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); /// Check if a given delay is valid. /// @param delay Desired delay /// @param[out] corrected If valid, the same as @p delay, if not, the /// maximum possible delay. /// @return @b true if @p delay is valid. bool delay_is_valid(size_type delay, size_type& corrected) const { bool valid = (delay <= _max_delay); corrected = valid ? delay : _max_delay; return valid; } /// Return @b true if @p delay is valid. @see delay_is_valid() bool delay_is_valid(size_type delay) const { size_type dummy; // dummy variable as default parameter return delay_is_valid(delay, dummy); } /// 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 typename _base::size_type; using typename _base::circulator; using difference_type = typename _base::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 /// Check if a given delay is valid. /// @param delay Desired delay /// @param[out] corrected If valid, the same as @p delay, if too low/high, /// the minimum/maximum possible delay, respectively. /// @return @b true if @p delay is valid. /// @see BlockDelayLine::delay_is_valid() bool delay_is_valid(difference_type delay, difference_type& corrected) const { if (delay < -_initial_delay) { corrected = -_initial_delay; return false; } size_type tmp; bool valid = _base::delay_is_valid(delay + _initial_delay, tmp); corrected = tmp - _initial_delay; return valid; } /// Return @b true if @p delay is valid. @see delay_is_valid() bool delay_is_valid(difference_type delay) const { difference_type dummy; // dummy variable as default parameter return delay_is_valid(delay, dummy); } /// @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'\:'=' ssr-0.4.2/apf/apf/combine_channels.h000066400000000000000000000360651236416011200173060ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 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 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'\:'=' ssr-0.4.2/apf/apf/commandqueue.h000066400000000000000000000170451236416011200164770ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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'\:'=' ssr-0.4.2/apf/apf/container.h000066400000000000000000000571571236416011200160060ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 : private std::vector { private: using _base = typename std::vector; public: using typename _base::value_type; using typename _base::allocator_type; using typename _base::reference; using typename _base::const_reference; using typename _base::pointer; using typename _base::const_pointer; using typename _base::iterator; using typename _base::const_iterator; using typename _base::reverse_iterator; using typename _base::const_reverse_iterator; using typename _base::difference_type; using 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)...) {} // TODO: constructor from size and allocator is missing in C++11 (but not C++14) #if 0 // TODO: re-activate with C++14: template> fixed_vector(Size n, const Allocator& a = Allocator()) : _base(n, a) {} #else explicit fixed_vector(size_type n) : _base(n) {} #endif 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!"); } } using _base::front; using _base::back; using _base::begin; using _base::end; using _base::rbegin; using _base::rend; using _base::cbegin; using _base::cend; using _base::crbegin; using _base::crend; using _base::size; using _base::max_size; using _base::capacity; using _base::empty; using _base::operator[]; using _base::at; using _base::data; using _base::get_allocator; // using _base::shrink_to_fit; // This may reallocate! }; /** 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 : private std::list { private: using _base = typename std::list; public: using typename _base::value_type; using typename _base::allocator_type; using typename _base::reference; using typename _base::const_reference; using typename _base::pointer; using typename _base::const_pointer; using typename _base::iterator; using typename _base::const_iterator; using typename _base::reverse_iterator; using typename _base::const_reverse_iterator; using typename _base::difference_type; using typename _base::size_type; 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); } using _base::begin; using _base::end; using _base::rbegin; using _base::rend; using _base::cbegin; using _base::cend; using _base::crbegin; using _base::crend; using _base::empty; using _base::size; using _base::max_size; using _base::front; using _base::back; using _base::get_allocator; using _base::reverse; using _base::sort; }; /** 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 typename _base::pointer; using 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'\:'=' ssr-0.4.2/apf/apf/convolver.h000066400000000000000000000571361236416011200160360ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 // for std::bind() #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))); // This also works for the case chunk==0: if (math::has_only_zeros(first, first + chunk)) { 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 (must be actually filled with zeros!) 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) { // do nothing. There is no contribution if either is zero. } else { #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'\:'=' ssr-0.4.2/apf/apf/default_thread_policy.h000066400000000000000000000046371236416011200203510ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 /// This header includes the default policy depending on the OS. #ifndef APF_DEFAULT_THREAD_POLICY_H #define APF_DEFAULT_THREAD_POLICY_H // For all available preprocessor macros see: // https://sourceforge.net/p/predef/wiki/OperatingSystems/ #ifndef APF_MIMOPROCESSOR_THREAD_POLICY #ifdef _WIN32 #include "apf/dummy_thread_policy.h" #else #include "apf/posix_thread_policy.h" #endif #endif #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'\:'=' ssr-0.4.2/apf/apf/denormalprevention.h000066400000000000000000000147141236416011200177270ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 ssr-0.4.2/apf/apf/dummy_thread_policy.h000066400000000000000000000105221236416011200200460ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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; unsigned default_number_of_threads() { return 1; } 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'\:'=' ssr-0.4.2/apf/apf/fftwtools.h000066400000000000000000000125511236416011200160400ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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'\:'=' ssr-0.4.2/apf/apf/iterator.h000066400000000000000000001357221236416011200156500ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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'\:'=' ssr-0.4.2/apf/apf/jack_policy.h000066400000000000000000000173471236416011200163100ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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'\:'=' ssr-0.4.2/apf/apf/jackclient.h000066400000000000000000000440061236416011200161200ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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'\:'=' ssr-0.4.2/apf/apf/lockfreefifo.h000066400000000000000000000122571236416011200164520ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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'\:'=' ssr-0.4.2/apf/apf/math.h000066400000000000000000000231051236416011200147370ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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() #include // for std::max() 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'\:'=' ssr-0.4.2/apf/apf/mextools.h000066400000000000000000000200271236416011200156600ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 working with the MEX API for Matlab/Octave. #ifndef APF_MEXTOOLS_H #define APF_MEXTOOLS_H #include #include #include #include // for std::floor() #include "apf/stringtools.h" // for A2S() #define APF_MEX_ERROR_NO_OUTPUT_SUPPORTED(name) \ (void)plhs; \ if (nlhs > 0) { \ std::string msg("No output parameters are supported for " \ + std::string(name) + "!"); \ mexErrMsgTxt(msg.c_str()); } #define APF_MEX_ERROR_EXACTLY_ONE_OUTPUT(name) \ (void)plhs; \ if (nlhs != 1) { \ std::string msg("Exactly one output parameter is supported for " \ + std::string(name) + "!"); \ mexErrMsgTxt(msg.c_str()); } #define APF_MEX_ERROR_ONE_OPTIONAL_OUTPUT(name) \ (void)plhs; \ if (nlhs > 1) { \ std::string msg("No more than one output parameter is supported for " \ + std::string(name) + "!"); \ mexErrMsgTxt(msg.c_str()); } #define APF_MEX_ERROR_NO_FURTHER_INPUTS(name) \ (void)prhs; \ if (nrhs > 0) { \ std::string msg("No further input parameters are supported for " \ + std::string(name) + "!"); \ mexErrMsgTxt(msg.c_str()); } #define APF_MEX_ERROR_FURTHER_INPUT_NEEDED(text) \ (void)prhs; \ if (nrhs < 1) { \ std::string msg(std::string(text) + " needs a further input parameter!"); \ mexErrMsgTxt(msg.c_str()); } #define APF_MEX_ERROR_NUMERIC_INPUT(text) \ (void)prhs; \ if (!mxIsNumeric(prhs[0])) { \ std::string msg(std::string(text) + " must be a numeric matrix!"); \ mexErrMsgTxt(msg.c_str()); } #define APF_MEX_ERROR_REAL_INPUT(text) \ (void)prhs; \ APF_MEX_ERROR_NUMERIC_INPUT(text); \ if (mxIsComplex(prhs[0])) { \ std::string msg(std::string(text) + " must not be complex!"); \ mexErrMsgTxt(msg.c_str()); } #define APF_MEX_ERROR_SAME_NUMBER_OF_ROWS(value, text) \ (void)prhs; \ if (static_cast(mxGetM(prhs[0])) != (value)) { \ std::string msg("Number of rows must be the same " \ + std::string(text) + "!"); \ mexErrMsgTxt(msg.c_str()); } #define APF_MEX_ERROR_SAME_NUMBER_OF_COLUMNS(value, text) \ (void)prhs; \ if (static_cast(mxGetN(prhs[0])) != (value)) { \ std::string msg("Number of columns must be the same " \ + std::string(text) + "!"); \ mexErrMsgTxt(msg.c_str()); } namespace apf { /// Helper functions for creating MEX files namespace mex { // TODO: check if (and how) user-specified overloads of convert() work // TODO: use a traits class, if necessary /// Convert @c mxArray to @c std::string bool convert(const mxArray* in, std::string& out) { if (!mxIsChar(in)) return false; if (mxGetM(in) != 1) return false; char* temp = mxArrayToString(in); out = temp; mxFree(temp); return true; } /// Convert @c mxArray to @c double bool convert(const mxArray* in, double& out) { if (!mxIsDouble(in) || mxIsComplex(in)) return false; if (mxGetNumberOfElements(in) != 1) return false; out = mxGetScalar(in); return true; } /// Convert @c mxArray to @c int bool convert(const mxArray* in, int& out) { if (!mxIsDouble(in) || mxIsComplex(in)) return false; if (mxGetNumberOfElements(in) != 1) return false; double temp = mxGetScalar(in); if (temp != std::floor(temp)) return false; out = temp; return true; } /// Convert @c mxArray to @c size_t bool convert(const mxArray* in, size_t& out) { if (!mxIsDouble(in) || mxIsComplex(in)) return false; if (mxGetNumberOfElements(in) != 1) return false; double temp = mxGetScalar(in); if (temp < 0 || temp != std::floor(temp)) return false; out = temp; return true; } /// Convert @c mxArray to a @c std::map of @c std::string%s. /// This expects a scalar structure! /// Values must be real scalar numbers or strings! /// @warning In case of a conversion error, the map may be partially filled! // TODO: allow wstring? bool convert(const mxArray* in, std::map& out) { if (!mxIsStruct(in)) return false; if (mxGetNumberOfElements(in) != 1) return false; for (int i = 0; i < mxGetNumberOfFields(in); ++i) { auto fieldname = std::string(mxGetFieldNameByNumber(in, i)); // Second argument: number of element (we expect only one): mxArray* field = mxGetFieldByNumber(in, 0, i); double doublevalue; std::string stringvalue; // TODO: check for size_t and int? if (convert(field, doublevalue)) { stringvalue = apf::str::A2S(doublevalue); } else if (convert(field, stringvalue)) { // do nothing } else { mexPrintf("Trying to convert '%s' ...\n", fieldname.c_str()); mexErrMsgTxt("Value must be a real scalar number or a string!"); } out[fieldname] = stringvalue; } return true; } namespace internal { template bool next_arg_helper(int& n, const mxArray**& p, T& data) { return (n-- < 1) ? optional : convert(p++[0], data); } } // namespace internal /// Get next argument, converted to @p T. /// @param n Number of arguments, typically @c nrhs /// @param p Pointer to arguments, typically @c prhs /// @return @b true if argument was available and if conversion was successful /// @post @p n is decremented, @p p is incremented template bool next_arg(int& n, const mxArray**& p, T& data) { return internal::next_arg_helper(n, p, data); } /// Get next optional argument, converted to @p T. /// @return @b true if no argument available or if conversion was successful /// @see next_arg() template bool next_optarg(int& n, const mxArray**& p, T& data) { return internal::next_arg_helper(n, p, data); } /// Get next argument, converted to @p T. /// @see next_arg() /// @param error Message to be displayed on error template void next_arg(int& n, const mxArray**& p, T& data, const std::string& error) { if (!next_arg(n, p, data)) mexErrMsgTxt(error.c_str()); } /// Get next optional argument, converted to @p T. /// @see next_optarg() /// @param error Message to be displayed on error template void next_optarg(int& n, const mxArray**& p, T& data, const std::string& error) { if (!next_optarg(n, p, data)) mexErrMsgTxt(error.c_str()); } } // namespace mex } // 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'\:'=' ssr-0.4.2/apf/apf/mimoprocessor.h000066400000000000000000000432041236416011200167110ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 /** 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 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" , thread_policy::default_number_of_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 typename Input::Params; using typename Input::iterator; explicit 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 typename Output::Params; using 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'\:'=' ssr-0.4.2/apf/apf/mimoprocessor_file_io.h000066400000000000000000000110211236416011200203670ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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::endl; return 42; } if (in.channels() != processor.in_channels()) { std::cout << "Input channel mismatch!" << std::endl; 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 ssr-0.4.2/apf/apf/misc.h000066400000000000000000000166521236416011200147520ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 ssr-0.4.2/apf/apf/parameter_map.h000066400000000000000000000207461236416011200166330ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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::out_of_range #include "stringtools.h" namespace apf { /** A "dictionary" for parameters. * All values are saved as @c std::string's. * * Usage examples: * @code * apf::parameter_map params; * params.set("one", "first value"); * params.set("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"); // throws std::invalid_argument exception! * 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(); * } * params["four"] = "42"; // throws std::out_of_range exception! * val5 = params["four"]; // throws std::out_of_range exception! * @endcode **/ struct parameter_map : std::map { /** Constructor. * All parameters are forwarded to the @c std::map constructor. **/ template explicit parameter_map(Args&&... args) : std::map(std::forward(args)...) {} /** "Getter". * @param k Name of the parameter which should be retrieved. * @return const reference to the value referenced by @p k. * @throw std::out_of_range if the key @p k doesn't exist. You should've * checked beforehand with has_key() ... * @see has_key(), get() **/ const std::string& operator[](const std::string& k) const { try { return this->at(k); } catch (const std::out_of_range&) { throw std::out_of_range("Parameter \"" + k + "\" does not exist in map!"); } } /** "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. The parameter has to be * in the map already, if not, an exception is thrown! If you want to add a * new value, use set(). * @return non-const reference to the value referenced by @p k. * You can assign a @c std::string to actually set a new value. * @throw std::out_of_range if the key @p k doesn't exist yet. * @see has_key(), set() **/ std::string& operator[](const std::string& k) { try { return this->at(k); } catch (const std::out_of_range&) { throw std::out_of_range("Parameter \"" + k + "\" does not exist in map!"); } } /** 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::out_of_range&) { 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 * apf::parameter_map params; * params.set("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::out_of_range 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 std::map::operator[](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 this->count(k) > 0; } }; } // 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'\:'=' ssr-0.4.2/apf/apf/pointer_policy.h000066400000000000000000000140221236416011200170430ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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'\:'=' ssr-0.4.2/apf/apf/portaudio_policy.h000066400000000000000000000176771236416011200174140ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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'\:'=' ssr-0.4.2/apf/apf/posix_thread_policy.h000066400000000000000000000163211236416011200200600ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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() #include // for std::thread::hardware_concurrency() #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; unsigned default_number_of_threads() { // POSIX doesn't have it's own way to do it, so we borrow it from C++11: return std::thread::hardware_concurrency(); } 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'\:'=' ssr-0.4.2/apf/apf/rtlist.h000066400000000000000000000231621236416011200153320ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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'\:'=' ssr-0.4.2/apf/apf/shareddata.h000066400000000000000000000064141236416011200161120ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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'\:'=' ssr-0.4.2/apf/apf/sndfiletools.h000066400000000000000000000071541236416011200165210ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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'\:'=' ssr-0.4.2/apf/apf/stopwatch.h000066400000000000000000000052051236416011200160230ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 ssr-0.4.2/apf/apf/stringtools.h000066400000000000000000000402171236416011200164000ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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; if (input.fail()) return false; input >> std::ws; if (!input.eof()) return false; output = result; return true; } /** 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; 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; } if (input.fail()) return false; input >> std::ws; if (!input.eof()) return false; output = result; return true; } /** Converter "String to Anything". * Convert a @c std::string or C-string to a given numeric type. * Also, convert @c "1", @c "true", @c "0" and @c "false" to the respective * boolean values. * @tparam out_T desired output type (must have an input stream operator!) * @param input a @c std::string or @c char* * @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 std::string& input, out_T& output) { std::istringstream converter(input); return convert(converter, output); } /** Overloaded function with a @c std::string as output. * The input is simply assigned to the output. * @return always @b true **/ inline bool S2A(const std::string& input, std::string& output) { output = input; return true; } /** Converter "String to Return Value". * Convert a string (either a zero-terminated @c char* or a @c std::string) * to a given numeric type and return the result. * @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 std::string& input, out_T def) { S2A(input, def); // ignore return value return def; } /** Overloaded function with C-string as default value. * @return a @c std::string * @see S2RV() **/ inline std::string S2RV(const std::string& input, const char* def) { std::string temp(def); S2A(input, temp); // ignore return value return temp; } /** 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; } /** Clear the state of a stream but leave @c eofbit as is. * It can be used as stream modifier to ignore @c failbit and @c badbit but * still respect @c eofbit: * @code * my_stream >> std::ws >> clear_iostate_except_eof; * @endcode * This may be useful because some implementations (e.g. libc++) set @c failbit * if @c std::ws is used to extract whitespace from the end of a stream but no * whitespace is present. * @see http://stackoverflow.com/q/13423514/500098 * @tparam char_T character type of the stream (also used for the output) * @tparam traits traits class for @c std::basic_ios * @param[in,out] stream the stream to manipulate * @return a reference to the modified stream. **/ template std::basic_ios& clear_iostate_except_eof(std::basic_ios& stream) { stream.clear(stream.rdstate() & std::ios_base::eofbit); return stream; } /** 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) { static_assert(digits > 0, "'digits' must be at least 1!"); // 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; iss >> number; if (iss.fail()) return false; iss >> std::ws >> clear_iostate_except_eof; if (iss.eof()) // that's everything, no suffixes! { seconds = number; } else // still something left ... { auto the_rest = std::basic_string(); iss >> the_rest; if (iss.fail()) return false; iss >> std::ws >> clear_iostate_except_eof; 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'\:'=' ssr-0.4.2/apf/doc/000077500000000000000000000000001236416011200136335ustar00rootroot00000000000000ssr-0.4.2/apf/doc/Doxyfile000066400000000000000000000042651236416011200153500ustar00rootroot00000000000000# 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 ssr-0.4.2/apf/doc/documentation.cpp000066400000000000000000000043441236416011200172150ustar00rootroot00000000000000// 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 - Macros and helper functions for creating MEX files: mextools.h - Simple stopwatch: apf::StopWatch - Miscellaneous stuff: misc.h Copyright (c) 2012-2014 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 ssr-0.4.2/apf/doc/mimoprocessor.cpp000066400000000000000000000016101236416011200172360ustar00rootroot00000000000000// 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! **/ ssr-0.4.2/apf/examples/000077500000000000000000000000001236416011200147045ustar00rootroot00000000000000ssr-0.4.2/apf/examples/Makefile000066400000000000000000000047441236416011200163550ustar00rootroot00000000000000JACK_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 JACK_STUFF += jack_connections 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: check_flext_path package.txt $(FLEXTPATH)/build.sh pd gcc .PHONY: pd check_flext_path: @test -n '$(FLEXTPATH)' || ( echo \"FLEXTPATH\" is empty! ; false ) @test -d '$(FLEXTPATH)' || \ ( echo \"$(FLEXTPATH)\" not found! Set FLEXTPATH! ; false ) .PHONY: check_flext_path 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 2> /dev/null || 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 ssr-0.4.2/apf/examples/README000066400000000000000000000001771236416011200155710ustar00rootroot00000000000000Some examples of how the Audio Processing Framework (APF) can be used. Compilation: make make pd make mex make mex-double ssr-0.4.2/apf/examples/audiofile_simpleprocessor.cpp000066400000000000000000000055721236416011200226730ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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/default_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; if (argc >= 5) { e.set("threads", argv[4]); } 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 ssr-0.4.2/apf/examples/dummy_example.cpp000066400000000000000000000072401236416011200202610ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 ssr-0.4.2/apf/examples/flext_simpleprocessor.cpp000066400000000000000000000111411236416011200220410ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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/default_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 ssr-0.4.2/apf/examples/jack_change_volume.cpp000066400000000000000000000155321236416011200212220ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 ssr-0.4.2/apf/examples/jack_connections.cpp000066400000000000000000000061451236416011200207300ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 showing how to connect JACK ports. #include "apf/mimoprocessor.h" #include "apf/jack_policy.h" #include "apf/posix_thread_policy.h" class MyProcessor : public apf::MimoProcessor { public: MyProcessor() { Input::Params in_params; in_params.set("port_name", "no_initial_connection"); this->add(in_params); in_params.set("port_name", "initial_connection"); in_params.set("connect_to", "system:capture_1"); this->add(in_params); Output::Params out_params; out_params.set("port_name", "connect_before_activate"); this->add(out_params); out_params.set("port_name", "connect_after_activate"); this->add(out_params); out_params.set("port_name", "port with spaces"); this->add(out_params); } }; int main() { MyProcessor processor; processor.connect_ports("MimoProcessor:connect_before_activate" , "system:playback_1"); sleep(5); processor.activate(); sleep(2); processor.connect_ports("MimoProcessor:connect_after_activate" , "system:playback_1"); processor.connect_ports("MimoProcessor:port with spaces" , "system:playback_2"); sleep(30); processor.deactivate(); } // Settings for Vim (http://www.vim.org/), please do not remove: // vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent ssr-0.4.2/apf/examples/jack_convolver.cpp000066400000000000000000000122571236416011200204240ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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; 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 ssr-0.4.2/apf/examples/jack_dynamic_inputs.cpp000066400000000000000000000077401236416011200214360ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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; }; }; int main() { int in_channels = 20; MyProcessor engine; engine.add(); engine.activate(); sleep(2); std::vector inputs; for (int i = 1; i <= in_channels; ++i) { MyProcessor::Input::Params p; p.set("id", i * 10); p.set("connect_to", "system:capture_1"); 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 ssr-0.4.2/apf/examples/jack_dynamic_outputs.cpp000066400000000000000000000072021236416011200216300ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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/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) { _combiner.process(select_all_inputs()); } private: struct select_all_inputs { apf::CombineChannelsResult::type select(const Input&) { return apf::CombineChannelsResult::constant; } }; apf::CombineChannelsCopy, Output> _combiner; }; }; int main() { int out_channels = 20; MyProcessor engine; engine.add(); engine.activate(); sleep(2); std::vector outputs; for (int i = 1; i <= out_channels; ++i) { MyProcessor::Output::Params p; p.set("id", i * 10); p.set("connect_to", "system:playback_1"); outputs.push_back(engine.add(p)); 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 ssr-0.4.2/apf/examples/jack_matrix.cpp000066400000000000000000000213411236416011200177050ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 ssr-0.4.2/apf/examples/jack_minimal.cpp000066400000000000000000000061441236416011200200330ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 ssr-0.4.2/apf/examples/jack_query_thread.cpp000066400000000000000000000064571236416011200211100ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 ssr-0.4.2/apf/examples/jack_simpleprocessor.cpp000066400000000000000000000055341236416011200216400ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 ssr-0.4.2/apf/examples/mex_simpleprocessor.cpp000066400000000000000000000152241236416011200215160ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 typename 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 ssr-0.4.2/apf/examples/package.txt000066400000000000000000000001771236416011200170450ustar00rootroot00000000000000NAME = simpleprocessor~ SRCS = flext_simpleprocessor.cpp INCPATH = -I.. CXXFLAGS = -std=c++11 # vim:filetype=make ssr-0.4.2/apf/examples/portaudio_simpleprocessor.cpp000066400000000000000000000054201236416011200227300ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 ssr-0.4.2/apf/examples/simpleprocessor.h000066400000000000000000000124621236416011200203130ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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: using iterator = std::vector::const_iterator; explicit Input(const Params& p) : MimoProcessorBase::Input(p) , _buffer(this->parent.block_size()) {} 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()); } iterator begin() const { return _buffer.begin(); } iterator end() const { return _buffer.end(); } 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 typename 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 ssr-0.4.2/apf/examples/startpd.sh000077500000000000000000000001341236416011200167220ustar00rootroot00000000000000#!/bin/sh PD_PATCH=test_simpleprocessor.pd pd -path pd-linux/release-single "$PD_PATCH" & ssr-0.4.2/apf/examples/test_simpleprocessor.pd000066400000000000000000000005441236416011200215240ustar00rootroot00000000000000#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; ssr-0.4.2/apf/misc/000077500000000000000000000000001236416011200140215ustar00rootroot00000000000000ssr-0.4.2/apf/misc/Makefile.dependencies000066400000000000000000000040771236416011200201160ustar00rootroot00000000000000# 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, 2014 DEPENDENCY_EXT ?= dep DEPENDENCY_DIR ?= .dep BINARY_EXTENSIONS ?= "" .o # 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) $(subst "",,$(BINARY_EXTENSIONS:%=$(DEPENDENCY_DIR)/\%%.$(DEPENDENCY_EXT))): %.cpp | $(DEPENDENCY_DIR) $(RM) $@; $(CXX) -MM -MF $@ \ -MT "$(@:$(DEPENDENCY_DIR)/%.$(DEPENDENCY_EXT)=%) $@" \ $(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 ssr-0.4.2/apf/performance_tests/000077500000000000000000000000001236416011200166115ustar00rootroot00000000000000ssr-0.4.2/apf/performance_tests/Makefile000066400000000000000000000023261236416011200202540ustar00rootroot00000000000000EXECUTABLES += 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 ssr-0.4.2/apf/performance_tests/biquad_count_denormals.cpp000066400000000000000000000162131236416011200240410ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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) } ssr-0.4.2/apf/performance_tests/biquad_denormals.cpp000066400000000000000000000163401236416011200226320ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 ssr-0.4.2/apf/performance_tests/crossfade.cpp000066400000000000000000000112201236416011200212620ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 ssr-0.4.2/apf/performance_tests/interpolation.cpp000066400000000000000000000115261236416011200222110ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 ssr-0.4.2/apf/unit_tests/000077500000000000000000000000001236416011200152675ustar00rootroot00000000000000ssr-0.4.2/apf/unit_tests/Makefile000066400000000000000000000034351236416011200167340ustar00rootroot00000000000000# 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 TESTS += test_parameter_map 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 ssr-0.4.2/apf/unit_tests/catch/000077500000000000000000000000001236416011200163515ustar00rootroot00000000000000ssr-0.4.2/apf/unit_tests/catch/README000066400000000000000000000001051236416011200172250ustar00rootroot00000000000000C++ unit testing framework from https://github.com/philsquared/Catch ssr-0.4.2/apf/unit_tests/catch/catch.hpp000066400000000000000000005432261236416011200201600ustar00rootroot00000000000000/* * 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 ssr-0.4.2/apf/unit_tests/iterator_test_macros.h000066400000000000000000000135331236416011200217010ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 ssr-0.4.2/apf/unit_tests/main.cpp000066400000000000000000000004411236416011200167160ustar00rootroot00000000000000// 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" ssr-0.4.2/apf/unit_tests/test_accumulating_iterator.cpp000066400000000000000000000053461236416011200234270ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 ssr-0.4.2/apf/unit_tests/test_biquad.cpp000066400000000000000000000046211236416011200203020ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 ssr-0.4.2/apf/unit_tests/test_blockdelayline.cpp000066400000000000000000000101671236416011200220200ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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)); apf::NonCausalBlockDelayLine::difference_type correct; CHECK(d.delay_is_valid(-1, correct)); CHECK(correct == -1); CHECK_FALSE(d.delay_is_valid(-2, correct)); CHECK(correct == -1); CHECK(d.delay_is_valid(5, correct)); CHECK(correct == 5); CHECK_FALSE(d.delay_is_valid(6, correct)); CHECK(correct == 5); 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 ssr-0.4.2/apf/unit_tests/test_cast_iterator.cpp000066400000000000000000000107541236416011200217040ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 ssr-0.4.2/apf/unit_tests/test_circular_iterator.cpp000066400000000000000000000213161236416011200225520ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 ssr-0.4.2/apf/unit_tests/test_combine_channels.cpp000066400000000000000000000072121236416011200223230ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 ssr-0.4.2/apf/unit_tests/test_container.cpp000066400000000000000000000307641236416011200210260ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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") { using fvi = apf::fixed_vector; SECTION("inherited types", "") { void f01(fvi::value_type); void f02(fvi::allocator_type); void f03(fvi::reference); void f04(fvi::const_reference); void f05(fvi::pointer); void f06(fvi::const_pointer); void f07(fvi::iterator); void f08(fvi::const_iterator); void f09(fvi::reverse_iterator); void f10(fvi::const_reverse_iterator); void f11(fvi::difference_type); void f12(fvi::size_type); } SECTION("inherited functions", "") { fvi fv(1); fv.front(); fv.back(); fv.begin(); fv.end(); fv.rbegin(); fv.rend(); fv.cbegin(); fv.cend(); fv.crbegin(); fv.crend(); fv.size(); fv.max_size(); fv.capacity(); fv.empty(); fv[0]; fv.at(0); fv.data(); fv.get_allocator(); } SECTION("default constructor", "") { fvi fv; CHECK(fv.size() == 0); CHECK(fv.capacity() == 0); } SECTION("default constructor and allocator", "") { auto a = std::allocator(); fvi fv(a); CHECK(fv.size() == 0); CHECK(fv.capacity() == 0); } SECTION("constructor from size", "uses default constructor") { fvi fv(3); CHECK(fv[1] == 0); fvi fv2(0); CHECK(fv2.size() == 0); } // TODO: constructor from size and allocator is missing in C++11 (but not C++14) #if 0 SECTION("constructor from size and allocator", "") { auto a = std::allocator(); fvi fv(3, a); CHECK(fv[2] == 0); CHECK(fvi(0, a).size() == 0); // rvalue allocator CHECK(fvi(0, std::allocator()).size() == 0); } #endif SECTION("constructor from size and default value", "") { fvi fv(3, 99); CHECK(fv[2] == 99); } SECTION("constructor from size and default value and allocator", "") { auto a = std::allocator(); fvi fv(3, 99, a); CHECK(fv[2] == 99); // rvalue allocator CHECK(fvi(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", "") { fvi fv(3, 99); fvi fv2(fv); CHECK(fv[2] == 99); CHECK(fv2[2] == 99); } SECTION("move constructor", "") { fvi fv(fvi(3, 99)); CHECK(fv[2] == 99); } SECTION("constructor from initializer list", "") { fvi fv{42}; CHECK(fv.size() == 1); CHECK(fv[0] == 42); // Note: extra parentheses because of commas CHECK((fvi{42, 43}.size()) == 2); CHECK((fvi{42, 43, 44}.size()) == 3); } const int size = 4; int data[size] = { 1, 2, 3, 4 }; SECTION("constructor from range", "") { fvi 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 fvi 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()", "") { fvi 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") { using fli = apf::fixed_list; SECTION("inherited types", "") { void f01(fli::value_type); void f02(fli::allocator_type); void f03(fli::reference); void f04(fli::const_reference); void f05(fli::pointer); void f06(fli::const_pointer); void f07(fli::iterator); void f08(fli::const_iterator); void f09(fli::reverse_iterator); void f10(fli::const_reverse_iterator); void f11(fli::difference_type); void f12(fli::size_type); } SECTION("inherited functions", "") { fli fl(1); fl.begin(); fl.end(); fl.rbegin(); fl.rend(); fl.cbegin(); fl.cend(); fl.crbegin(); fl.crend(); fl.empty(); fl.size(); fl.max_size(); fl.front(); fl.back(); fl.get_allocator(); fl.reverse(); fl.sort(); } SECTION("default constructor", "") { fli fl; CHECK(fl.size() == 0); } SECTION("constructor from size", "") { fli fl(3); CHECK(fl.size() == 3); CHECK(fl.front() == 0); } SECTION("constructor from size and initializer", "") { fli 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", "") { fli fl{3, 42}; CHECK(fl.size() == 2); CHECK(fl.front() == 3); } SECTION("constructor from sequence and more", "") { int data[] = { 1, 2, 3, 4 }; fli 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 fli 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 ...") { fli 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 ssr-0.4.2/apf/unit_tests/test_convolver.cpp000066400000000000000000000154641236416011200210610ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 ssr-0.4.2/apf/unit_tests/test_discard_iterator.cpp000066400000000000000000000044001236416011200223520ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 ssr-0.4.2/apf/unit_tests/test_dual_iterator.cpp000066400000000000000000000073361236416011200217010ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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; (void)one; // avoid "unused variable" warning 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 ssr-0.4.2/apf/unit_tests/test_fftwtools.cpp000066400000000000000000000052761236416011200210730ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 ssr-0.4.2/apf/unit_tests/test_index_iterator.cpp000066400000000000000000000102211236416011200220460ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 ssr-0.4.2/apf/unit_tests/test_iterator.cpp000066400000000000000000000070401236416011200206640ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 ssr-0.4.2/apf/unit_tests/test_iterator_combinations.cpp000066400000000000000000000054711236416011200234370ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 ssr-0.4.2/apf/unit_tests/test_math.cpp000066400000000000000000000156211236416011200177700ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 ssr-0.4.2/apf/unit_tests/test_mimoprocessor.cpp000066400000000000000000000052101236416011200217310ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 ssr-0.4.2/apf/unit_tests/test_misc.cpp000066400000000000000000000121761236416011200177740ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 ssr-0.4.2/apf/unit_tests/test_parameter_map.cpp000066400000000000000000000070741236416011200216570ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 parameter_map.h. #include "apf/parameter_map.h" #include "catch/catch.hpp" TEST_CASE("parameter_map", "") { SECTION("constructors", "") { apf::parameter_map pm1; // default apf::parameter_map pm2(pm1); // copy apf::parameter_map pm3(apf::parameter_map()); // move std::map m1; apf::parameter_map pm4(m1); // copy apf::parameter_map pm5(std::map()); // move } SECTION("stuff", "") { apf::parameter_map params; params.set("one", "first value"); CHECK(params["one"] == "first value"); params.set("two", 2); CHECK(params["two"] == "2"); params.set("three", 3.1415); CHECK(params["three"] == "3.1415"); std::string val1; int val2, val3; double val4; val1 = params["one"]; CHECK(val1 == "first value"); val2 = params.get("two"); CHECK(val2 == 2); CHECK_THROWS_AS(params.get("one"), std::invalid_argument); val3 = params.get("one", 42); // default value 42 if conversion fails CHECK(val3 == 42); val4 = params.get("three", 3.0); CHECK(val4 == 3.1415); if (params.has_key("four")) { // this is not done because there is no key named "four": CHECK(false); } CHECK_THROWS_AS(params.get("four"), std::out_of_range); CHECK_THROWS_AS(params["four"], std::out_of_range); } SECTION("more stuff", "") { apf::parameter_map params; params.set("id", "item42"); std::string id1, id2, name1, name2; id1 = params.get("id" , "no_id_available"); CHECK(id1 == "item42"); id2 = params.get("id" , "item42"); CHECK(id2 == "item42"); name1 = params.get("name", "Default Name"); CHECK(name1 == "Default Name"); name2 = params.get("name", ""); CHECK(name2 == ""); } } // TEST_CASE // Settings for Vim (http://www.vim.org/), please do not remove: // vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent ssr-0.4.2/apf/unit_tests/test_stride_iterator.cpp000066400000000000000000000070521236416011200222410ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 ssr-0.4.2/apf/unit_tests/test_stringtools.cpp000066400000000000000000000164741236416011200214350ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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_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(" true ", res_bool)); CHECK(res_bool == true); CHECK(S2A(" false ", res_bool)); CHECK(res_bool == false); CHECK(S2A("false", res_bool)); CHECK(res_bool == false); CHECK(S2A(" 1 ", res_bool)); CHECK(res_bool == true); CHECK(S2A("1", res_bool)); CHECK(res_bool == true); CHECK(S2A(" 0 ", res_bool)); CHECK(res_bool == false); 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); CHECK(S2A(std::string(" 42 "), res_str)); CHECK(res_str == " 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(" 42 ", "") == " 42 "); CHECK(S2RV(" 42 ", std::string("")) == " 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(S2RV(" 42 ") == " 42 "); CHECK_THROWS_AS(S2RV(" 42.0 "), std::invalid_argument); } SECTION("convert_chars()", "") { std::istringstream iss; int i; iss.str("2"); CHECK_FALSE(convert_chars<1>(iss, i).fail()); CHECK(i == 2); iss.str(" 2"); iss >> std::noskipws; CHECK(convert_chars<1>(iss, i).fail()); iss.clear(); iss.str(" 7"); iss >> std::skipws; CHECK_FALSE(convert_chars<1>(iss, i).fail()); CHECK(i == 7); iss.str("a"); CHECK(convert_chars<1>(iss, i).fail()); iss.clear(); iss.str("123"); CHECK_FALSE(convert_chars<3>(iss, i).fail()); CHECK(i == 123); // empty stream: CHECK(convert_chars<1>(iss, i).fail()); } SECTION("remove_char()", "") { std::istringstream iss; iss.str("a"); CHECK_FALSE(remove_char(iss, 'a').fail()); CHECK(remove_char(iss, 'a').fail()); iss.clear(); iss.str(" a"); CHECK_FALSE(remove_char(iss, 'a').fail()); iss.str(" a"); iss >> std::noskipws; CHECK(remove_char(iss, 'a').fail()); iss.clear(); CHECK_FALSE(remove_char(iss, 'a').fail()); iss.str("a"); CHECK(remove_char(iss, 'b').fail()); // TODO: check if iss is empty now } SECTION("remove_colon()", "") { std::istringstream iss; iss.str("::"); iss >> remove_colon; CHECK_FALSE(iss.fail()); iss >> remove_colon; CHECK_FALSE(iss.fail()); iss >> remove_colon; CHECK(iss.fail()); iss.clear(); iss.str("a"); iss >> remove_colon; CHECK(iss.fail()); iss.clear(); iss.str(" :"); iss >> remove_colon; CHECK_FALSE(iss.fail()); iss.clear(); iss.str(" :"); iss >> std::noskipws; iss >> remove_colon; CHECK(iss.fail()); } 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("33", res2)); CHECK(res2 == 33); CHECK(string2time("33h", res2)); CHECK(res2 == 118800); 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 ssr-0.4.2/apf/unit_tests/test_transform_iterator.cpp000066400000000000000000000130371236416011200227620ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 ssr-0.4.2/apf/unit_tests/test_trivial_iterator.cpp000066400000000000000000000075751236416011200224330ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 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 ssr-0.4.2/autogen.sh000077500000000000000000000032751236416011200143300ustar00rootroot00000000000000#!/bin/sh # Shell script for automation of autotools. run_command() { # show message; run command; if unsuccessful, show error message and exit echo $0: Running \"$@\" ... "$@" || { status=$?; echo $0: Error in \"$@\"!; exit $status; } } # change to the directory where the script is located # (in case it was started from somewhere else) cd $(dirname $0) # TODO: find a way to move aclocal.m4 from the root directory to somewhere else #ACLOCAL_FLAGS="--output=autotools/aclocal.m4" # Preferring autoreconf for a more transparent configuration/debugging process. if test -f autotools/config/depcomp; then if test x$1 != x--no-auto; then # TODO: check if autoreconf is available? echo $0: Using \"autoreconf\" from now on. echo Use \"$0 --no-auto\" to override this behaviour. run_command autoreconf echo $0: Done! exit fi fi # on OS X, which(1) returns 0 even when it can't find a program if type libtoolize >/dev/null 2>&1; then LIBTOOLIZE=libtoolize else if which glibtoolize >/dev/null; then # on the Mac it's called glibtoolize for some reason LIBTOOLIZE=glibtoolize else echo $0: Error: libtoolize not found! exit 127 fi fi run_command $LIBTOOLIZE --force # in most cases, "libtoolize" is used like this: # # libtoolize --force 2>&1 | sed '/^You should/d' || { # echo "libtool failed, exiting..." # exit 1 # } # # This just removes all lines starting with "You should" from the output run_command aclocal $ACLOCAL_FLAGS run_command autoheader run_command automake --add-missing --foreign run_command autoconf echo $0: Done! # Settings for Vim (http://www.vim.org/), please do not remove: # vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80 ssr-0.4.2/cleanse.sh000077500000000000000000000026441236416011200142770ustar00rootroot00000000000000#!/bin/sh # Shell-Script for removing all files that don't belong into the Git repo. # See also http://www.gnu.org/software/hello/manual/automake/Clean.html # always exit on error set -e # change to the directory where the script is located # (in case it was started from somewhere else) cd "$(dirname "$0")" if test -f Makefile then echo $0: Running \"make maintainer-clean\" ... make maintainer-clean elif test -x configure then echo $0: Error: Run the configure script first, then \"$0\"! exit 1 fi echo $0: Removing miscellaneous files ... rm -rf autotools/ rm -f src/config.h.in configure Makefile.in src/Makefile.in data/Makefile.in rm -f data/MacOSX/Makefile.in man/Makefile.in rm -f doc/manual/authors.tex SSR_TARBALL=ssr-*.*.*.tar.gz SSR_USERMANUAL=doc/SoundScapeRenderer-*.*.*-manual.pdf # BTW, the user manual is copied and renamed on "make dist" # if several files match, all of them are deleted for i in $SSR_TARBALL $SSR_USERMANUAL do test -f $i || continue echo $0: Removing \"$i\" ... rm $i done if test -d doc/doxygen then echo $0: Removing Doxygen documentation ... rm -r doc/doxygen fi # we do this last, because maybe latexmk is not installed: echo $0: Cleansing user manual ... (cd doc/manual && latexmk -C) # this is only shown if everything went smoothly echo $0: Done! # Settings for Vim (http://www.vim.org/), please do not remove: # vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80 ssr-0.4.2/configure.ac000066400000000000000000000702721236416011200146160ustar00rootroot00000000000000dnl This file will be processed by autoconf (which is called by autogen.sh) to dnl generate configure dnl comments starting with # are copied to configure (if after AC_INIT), dnl comments starting with dnl ("do not list") are dropped. dnl see http://www.gnu.org/software/autoconf/manual/ for further information dnl Ensure that a recent enough version of Autoconf is being used. dnl Only from version 2.65 PACKAGE_URL will be set in config.h. dnl see http://www.mail-archive.com/autoconf@gnu.org/msg19725.html dnl see http://www.gnu.org/software/autoconf/manual/autoconf.html#Versioning AC_PREREQ([2.65]) dnl initialize autoconf, this should be the first thing in configure.ac dnl arguments: full package name, version, email address for bug reports, dnl tarball name, homepage AC_INIT([SSR (SoundScape Renderer)], [m4_esyscmd_s([git describe || date +$USER%Y%m%d%H%M])], [ssr@spatialaudio.net], [ssr], [http://spatialaudio.net/ssr/]) SSR_COPYRIGHT="Copyright © 2014 Institut für Nachrichtentechnik, Universität Rostock\nCopyright © 2012 Quality & Usability Lab, Telekom Innovation Labs, TU Berlin\n\nLicense GPLv3+: GNU GPL version 3 or later \nThis is free software: you are free to change and redistribute it.\nThere is NO WARRANTY, to the extent permitted by law." AC_DEFINE_UNQUOTED([SSR_COPYRIGHT], ["$SSR_COPYRIGHT"], [SSR copyright notice]) dnl read AUTHORS file and save (reformatted by sed) to SSR_AUTHORS(_QT) SSR_AUTHORS=esyscmd([cat AUTHORS | sed -e :a -e '$!N;s/\n\n/\\n/;ta' -e '$!N;s/\n/\\n/;ta' -e 's/.*/"&"/']) SSR_AUTHORS_QT=esyscmd([cat AUTHORS | sed -e 's|^.*:|&|' -e 's/ /\ /g' -e 's/é/\é/g' -e 's/ö/\ö/g' | sed -e :a -e '$!N;s/\n/
/;ta' -e 's/.*/"&"/']) dnl reformat AUTHORS and write to authors.tex for the user manual: esyscmd([cat AUTHORS | sed -e 's|^.*:|\\item\[&\]\\hfill\\\\|' -e "s/é/\\\'e/g" -e 's/ö/\\"o/g' -e '1s/^.*/\% This file was created automatically (see configure.ac). Please do not edit!\ \\begin{description}\ &/' -e '$s/.*$/&\ \\end{description}/' > doc/manual/authors.tex]) dnl the literal newlines are needed because \n doesn't work on MacOSX! AC_DEFINE_UNQUOTED([SSR_AUTHORS], ["$SSR_AUTHORS"], [List of SSR authors]) AC_DEFINE_UNQUOTED([SSR_AUTHORS_QT], ["$SSR_AUTHORS_QT"], [List of SSR authors (with Qt markup)]) dnl include macro definitions m4_include([macros.m4]) dnl Check if the correct source directory is used by specifying a file in it AC_CONFIG_SRCDIR([src/controller.h]) dnl Automake looks here for various helper scripts AC_CONFIG_AUX_DIR(autotools/config) dnl this gets rid of a warning message from libtoolize AC_CONFIG_MACRO_DIR([autotools/m4]) dnl Compute the canonical target-system type variable, 'target', and its three dnl individual parts 'target_cpu', 'target_vendor', and 'target_os' AC_CANONICAL_TARGET dnl Compute the canonical host-system type variable, 'host', and its three dnl individual parts 'host_cpu', 'host_vendor', and 'host_os'. dnl AC_CANONICAL_HOST dnl Runs many macros required for proper operation of the generated Makefiles. dnl see http://sources.redhat.com/automake/automake.html#Options for options dnl dnl We use the option "foreign" because we do not provide a ChangeLog-file. dnl Major changes are documented in the NEWS file. AM_INIT_AUTOMAKE([foreign std-options subdir-objects -Wall]) dnl Check for pkg-config manually first. If it's not installed the dnl PKG_PROG_PKG_CONFIG macro won't be defined. AC_CHECK_PROG([have_pkg_config], [pkg-config], [yes], [no]) AS_IF([test x$have_pkg_config != xyes], AC_MSG_ERROR(['pkg-config' is required to install this program])) dnl check for pkg-config version PKG_PROG_PKG_CONFIG dnl Create header file(s) with C preprocessor #define statements (e.g. ENABLE_*) AC_CONFIG_HEADERS(src/config.h) dnl AM_MAINTAINER_MODE enables automatic rebuild rules only using dnl ./configure --enable-maintainer-mode. This can lead to insecurity. dnl We avoid using this Macro. dnl see: http://www.gnu.org/software/hello/manual/automake/maintainer_002dmode.html#maintainer_002dmode dnl see: https://cims.nyu.edu/cgi-systems/info2html?(automake)Rebuilding dnl set the relevant programming language (e.g. for AC_CHECK_HEADER) dnl this is e.g. necessary for the C++ header eca-control-interface.h AC_LANG([C++]) dnl check if the user specified CXXFLAGS (as environment variable or dnl in $prefix/share/config.site AS_IF([test -n "${CXXFLAGS+x}"], [usercxxflags=yes], [usercxxflags=no]) dnl AC_PROG_CC sets default CFLAGS, this can be disabled with dnl : ${CFLAGS=""} dnl AC_PROG_CXX sets default CXXFLAGS, this can be disabled with dnl : ${CXXFLAGS=""} dnl Determine a C compiler to use (this is done anyway, we do it explicitly) AC_PROG_CC dnl Determine a C++ compiler to use AC_PROG_CXX AC_COMPILE_IFELSE([AC_LANG_PROGRAM()], , AC_MSG_ERROR([C++ compiler ($CXX) not found!])) dnl Add support for the `--enable-shared' and `--disable-shared' configure flags dnl we don't need that (yet) LT_INIT dnl AM_PROG_LIBTOOL dnl set the output variable LN_S (used in data/Makefile.am) AC_PROG_LN_S dnl If the C compiler does not accept the -c and -o options simultaneously, dnl define NO_MINUS_C_MINUS_O. dnl TODO: check if we need AM_PROG_CC_C_O! dnl AM_PROG_CC_C_O dnl Macro AC_PROG_LD is obsolete. dnl Please use LT_PATH_LD when necessity is discovered. dnl see: http://lists.gnu.org/archive/html/autoconf/2010-05/msg00016.html dnl AC_PROG_LD dnl Check how the architecture deals with big-endian. dnl The default for action-if-true is to define ‘WORDS_BIGENDIAN’. The default dnl for action-if-false is to do nothing. dnl AC_C_BIGENDIAN dnl AC_DEFINE writes values in config.h dnl AC_DEFINE_UNQUOTED performs additional shell expansions, e.g. substitution dnl of variable with value. AM_MISSING_PROG(HELP2MAN, help2man) ENABLE_AUTO([all], [all renderers (use --enable-xyz to re-enable certain renderers)], [ AS_IF([test x$enable_all = xno], [have_all=no]) ]) ENABLE_AUTO([binaural], [binaural renderer (using HRIRs)], [ AS_IF([test x$enable_binaural = xyes -o x$have_all = xyes], [SSR_executables="$SSR_executables ssr-binaural"]) ]) ENABLE_AUTO([brs], [Binaural Room Synthesis renderer (using BRIRs)], [ AS_IF([test x$enable_brs = xyes -o x$have_all = xyes], [SSR_executables="$SSR_executables ssr-brs"]) ]) ENABLE_AUTO([wfs], [Wave Field Synthesis renderer], [ AS_IF([test x$enable_wfs = xyes -o x$have_all = xyes], [SSR_executables="$SSR_executables ssr-wfs"]) ]) ENABLE_AUTO([vbap], [Vector Base Amplitude Panning renderer], [ AS_IF([test x$enable_vbap = xyes -o x$have_all = xyes], [SSR_executables="$SSR_executables ssr-vbap"]) ]) ENABLE_AUTO([aap], [Ambisonics Amplitude Panning renderer], [ AS_IF([test x$enable_aap = xyes -o x$have_all = xyes], [SSR_executables="$SSR_executables ssr-aap"]) ]) ENABLE_AUTO([generic], [generic renderer], [ AS_IF([test x$enable_generic = xyes -o x$have_all = xyes], [SSR_executables="$SSR_executables ssr-generic"]) ]) ENABLE_AUTO([nfc-hoa],[Near-Field-Compensated Higher-Order-Ambisonics renderer], [ AS_IF([test x$enable_nfc_hoa = xyes -o x$have_all = xyes], [SSR_executables="$SSR_executables ssr-nfc-hoa"]) ]) dnl Note: For what happens with SSR_executables see src/Makefile.am AC_SUBST(SSR_executables) dnl Checking for sndfile PKG_CHECK_MODULES([SNDFILE], [sndfile >= 1.0], [ PKG_FLAGS="$PKG_FLAGS $SNDFILE_CFLAGS" LIBS="$LIBS $SNDFILE_LIBS" ]) dnl Checking for FFTW3 PKG_CHECK_MODULES([FFTW], [fftw3f >= 3.0.0], [ PKG_FLAGS="$PKG_FLAGS $FFTW_CFLAGS" LIBS="$LIBS $FFTW_LIBS" ]) dnl Checking for JACK PKG_CHECK_MODULES([JACK], [jack >= 0.118.0], [ PKG_FLAGS="$PKG_FLAGS $JACK_CFLAGS" LIBS="$LIBS $JACK_LIBS" ]) dnl Checking for libxml2 PKG_CHECK_MODULES([LIBXML], [libxml-2.0 >= 2.0], [ PKG_FLAGS="$PKG_FLAGS $LIBXML_CFLAGS" LIBS="$LIBS $LIBXML_LIBS" ]) dnl by the way, AC_HELP_STRING is deprecated, use AS_HELP_STRING instead! ENABLE_EXPLICIT([debugging],[debugging symbols, asserts, ...]) AS_IF([test x$have_debugging = xyes], [ dnl DEBUGGING_FLAGS="$DEBUGGING_FLAGS -D_GLIBCXX_DEBUG" ], [ DEBUGGING_FLAGS="$DEBUGGING_FLAGS -DNDEBUG" ]) dnl overwrite default CXXFLAGS set by AC_PROG_CXX AS_IF([test x$usercxxflags = xno], [CXXFLAGS="-g"]) dnl select C++11 (unconditionally) CXXFLAGS="$CXXFLAGS -std=c++11" AC_MSG_CHECKING([if $CXX supports "-std=c++11"]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM()], AC_MSG_RESULT([yes]), [ AC_MSG_RESULT([no]) AC_MSG_ERROR([C++11 not supported! Please upgrade.]) ]) dnl see http://www.open-std.org/JTC1/SC22/WG21/docs/cwg_defects.html#1123 dnl and http://stackoverflow.com/q/11497252/500098 AC_MSG_CHECKING([if $CXX implements core/1123]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM( [ struct X { virtual ~X() = default; }; struct Y : X { virtual ~Y() = default; }; ], [])], AC_MSG_RESULT([yes]), [ AC_MSG_RESULT([no]) AC_MSG_ERROR([core/1123 not implemented! Please upgrade.]) ]) # on some systems this is needed, on the others it doesn't hurt: AC_SEARCH_LIBS([pthread_join], [pthread], , [have_ip_interface=no]) ENABLE_AUTO([optimization], [code optimization], [ dnl for now, we have only optimization if GCC is used AS_IF([test x$GCC = xyes], [ dnl add -O3 to CXXFLAGS (only if the user didn't specify CXXFLAGS): AS_IF([test x$usercxxflags = xno], [CXXFLAGS="$CXXFLAGS -O3"]) dnl common x86 optimization flags OPT_FLAGS="$OPT_FLAGS -fomit-frame-pointer -ffast-math -funroll-loops" dnl OPT_FLAGS="$OPT_FLAGS -fexpensive-optimizations" dnl its a little sad that OS X doesn't make it possible to identify dnl the target_cpu a little more precisely. on os x we always get "i386" dnl as the CPU type. we miss out on some possible optimizations as dnl a result. oh well. dnl anyway, the user can specify -march with CXXFLAGS, if desired AS_IF([test $target_cpu = i586], [OPT_FLAGS="$OPT_FLAGS -march=i586"], [test $target_cpu = i686], [OPT_FLAGS="$OPT_FLAGS -march=i686"], [test $target_cpu = x86_64], [OPT_FLAGS="$OPT_FLAGS -march=k8"]) ], dnl this is the end of "if GCC", else: [ AC_MSG_WARN([no optimization.........................]) have_optimization=no ]) ]) ENABLE_AUTO([mmx], [MMX support], [ AS_IF([test x$have_optimization != xyes], [ have_mmx=no AS_IF([test x$enable_mmx != x], AC_MSG_ERROR([You have to --enable-optimization in order to use MMX!])) ], [ AS_IF([echo $target_cpu | egrep '(i.86|x86_64)' >/dev/null], [ AS_IF([test -r /proc/cpuinfo], [ procflags=`grep '^flags' /proc/cpuinfo` AS_IF([echo $procflags | grep -s mmx >/dev/null], , [have_mmx=no]) ], [ # this is not linux, but assume that if the processor # is x86 then it doesn't need MMX AS_IF([test x$enable_mmx = x], [ have_mmx=no AC_MSG_WARN([Assuming your x86/x86_64 system does not need to use MMX. Use --enable-mmx if this is not the case]) ]) dnl if --enable-mmx was called explicitly, have_mmx=yes automatically! ]) dnl now actually check for MMX support (if requested) AS_IF([test x$have_mmx = xyes], [ AC_MSG_CHECKING([whether we can compile MMX code]) AC_TRY_LINK( , asm ("movq 0, %mm0"); , [ OPT_FLAGS="$OPT_FLAGS -mmmx" AC_MSG_RESULT([yes]) ], [ have_mmx=no AC_MSG_RESULT([no]) AC_MSG_WARN([The assembler does not support the MMX command set.]) ]) ]) ]) ]) ]) ENABLE_AUTO([sse], [SSE support], [ AS_IF([test x$have_optimization != xyes], [ have_sse=no AS_IF([test x$enable_sse != x], AC_MSG_ERROR([You have to --enable-optimization in order to use SSE!])) ], [ AS_IF([echo $target_cpu | egrep '(i.86|x86_64)' >/dev/null], [ AS_IF([test -r /proc/cpuinfo], [ procflags=`grep '^flags' /proc/cpuinfo` AS_IF([echo $procflags | grep -s sse >/dev/null], , [have_sse=no]) ], [ # this is not linux, but assume that if the processor # is x86 then it supports SSE AS_IF([test x$enable_mmx = xno], [have_sse=no], AC_MSG_WARN([Assuming your x86/x86_64 system can support SSE. Use --disable-sse if this is not the case])) ]) dnl now actually check for SSE support (if requested) AS_IF([test x$have_sse = xyes], [ AC_MSG_CHECKING([whether we can compile SSE code]) AC_TRY_LINK( , asm ("movntps %xmm0, 0"); , [ OPT_FLAGS="$OPT_FLAGS -msse -mfpmath=sse" AC_MSG_RESULT([yes]) dnl the convolver checks for __SSE__ dnl we check if __SSE__ is defined, just to be sure: AC_MSG_CHECKING([for __SSE__]) AS_IF([test x$usercxxflags = xno], [ CXXFLAGS_BACKUP="$CXXFLAGS" dnl add OPT_FLAGS just for the AC_TRY_LINK test: CXXFLAGS="$CXXFLAGS $OPT_FLAGS" ]) AC_TRY_LINK( , #ifndef __SSE__ #error #endif , [ AC_MSG_RESULT([yes]) ], [ AC_MSG_RESULT([no]) AC_MSG_ERROR([__SSE__ is undefined! Contact $PACKAGE_BUGREPORT!]) ]) AS_IF([test x$usercxxflags = xno], [CXXFLAGS="$CXXFLAGS_BACKUP"]) ], [ have_sse=no AC_MSG_RESULT([no]) AC_MSG_WARN([The assembler does not support the SSE command set.]) ]) ]) ]) ]) ]) ENABLE_AUTO([sse2], [SSE2 support], [ AS_IF([test x$have_optimization != xyes], [ have_sse2=no AS_IF([test x$enable_sse2 != x], AC_MSG_ERROR([You have to --enable-optimization in order to use SSE2!])) ], [ AS_IF([echo $target_cpu | egrep '(i.86|x86_64)' >/dev/null], [ AS_IF([test -r /proc/cpuinfo], [ procflags=`grep '^flags' /proc/cpuinfo` AS_IF([echo $procflags | grep -s sse2 >/dev/null], , [have_sse2=no]) ], [ AS_IF([test x$enable_sse = xno], [have_sse2=no], AC_MSG_WARN([Assuming your x86/x86_64 system can support SSE2. Use --disable-sse2 if this is not the case])) ]) dnl now actually check for SSE2 support (if requested) AS_IF([test x$have_sse2 = xyes], [ AC_MSG_CHECKING([whether we can compile SSE2 code]) AC_TRY_LINK( #include , __m128i one; , [ OPT_FLAGS="$OPT_FLAGS -msse2 -mfpmath=sse" AC_MSG_RESULT([yes]) dnl check if __SSE2__ is defined, just to be sure: AC_MSG_CHECKING([for __SSE2__]) AS_IF([test x$usercxxflags = xno], [ CXXFLAGS_BACKUP="$CXXFLAGS" dnl add OPT_FLAGS just for the AC_TRY_LINK test: CXXFLAGS="$CXXFLAGS $OPT_FLAGS" ]) AC_TRY_LINK( , #ifndef __SSE2__ #error #endif , [ AC_MSG_RESULT([yes]) ], [ AC_MSG_RESULT([no]) AC_MSG_ERROR([__SSE2__ is undefined! Contact $PACKAGE_BUGREPORT!]) ]) AS_IF([test x$usercxxflags = xno], [CXXFLAGS="$CXXFLAGS_BACKUP"]) ], [ have_sse=no AC_MSG_RESULT([no]) AC_MSG_WARN([The assembler does not support the SSE2 command set.]) ]) ]) ]) ]) ]) dnl enable/disable GUI, default: enabled ENABLE_FORCED([gui], [graphical user interface (using Qt)], [ # Checking for Qt PKG_CHECK_MODULES([QT], [QtCore >= 4.2.2 QtGui >= 4.2.2 QtOpenGL >= 4.2.2] , , [have_gui=no]) dnl BTW, the use of AC_CHECK_LIB is discouraged in favor of AC_SEARCH_LIBS! # Checking for glu (depending on the platform, it's in GL/ or OpenGL/) AC_CHECK_HEADER([GL/glu.h], , [ # this is only needed on MacOSX: CPPFLAGS="$CPPFLAGS -iframeworkOpenGL" AC_CHECK_HEADER([OpenGL/glu.h], , [have_gui=no]) LIBS="$LIBS -framework OpenGL" ]) AC_SEARCH_LIBS([gluNewQuadric], [GLU], , [have_gui=no]) # on some systems this is needed, on the others it doesn't hurt: AC_SEARCH_LIBS([glSelectBuffer], [GL], , [have_gui=no]) ]) AS_IF([test x$have_gui = xyes], [ # settings for Qt LIBS="$LIBS $QT_LIBS" PKG_FLAGS="$PKG_FLAGS $QT_CFLAGS" MOCFLAGS="$QT_CFLAGS" AS_IF([which moc 2>&1 >/dev/null], [ QTMOC="moc" ], [ QTMOC=`$PKG_CONFIG --variable=moc_location QtCore` ]) QTLIBDIR=`$PKG_CONFIG --variable=libdir QtGui` AC_SUBST(QTMOC) AC_SUBST(QTLIBDIR) AC_SUBST(MOCFLAGS) # check if floating control panel has to be forced PKG_CHECK_MODULES([QtGui_4_7_or_higher], [QtGui >= 4.7.0], [ AS_IF([test x$enable_floating_control_panel = xno], [ AC_MSG_ERROR([With Qt >= 4.7 the floating control panel is obligatory!]) ], [test x$enable_floating_control_panel = x], [ AC_MSG_WARN([Found Qt >= 4.7, forcing --enable-floating-control-panel!]) enable_floating_control_panel=yes ]) ], [true]) ]) ENABLE_EXPLICIT([floating-control-panel], [separate control window], [ AS_IF([test x$have_gui = xno], [have_floating_control_panel=no]) ]) AC_DEFUN([BOOST_LIBS_CHECK], [ # try to find the given Boost library AC_MSG_CHECKING([for boost_$1 library]) for dir in $BOOST_LIB_DIR /{usr,opt}/{local/,}lib{,64}{,/x86_64-linux-gnu}; do for ext in so a dylib; do # check for "-mt" version first for lib in $dir/libboost_$1*-mt*.$ext $dir/libboost_$1*.$ext; do AS_IF([test -f $lib], [ BOOST_TEMP=$(echo $lib | sed -e "s|.*/lib\(.*\)\.$ext|-l\1|") # the first match is taken break 3 ]) done done done AS_IF([test x$BOOST_TEMP != x], [ AC_MSG_RESULT([$BOOST_TEMP]) $2="$BOOST_TEMP" $3 ], [ AC_MSG_RESULT([not found! (Use BOOST_LIB_DIR to specify a directory.)]) have_ip_interface=no $4 ]) AS_UNSET([BOOST_TEMP]) ]) dnl enable/disable IP interface, default: enabled ENABLE_FORCED([ip-interface], [network (TCP/IP) interface], [ AC_MSG_CHECKING([Searching for non-default Boost include directory]) # the order matters, last one wins! for incdir in /opt/{,local/}include /{usr,opt}/{,local/}include/boost-*; do AS_IF([test -d $incdir], [BOOST_INCLUDE="$incdir"]) done AS_IF([test x$BOOST_INCLUDE = x], [AC_MSG_RESULT([none found.])], [ AC_MSG_RESULT([$BOOST_INCLUDE]) CPPFLAGS="$CPPFLAGS -I$BOOST_INCLUDE" ]) # now check with possibly redefined CPPFLAGS AC_CHECK_HEADER([boost/asio.hpp], , [have_ip_interface=no]) AS_IF([test -n "${BOOST_LIB_DIR+x}"], [LDFLAGS="$LDFLAGS -L$BOOST_LIB_DIR"]) # try to find the Boost.System library BOOST_LIBS_CHECK([system], [BOOST_SYSTEM_LIBS], , [have_ip_interface=no]) BOOST_LIBS_CHECK([thread], [BOOST_THREAD_LIBS], , [have_ip_interface=no]) AS_IF([test x$have_ip_interface != xno], [ LIBS="$LIBS $BOOST_SYSTEM_LIBS $BOOST_THREAD_LIBS" dnl to be really thorough, we now try to link to the boost libraries AC_MSG_CHECKING([if we can link to boost_system]) AC_LINK_IFELSE([AC_LANG_PROGRAM([#include ], [boost::system::get_system_category()])], [AC_MSG_RESULT([yes])], [ AC_MSG_RESULT([no]) have_ip_interface=no ]) AC_MSG_CHECKING([if we can link to boost_thread]) AC_LINK_IFELSE([AC_LANG_PROGRAM([#include ], [boost::thread dummy])], [AC_MSG_RESULT([yes])], [ AC_MSG_RESULT([no]) have_ip_interface=no ]) ]) ]) ENABLE_FORCED([ecasound], [Ecasound soundfile playback/recording], [ dnl Checking for libecasoundc. It does not provide pkg-config and installs dnl on some systems to non standard path /usr/include/libecasoundc. AC_MSG_CHECKING([Searching for non-default libecasoundc include directory]) for incdir in /{usr,opt}/{,local/}include/libecasoundc; do AS_IF([test -d $incdir], [ECASOUND_INCLUDE="$incdir"]) done AS_IF([test x$ECASOUND_INCLUDE = x], [AC_MSG_RESULT([none found.])], [ AC_MSG_RESULT([$ECASOUND_INCLUDE]) CPPFLAGS="$CPPFLAGS -I$ECASOUND_INCLUDE" ]) # now check with possibly redefined CPPFLAGS AC_CHECK_HEADER([ecasoundc.h], , [have_ecasound=no]) AC_CHECK_HEADER([eca-control-interface.h], , [have_ecasound=no]) AC_SEARCH_LIBS([eci_init], [ecasoundc], , [have_ecasound=no]) AC_MSG_CHECKING([Ecasound library support (see above)]) AC_MSG_RESULT([$have_ecasound]) ]) AS_IF([test x$have_ecasound != xno], [ dnl check for Ecasound program (required for soundfile playback) AC_CHECK_PROG([have_ecasound_program], [ecasound], [yes], [no]) ]) dnl Checking for Polhemus Fastrak support ENABLE_AUTO([polhemus], [Polhemus Fastrak tracker support], [ AC_MSG_CHECKING([various header files for Polhemus Fastrak tracker support]) AC_MSG_RESULT([see below]) AC_CHECK_HEADER([termios.h], , [have_polhemus=no]) AC_CHECK_HEADER([unistd.h], , [have_polhemus=no]) AC_CHECK_HEADER([fcntl.h], , [have_polhemus=no]) AC_CHECK_HEADER([poll.h], , [have_polhemus=no]) AC_MSG_CHECKING([Polhemus Fastrak support]) AC_MSG_RESULT([$have_polhemus]) ]) dnl Checking for Razor AHRS support ENABLE_AUTO([razor], [Razor AHRS tracker support], [ AC_MSG_CHECKING([various header files for Razor AHRS tracker support]) AC_MSG_RESULT([see below]) AC_CHECK_HEADER([termios.h], , [have_razor=no]) AC_CHECK_HEADER([unistd.h], , [have_razor=no]) AC_CHECK_HEADER([fcntl.h], , [have_razor=no]) AC_CHECK_HEADER([poll.h], , [have_razor=no]) AC_MSG_CHECKING([Razor AHRS support]) AC_MSG_RESULT([$have_razor]) ]) dnl Checking for VRPN tracker support ENABLE_AUTO([vrpn], [VRPN tracker support], [ AC_MSG_CHECKING([various header files for VRPN tracker support]) AC_MSG_RESULT([see below]) AC_CHECK_HEADER([vrpn_Tracker.h], , [have_vrpn=no]) AC_SEARCH_LIBS([vrpn_MAGIC], [vrpn], , [have_vrpn=no]) AC_MSG_CHECKING([VRPN tracker support]) AC_MSG_RESULT([$have_vrpn]) ]) dnl Checking for InterSense support ENABLE_AUTO([intersense], [InterSense tracker support], [ AC_MSG_CHECKING([various header files for InterSense tracker support]) AC_MSG_RESULT([see below]) dnl the InterSense people changed the API with version 4.04! dnl Checking for InterSense header AC_CHECK_HEADER([isense.h], , [have_intersense=no]) AC_CHECK_HEADER([types.h], , [have_intersense=no]) AS_IF([test x$have_intersense = xyes], [ dnl first we check for the newer function AC_SEARCH_LIBS([ISD_GetTrackingData], [isense], [have_intersense_404=yes], dnl then we check for the older function [AC_SEARCH_LIBS([ISD_GetData], [isense], [have_intersense_404=no], [have_intersense=no])]) ]) ]) AS_IF([test x$have_intersense = xyes -a x$have_intersense_404 = xyes], [ AC_DEFINE([HAVE_INTERSENSE_404], 1,[Using InterSense library version >= 4.04]) ]) dnl enable/disable compiler warnings ENABLE_AUTO([warnings], [compiler warnings]) AS_IF([test x$have_warnings = xyes], [ AS_IF([test x$GCC = xyes], [ dnl all warnings ... and a few more! WARNING_FLAGS="$WARNING_FLAGS -Wall -Wextra" WARNING_FLAGS="$WARNING_FLAGS -pedantic" dnl this makes -pedantic less pedantic (Qt often uses "long long"): WARNING_FLAGS="$WARNING_FLAGS -Wno-long-long" WARNING_FLAGS="$WARNING_FLAGS -Winit-self -Wcast-align" WARNING_FLAGS="$WARNING_FLAGS -Wmissing-declarations -Wredundant-decls" WARNING_FLAGS="$WARNING_FLAGS -Woverloaded-virtual -Wnon-virtual-dtor" WARNING_FLAGS="$WARNING_FLAGS -Wwrite-strings" dnl -Wall sets -Wstrict-overflow=1, we set it to 0: WARNING_FLAGS="$WARNING_FLAGS -Wstrict-overflow=0" dnl those should be enabled at some point: dnl WARNING_FLAGS="$WARNING_FLAGS -Wold-style-cast" dnl WARNING_FLAGS="$WARNING_FLAGS -Wshadow" dnl this may be too annoying: dnl WARNING_FLAGS="$WARNING_FLAGS -Wconversion" dnl maybe only warn for sign conversions: dnl WARNING_FLAGS="$WARNING_FLAGS -Wsign-conversion" dnl interesting, but maybe too annoying: dnl WARNING_FLAGS="$WARNING_FLAGS -Winline" dnl suggestions from Meyers "Effective C++": dnl WARNING_FLAGS="$WARNING_FLAGS -Weffc++" dnl turn warnings into errors: dnl WARNING_FLAGS="$WARNING_FLAGS -Werror" dnl WARNING_FLAGS="$WARNING_FLAGS -pedantic-errors" dnl part of -Wall and -Wextra: dnl WARNING_FLAGS="$WARNING_FLAGS -Wuninitialized" dnl part of -pedantic: dnl WARNING_FLAGS="$WARNING_FLAGS -Wpointer-arith" ]) ]) ENABLE_AUTO([isatty], [support for isatty()], [ AC_CHECK_HEADER([unistd.h], , [have_isatty=no]) AC_SEARCH_LIBS([isatty], , , [have_isatty=no]) ]) dnl trying to mimic the default setting pkgdatadir="\$(datadir)/$PACKAGE" ENABLE_EXPLICIT([app-bundle], [creation of a MacOSX application bundle], [ AS_IF([test x$enable_app_bundle = xyes -o x$enable_app_bundle = x], [DMG_NAME=SoundScapeRenderer-$PACKAGE_VERSION.dmg], [DMG_NAME=$enable_app_bundle]) dmgrootdir=$(pwd)/MacOSX-App-Bundle bundledir=\${dmgrootdir}/SoundScapeRenderer-$PACKAGE_VERSION dnl WARNING: any user-specified --prefix, --bindir, ... is overwritten! dnl however, it can still be specified at make time (although kinda useless)! prefix=\${bundledir}/SoundScapeRenderer.app/Contents bindir=\${exec_prefix}/MacOS datarootdir=\${prefix}/Resources pkgdatadir="\$(datadir)" docdir=${bundledir}/Documentation dnl linker flag to add space to the output file header padding, dnl so when dylibbundler is changing paths to shared libs later on, dnl longer paths will fit in too. LDFLAGS="$LDFLAGS -headerpad_max_install_names" ]) AC_SUBST(DMG_NAME) AC_SUBST(dmgrootdir) AC_SUBST(bundledir) AC_SUBST(pkgdatadir) AC_SUBST(OPT_FLAGS) AC_SUBST(PKG_FLAGS) AC_SUBST(WARNING_FLAGS) AC_SUBST(DEBUGGING_FLAGS) dnl List of output files generated by AC_OUTPUT from their respective *.in files AC_CONFIG_FILES([Makefile src/Makefile man/Makefile]) AC_CONFIG_FILES([data/Makefile data/MacOSX/Makefile]) AC_CONFIG_FILES([data/MacOSX/Info.plist data/MacOSX/DMG-Layout.applescript]) dnl AC_OUTPUT should be the last command (except maybe some status messages) dnl It generates and runs config.status, which in turn creates the Makefiles and dnl any other files resulting from configuration dnl usage with arguments, e.g. AC_OUTPUT(Makefile src/Makefile), is deprecated! AC_OUTPUT echo echo SSR_executables: $SSR_executables echo echo CXX: $CXX echo echo CFLAGS: $CFLAGS echo echo CXXFLAGS: $CXXFLAGS echo echo CPPFLAGS: $CPPFLAGS echo echo PKG_FLAGS: $PKG_FLAGS echo echo OPT_FLAGS: $OPT_FLAGS echo echo WARNING_FLAGS: $WARNING_FLAGS echo echo DEBUGGING_FLAGS: $DEBUGGING_FLAGS echo echo LIBS: $LIBS echo echo LDFLAGS: $LDFLAGS echo echo echo echo ' ___ ' echo ' / ___ ' echo ' ___/ / ___ ' echo ' ___/ / ' $PACKAGE_STRING: echo ' / ' echo ' ' AS_IF([test x$have_intersense = xyes], AS_IF([test x$have_intersense_404 = xyes], [intersense_version="version >= 4.04"], [intersense_version="version < 4.04"]), [intersense_version=no]) gui_string=$have_gui AS_IF([test x$have_gui = xyes], AS_IF([test x$have_floating_control_panel = xyes], [gui_string="yes (floating)"])) echo "|" echo "| Build with tracker support:" echo "| InterSense .......................... : $intersense_version" echo "| Polhemus Fastrak .................... : $have_polhemus" echo "| Razor AHRS .......................... : $have_razor" echo "| VRPN ................................ : $have_vrpn" echo "|" echo "| Build with Ecasound support ............ : $have_ecasound" echo "| Build with IP interface ................ : $have_ip_interface" echo "| Build with GUI ......................... : $gui_string" echo "|" echo "| Enable debugging/optimization .......... : $have_debugging/$have_optimization" AS_IF([test x$have_app_bundle = xyes], [ echo "| Mac OS X application bundle (disk image) : $DMG_NAME" ], [ echo "| Install prefix ......................... : $prefix" ]) AS_IF([test x$have_ecasound = xyes -a x$have_ecasound_program != xyes], [ echo "|" echo "|> WARNING: Ecasound (the program, not the library) was not found!" echo "|> It is needed for playing (and recording) sound files with the SSR." ]) echo "|" echo AS_IF([test x$have_app_bundle = xyes], [ echo 'If everything looks OK, continue with "make" and "make dmg".' ], [ echo 'If everything looks OK, continue with "make" and "make install".' ]) echo dnl Settings for Vim (http://www.vim.org/), please do not remove: dnl vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:comments+=bO\:dnl ssr-0.4.2/data/000077500000000000000000000000001236416011200132315ustar00rootroot00000000000000ssr-0.4.2/data/MacOSX/000077500000000000000000000000001236416011200143235ustar00rootroot00000000000000ssr-0.4.2/data/MacOSX/.background/000077500000000000000000000000001236416011200165205ustar00rootroot00000000000000ssr-0.4.2/data/MacOSX/.background/background.png000066400000000000000000003037161236416011200213570ustar00rootroot00000000000000PNG  IHDR`M pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_F|IDATxw$UTUٜJQAE@E0ʕ z}QDWE *fDD@ҒavBǪ:vtة^ϧ١gNթtZkM dqh'JH$jd2MZTROTjEj&ˑNR66͒J,vR>b6dv@Ksn|ߧP(dB2\D"m۱Bֺu}ujƴٹ,C("Em4f5ASMZ,z1NVJZE.*l+]6ŤbZ_4[͚;7QER-\`YV-J"LES*Z h]* n+BYi+ GzzN֮mqgu}9JPnppp&1g48Rixx=׿R TZqخQFֵ KU0¨9MRM T*dDe) Tܸg¬̮vR[Hzk55_Bt:]/R6]\e ՚T*fs(jcTKhdS8\O`W Bb6V9՞iYV cPP>cnkMzP\3!Ms`co#>Q%R 9|"ۏ o{4K;[%؎u9ٌLFt]*i-av2k}\ Jȣ4L;W QsC QOE=zµv:*t+x54SH_^=6y)eӨ0>!ʚݏ[ln4x:ESMD-ZY%,X 2p'r6۰cc<8HhqU+߮n$NA}aooW÷냃ǣ~;O9ׯgܺuv-6m#S)r9N0NktI'!lAymxq!K- h7N7-K({mZM /kf\?)J /gC$3tƍ R:r9XO?7-O=0 .>s{f2Bw7o[W.>lmKikvGÛ{\[U,BN޿BU/0Ѱ /իYj^x!.</ QFl^koc?#h/rĉܷtAr92l-~~;]t;3z*d\~un a͏FX*_[hc;-)86l;!z[&Mbo ˖)X}MJwgk٨dvu0wz׻ذaa Io~UW]ŏ~#/k0004.G+ c@)^U-z5 /^?`Ji'u?dͤIrХ\^{ x" >>90o===<\v.ZR,YBww7t'93(Js9b]ǽoFf͚)5ϸc#q[ hd<Ɲ+ ]i1)煨+RRDXP(я*Jx B~OsY?Σ˝w~ABrfɟ_F^u+PMs͌=.97[]Ƈ:RCTX|9se_|183s=ZXk֬ӟ4?gqFC&G?+Wl[[.T"z9q!pWJ>Vʌ1s(E+~?!2Mk nuk"WCZ0^Z /z XBVF+jaDZBUGVKRK]äIeuSf̀w{gĢE|T_'L7=]fͪ\̧=믾g̀"e8aVXvqN99s9X`Aӛ$駟[o3LC<իW0wܖ/Wn&\#+`,4nnUpBbY~;RJN<ĚRK5h,ALKhBBqELA /JCgQLD(fOTx[WtUwU޷eM% p݀V% bA߮AYK15ڝNC4fѾ[ɁoȪ.{3Q*ij;n[hdm_'K0hE+WO>_ϗe-_>G;KP`֬Y|s?A(L8 ~s=rb ![yCe*… `֬Yu+g`֬YÁo;{%K̋/ȓO>mCde%GۍXJ6ϻ+? 7Py?C N8Bʼy7#@Yr>:.B3* 3BWQLgT[` (%tYf4 e.Bj<º}qzB)yR2o<;<|2<է !xG}T*.B.)X`,:!\s o78v_O~‚ 8#ZcvVMTh \qMmjF\@6{~w]oXO=SL,G 1Dm7h6Dip 5 kYZq1m$Z8kK;lt.L.v-3qܧBĜ9ue cU2ڵkoH9sJX|фּ<#h?^3[)8Be <ĉm@XjʷCy'? +FW,X?_צCq^~e\%0y}0sҥpm`qmZ!õ^F),TZ M*S&L/Ǒe :hۈr"tGW$yոs֚Hp~!z{Q;턿뮈jJ1J>;w./,NmUL&ÐQԕN$̜9cdž0;3gG ϚY@&%G#-܂RL&1Ӱ6^Y5k,ƏԦ0kMC9$S꫱,k/*A?-dr^JcǎeZkOPਣbʔ)60{-J`wЪŽl V.*TvXe0Xj!H!IRR)3 LŋI<8MSO=W2DKJ9Jac=~<]]]?bmsEZ;~FbSߞ7;sء>/[H-#z3nxm.rRJN:tLJ{ qmS^|E^!l5k֠nZaVJRHyRA6/_.¸2HOOϰZ'(PR7^rȮQ+ۤ9&ɦf5=gXdP*15dv; rEp0u*g.ߘ2e w_\馛>}:l!?i{= mbŊ'z{Nɽd2̜93dgnBZ|9J)oL85&Mjn-oG? ׿62}}}!顫kس 5K.`= LJh?׮]KwwwKZ[nZKzĖ8=/Z wz׻`a84f g=0w.pF3Ô) \qvꩠ5 Ϟ{Irp0(r}_`RUD֮]K|ah*j.CkAm8Ä*}&LG*!:DV7 BԎ~^կvZJfΜVWZE?Bvu׶- 6fĉ oF5o<"2K7̅ 7Yfq}m5Nr&.RV"#&KO`TȸXkLxywv9f#}jU4nLY_?җc*D =o޼Pue]  @1 3J1i$r!7Tc6^~elm֯_ϪUm9slSO~/x1cڰm8S7G>>OlרXn]8glp}bjk̽ M4)LnϚ5 ۶ RC+$v}q'`tBG!uWYVNL~56w.ؕ^ɕW)SVFԧd?Pƍ (O))NJFJhp-zwnضK/u]ҥK?>sL~~_>gy&< "Hl;K{g&m/|!:&Ԫjpe2O2m4ͲF7l7 RmMpH$- vZ3}t^|E-Z{=bBSY;7W;K@c4.qLZM+@)-|RխzxzL22΂\G}qxց"?AԚ\Y@L8 Ρa+h=X58}}}H)9>{o^~a)p5 ޕyc=ƶnq;ڵkO=TxVxڵkVdsq=pM䶷[n4M j{ur駳~QkppMB9>===H)4=.^mvDY$ԃw X=8z D؉UJ5FZתU>_φ H$ܼ>öš5<~Rc &S)X].B1\ABR3f@\rqxCxc9SN9d2… CwŊ{?a~_ oy >y^z)sc!s|osțH$zH}}}wq'?ŋl3K/Yf񖷼_~| S6v0m…L6kvZBi48F#v Zikd'QV\xGR^'OfҥK\;ޱpU)IIoo/ƍcR ɬ[m Mduu}u߲,7K.ϰa?rGSLbu׿o|+U_@U!œ9sSO>߹;9 u]qi8 JؼwϟϝwwM:W_eʕL6˗ssYg;pGsi/{0-3Z rGf;8.7#𶷽-~Cΐag_B2b8lrܹsg? /;Ȗvō+XJv@fkhݝMxTkRfY(I7^___@W.֭:/g\v17ך5,.`}%ӄB xn3g^m}7ҍ{ڴi!x衇y't 6_)%Ǹg}xc}vezPs=^CcڵkC|9 NY3fp_ pNg6mZjJB4k,N͛iUW]EWWWXsG4yIf/Ylǁy֭rܹs7fMʕ,X9QTM$ׯgҥNaN;ċ/IeH$Ї>ٳYti =5kea,YmN0\r Gs1c0aV^MP(w;e˖q_*3f |.n-_TtuufYn\r ?yWCѿoM)$sh ŕW^ɭJ___fZLrGXdڵ|f͚5\z饜s9,[,d2ɟ':2 gN`ɒ%\q<x믿믿:8Noo/d)%fBJɢE}ݙ2e K,^*ތVp«Zn-UrڈjA JjV[W"GEi61\WFæ'hdB22ՠ¢2md]*Jt}܈+`Db2g/^w`rX[5+T/du>e ]Б׸E\C|lL+hkc̙+,Zvډ_~o...!~,[,D~7XfX"<ùCAs~yT'x~,⦛n=rرcYjGqDB4x|0r \s _җ8s9?>\nHF3˙g_ξ _=Gu=hZItSM>D"<=/`nN8x ;_s=L&#?D Qd&WSf]hz FJ4T-\ o\1ڲx=L?+jg{I& Wi]*jfRjHR-UEL&!† x'(z-'4*P x] ,@]! n|Aw_ qjVS̜9u|+_u]N=Tƍ `19sa@JEUV ]bȴiŹn~(0 >PtA$I-Z~|>'ϓfdYrܐOX Y)'Nu]ǯo,Ys9'dF)= ɓf ロޛKsW.,$Urn:F)G?Q9NL&须; ~ tXNI&!We?ǼO^JHGQS)Z&JV!:U"غyݡTbrfDÆ*ڵP(b! D']c c~opugvX`=^?s&Ş B(T|_#duYp xL0;b hߞ|> /|k_cgҥ\~}]D"G9dlB/z_Jo[epyR!yPJqs17.?|ӟF)'̙3?~9S{Ї>*/yPxvQD#*;y? O<1 ?s)p}.?3mڴYz^tEk.s堃b̘1d2v[-ZD&C x Q0Մm2N#|͞NkjuݪkЊŢ. P(|>'|>ŢVrU?ׯ:l6s\sv~/~Qzާ>P[N_^y=8866OCSOź?i'xR?|?d]ugu#)=.{4wm75x.}wvpp0|B5ktPkƩX,uk@׹\N|>gΜ;7=ayK40N5h>3~~NC3fh@+j?묳itOO'>/_}m{WgYD}{nf:χ?00yN;M׀9sX vm&衇j@O6M }}}/94٫T*5E~^~2E],uT:RKv]wWju)Ҁ~k_ӟփO?.uy?S>T8C~VN] _/8qΟ?__\עE o +Vٳgt:y晆Rp뭷/}K{^駟OirJ^{Vx  7 i}Հ~RHFZk}w]w}MH)bFC!ZVHb16_6Ccd2($|KqB!hzr <0w.}pܽo{?>hqVѩ'K ^~[m~/d›r9lC_¼y|WH&ہַĈ<#y!ĉ'__* K_<ݜvi̡֚ض駟ޔ˞d2C\'|>˗{|gltL~a&LP=IbiңP(n]Tڄn]+|_ ֩D7.Sl m7600eYMILiy&]/U1<R _\e4ZsZ=y睧3йŋ7ݤ7&S6o9Z׾xF_ZbCi__.Jo֟'Y<#7|x^J)߯}ol]-QOX,ŋI&i˲M7T=Z3#<'[=kJ~ꩧK/T*ofl[Oz:_Z͘e]:뾾EV3\lv]]ppM\l]|Og;dG 'fe9nT ʕEIvuo@X\TN!8/;n-we8}}b rǏa{s6;ʡI9q9fnԷp s1y#r.SoG4+nسbuh.nEyAp\GU?3߀O3a١T.  j T Ǝ*?H;<0(G][wޙ|XO^{.dرJZ8NYO0\ wqL&LAߜqUW]iƇ>!XFSv'K)A܆Ȉ+x%|x8^.v{,o=|8؀^CP.YV|֭Ó= &NVɓ9Wbݺu|=y5mjAEaAL*u-qXqo癴Zk'Bpjժ6\ [C|crkSz9qνE //;Hsw^W^o;cwF5OU>X$ Z^x^}&N[^ 8[)<<ߚ3Y^GMnfo/g V<,'t=JG l5ʶɎdvm!j;tSSz)MSnO.; (C6oɊ΀V68 i+ZY&f15ǵ,( Jz@!c VۙA01Tf`o G2&NȮ?f̕RŖҾs\X588H:nzVk}UW@JM,+$o Т_ x!,(Vkh{ Bv@bSH⤙x7$'l W6͞=t:+Vdɒ 駟g`\.Px4yd0aXVqSPo.֗&Xz5O>dCTR 0rs%Z I59W͊j_s:w}ћAC޲,,eJ !K]RJrRJdU(Bj(e!dOOOMTƨHq>VC!e2!'4f8|>v-7>dM_uZ.\?φ >}:seP)yW630xRdi@~\׮B+VpII5b}m Pa5+cƌʉSMFycڈ箵Qԛ&d׬l'c O<k(Ť\.^H&L6{ϙ?]vu+fC#%ͪ$+eҤI?|>pL{mWDrs\<ܳTF!U*zaЍTVm{ƫjqyRtuuU%ƇwSMGuk^6fF!(XLQgq(RD^،$'p{8`34"`jT?\Ma.VҼTot, Qa]Z@RlT,05Q`P4(PBg !˲[MѰ5FijkmV  m'Q~?ks$ ms],R=2eS%~,J$ɪBAX)J!LU^I3`00Vհ ͟@5D3 1 T-e V}mFAޮPhhF`Fl4 e&L6?my@Eڲ(;8dUnVKU*UnzQ)#<)jfbuknԳ 0kqP,BR#W"x%,_QmKrq 诣@hm~/-Z tnN֜w3>(I=jJ󼪠 @dnS=CV1ͽe0%͢ɘjFVS͢r-q4AT8UZqϚ&POWΰoFQ+A]7FP JR0Z/}n8}zw[ &TPM@ԺvC]<E\)!8;LX僂Dk@+@}+e!l' n:It}T\DQBiT&!Hߍ^(B5eTot.*ͤiuWJ5me Æ&Tu ˒ض9ux^qbS}`=)m--@H 4|64i4C5r2a mD{%B/"4Z pcz -YS#SS*#ݮNCj8#v>o(cy!`@eb DCU *Y-ʍFQOntV,f9qZ=k+yS#@QA%]"X_@;HKq|5R mrv, B+^(^EB ΚePJb ),YDש5PTcAdIe8IJ 1n4+:8V T=jo-%`BK A#X0BY(VϵpJ<8M>Bi?jTP>IXND!=<(B Ar]-$aIUwLYva?0RXBK!?-@Xv pTh|~ AD]܊e4DkSj3Nj[%2c*Лy^p :6zY\ E."N>A{ }JڷXwJ8X " rގV>6J BEF4CZ#AD9%7% `ݝBr}$VgƝ)łЮ ҭ06qS5wJŽX UV :8]Rk H -sx"$d|l逰ў @ FkV\WH&ZrXRT,ZPaY| H;7jKƀҠKh KH<-)&bYNZHXf3(=Mn>SXI2NQ)DUv9Icjx֣ea'(4Z)hUDXIL`Z*g  ˶vJGk J"N7 $$Ð?\I;AI.A^NaKe `])蔫\@u sؤS,zK:4Id[{?4b9,nJ;  ؑr%'RB ZwGhrhe"Vm4Hސ1 O;dQ;2 hI'"HFixW|%$9BhQГ3S4iFBE䀱N-2@ukZ(*o>:VOPUטZ#{عPJ؉1^ $"B_h M9!BG]Mh/H"xdKy vd G2#inԝ.QV.ۚ-(n绉<MB Px"Kd)$&E3 2HKnǹ` U$jp; ]mû ! 8h R#u !,,J(iZE"S"#\ %AvKPwM 8my+T&cJbF)ޏVm5UA>GkXV"TK|RDTO.sF6^d;2bRDz< 6BXhڗ mQBnļXtCzFw12HBpR]Nkg[Iiє9dYIY~za0Hx#a[nPlu5ehPZ SKhzƛpw44zϨvb1%G D!j)yjR-Y L638tF#c7Ud Ce+#$mM/vH',DyV1j}8+}[Ri2>x*8x)E ITEaj<-zbJ/abZ2Q |>c5j+#JO5aj5@գupJjX}c%E"PSmdN}e(t:4Ѐ8BhdhZT7k (f!*Pk j@MUΫei}?lHLBP4Z;X!pIr Wm|q,(V5 j@&4ag V`Y P(yB!+9* _8 *{VEPR džнɚ\;͘d8qYQfQa, ZBcp_-Z9֍ʂVebј!*{0I-@T.РReKeL,v;!V"qGq7:#PkPcW4\%X-zn\w0όƄ]lF`a*X;۶C68*gBZ$j՚32zYMGco9HMEr.@qn,@ | c}P"EBBFx0sLۦ7yaQ4 $@ k,]rZ$щdB@B"l[xh²PJQb5$mŒ >)YR 7"eO0 dmKfC~PٔL˲j PǸAR[gy ^)ܘ&Xup"_qэ H)4Θ3H,m8o,miEm K xW.fQAlut3GN>Nkg&tF;w؎ qZyŃ|.PFehV֖<#3Z6X2BxAt|dRMCJlҩn[@^GYք-zl⤁]`f-uqoD(N Oi,–ةSh%J8e6 簝5jN< sa%R@$aa!%Ji(Ճ 倫ˑo!y)qøDFſ'Gjl٬U \?bnpl|@ @ZvuUxvo/u;g$BklOTHXBй^Dd.ȇ/m.c|fͶqlظ96 g))kV9\Dl & Pb(j&@}ѷ vbtוa>guSVmkS|w`b:% ֎ێNG"^kYڭF{Pƒ>ŢFJZH.Q( D"b]Cki5 B(He?4P( AU_I[apH08;jRքVڙF+³ڞVhl[\vՌòpCJR(H! dGflq#5cĮk=< 6qVqٛa8ɵk嗃/!v0R-ŋabz^{ Y*!l2Y``\۬=D$nv:%#pkkfɄHr#Ma$V"SVdTǸdzq;dgݓ݌enbm7}jUطO? XVI\,z{!Pa;h~=0i;̞)mhcG7lT;5%hS_ah Br*NJZJJHRZԪw<{w4]ҪG쾰[]б~36`Ŝ>M<.yRpN<3u*/>:l>Rgy$} /SaΜQJbѵJ'?8FF(4¶GPY(DTpLȨF7gXSVtntO]=k -wt8@Zݝwr=Լ7d8#8n]9^)Ē%p%pYYb֣e̸7^\a4=/nDY#q}}l*", 87âUكپh}W0>&{ժ@9] ;ÞǙ#<>)>5},Yw#a{{JA`,Ny.|F{.! 4vm7lԖ3D7e4dp},,僥%ᢄvqѠ3Fq]v\B!aRx;.Zĵ^ 5\RgBisWp)p̙u͚hlJY$7GP]vo'CO?o>ПK,"I0Nquh*kM)T( w ~=6|tRl~џN;Pi/o~Ǝi8?`ʕr)p$*MgÏ<Ÿ'wsre0>vŁ,"aX﹇/?g_?p.bn8p?6~miYܞhK+^g!ue!֠BFK]> FR|IRv+־/Fo~;>']z)vܹp0aBFX8t_L'iN2D`ȜH hΕRAdOJ)A+́K\YvejAk׺y7h&Pa-btSM?'^d5k-w˖-ㆋ.⸵ka8T{oXVi%@)D ;zƦrMAl[m\ oOmP]a.F-/ aFlk d5jmYV^7f< pc+w5tZn ݖoj/!}LkOh>dvz%X̓V &Nllq|.>rWpα'@WE6#FVCvq_)ԪuS GyP sqc;mx(4)\\W&/y@STQb) x7Xu) uCy(Fj`y٬ 0bʿTHA0*@֒[Ei{H33οBn;9qJ8Cx1zjNf<1cpSOEgnpI0y2_Ywuꫯrw"C )rs*z\Kx6Fs&aKT Yr5X`3V+ :˲BZ(Fpsg(CwZԷn3a"QUXWEi=ZqMBiyL&:o;LD`PdEʋf!:-eörN`.8pX zJ%N R a{_wꩍwC~yp ĉSnwTVkq "psP%T㌩Ge w-+fmFP)@T,Ja*%jyQzjCj,( u=#1}Lai׬k<ڀ>(HHCӌ0FH9x\lӳـ ~ n&>N>8h3k~fӧܹttI i'&RL,n]G5ȡZ5PvcrnûxG[*V 騂VJX#+xRJ2LC쏕JZHi0JxYXZ 1rIBzX \vA }+^OmIji'=ٵ^oUV-,P9&$Hl$<ޱn|;|? 0eY8,2G+d3bH[utH!6 ċ#D ;#}8/5{, !$>k|?@mn*1wuo}k p1pY-c}Ć 0aSXEInnAkQ"$i$9n?eu=-`1k5Hm!pH)6˾3F#wڪ8M=*c/É's<3#KW36K-ossd6"Tnbnp#sW <ָE1 kݿjP0³$tZS>%E9xAjE  Rir]FtYiHHeQeؚ/a*[X \ψ0DbW#FX4vbㄊĬMR_.f.+8F#PF滬2^D츄`v}7Wǧ>qf}{g n<vg&rx6(v-DdqD̵l|;,J$8Yv-~:H /wqXsnu5T_Wņ%%j<ф *ۤ GB(UBT54a8O<0Xx".>\MV0uP L j2THF&4԰>$I')B+$B i4vK2lzDV[c>)  Ijh~Y(V dN<}?v,_K8`?h~[u0iRf$'TB{{:YI̙P&a_}5's啔J%L~Y]w%@Y0Uj CP)k q-!+`>FnV*zBj.eW/mXցPEWQ;#t3LK6J5- aTJ3l̛^C!fZB*C dmt*U:hUz Xy&!\T>C;uطƷݖeoyO=x{bZCO{F?R뺁Pr]Do/-0JqՏS>gx S8XTRԊ=G&3Ԉel# j!hl mgi5fY#h4CQcqkv=#.{O:Z W,IUWq (mճjUYWw `wG+ %K+p$hEZh]+EʞJk:6Ի(uky ,jC+b%o"l?!X{;u)\s5|@z(+LR"<aAhN)Aڵ$_y,A-Y<_:P^x+HgAcPѸVœ(H K ѾeۄJ1ٜ"W*X " oL2`LQKֿ? HHtbwQuB?RHiQJ8.Ntpa3JZi ~~=%)~ LC 8΄PAZ4eLCI"8R[ kt3Æ&>YFo\=ko>uG?==|3D!x gr9(c&]Goo/78ϲhQIn'%eR|} (F'JXH$aBbL/>uZ{L!6N9x˖- bIM^$BT:֯fL:v3&#"EcӨDZ&q:B QGc%Ie\k(כ!iHX¼]` rR\Bv' t.^_rrs10c >1Kk2ZyJ ?0W\qE[ltIO)26Go~sfu P]F):nj4N9}\ho\R"hXjnzsσF 5/?κ/Rڰ&0sG S |k@hV daH4X.Wb1(;Co-n)˺p3eB6hByjc‚ +Be"HzRݤO&iD|n}^E $& C3h!Ӄ:0ܽ멧?C-3f֬ךU" (ŶŎJ1TrhpJ%:5;찚h<a[ %:esx"B"\d,NgUiTJI !K $@{@?ڵJjTb`/b6,OLϸqӶ,f6Pw7vZ@Cn ֠pǏ'N0v[;Al9Ci'j_YfJ%E@ֶm%˞BZm5'csm7h!D 2ehKCcK34i g]KB2l5Y>1FMjV>qw:4%ϣ#SwdnPeϖ!3{Ҳ t\:zճk3zW&|MH\T=qEP$BYaɖ^je; ƩSW>F|j:>ڨ[|1/=}ֆl7-7 |l)C x$Z#_ Ck kMq͕1P|6˘LB#T ,TB}$?h!B'A 8}Ip%T=݌2TB|YvFi? oXlWYj{:)|:kZɋ!(xO2 e(6 (%wlUfBSm9@Z=Qz͘ &S-TE$"a*]>{Sh,JhJ"aYCR0ٮFFk%m(.^ɥX̑gdzȗCBR(!ha~."4 ~ltj,"1!>oPFJ:,R@zD7+n;:P_-zb#ʴ*6Ԧ%/qaXD5( Ճ22WƩ& R\ {tԙ%XNK88UVhض%R"xLx* <qd\JZ'Za9H`˩P&<-p-RȴC;INIǀ݅2Wz{I&x( r_4BXɲ2R !!؁"5|&+K!jA[jXUGfeZc{nPi G"bq!b( TVBHUz=kЭZ$cQ1THX˵8CT:۾֣\= R5J V(#{^.VRqQ5$(Y-< j6 mE%M-k^yLH$1k7 (RJ A2k9cxTI$J.I$HW!K%|c!-VJضUS[HB~:=6B!͒؆@1PrnpJT(ʂJkз,\.7DX"2B:$*& p jԹ|ٞ k)j4Ya(ch{Q'&JVIP]!DB)ӧRzsQN'5;GDg qm}qyIo6IQx~ l!|/OQB M|d!߬lI#kѽ}m4sH&[@ P"DRl / _Jb9a1QOtY RڈDBH-Ps8t оDž-tU|P&MJ4kS 砱 P4;}*Iv~waZ̚V7TBT HWJ)tU2zӮY!hӚZ[%3֪QPq*p !pʠީrxM`p|-ed2HlXKҢ ZF<x |aP` 6+[ߋ$> $ NtjxDRC)O"كvHaHKbcP5.Aܒo%ޠa9~BzA)lL T D%{"i%.{eƎ{R m[{QOQY~=tLWb(g)$uGJ> y(5,Rca܌R(jMB)nҤ豬ȸl7%Lm)&=Di3H3&%z$n(SǑ`)W/T .0n8yOv($* 4_ LjȮ@`?~'.8I;_*{KH,c{Km!JERTpU.6kW KJ!x 'ikG頋(CNΧ( &Ct{)!5ܙUuZ^gh{ tz 9 ȾW<Fy2"BYYb+ƍcʕ@GPdϿk.^{q{ @BW_#N;UeV5 'e%Gc"u\R#M) * .Wcvx2vg,tHYpIl "3cvW7kW~fDL܎qėiJI[tBփϘ)3 JP=226Iтt:a ~$$>Ǻ9X3>>ꢘSLOVDFD)til ?O +vVL؜o~sglvmq y\s}#cCw1R'PчCɃhH'[:B ]ݤ2J|Fۚp%e`#L0̶;]kj.+ `)(e]8 >"IbB AN nh#nXŪ0coL%ADAz\ va 1L껟 ?ˌI;ݍngrHdP I+ xyX{" |PZs_1yHfZU *1*+E mt[JhB~_r7ORAlGi\-Y&CrGቓ(\n6l`r9\ץ1cl(d_ {yvˍ7ȕW^'?IVSN矧P(p嗏o??cUHqSC WnŶо86XQhjkc^e_GZ><3}M ȹD8,J ~b, +bR [o^{SO=5D >/uIӛ(zr8*Jw+ZiܰQHQ4zT'V}2nLTwо؏v TYlH@Y,)r(9c8>tE.1H["DPi[*#H!L9[JUWSe8D(KEV:\9eCAw!Dt&ӴX3X  fd @{ZպzYJE˶H$@:FPF=CIJЪPe%y9 AӮ7d;*P/^ ,^{ q QH!ײBl+W Pbȝw>Q?yo`-Zܹsq]b8$R)ڏ\.GC1*v؁SO=|>?lx!*UDTA-Am(Jx≡r?]$r5xF+*#+,@v[Epz$ڠ4jlXG ùK&}kd^L=?_;`u HtH)Iuy(-AYdH*b]Od  [{Z)Rza]}gQ P*BHA ѕgpU?^Yvq gCt[֭F8iA!@sNjپ2\wAGP(qjh]& -<ʃB/Jڗx^ !S( WXIJ%!썓%|*zџ]"bѺJw,4iJ0a̘1dY^y&NHX XFζ~C$n˲3gB3<.\.]wŜ9sXQ![cjD򖷄 πHkM2X@kMWW,]D"A Ҩ0 )JqWp]wYg5l[#t)tuuQ*r |7dMam8|,;멂c\kp^ci z%@V.JC# ʶ헑SϷlb ׽ iᔑʣ8.Z٧eg1 ax"H'gag +napK)Aծ][m ai=HgG#)xqU?H6g۠u KHO)<הm T>*-(ᴩTL [9`_* ±eE58oT @OO|rΝV~Oz5"p̘1t&ٕdn糟l2LKgyt dɒ3l%Zׂ x&fݼy7{-F7{:fĉJRR P1fl v6Z(!6ɛNL+ P%ٝ8X%7._.{-{60qTlۢ́I9ZZ(+c$HdPiN:ETOh-4iCH[̲qØ6oVHhrO;sǚM&V-8Plp]ϳH&%~Q#(@Bj|_ ֪m@P &(( \NYhXP 8I&ո&Z-T/-h@6JDaig(>zzzH$a]X53?Jzh>qB_L8Ha1{l96B3^M0sjתU{CTk\\jUHMl6[5L&;vbe]96[j\ ΐ}B 2r pZ5E M|w8npi M_?!Ȯ]K!ɓ}$$+Jbx"*I+'=t -!$,_H!`p,-d%Iqp%&K+k~vhȈBYY,$Bx,QUf<];JUߏЃh iYaQ*TV;`-FZi@&U9BH G9RGj R U!d>>P>S^taR~*)O mn+6BO>|C9:3gJ={vh/_;#,g7<1^e]UW]E]԰Bi]]]a(hCyP1oq]g'=-\+kWYe[-[X2v0 240f //|641ֆn6=n xAޱ-ے%KT*rqȬ'Tq{g~IWV73hܹ(8|0۲ {9N<֚[no`\s59s_+oxުPֺo| _~.,C;G}&騟gF1khǭ6=of$U)cP"6qqf a9ͩ&iLa{)vaOe8F>g4NaxD O= & V@z c:?N 7r1B7.z0]ž{7XsePIfP*1u Xi=w9"*@6H 7e=Ja}Y)Ul2ţVnQd6^N7'rsh=E%(bp)GS{!MȐc=]wXa֚n y/6sؿ?;wgannq u{Zk^W?|c{+鹨ѷi7~72??~Vw1R4 ^/|!'yQwU32C񲗽l#<P=%Er\{B>XѬHB"en~L9Nbpq%pPuH1- v'LDSۆpj^q 9r'N!β( OrEU)O?[F"2}Q:ġCy׻K_R^?;??Moz~|3ݻk|38qb߿o|#_- ݧz7۷}<yO|,//pӡ*:;;~kg}Y~:䟬&:~7X-ُRpL5:Hl% "s &푄:ElAj("Z^c[И0I"E{VG!販OI-"\<~Bƒ?7yhSbd]GB9L(ĭYA"%P&J _  Ҕ(i'%1JX :p6ȽU;SD{ ^d9) 7TZ I5,1V kQׁ͆t&rzU' @|_о~EFcr??}/#XqA ÉEG?5o{{׹{x_.=OO756Go6fff(x_ov^7{?~l|˷| ӄa;^Wq1ga ?Y՘T}WW|7}ӆO'Ow}7F(;x+_W:7xcL}~~_ŗ5kk}et뭷7BCN+9U}c z" Lh<ܴR Qv΃[`iwnϗh!s`ab{mTV`[hEWJi2`"2H *JaW8ﮞ=wW4"{ ֶ!Adρi ԅt.ú'uE\˿lKĶEHqR8%6;!YòrBĶeފHybKԓxq,"=#i}5pJՒ,K%s)ڧ$?}H\礸  1H"++˒+bEteӓvH9,6;.aY>t֩>!nwĭm|NG{t]M?$IR]m&KKKcge+|73ַUYXXZmYYY\NQ~ʝw4MeϞ=H@ͦ,--Ie缷zH㢔;w@1R|\Cku@(z޿Vqwcoo_;w'=#vOZ-_e䵯}m??!zwwˁDD-oyKsQ9??>3EDd޽//g~hhg[ɥiK}F$?)yN\HyqQ匈tDĮ9n,ˤx\DDZOSG)?*.?&K'ӇI,?.n1q^ Y9U霸W3dG{쫒tN~Dz> eyC] 矵ijv:)b=nlʨFч oC)7oHP-+b,yPA0nZq4Pa2Cu2VkFg5D}e>pa?Їx_˿=yy{뮻>E~w{;vUw}}_%??͗~eرcկ¡C~CkMep \?4Xa<ᮽ~X]zq[׼5z5aį~gTQu X7DA|>Z"FD40a@cn 8iROZ|HkК3%TYGp(ֆ Tc4QPYh0.i6 # "d }IGƩ}"$kU. S6d RnCM60,Is7ImQB^ȚС jL&i$"-,E֥BmЁ%;Z+ +C P4QAyznv@ns~_2oٿ?ԧU-7-ַ7ͼ[9u@?TVDq<񒗼Zg +_ oy[~|~4 Dzefff~;v9sss?C?޽{qqOW}{ÿ׽u/JתB?_ׯtϞ=/gĉ,,,gw|ws?s>}__+oM?vQz|;YXXVp0|c3 ]ٷ򗿜@$EJUh8󽢶%q_rX;"k{@g( y礝e YB!⛀{okՈ bŷ쮵UrByYS6ȊU8I K''yٷsv_bVtV)MJNm:Or&ES!6 % bIAP)T@N-r_/K=uĘAT{ R$QGՁ/wR& _.+2(BBoi!A z62a& O]:~~o]OMM_>|~rUWzN駟СC(F [nw|'w~wGcnn I(}/?<_k_?%G]xS;У,2N D(Qԓ4AQHK [dEF!+V Aޭzr@<8]\x"?C?-ڵ(%p+%>U{dU!zUT؉yey2 Wh/^ntzz=:v4M?ϭV^hp6(4Fdw^KPPK X EuQSTyJ&* aP"POAAJT*\@;C ")TK)AaՖQڵ/%??s=ǻn~Ͻy+^q^+^ zDu~~7Rn٪ǹ;ɲo&|~k_uvɓ'qc _={orm޽{Vgnflfs7}d>e໿v}~h4Šʯ Uz}kyg>|Iw|{k+Ǐ/wqf}?|eeeivڅ֚D+NYp*_!Ӛ\PszgAHrN,D7q qBL+^f@K)6@+Dg*G>7DVFzȁ 1ezkX+ ̚_\Fy8 ņoUOG5FOZ%$YU9÷6*;q^jwWqF-RB pA-4Q*@ܫT!Q 5A\NGOcsT )<#??0ѣ:7|3~-^FF?U#RA-w= [nmo{Yer-}s=OǾ{~]z׶bgΜA)9mo{˿Lz377ǯگ++yΛ&nfN8W}v./|/| ̝ww~w/})̻.{1~a yZ|x衇p3ϐ9Zkfgg9y$wu'?ͧ?W5?A֚]vh4cZUE޸??bݤYF jBGdQX(#n+*73,<yS4QP{e';~59']NDsa.dvW@NfS+4IP囖0#$(׶+e1Ƒ!'cq1^NJܨ! ke3Ga SɌ{{XӡIŠ>ɭA`3K`K؀yq >qӵ!ʵ)J0+!8v 7pw}ٟ]4'~y^WX~YQG;ˣ|slg>gO o{׭B"|'|;OzxeY(jh>xZu]ŌJp77'֚n|`vv[o]3gXo~~mNx Pk0T \Q sX6 ŕ ~N2 UIK{$h;NLu=r$hRՙ;pac{ΜxLňlRrre*G.:˙#G8u43׾.@*tVXAP9.R9tCj8ˆ!15 -w6+Z2NQ8npgYǤ(a Ÿo&>OggOt?u]}$ rè+2~.oy[=3y0_ Ðe}TaQvw<gU.kmT)|‹,_p}mm}kQk ^:Q7wFQJ)ݿw}s+EFHJH&2:CdR+^Bva={/QݠY^ D 23fڋة]$&$6 QC9yQX SG"8}??_߱)^ q_Cx^Ƹ8o?oj͍>\Z ]B 䊞a0xuđsvM_ HX zgC`O[ Q ϽP̹|9wO|(x׻~>\{k>_m؊xpdRm^W裏g~~ l%k4i RL ZBn׳+JF}fT|%ʬ02Bh @P8TD1HipE/' ?=n[[y__77=!^ןqxpq]w4ĈAPk` wtNNc- +_ GVHvDUPz({9*6zʓwڅszN<A㸏EqLAono [m\jjkTa8[\\kE#D9+$oJW3QrU;9sdNc}&y^\ME,8u֠y%ǘO,,ъMi]&bOrs/wZ,*bpILlGSEQzHv:`jεtݾ6#M+.%Jk,9]BipFvإՕ'7",)|InVVVV,x^]h.6*Rܯc+ w +Cm!U?{{ o''*Gʠέ ׹B  Ð8$IHv򗿜(?΋^"O+~pQup_TJp&y 'A)BFD^9f8=8"#hUFu*t%? Aw'TVtY@ G{։e=3J#Rz׿Voܪ}֚/}qTIʻPDQ1$Iּ7A*v!8𕂧N#T%"xyy(ɳ5Jy{,]|1M+$?lUو02VnuUWQM7ݴi32M&V5s0Ν#0ȩo@9W" xA\qāEXiа<{tWG2& vmj,K5x1~@52+Da})',[Qwq~4&>6ykxGxyK_:5|r-}(25eZGgR U |Gn+Q>Zޫt]7FxPO矧^v~6j\wl*@t 5mdy-dBK yZ3)D$"stx櫇y]?9=B@يa ;$)qTO$$mIVUeCFE=N܃6wo߾}"K;7pÚnl6V#| !!4:YLV+4Ofii}$4vz=$;+%6Ъ|U~<pIy8GGׄQ~q\h =Y.I(iќuSղH2 I8 sB(˼T QX+%X@ C᪃Wc!ef0G)02fbl[t&l+{ta?k*fGHezh `]:&WyTA>JR!IMz.6C㮴!+ (BCaY.ټl6np!B!jl{0oj7Ξ>};wr "K_ҾlZjo;Z> 8)Ux`bU^YK)uzhJ$A\U묉J鏛hoqjv/U13´ FAH[$U:;wx;Ae8,>,lx-4ғUEfE7hxA`d(uN{ӭ4^8@Pk& ̴c]ĉͭAm;1ւ??_~w~駟hvם bSAbKnQܜorލq-=,ahPS (]G08[ %h&F}z"^Yf.(x077}$ .vCh[A( B7-\xcHBt:'({:!?O*kg3PdR8Wm7a?Ϗ؏;w)ao{FqWty&=[݃W|jF,>![d*mXZn1*"c<$ ! Bp'g!Ut)zUKŤs`Q6:;O?δy/۹5o`b'L|!I[۹ϒӍ>eu/fPtP \ [|BTQk̙C(QK]  Q:4"fb<F!yeb 47x#wy'KWCОM8%]8٧!,/X}ʍvF _չvJse( ,.a'07/fZAiL,]::04J4EbD OQ-lqَ5-)oiJO k=":֚oo-oy ?C?40vunt$U})"wr/O٘(r~o@ٟ)pɞ4hghaqdFJEjF,⡗IӄWs<'16@jMߋ~GՄ\}vVcuzd{UכBⶪ.Vla.!&npj9zoM)(D:A:98ӻ& Uy)qjS65(' 'ZAҾMozzիx衇k'B cn63271}V aKw=Ry#,@!N0gl™wn$gT)-kI0ʡ&;&I M9B5G\KqKVMTx7+Ef-)T\lsֵ_>.H2;1*JpLSVRo|uƍD W 7bfV·:o0bfȠЭ( FZCJ$s@rA /Tsq >089>BbpLe"B ЍC}WOTU '9EÈc#(jMƹ(ƒ\{ -ӵXmz#/Y9M<5MLV ɩ弱'&Ts=pb6zs+ 1nvpbu]4!a5*A\yP1itHI)t@Y_gd,Xp=VOΕc Nej ȼTbs+h שּׁ*dp5}E>D:F~Ĺk0λeTH$t;c}P%7KU ]fnvutƵ+YH@af~ø%L4Mu^@+eIN{{nJg4օDLh_-FA {(yXp8.{tY<'<łm_h|]n,q оC)Վ8OB6L8!>ƕP@AdNzl>7;AcO|Ѳ ,LjEk(3GJ2즔Y]=~ ~Ck͸1U)MEWη (+ KrTϳ=j &$× \z)kv\CU#Veذo-@HU8d]!^szccPQh4h֘ Ոk˽žW*x7ȰJE@hVt&3)r )Vhy`81I]69 U9Fedh 40FAiLP*BP՟,U)FwnV: hwSL0SJU狚`kb 5LPӓ.þ~asj%LR:Jy177EX?eVZ$"tPu BAr^|\_!+P(Q‚MHk@*sb5ZwmW @2>B+Q+) # p IQ>si}K.ȡqD=&.mlm(MVKρJ }?&4:3Eb{3xz2KQ"BW9Es)1~Ʉ3˻bk#FY>O~32JwlY;ڵpCLM]69Kٸד@6 E?~3I|lٝWb^EAfA<8ks7t `bImbVDDA|(S am.z.Pʾ5NiTPztz^s;!sqg- 9YQaA6~/8nGFM~ܹדtvٗ`}7q:ffl/Bis^Ɋ%1h ,펍7b";P7Tu{]H(D$r_tCV6' E[ӞT⾉ :p@YHGU1оQfQU'%:EB$jx"B0CDYv 2>vz: 4MQð\( ~F69,[끳V) ]juQ-V0. zzcz%>D@QvldT]q&빔곻.>I-6<$j~n(s r屮5Ts#U!=&.c9o;ؐ:oX8 rT})U֑Xo5*h@!WlJe[c6^Gv_S*$zgtNgL3\c7{Ӌdb56ÂzAR`õ>(*UE]:(HdSZDn \W2nOl5κՍz1,j(V%W)vSo4V)朅[ M)|E_4@Re"9p8Q`06zVEk(6q\/YިOoA+\)yd q{s@P"w\sQEń;Q\>ĨgFJ{iE"+ ډB$PjEsj WS8q6A]SI3;x\jG!l(˹JڛY9e  S"1Z/^&^zb# X Qhc `ftZOSk\M VGpad݆e7 | zUU嵑:) dU;Q*-#$~1s[q FVZ밀hPXCDM&~[i2viӨ'tñ;nBB  Ap(ofpvPkaWq'Gj r> 2D %)YOZ[ 1T9NhbQˉӟ++*<ʵ0%CоA.Y-CmVwݺA4YF\.fӣB Lt x!H kW(RJvCn bN}~Ơ_ AAs+׋uZU(( (:+a޽VHވqP 9FYѣ '1lN9AјLPFЈ qqH=%Q* $eGc rی+T]ɍk o; /B8y M zl1(c($:Wz,WUT@4qc B1Y'7 }ўR=E ]9kNq)-:3zCә;Q0`hF^+ʵ}BMA6 J]{(6coia#%^km84*.yHTE!E &Eٮ (p.G!TG9VYϺ*&7ZO#i %demP!ׄqHg8ݗUTTq)Yp.]&?3Bqj @ a?d FSml^\ UE $iBZKʚ?sa0WRW"% q˔.Z$aDP=T Rьw9.U~Py ZR^Fq1H1aT dicDBF!wM@CwsBЬc!F.QHFPƳ?#Y^pt.V!ft[ȻO,&DaMacyśx7*x4hQhUr "Bly2&LpݔףZ$t#,1Q$0UqxзØB 22P kMj27/*/O^I^Mu(zqs Z.u H0ZcTԆ^Nrr8P!ՊDך0:َg}~ 5 ߂e-vk?$W)1DBĎ'YnFB_D0 UUi`(=j#߰2Oi߭ >1Nv{ĵMWJ &!sEAE% :U TO2r?]7`Ut8NzmPV%(hȵzhf3ϪjC"ACYϰtz]5_c!nDeR!U6N IWxj7d6$ƪ AZ7R@u@ÐZi~Q9a%VQXltWU| \gy nL t< )D)<WjO\W֣a=VP.;F><(*CofgPzi@t_#  nfȌ T?5=G{ɳ0((Ay'L9`lR[tۡ1\b[Ģ\ B XedO⫨["'8J#XCt#SF e%͞Ƙ 2H- aAQjPDzBz52tu wゝtO- D0/&@T$ ai_V?oMF#?s=PQc,ckL+7<{iM ii_ޤ-@o0fmHY (2e.\NB{+-A}%őC$i8.-azmA00e B M/UI2PBX3Xtmd3^M-P7OMaT8 szp8+$Q,X9\| " &X*puTTC1XG>˗I hE &,IOauWh41J>1d0ưRz}xQWxۦrY/.)ƨ>W5 Rh1"uGsɫ{=U>s b5Csm=([}zoPA,&!Y'5b'%զ` Q -z."B$}qѾ*y=mnc!J"An#DXtq/MH?CUVX(l 6uX^ǹǹEPҸj[UXU8OeʻpIJe@뽩\b< PI;0]dˏ7N?I2dՉvg5 mB 8JZR9[` G(: ؉"DĬ5,Q(A:⺛Bz׳N냏= ;T ΰE @f1A{%R#^wWJ1BdY]%bQ~kw{8Py{OQQ{rP'SudFmzW hodYzqRm;q\DJUcv\{TTpJ7n?l&a`}fu'%AaQfoEXo*!!]B2b8"9Ѹ"PVZumYFKحqDRg-a\V>HؤFAH )#9.2UujP)g E/$&; GT hX;rw`6" TĐgB4|vF8fCb]+pR(fzeَbBEk60(ZIlHs.CPDZZ2Q`@!xHĹ6&@e` Vg EqnG\K<[$8ʴ6 9PE&"cZ+V Y{0.JKBG1Fcĥ\uv0ϫh5e;بL s{*" )| :Qo[_.U*2AWzp*"=Vuq2|p yno8(lQ@)-_YW@THAio-š R@XGe'fNAR2cp0M" juV#*JO-%ZGNb ;%q𜹤vd:ً1)A#54:9VZ;8yF.e M@(4N"F =SdH WeqՔdiFM긢 k`4E_ b :'"rBiNr8TMs(ҙ*{?V[N(Rh-(pNZ' F:yV'zB4 &{1RV)EuIJDyݜ0A@ D U`m%j,!JvG;|EaV&xו벾*SPNFAd 6^1IT[0QufՐTKbM(zd2K#14J{>,JMB) r\ TEk2*6+;KeaD \ uF7hK P(-(%P9J[q%1Z(qʜlQ2c;e[#כ;Mz''ffn*2Ú j8Q:s]Wn^/u j58&E- K#3;mIebOkBm̪vPTN(^mP8 j!:0L(EjW]inm}ph]l@5.Į`)YM;ˉ,ʞB9BVG[KWgB ɨ Ye g"I^9ߌg!)#}y4#{N` B/+;xs,|5\qk֥T׳ @7MtXi"uA*+*0Lذ,ޖ@)!c *'(E鲭A5V¢ 1IV&#j5lLY/m]SQDhB D :fMIp!8 ],'|y^NAءw +fZ(֎8$Fí\}9 KT ,[,|-13w%:.")jxm|%h Jih VAGTL]۷@DzI%ev4!hBN.tKtO.6 y1JА3%Mx3eO)nl$Gǖi1}hVK(VKlHK q*瞥=x,A뾑xV=QgzuHps yH/oDY&eў{Q6H,b]2$g ŷ8x_kycέ/QʭQL\eBO|e(ziqz͗%?^5wv+J mn l|!8-4A!—z=A M@&Ij W"B~]T <6(OO]*YFRߨ **oE{HCȋuW=nXBTLh =e 9"u=/ej'@ s{n:5(V" VI0$;3ϐ|C]>=Wrl<1j0$6v-HAԪ~YV#J2\C+ L{m 0BYPe羠C\Q:r%̔WQT8=:he1آ,:0jR}NH^!Xm*/V!oA6sEqȩtzL-x IJi]t"N(lXΰ-4鞷'RUvO!K Y%AUYʕ@tΡФ.1>UkEmd]-hc@Ш_A:dK-5xFI`W|Yk,!qszɯq/;qݗ!ʖI*85j,.yTqX۠Z7Ak⬔o'ɦ:h%L<׽SRCxtj'q$1LbQTYآSc,xB<%=g4OԘHNnDtN"g>w 6/0AY@0&D2&xIVZ tZ+Ԧ0sW*$WsJQk7+ UpqF L D0JV_~|)V%4fh΂rtSkӸ)"/mWgn,?sc}/zY/!fDfSWDP8GM~nF<|LM5bUU2!06",Ji@XAi\MH# BLXGiA\ń!hцFtaYo/92'T CKqѤkSǁ-VCKU >$iaEldj>iPr2O1 ۭAӜމ1U<&8 ƔWQB*D D 2Ng ;Q4Աb}0TE:B3Dγ?`w݈qЪU;LDp;YYb_?R{RR0isNJS"$"FTpb' Q*+{#A e<:vBhr; x>1*q]WZ*i8;3ԅF7E+՛dCO4dwyVceJ 8 (%}20z ./cjS4fv&oGmaY ZD sV\MģOp aV!E [U]LjhL)In.` P~at6#0Ȃ͠6mE m*.v$Jh}%=Aծ^Ӕtߨ¸Q<>Kp`%`Bu]PإHwfΫs$a׳&{5t=u17{DBZeDd$ $b MKF)fUO|Y2+ ((qѬ@\s;q깯O0{7⻼jM 2FVǶ !y4-qʊ0qBU2#V1o%yt|A SS/V$N/F.B{V$i :!{X=̮8qwx5` eHQC iX+e1l W؅^<ω,滺^o `BWbrԮQQUq4*2,8w#h0yQn'lj>D:s WtWq♓-Ccw7鹲$+2'P(Cf皴*;Chxn?4{ U605홴z9/hw<X l (sҡzyE]:oXT EQ%?)7=W]A =]Ź pDQD{4MɲlSaGݕ󆑼 Sg";JmTA:wjCvJ[')u%N"]n:z}2 .N":rS%k}<+)N2Ss4 xZtYFw0AH4=EIiBXZy[Hv\Ґ=H{Dkn*&V!W [on>+87xDG}5J`8G=U``1$Ьv]ϺREkza0AgDmy`= ހu#k=L-E$In ֚Z6ð:;ֺVoO:rɐ0:څ"%Z=dngX)fعp#1:_Yh5oP>hm($3cIt%+Kf Wc./rL%nwHۨg$9ɗ`᪫w+ %R 7NGFF(-Ȣ(EQb=Qw:5QB<( G"oE`:fͦ&>:PZM^5u(zh냿WzX( ;֚4MV( (Fc!Wkns9,C"6}*CS1ˮ}X94 _Ucyq8 |꓈eP%yY G69k E)X^NiN7ʎraλF6e gmԮEizg`h-+"Y)FL\ t;|+Mx~`mϡ>àeyEQ ikm\SV(bM(d+a=8z­ A:oLk=}kU Xsf^eAkA@ecW0fJK0X2u7[ ^OabtMaTH:E8 tp%_{,q) 7`UQv^ j=rB )µGLhw[ݯA0akuUJnɲlZOab,n;*C=V6;Q: ZSj6Œ$hlȎCk)Q;NP ϳ5UXoҭs eQ-t+%:y)$g+\.FaRBw~0@NsTT7Kh* 8!kјY` d/FR# !9lz 1hኜ Qnlz%P;btxfкzqQ^UQ$I2pEصY+҇T@? ;m( 6u$аBfQjҘaS)W5#uiDu & t,W(َxAﱬW!hkz2qDVEYsϮkolvP&S)}'D\ xR_x١8?bP>DP"{]r>]./\\Pa]5Cqt :e((l2h1"[ءKpMxB(7`V uei,0n@I+;ũ'i b'MC `eF=-/RK(RrQh2 9^|+'>Au|.+aUrI ;.<~= Q޽SM>D!&:P&Ęb" 6E ox+O?,<)<+87GAΫ a,oZJZ,ơ`fqxnNˆu ǾybH.Ob/7&=[@\9b[xⁿJ h^mk2}A8Mm4z'8a._ BhUPA+ b5)S37Әn``.U>Voh^RzS}pSdPjB]c5q޷'"pKE>iOn^kU[vA![UB/;3 2w :\#C7m UƻwrX3OڟT"Am1A W?7ޥ {?iwxS:Pu3do`itɀhShuѦAX8\n,?)%9Br)5!mәdAK5f^6yTQ, ] EJ35 }X5aBYk^ks;Z#$vB}۝|U1EQ),}[Pie%DQ-yS\?{XnBd7_B{^W:e$8/i^vx&]6i>IDV?QN>5nM|e(CCm .}L/7}w1=B rFh0f* iql^#`~[)p>[yM\P\V71]raeab1A~ehNBI) k~gW:ydK~m k *a=22|]ȎwT8EQPWur`׍GccCĐP/z%_f gCU SIltս:F mfM fC -2l>*V+0n,{K* LC}.HƩ#EQק|T\z:O?O\(}BxAߞ4jVC  i#j[P, >;\iRdYַV!FY6 $Ap강>eYk#a T؋YÞ`Hh3r`- ^D\w5*xhsR5@)rК+&?@8 q+qϫg\0 cer\.', SSӘ4,2C("gfd!b2ʰ4{rkШ0,> I&]s?̞g7XEAYBpPHI DhljΜ"Wɣry %K-j} f u FD=*"QVzqb8>as0OAr ,usE0Q /mF)%n*5.hm5&BxU i"ABBx#VC|?}i(3/y=7U Pm@)rL ,{N=Kk2;u"b& .YpZƃrGt7:V`={hp+}zB7t`Q숃ÉQ@v+/b5- J" C}s? Ksh T] *@Ɏ1v\,ʍª(OނV^{Qo MT!q|\'lG3~ESٵBQGMOq+⅐Oeq6 J|9fh4?D4dZW*ePŭ㿃6FF-A Y} s6cAX Jz{C%̆C9`DTRWaP"j&y h+HJlq~їhuu,|#)l5VIn(\CϋU3I\rBBߪ;(L]7BJ)p^6"B( OSٽ$vyA3ucyH ~9Gۥh{>aڸGEDQ4Q8F8 i*m8q"._|O|c,,':Wz+'Wa\};b4^hB1vȱ8-w<J{ceʶU4 UfvR5M$٧9'6qp{[ W0ȭ3LB .F:GK!&KOEgbS{ L.Nq={ax0~vI*IzdہيNsV .ʱNWY4_FY>[G+l ],"(&.BʊB6ƑžJ[⑏*u/4ftPSOǟEPn) 7}@0Wiq2u+,-r ulgl&4\\&mOXoj+cHS-Ph _ j%0M֚mb2ێy[YYGl"iɡsI)ob)w ,{\A)TH! ɢ3G|R 'j0'n0i/pT _y $#6)!]魜Ķs0ֈ[-dS*M& f׭qWycCk#%:I#vnb4MS,݇$J7)@}~uk/o <(Wx,e(T(Z,1{8^V] ouEXu9ҶW̛(q{.\/bAՈf"{3G n,,etZ4:'Ũȕ@;R'9GI]׾|?N4_NBzT ͍nT O?O)ӨE@u( [oaez]!iqz  E5 Rk=P d MωֺAOs3a5H@=q7Ũ&$6 kPRÕG V({>E`.}+j{J; Ơ.K}FZ1ip*v;{ppASi?%NVf,:7v!1yއ>oA3 eٺg J%*QVqRH!| Q@ŠB cvp_sĎ>{UtQ*qj0 bg3A_s+tqN>~7-51m\j2PyH^(M&*Ŷ Xe)Nt'negwYr`y9vEy^̠ΛYzYNBؤJ] Uv[G<8sFWVvzY#lޯezcc_qT o`GXQ VwY=EQqPI rP@G;1,p +dmrF]V KQks!W`JQakKPI7$AQpŝOEx _sitOf ѬGs?IkP(e.f8jCꘙ+2sNG9x}3w+i,‚h`c75*zM{++LnsV%\-FhT9DHKnSw#YAzB1IVNu(ޫmmA,FТ`Kǟ6"{Pt:>Nܨ *:t*Y57cqd\t #.Ð( T5Xkn֡qϩV5vnMu=ce?̩{ #hr]# P© rPH.d4hrN؃_'8pkw=J ~Cq9oFz [J+2.w8Os n̩6s gh' ʂfw5&C8*\ъ~+d9v߇qCsك5}@Rh@oPѳo8#hъz`Kt${ 6oN&VQP^O o2dX 'N%SH՘qQ ۤ80>Uq(/hwSsc7ҙ{#}@[d "V <}:O1={QXabq:6V6Z?[Fqkn559t>\; h UFR^!i(Bl౅{.]ˮӤs/^\0C} v]⹆Ͽ{ RO={gsjS+']t 0ƘMYB 1$I&l3JZLZmIhǵ+?H6u5.*趖*67ƉA zݣhKZ~=Wr:? [V=!i'? dتrx :?mէsaJ"9*?oyWpHT}-(LXrW*>ٱ:OxgX9yf~?sW܀/7v^z,/{׮™s^\a(NŽS;]89ϡa.7jЋѨ|1qb(PL}pȡ9s׿ A 5HZ<XKZhkF=h9A0s;jػBYij{3O"ʐLQSA4sQdM4;3fh9禊^1X9,':Gh4Hph\TCeŗ}k`r idElMc>@lXZ"赏/DEpXC0}z,~Mn>  `n O]P0%u]B)C}#f;7,#T!ӱ5Zf5ctp}QEgW>NpȤyX. Ld\*p$dit%ټ$*ݛs =KhMs^Zϝd!H+rJP|LPV4;#\{1 1FMBD 5Ad_:3Y2}{<T5[hͫ_so>]HPG8XFXfPgY"\깉 zeԨmW.y\R^ד'[Ϫm8Ux禡0`VR`1)M0rtsPA\zluMGi&344(k4s0BE0Bĕxd2˲,4Uqflb5*FT. 4q|:GݩaX"]BL q\/qVNq , >D@SJ+%Jq0P&RwMvYvi7FDruzyaxd !$~"1-JmdzXV\[5UKP(Q+u2'߰YSJ"u \,an|Bռ$As9bE,}oIlVZH~6}jHhߥR?{P[5fea{F=t} +Y}ouIIk(OBuӴ0lIM 8W USfCJ `""IepuReQH2bj=(WUJJnB4 4 < fuTѥ0k D!x2w&ĕib\Y$8ܪǐ]Ο46a(Jn.Z-Q6:tZTBf3. àR}֠qsGOa9wz }?҉~o;4 PVuXrr ]34x H<E7W.C5vu@H!`1P~Q+Y~#H J0 Sp,?PXnA$F>GM8ON@Np>A( x<2xu|@XVט;Gْ@"Q. qBx@!8QQd6V:Qmh,R$)P^幨Ic!@e0W(+qjS =ڢOzs*؞D}L)s*ucfpB;85 kx6 r'X4'QL?w~At.*bGM%`zgP#L=ekO`#w{Ojx{ND2τP 啑Fy4gMn % [xA<Dp˰QNì#!Me$ocI:>ЦiR.û4Y,mC )CO qhK?j%/Y{̵`Q*c<(24nbYt˖3[ hIYP McވDybAMLsA34^ѾN~-<,xBJa%ˎ8!_>16& y PڹZ#'7HxI"J]`E-Qf^\r簼 T|o9P ,f_LeP}֬qB)BsIȭ`} 0MZFcQoӘTżN?mPB9D z ?T[ïdֶ1♹YBPdh r"|UVY(`PPkS>ʶ) H_u|CO³|ԫ{Xv) a擇#Aat-]K B'y @CPH|WgWM2 qYܢok;? $6yUưǠ^T/1MRQDmzuz Zs\p ǭ|FCE #Ì)EXET*1 murQrI)QGm~q Y$>"e@/116^JFUӐx ;gSkdR}³W MN0%n=KHz֚ SFp@t|B]]@+ |>b<+ k=%AڻoAN>AIW/%malZᩔV~iH|Z\xH]+|E\ƞ*pxRP #}rAhZ\ʹCxVbVFLڱ^> Z:኎[̦@J(B+(/MX} KN\mOaOF d 9H!v .:샊vjG>JHC)|(c #^wva E W^"E"@_U:0(ﱿT+ABj7 5ɫ(S(ld/P5 jTPF)~pɇ9H SSP(G 'HIU+0D8RډW0 !Hc2P~*Y:Q5(9+(c[LǦw)#&rVMÂ}|nӺMנ#$=H)YsS Z$?54f$:M}f˾lz+|0= 롇`t4PL?)l wms][ڸf ^wۋ_.rV2QhZ(M3Dׂ> #xk_i]n{4I4F6CI^ խG4ۦ}#>U¥£-[x?#۶mP(| 17\=w}7R)aMN8?׮]ɛ7cJx^/z71kA*oң4]^f젵"z^' _GA ɬ7]J8zo?-O%V6C W5a'^Or]7$=,+>B}픆F۶񕣎Mo72b`se_sO8l¦+XHy<05snVnV8 >~+|C$%R*Ŵ zZ}ulߑ=_0A A#A'|U"gk|G͛_>!^%V.mW]]wgKO<߱5##Ct^/p*8{_b\}]G?nz}sL*ЖOFvqXugM`|;?)l*[?Ao Tyĩ)ēOoS9LBT^u\#G0s9^5GŻ'|_.M7ߌ<8x&@]T0ILKil|{)CNj[)v .#m\pG>IRj5VO|O_g l݊isυQRbn er*<Lx7r?#w/ǹQ7:m)\X-61,0AX@kb-˳w 27Uxhxyڹ?φ ?P_6Pi®׬|f5f}<(DPHqh\}A zz>yo<>яr޽07x˿{<iqǡ=J%]5kO9orB _o MSS<꫹SxB!.U Ldglxۨ6@=CY ë2>6X{@q9sN.Bl͚#mH:kT^BoY , UQ5Iػu)z(9Pk?~i|)0xX)bNJF!|m܂u p%BJ'?<TІ wA"5;;w]~iV^QGʼn' w ȡDmu]) pX"\˲e˘Ceٲeߞ|I>rG󶷽c/Z5hYAkdոԗً˗6>up?K|O?z6ט[0+j.n/x<θj8ĺ#EQ=`iqapᗿNVlۆ+_~sg333o`M|ӟc]|WX61Vs<~GMb9ju<n,rLR,FFF=EJʤC\`hh( B@XlYX!^R;H^ՠ( (y:hkǎgr+7x"⠃_vVme"p7(@8y3[r9>aBIBYyu+Xf #"<qxcWb`y(Gͫ\'G={`ۿ[¢H]̷rJkkʳSQ2rr9 k׮EyCqx 50733.0;v`rrM]zj/ˬ_~B6(,ϐzA4$lN!+Yuw{5M: W^gQ+WRu|Y*(Yᡎ]:DSne@>\sW巿U0}ITa{by 4 of YAyo=>z '%\®]rano&F F@ٽ{7LLL Y7Ofi;BhHnzz)jWnZ~%|뮻}S,YapKy^ $dmf/LT bf<ۤT*l&'Q_Gu mG a`b%+WbJ:y}^ x&'>L j UylVٰa333<|\tE\z|yk^(2>>Dz-˲X~=Pn 14<m۶Jm~O>$sssp衇6m׽*j>ܨ0ѯBvֈY5^/m/m-1y B AaZ*jqh8J%Y̾O>77M(" *ʭSQFp=5]v˿Ϟ~19܅br(?dz.Ljax$%ۜ{}ݡBڲe wuWsg/²A\)ϠLC255E.C j)pa4ۻwobŊvb%_bJ!3-xnRI@iV2Fc&ƽ/ ᭢_ֈma8Pm+ߵ'vXe-@"Z<`~~Þ`#``Z`(Emni M)3w%|oo`ZNg?8hvyl7MJ[lauA- FHKQV?s83<Û&nfggysw]6ndc~|uY|9蠃B^-[jCLtH(XS9C055E>P(ZƦMؽ{7q!P. ׿ۿȲ,~w~ 6psg)IHE]DXdÆ y*dV\[I4ͬ(O4< N6y-<}'Qs˲BV'{Z[N5~nC}ݢG[3mH:벆(:FOm`zє"%/nW︃ ==?_*W]uW@\~'j77\q8ꨣ2-[ J IJ|C|g={xss=}~UW]g?Y,o|#\pEw}ȶmW? .PAeo+?(,Z6~z=s o51==z8wuccc1227ȹaƍ7nR#/:hp''q]7oU.CRRGeff7rI'蠃4M>prs]ww .۶׿u&''C|ocժUض40aCC !(s\s ^z)_|1ԧx+_8 oC99N=TvK^~ 7@>o޼N:ضUW]Ƒַlu/=~r!j5(B`<A%˺]/ Y'-)s7H2bA %  ON"d[>,;r\-rymzfr/uf{72V:~{7?5T0-(: \!mGG\}!'Ν;R~z6l~3;<\|@Ѷs0]No}+j5DJ?Ӹ;CZR2n pPM$}kmll}{!8餓0V~9^Tb۶ml߾Y e/{\p_җ3Geڵmwڵr=PT /V~iP04Fշ4ant|m!khCs|G8m- m/!!qFi?+V^󖷼e* mތpaj|>\s i|% I*۸馛8ټy3O r%0l#Ķm뙟G0^{mH~?гyg|e]SO=ſ7뮻կ浯}-B! eJt?|B0z~f RpƦutT+VjI~qaw^sTRqS\n<$|2~Jc6;wdٲe\y9PXD)Zo|@Vj*R<#OT +^ V\_׹袋غu+[lA)֭[_wY~=?~J@{RHdzPߠyKWُI[@+Wƍfg9bh(P x!'K{_~pg={PG_`p}LOOww0]=Xrs1322—enfN:˖-cݺuܹ믿>X8x'PJ1>>ΝwމS/!u>)wOOo#`'> fff8#p]z' \~j5L.; 7d!~ysB뇷B?r)ky@3nylf[|x֭Os]wqI'㎃j=,l{pp p |jr2 <\tr awooػw/կ#`bb"D~^ƍY~=gy&rHy5E/zQxf36/~qغu+_?>ѷ{n}8V!5͛7c&R*_׳}v㎰SNᨣkSOfӦM\tE|x+Xf eqw}P&d1n4,AC>н%1ݱwoV%/;'۷C睷`< |+02ƒ+Vg?9?8i,=x6+ھ'>ʕ+gcp)U7 P(yR~7ַ9V^OXm[ZgqBJQJo~5k>\r J|o}[J%֭[I'ĦMT*/fheɃ⡇[n .+7ŻZvbf;oxٺu+-^R3KwƍEz!~svԮ#|{7|s\j@N(8FbҬD ~)2soq ./fŶm\p /uؼöݼ֮ennżߕJw907u<;vռロO<ػ7.0 7x#irGy\y啜s9կf~~+V𶷽" Db"{B9Sb gDij#<KxK^ґ,yGrw𲗽lQBo۹O:$>Os|#Tz #Iix6~3*ԗ5($ ۱͚M7 M{k9((QET*y^uqv.0'? B1^@%]|aLqc4~7vVn+IJe~0 }ifĮ]Aś /yɂbnVRpLnzAjy s!O3|:*Z%23=J=ʬSd_u?я-oy Vj9j5y.2ggg5^y99ꨣ(q…-Hv]pVQb޽Kjwўw;q5m!LsqQG8Q(9H+b)]+I;-̚ Rp_ / J%0†ѶW{b1I{D OR xl9*|~?8F+vN Ia򀵲RRV}?x$d T*J/n7^\Z!u>yHJ\K/`bb7 q8u1ɑ!ynXT*\~u?.SS":8jb &yH/j( g*tk- Q6PBQ‰B  BUF7B;EmFH&߰¬\.﷟ J\.$7J `~~>THIhIa ? t1}QruJD^OR0z.,Mu+JH JhFF"IRvRhR Sa5VzZkg%RT77!яip>e-[Xb\zΣ> L::?{kyn `=FK;SCc6eQ,r{\. 5jr3X֑9-fI֚xQfg"Q9_!akR.MCQ6J .4'iKykRxF.&NxF4!"Bk0ԟu)e"}Ap BDR' A}n#@72N6zuHe׏%Mio;?cXmC''|$ߚ??P,_3'-b&}'|9906<>>~f۶rBR`aM`C@H@LR݈ա6l+A7YSt~km;NS[ݴmA,PJ#[.WLcKV_Z`;tW'7jH/7=;` /~SXssA40fgQ"R)mD~:SVn] '1m*Oʭy睇a% ޭ 0ű- ~w4(Gł9;)˳ i?tͦAiqr]7 5ẁ Na,&ՐFd x4I>A*,(^栄 Xu>L2Qa\`ځ%aTs e`F\pR nL´EO\i'.lV#Җ$ȩfPY@ٸ&k(Oҽ‬sQJ fٚurVE/I&L2g=,j Lz8J_,s٣ŽsSY\Q{0fN(B]{k&?𓋢ßzm)p M)jEn3([t.*Vmci$ZR5=C'IgqTVߡCvw SQk^{twg1q>DP{BֺMc.vyFjYuD]#p^n'7(Yv<'IB!wPR5v=EnHǦ|P>WA*1-S%&6=JihaM`~B"QXVNmsˇ(= M]Zq%ݚZ%q4 z=B Zaƅg3Zߴ}[v+֨L14: 0MC&BپܪCꖋ(z(vBL|>VrR[ a*A+nnbm?ՋO+{Ykt FGOqelExBBWs ųZٵ6y@o.*vuLK[AD=O._242σrAAjw#! l^e)StR)ӄ_,[S9F/8SJ[ K#7k;(º :Cʚ+#ɶm\%ϣ%p6/"/x|! V|݆tnZchORP 9 Lnu2GR&'#ׁzCAʽYVU{C .@l-u sY_ @Ȗڨ0 A(@F%/d`&5ܺaYp&ARPZMN! ~ `|||o <K2c;H՛Aƾ:E4_[BUiflzAiU㝒R+@%$J^飄 ST#ihJ WN SH!0`*]W={:ő B<*Q>HfE: + j,"}PY),5($;=f-bv(^XxQrFcB8il`H0 )AWG _~4( HU( Ҫ"Tse_N~m樑R6iJ;܊F M-b(Ɖƭ$f3kT<`z.:!#RJ BE&Ou+tzN&} Lœu6kبUDCXEI4||a/)mIh$3.pkfk3k0PSjZ4:D1ӸY 8â[Lҏs^5tJ,&ɱ-V`"]/76d[)=幠~#X0a1i}N.=aS#s(Ǘ`&q$0( B2*`(՟ffDNe0s6eoIz٬iGeHg˚HSO@~a: ̨2640;r=߫!:́T(wW>RW fOV^Rk(-#1zRx / /iD~#*`ſ~p:4ҭRίdb&EMl/Ԭana9Qei @ ~,3r<]v ˪{T7ph \JJχ#o( С&\pTABPA{&C1rC &Ja>((,, aPï] Yʺ>si\~0 ҥz_6&Jg3Zxn.A"l7Kgn4LaEkz'!qpu\ic34aiu=P^ezYY#E;>0i(6Jw~i@"΃&He'.'6MeAfw X ѨgNF@Cf%ԗN{-%4LQ؉S@9 e%^w;BҐA&(6"q ?'R0|Dw8J9P'oyFƊH{CJG\؃EA5@@Q2zY_K0{_.XN"fyʭ!); $ժU@*L*qpR!, s={Yuir9rcAJ$ * 1)@_z+idMK!_?z t3zV}A$͙i6bZe9|cV9T($W\8KZu v#b)9à|F#?%*QM9(|A 0(s8(gVs֍BvIaiT?UJ/*x٫\ԁf!!` 8 ia`ؐ/.P!rUR(JAamP$MKy MsHu^}ځjᗄڝW^NBHn4b3!8$`$EP{TU\MHRH4FrAIϡiMb %J/ĤP2YE0ׄq@)f~XQhJ%YQY׊b!y!/N{BPVChfXIJe~~>K9ERR.,kl'y LESMi$?Z0𺽗^;7(iz]|F1Y.MYv@L@tr+J ޸嫟˶L$ φaʤBJl+]vVn|ai 8[5g>j՞Jn˲r][\.T0ݴs%N,ux0M[b\7Kv FWl]VjTUr\NB-іR'Sksm'6cvuff}AIkNM&dhh-YZ|Am=M ?P7^?ƟXD4`%٧<%qߨB|>ߏų7=n9q.]Kk]knvRn~f d}~A5WgQR(;U𲧹<7Ҡ,ACZX=,xR("S}M[$`d A^ˠP,rl^/-ϯTg >AimQyBz8(Xɧ{uؑJF5(!AxY ~`-Ub{vpլ3в6FA7(WY ٫5 ! Ib 'd=iPD(g} ڏ Y.܋e#oPT Y}m)^R2.bNUy>a a|/!,9H[iE7ʭM+4k8˅88!$hH-uC$"5 K?28t9H "keKwևYG9t!v?}+Ei% [[E!G;uЂ> /hZ-,w]ehҚ6b P(~|Y= בuhPA9PY(lN ERz (V+WV>uC\i$tf*w'ejP^XR1 ܪMhtXWCqi{4k=n@ i#lth̬}Y*mNN5#BϚ3&j%[mF(lp2yzZmΓ.ɣB\E1hXE{I؉_;~(|s+$ZMЊ"< BqZ^gmw j04YOYz Q5!v@I5n&3Dxt3i!_IG[qB$K5NѺh)t ŽV`B[IsF-8w3(%E3K.L [ a="KiPZ4NZ^nP_ ZvYH?^nDt@aZ-Ct3j8Bw+:fbтK&l<:4WNq: IHqM ?KҟU%IhsCv̏QvjG'ͶvW.ںrx*i@`m_!,ǦYucD:3M۶CkЄ>HVXG+ge]gMD6(N[FW֞研dMfVr?N%5dI =ُg|c5l :6kt~x,Pԯx A $/D6٫2(kkPZ׃` ڕu]_?L5fyeMq r6^ڳf)K KFL?~XVϴ٠P׹QUfV PCJjW4|a?Y/FS3˴%A:wٖv-eפ!fic)6ӬT/A9OzVW3~ɸ !p]uqggU=Ed}P.-  ŠH/AdM`u"TF*>VIHB_#P$E)Nb$=5 .kрԑj8԰ORb^S) ^su rЏqLEH2H,fk0|JNLj+q% BB^cvӳ^݊NAGJ\N[.~8.'י2kQs֒Vi`|o eiC$^4WڢN5(Vd+pmߵ%* `+__;1*;V("v4 r~>j&!x4AD-VG[ZCyŁhUbQLmZo3?h>ښ~}b:lgTضk[,C;Q4HiZ[bfeg%зUf(5)|n$ZubMECjPxhed7 X bѡnh IHGV_iKqą~Š(ᗴg(ȺxPm_4u=(./Vy#aз1i7Y{RY{diW~,^=8cV BhA.Řfͨp]oiWs1n0:냿~(AABNWYvV,N;dd.,Cf2a^^gO' /bKq? CFBundleExecutable run-ssr.sh CFBundleIconFile SSRIcon CFBundleName SoundScape Renderer CFBundleVersion @PACKAGE_VERSION@ ssr-0.4.2/data/MacOSX/Jack.webloc000066400000000000000000000003701236416011200163700ustar00rootroot00000000000000 URL http://www.jackosx.com/ ssr-0.4.2/data/MacOSX/Makefile.am000066400000000000000000000047721236416011200163710ustar00rootroot00000000000000## This file will be processed by automake (which is called by autogen.sh) to ## generate Makefile.in, which in turn will be processed by configure to ## generate Makefile. ## comments starting with a single # are copied to Makefile.in (and afterwards ## to Makefile), comments with ## are dropped. ## This file is only taken into account if --enable-app-bundle was chosen! if ENABLE_APP_BUNDLE dist_pkgdata_DATA = SSRIcon.icns qt.conf contentsdir = $(prefix) contents_DATA = Info.plist dist_bin_SCRIPTS = run-ssr.sh dist_noinst_SCRIPTS = run-ssr.applescript nobase_dist_dmgroot_DATA = .background/background.png Jack.webloc Getting-Started.txt ## the helper program dylibbundler is compiled but not installed noinst_PROGRAMS = dylibbundler/dylibbundler dylibbundler_dylibbundler_SOURCES = \ dylibbundler/src/Dependency.cpp \ dylibbundler/src/Dependency.h \ dylibbundler/src/DylibBundler.cpp \ dylibbundler/src/DylibBundler.h \ dylibbundler/src/main.cpp \ dylibbundler/src/Settings.cpp \ dylibbundler/src/Settings.h \ dylibbundler/src/Utils.cpp \ dylibbundler/src/Utils.h ## the makefile is actually not used, we include it for the sake of completeness dist_noinst_DATA = dylibbundler/index.html dylibbundler/makefile \ dylibbundler/dylibbundler.png dylibbundler/maclib.jpg # this is done after installing the executable install-exec-hook: cp `which ecasound` $(DESTDIR)$(bindir) for exec in $(SSR_executables) ecasound; do \ $(builddir)/dylibbundler/dylibbundler \ --create-dir --overwrite-files --bundle-deps \ --fix-file $(DESTDIR)$(bindir)/$$exec \ --dest-dir $(DESTDIR)$(contentsdir)/Libraries \ --install-path @executable_path/../Libraries \ ; done osacompile -o $(DESTDIR)$(bindir)/run-ssr.scpt \ $(srcdir)/run-ssr.applescript install-data-hook: cp -r $(QTLIBDIR)/Resources/qt_menu.nib $(DESTDIR)$(pkgdatadir)/ -cd $(DESTDIR)$(dmgrootdir) && $(LN_S) /Applications # create a DMG image dmg: @test -d "$(dmgrootdir)" || ( echo; echo; \ echo "MacOSX App-Bundle directory not found!"; \ echo "run 'make install' first!"; echo; false ) hdiutil create -ov -attach -format UDRW \ -volname "SoundScape Renderer @PACKAGE_VERSION@" \ -srcfolder "$(dmgrootdir)" \ "$(top_builddir)/SSRTemp.dmg" osascript DMG-Layout.applescript hdiutil detach "/Volumes/SoundScape Renderer @PACKAGE_VERSION@" hdiutil convert -ov -format UDZO -imagekey zlib-level=9 \ -o "$(top_builddir)/$(DMG_NAME)" \ "$(top_builddir)/SSRTemp.dmg" $(RM) "$(top_builddir)/SSRTemp.dmg" $(RM) -r "$(dmgrootdir)" .PHONY: dmg endif ssr-0.4.2/data/MacOSX/SSRIcon.icns000066400000000000000000002277271236416011200165020ustar00rootroot00000000000000icns/is32=57458? >3 0 ? < =95;? 9<;;:>93447; >>-211:; ?% 1? *) ?>>;=57458? >3 0 ? < =95;? 9<;;:>93447; >>-211:; ?% 1? *) ?>>;=57458? >3 0 ? < =95;? 9<;;:>93447; >>-211:; ?% 1? *) ?>>;s8mk$Q䶳TnH % ]{W^ kiĻ!D}"6564|l\il32b ]opqnt3W <׏ .   Gd  & FgSP  ܐs Hi64" "! ހT)1ۀ s R$? CDKLC)_BFDEA #\PBYUWVVUPQ" SeXG5˂ ìD  QF NE-% ]opqnt3W <׏ .   Gd  & FgSP  ܐs Hi64" "! ހT)1ۀ s R$? CDKLC)_BFDEA #\PBYUWVVUPQ" SeXG5˂ ìD  QF NE-% ]opqnt3W <׏ .   Gd  & FgSP  ܐs Hi64" "! ހT)1ۀ s R$? CDKLC)_BFDEA #\PBYUWVVUPQ" SeXG5˂ ìD  QF NE-%l8mkQ~?/ƞX% %%%%$  `rJ ONZ k]PPPPOQ8ROQPPORKMT2GQPPPPOPP .Y֧C%q.,iA2011111+&NB-K)~;  0KM&` it32*u  )=JOSRE5 jo! u /  r   fa[ޮ<~|D(#$!  xj x~x~}} GbtpN} x>},| \} v !|ue P{#|z֚ȹ9{5l* #zz4&y y1 W$  Iy4VO4x4U] ,X~@2f3U v q3K@ ./ a_,1Nq^t)[-U2T.2ڦP TeB2SQٵ|<e2RR2IR9 CHV* 4RX-  ; \—GV)ܛ P("߁B?SV߁(8 E&Vށ(]؛ SbGU݁,Laot uqh^K. S܁(Nۂ(Uڂ'Tق'T؂'R؂'!Ӆ'1׀&'5 5ą"  )=JOSRE5 jo! u /  r   fa[ޮ<~|D(#$!  xj x~x~}} GbtpN} x>},| \} v !|ue P{#|z֚ȹ9{5l* #zz4&y y1 W$  Iy4VO4x4U] ,X~@2f3U v q3K@ ./ a_,1Nq^t)[-U2T.2ڦP TeB2SQٵ|<e2RR2IR9 CHV* 4RX-  ; \—GV)ܛ P("߁B?SV߁(8 E&Vށ(]؛ SbGU݁,Laot uqh^K. S܁(Nۂ(Uڂ'Tق'T؂'R؂'!Ӆ'1׀&'5 5ą"  )=JOSRE5 jo! u /  r   fa[ޮ<~|D(#$!  xj x~x~}} GbtpN} x>},| \} v !|ue P{#|z֚ȹ9{5l* #zz4&y y1 W$  Iy4VO4x4U] ,X~@2f3U v q3K@ ./ a_,1Nq^t)[-U2T.2ڦP TeB2SQٵ|<e2RR2IR9 CHV* 4RX-  ; \—GV)ܛ P("߁B?SV߁(8 E&Vށ(]؛ SbGU݁,Laot uqh^K. S܁(Nۂ(Uڂ'Tق'T؂'R؂'!Ӆ'1׀&'5 5ą"t8mk@-ASiqqqqqqqqqqqqqqqqqqqqqqqqqqqqo_H. Hݛ6YC: \R k jV~.Y 5Fh(zF" HƻyZ7 贚s_B'H짇}upmlkkkllllllllllllllllllllllkjf^Q>) |iXNGDBBBBBBBBBBBBBBBBBBBBBBBBBBA@<6-! H|[D4*$"!!!   V<% Hf=" E'>^p|b6 H^2L#A" SI]0,eA" EH]0T A" J&H]01"A" H]0ZVA"  ބ7H]0 pڼT-A! $ݾ}bA#I]0 h󿛍}saH-A" |qjfdddddddddddddddddddddddddddb_YN>, K]0b́kWJB=;;;;;;;::::::::::::::::::::962*   A! $_G4(! %]0 rcA)   +cA! %C(  ?~]0 rU/$Kpf)A! %; R` ]0 rS, RYMA! %:  W0 mS,4R n@ ; Ao}O-iS,/s ʅZ8 %: ^ ga@$rR+n9 :У~bD)%: 0& ,aΫw_C*rR+M" %HsĴ~nW?( %: 5־d;  7Xs|o^J6# rS,ݻkM-$=Vjv|~~~|zvoeYJ9)%; 5Ǚ~}zseN5 $5DPVYYZZZZZZ[[[[[[[[[[[[[[[[[[ZZYWSNF>3( rS,ʌ{ne_[[ZZZZZZZZZZZZZZZZZZZZZZZZZYXTM?/$+/12333332222222222222222222210/,(# '; 5kWG=743333333333333333333333333321.)    R+eK4& : 6tF-  !!!!!!!!!!!!!!!!!!!!!!!!!!!!%;kR+N/  <~9 6j7 rR*G'`z: 6h5O+F&Gj: 6h4c܂K)F&^ߏZ6 6h4Bדd@#F% 'ÍhH+6h4 hЦhK/ F% CʪydI/ 6h44ZƻoZB, F&'Ecyzo^K8% 5h4 -F\luxzzzzzzzzzzzzzzzzzzzzzzzzzzzyxvrkbVH9*F& (8DLQSSTTTTTTTTTTTTTTTTTTTTTTTSSRPMIB:1& 6h4 #(+,-.......................-,,+)%! F&  6h4F%6h4F%6h4F%$h4@F%Lh4IF%6h4 "G% Y_4\r|mU8 '=Q_b\L7" !.794* ic08= jP ftypjp2 jp2 Ojp2hihdrcolr"cdefjp2cOQ2R \ PXX`XX`XX`XXXPPXdKakadu-v5.2.1 =O1SUmiK%`1[yBAݮ1ܾ\׾[Ol(V)07 ?8Z#uQ O_݂B\ziƨ9hPf 8e}> J.DH q0 mޙl-8fJ,~gQ$+O۷$fYm4ٺjxtlhVoT1,-qm"]a lr6Iw7Cm _C[ֳ_Ώl )WO{(+N} O/~?©T>HKMM|cq.]ôK(~~g$ 'O'.=R' SRQp)zexCyf In5ٮcPQ8N>x-ׄz35cv=u$IoNB#47$]ڄQ,Zzj\𮆍OQ3Pxpkl4 K, don"=Ku=<\oY0az0x.JVG+AC1^WQk;> p*sK5jX?9|5OBAu6ĭU"w=*+Qs%d+ZBnۙOó5+wqdILB? fsɄ'BM雌-LBYmS-{UZ; ž/ .\!z6u&/Sy5N܂\w]a*PJg31lOEPb=ݴ)|ռ+hcRnJ"UpYV] 篐rC[8 B9h;@ESS1B!/m: ~g0xe$,Ju6&#Aٸ]e󘩏yca;ec<j-N܏?64QT_<TM],a4e=,[htik{\}`xCTS{4$˥Jd R1rHM j*p9 k? !^.Ve0|"Hw=VM0RnAؐ2zͮ)#SX g%|uS$/lqm8 RiAWJ>:!W]K6/U!Mq:T߳}aC"4❇"@~1cORT+";!;F$RitڦSjhW:5.M6,T(Uut-VNl2g~`%6FswdM5ntZ ׀JS0ؚӌdUP<|8(Gzɐe9A:6='&mjɮ k`/adklxqbS-YֈE\R~P,' ArWg245SyԽs|*c˄7jPtrï?dS$7^!e]w z=9er-0#1a8Ʈ&D$ ] VDXt߅ݕqqYr"1l$? 'Vox(Dtib%lQr!CgƠX~c)Ⱦ5-!rwޗ$S_#oO<%/nl=Y(|3eꊃw={X/;W̹h/d ,X}JINͅmd*4D[%<-5q*v\puh塶띚ah3ގ3 SVx1A#Ucy"z~v׍4\Ɲǝ@Lq)ؓr}r (F`A!|º x1%]rBi>k Sbo '+Do%YIV%mA5Y{EŤGyg'!t{uus`}3뚕w?=QH&Ĩ؝h_AYLm'-SEu=q^ɑ8߿,`I:ђ_Q7g+ 78`U/7ij\e @4MY84BxA(*S;IM ӹ^[yk7`Wu>Hm1a70M{]'̓8&SIV9/u~1K Бp:p7BE12k(a9oz 71 *5*N ߘ=m.<+n9}0O6_m1Od'zd5ϼ6{Wnb'tG_Q}\& h:4dXhOa؎ CHɅX$ݰ 0LѶVbO).Wùl/ ‡4T )"\CNMݻH_&DF&\iC 7q'V=)g;d¾rv'a 'IJ~byr!4[oB~vh`–dE_.Wy@eKrQ[)lk RD[tb 5wOQp!=C"&Ou6p5X?n:"7N\;yҔ[#g{YK'ǚy=q6zьC?t>7 ɻPB}VR:J],ggn R7g O|ܳ\6Q۬ 7u|6^By !ڿBީ!񒑍&8G1`W86vۻiS ǥ_+mh/~- 9能 ܐ *88[NXԏ!{{_NGmXVE\#:wY9KCP;uo@aL/>A,@0K(] +1^WEmL|5."=H),${.Z!ē& %t1|WES'UeZI);ޕ~z5"X E#t@rn|\ҞeBQ*AU- |@kì*{$TӢΎ-_~20g  4$PYӱi~L.Վ%0%bҝy2C\Ec~𗀎g{e~ZCɣ#T#i“y ԳJ>I2$[3@jF,hqi]k6(xc! smLҚs'ҷl_[]b^~F`or rF겋?gOR~nK28 ]|Q uE+ۢϡ?:^ο08ڗдr\3 ;TZFNjo鵘. ՃaѦ7&Eӡ݋jxv\¯2+wYu4E(;r~hU\Ϛ+PF:q^y<\bIkMʻs%#ֹPOJsaIo?VAP8f҅qKM(mU&k*3Gڸ}qfȚ 0JPCr珅o?Dp{ .ZSi$6k7W)h.7xl[dv'MX:+F/ږS\]U~<7l"ЩFjq;ZDV~jw,ghT)y^>͞UYsɪ=8ܸ}aQ~ӍJ ³&Ɣ:h#,lɩyS.f͆XE|Aȩ0Aі=r5wM N#3d=xؿAgioi0z3޽NGPWW/Bv !Bkgw0I;?dSO96Iz\:ޠQa4' oQSԐN/*X=B`֘A1@h\Eb)%(NoO7G\ (_4DP6ZXH_R{u:u-(#-֥!0"z;Ad|!''궙 ML.B-8-0lCj޹R~57rwүX=E‘@K+kg&7m$ّ^& ӊ0jJڻDb[ ߉Dlm>w96" UI㳅yUdRDLǵ|+&L~z[}f/mEP>92GRI%BĦ& #}heN C!CU i+h+F8̍gpQ U.g+*%O%Q\0+*u&p\$؛+Y)˜PjD0?x){zEK@|c2K cjT\n"z0݉p WujKlں/cUH0J:=دb6.0x#HfiOEi~3Lçۭ" OPS ӋI];ۥoRT{]]y*C-qbw;)u f WH(H,^mW|Sdm1#OwX+7齎d@nz% \WR@56VvrXBTQMC]lS3q8@evCwS% Ll<}7u^H2LӮe\=5qTHɼ)RirWȥ[ځw6 ̸Jŷ$!RD( [vsd b"wp|s-; œ_^,rt2=5' 3arj|E_wuPסjbv]XAH~ nxb|l)/sc_hs@؋ϿcZ5snt%o{4(^-_E6pu½0)T0( &@CJTyf+S_ bKx(J9B8 /qr8FqJGqq2~% $mmkKhG泒6/ lR2zji\\_$AyI1|~2XrgMtRCX4q>C~|D طZ4&hwaJqyp, 29vA /xƊ1 7Q>ܨx@`G|tFJ]' Ҿϓ#j{1Ǝp[JlU"-#KnayGk[Q;r'M⹏I9'MmsoFkclU;f\PE`_g)$hO"WI;ŕ :pz\``W mcC@P}C5[CdmmmDJfT޼({=_ |'O㨠nI$I$53}IAV<@{$*MkQڞ E\Pï$HRj]ݿ"m}8ⵐfo4N[)?Q!n[_]ݳpmbn_w!aۗ4]8Dɬ+jda1R?-P4d}+,8++UbneHvύ*K.].;zgS[WKJdR (=8I`WsI!PYW  }yE<\$BKw>rZO!Std ;LmzM?`A Ȍe(w1`'@OgL@F∏ rI$I$]>]LB mPK†&gfmb+>"tWcm e]Bk mlfiu30vs)bA \72o r8<™.q86F/T~>Cvą^ט6aQ"c=l`)A2(1ϪuĎ3Sv3dU\~4^zv'&ĨTy#}HSڒ d~[\іG& >48RUxy umҼTN)U5c/r/G9 vLp#Ou+2l .Cz4_Cj1uoTH)xjc[ҷ51b99Q$I$I$@7&Xa0rcUSa fݐ]^<0],t_fn!o}l|)FEzz -S}#]ӂy2 u>]Bd;i|?MY:Q4/UHrɚT y*qКt7j_tؚ z)Ƀ'k󓡨xl2?gTct'?iԎ*-@v)BfQqLOOzTĽQMY97kDPHjَ[>ew9"$}08$&`4=ِ,@ntK;Y~xIP/D_礹<_kMO rc g(ϛ!Z|! w3O* Ƀ Z:W%{-ix:-TR眸>[oxjGZ_h\Is]8@b## 7.MJc5nG(tâ,hүv:"Y^(Lf0xPou@sYHYN֧7mM%s@S5N[Rrp6Ƈ0?ʑWj0dC^GYOTUbHn YyflHSrk0v;y &28.5[Td C`xm c4`2{2V]J"y%kʨbx/G> q< =H R 5#^xFP9*D*/xn'Y>jiyJ(B))y&:S2N*L,*ocb h]=q}I8oOCqO2>_f!\=8ׅ,`UE:t\V!o<'PxX|g ]N.]VӠY!>?K?8%႑TU0Cr578uF3$fw,Om ;bG>fZP=pstN)aJ_LWrX8lj,g"h[QASX<6! ?P(A^_^{lM2_7Y<?VFyQс\BA|p З75IRkvM`t_Qډ\v\zuէ[Va[t'Sj ?Ho /v~jr Jz40^=0)Mcגc=kMirta:ݜN{ fTiV6Q=O9Yͼʈ) XԐ@K^\`Uvګi-t5DWGYr1S*O4$XJ;_!t,7BLZjҲV:_cplG}/z6W}3I;F^.`滰yc~-jz ~u<QV*+G7UͦV v ~9)|yk&w4z*Z>27ӥ3|%Z>~)!yJr@48mOƬԞlQsAv{7C'`Ie}oh8_hz睁߻E5#K;.k14˔Ѽy8TuCJv\^^5*1ҰPunUIHE\c m~,DC4@[h%Ȥ k{)W,Kpa7f#l>$D;FD^weQJ؞4lY\-XXqi]LI걋eMǂd́P(h(!bKZ-[ $M|gPDGj'?{gT?EǗ /I*N'Q1EvCm~[$3K}64f H"(NȧXvFнH0+癕*2!Y3\ rzN+_vI$HՅyoor}$ϩ G.OWϭM㟋+N eSVbDa"}8qlzo |a"j>7a+̙M9̼Gɲ!bX6crlO "tBW'AV\CdaDeV[!R@,炼M}|mwsXeԹ+p|W sRa->x6ޤ}bl9)W$0Oݑ%;(k^sNn y~xՂI3*vnАŇ]lIx%̍\Y@ ڔqDG"?*yRkͼ]aX$Ф e|[;!//EZom,$'F~w=>A *E2B~vH%@4p `&:0"kt Wq>-C{^U.cy 4s(dcwh 08R;rh s^#^[&OCUf\XⰃtQ1Th6pd#wovTqٞFH#HDNGA\ .q`sz-.S#Q"z!ȉ`oFI3U1( tsY}+]"Pyr Ldyg!w 6H6_iʅd,3,qnfU _X)m E.7Q3*Ġ, үrBSe6l'c5M}nirUirTQ4sD=*Ǩt\ߚC ڻ!4E~̘n23~  {GZپ7|iB2+2ik+Ƣ3A)SL9EUϦhq|PW\jSj/axö؆Ze3 P2JƂUcUC/kdç)gKIT9GoNO3A.qe8Tj8.f9b*qW=FQ$\I,)+ +)J0n$+MG o-kMR@UhV#`aM8SѢK%.Yύ7k.S6w}?R76U`}T:"y/Iq;[muM4~ !Ej Ǵ10tr, Ԥ/PVjct=w;{%(L4)kWUEŲ~eHm Vs|(mɆlmgtZ< Opa`CI]1# 1h}p&JODb.M!APQ9 KD_VfR'`tIk6 /YW!P-!6%@?>L|SM~ޣ9Ir$:v,ZfnP̖PN Ȃw"L;Zp'h#ݑ(rYo,t1*w^9o5Gq4?ԟD؅r Ky%ׄ+xA-)ٱMlǽw1\Na<(9„qf՟yvI:g̚Ywx P=]`~~~yLuɜZi+|8ǟ[=Ku=<\o%ޡ"5)0XDjk(%u}z9WV*ɻ%\cCtyX>'|A֟Og۸`>!ail$ i $ BMmZBli(9Ow9W:Am}ֆ}Nh/ E<B/YrsPl`Tg- /)ypgk&R=W}N&Y aГzЄBtl`e N.ˆ΁šAsɄ'BM雌MLBYme⩖VҝY+ $BťkMEHoLLZ+5^f~sj zP#OjTu΍ziiE'$|ݥ`֙6 hLT_o@H<,@?.b v6$ͪ@lD? #^.Ve0|od yUv\&!lgS/ ނN9\p2t= 0|)DY?=m%r%KBSv@ҴWUj.}q1!B6{&+(A?y6tčۜ=, 0*d;.nCq) 'bF[ǥ,:To,9lP~E ,s1o7GLN\:{.n"ۺ5YGu{׎N2y[윦ƺ_`SR!v^-&L5_=uщ! ?t Ǝ)8n@M_.CMtHM*8̄M+TY0-4*zf㎺>P7 3=q:bEtCXM|tOqb Y"&Qȭ`CORyYv;EY߮ɩև WB:ڪȔIiT΂nti+\M@yS\ci"OF>cJvjcjдUX DSBm$}U %qȱ.sNUw_wL5P':,Ux&Hnl1SHBX@#q aLlTAZ܁\XG˳־}P['_,eB&hp4B̀ld? (Vox(Dtib%lQr!CgƠXL퐁T;eϘ D%SYۮBT?ob[n {eln% $ k 9j1hki;9b0zz-,S3CW\)^7do,=փ,޷ӼN_j8n:.)r? |vt\2>)R8ԬsBWIu 6U8KRIEs r (F`A!|º \AtjZf j_[tH~;wi2X9 eqptd=@;ă.|ӏl[^#S0]e^ qf4kN `E4ېpP0(xG+C_;4;U|{0&̩h+=$vNC[B=OҭT1Ӧ kWҴ~|ޏP]QvV:O%BUBpφR=#?Sg6?mF"]13Tx}["_˧e\4騨/$i Pʂ%}eU?a葡Ԕiim Ͷd8+f2B#\ w3VWcZ$Ӯwɭ.^CPN1ɹ7J][7˜&SQ"gZ5e-U+PFH$m˶q "L\Smp*g皬xxLrᬆ>)0-'GISB2L ѳheLm{vļ5<v괄d@@ Qo%mZ1N#cg͌a G9<\~CܟR&h ì]ɸJG߼Ƹ @W"i5A(8F]{W{ C]bKs?!a w= ]0x4i ~VZzvwL5]<·j^P ^lȥ3SqWbro}efހ0g"$:cΪ#}&=A+?ytƸ|jϕ.n`YtD $rvPfQ_:FܑȂ@;p ɰѲ*7sQ zn(&rwی,2Kc= f(Bx g Ņ2%Ŵ3yJa7@'q2S-q<|qDp|iIa3 SS@V juK܃9Eb* &A~dVhZDB+SxTB]ZjׁiigPʼnB/-rҪƺbWD79@nfG0־[zr:s=yP<1)|B9)MHICb)oUz/~Stjj|IA 6`]G1g-8ʇ. pI`V($B9R/{5Nz9\"qjS8v̆Pz<2RmQ7 CX)Tp>Pgh>Qu<߃`$6h0-cBYWo.0TH - oGQaBfCR_d!>fuҞY=HcY v̩'5 Z_rM8\gRoetz*ҭ b{R ӎTsJIW t _ ~LeCdx D~Z"[1x震5QI`/(I{Ә{؋M!=c@'[$LGN Tam_9ţ4XĎ؈ B)Ho*d:Қ. 4/AՊMD,Kxʒ1H @˜YJ ` w?Q8ӯƎT s:4#pC7I?$j95O};D۱Mtk!z`ܟ@z"u7;TM_'6C'F+1jAJ؊o>({_Sa0wd w_5=wb[:"\ ?,,== s@@,dhyIaP0TbJ'n9Gmnn$`0Xk>؝"y]aE.s>㌺;')i (r iϴeC*1FSe]XEq"!l% u:Sw9aa}MUsh۠TZ\ p߀g]Vy"&tFVf\E%H4J7 -!xzY:ҹ؁,#BpP4lYtcWzuIhH' Uy0pȧz/ JdK,Ţـ(7ZKOy (SPL Մ NzϓhքL>r`>tbdZS%gq I5i~pԴ7v lXW{m ΜЎ_?rk5O-#䪼`:`y% pʶ7S.\J^/rN\Un= P::#"MzUG ?s13_Of#[$ʍT]UW WR2[>ACQAG2$! 6*Pr KDl8i'!1_VPpd\!LN 4\ڧg70oo%KQ#o7 U ʋNv(Iufhs "*6= "K:+_pZ1S L_ghl!y+/p;M:sZ$לk!+Ia'OdKd&#:.׻nx. D '=_eD\Mv"7q>@F4 lo_xy^DLo3E+(Qb2hjjY_r*08x ow'A_"=5zezeXhk6So),S,X&/ѩ{-nd|e`eun/B;cG,ҷRҀxbr˞mb t yyY?Ck&10dC 9#¤m~r"DQhZ&gW0֤qnL6&՝}Ƨ!Np(rӥt! ZfKzajC[Azwsy䄭<* Ƽ{Eއnoۢ߷J?natٯ۬~vv߅n:܅_ x7nv_p@tgtcOs&> =j8@/)Dcy. e=Å7_9_M`="\e+DHDCSZ4cw`D#b7+A#5 E~E;VR g5 q~1Y ߬.ڵ:3ɄȮ8-b`XFr:Ff5z酓mS5P0hŀX2E31')Ѝ%J=tx<I0,""zqȷDH Zl^P黨2R`(%uӱ0p~n VWeg_|sR7El=F(4ImFKM8!FX@.v4qolS-"oY|@n3π˟ںZEC`}^79,To ( b$D,HD`ܨmaOߩ'?U7DȘp"Xde{˦"C /G泒58ؑ1QdH*$i@u dK?/O"5[6:0*Ӳ孡Ng5c-'RsL~.z5|d! $_EAx?vw[| +‚U'. s3{zGlcCBj}CDO(@!>'A )2Of-x/"ѿg&I2ؐ*ÑK Oث΂ a{wx?_3j_ y%|+<LJCBK&Q]t/ר-$>0‡P^ ]IH >Ha4@V=%L!I?W}`2]ԃ =#i OFoN"1\mzz c&RlY^sp\>p=AV<By~)tu9t[~hBmR,2WDmĊz +{i:piJnWtTϲ`2kŇʹ#k2['vfUr P%<9Y(C*-HMyՠ%hF7|B8~53Ig|s>vK׹&5ؿ%uHI.tpy~xT[MX" zeX00d%7N6i0xv΅eyP (+UB+yڧB?Bڿ rep_RSF gJkv!vf; E$CBDCs u(ӫ#bl`([aJ_ In qX V,*kgsN:=:F`:=)n~|0Hw"dk+O YrRe"IGaS%;#%N RAXӭIl?5#0ź>qC/v9έц[œC}u6!wǑX"eEqG3k1Y`y"Ñzk5.g$iUaE5k GKb1 2oUi HSdIڼٕ̈=`t=.ICsoﳼN[&UCeȣX=\[_+ppz|v- oL( Vg]ZFg# sAU$XS6[ˏ W$) UC G"Ӌ@|!+ leDj|2)aoи!QsWg2*j/^= I?M$E-.w|J/YBTm^bmK^鉐%a#=|6 v@iʽdDWk_%P;z5 g@j ;&W>+Q }e = -[g%B𗂷 kZ; u$A $y BY>mC}'J4[_)/NR7֬VG,8bJ/u3a +oɕ{ф=iwῠg)ʻe=CokZ6ư7٠|1g]WZ\JzH)j^[Y[S3pI3/|^f,= ZF\.xbc$>Ĭ+J>kV( ΥƼi}K2O7Xh^@h`ˎ!._D8C%+E[rqߋ IfNS1nTHd~م |}F?Q/0ʝbF1+Cdإ $9NqºaU&CeKBV`Qdjv4,hG@t#(*ڪ\ u_"Ql<ވd,seB"ST%P4V <> h)@%)gѷ1lTnVq[Y#QP 1zEsO9Ia1O^Od-p;}l!tR\EJpE}=g7Ғ>Yu-.DDf: ?"9pPv\VgpF=7[Nx;ڂCSw{Ό !%=k9 Rr3p~엀Z3k|]~ZZJV_?E/v~jro:2rNLXJR*R:/OZSd YFMGRԟN_Î {߀m]]j@Jػ3h&3G卮-|nI(;%ɲF߭+$M,1¸};iVb咝uEuRmc68' z4&zO 0cgvF䦾'KLJT,eR(Cj%kl̻F3gcށ#a ,w'&FU̵h!J_q v9iS|&c8ȗnm;򇘾-?!^拇2H 0{S\ S fXIՂU=z` q*˼_mYXlz  |qPZfr< !,sqѕӽiD^weNߢ=ԲT={Jo+xMnVINfc@?'lNFߔ4+oɣK'RDg/ 0jKY\Uh,dS#maYmBgdDCѢQ<VtaB~Fu~+1D륶kc@B~qoO"BxEF mKvH9pi,v̠cu.绎#7%YzK)FGѽ@J=a[qG#&2qOkaIuJoZ^dC:Hrlh S2YG֊n-sWEWr`^Q\$翜l X]E } C..#2؊ h"ϒAޮ C'aZ'JXif}H\$}9y>yZG{t_v|m$RmRD}jFc a JL\$xWUV-okedSph4Ѵ]?ëmkܶB*Fs6B*چQ_\pE2 wQOUB8j%L:O,Λ312M膚c!p>n= TE|Յ@mr? ӊ:zE1+i K'E0;mP*1 /7B1(?u!En;3va06Zv#![" gV=;rAoq_&gp C*IY@׾#Ơmg Kxia ~Fz~FvRw G}hq1+}\u 1J5B.Vh01!ے6vpApP#kw.6M$__Z9H@v)R7<&&{'絲*t3`-[`_5<oaMy1L^;R N%hG5ҫo_o #~ofD_⠱gqjhs1NC㈑Ta۔55xw#~CYT.q\|Y bT+םWFB}=)]ˆ7gIɂ#*(3 eO#ّTjkDz +T_boXbY|kllm">ZL^L^cyJH*q96/Ū{.9J)\aM䖩)L}M{˜)8_ 7l ))d樇.PrNvRD T C)gˇTP"72p r&%ec.I > 05gG. &CdNGnJl$ˠBȔfV`$.Z؎ϿOY'@EDrz6Pb ,s~=CߣyP_`l2֩yKWnXծK)8N8H ) 5lt`0M eob:Ёj:shώ~ؑ)R@(yp{eQj(гXaj2V{xK<់iYY2DMЎjʳOmԊOU;ʎ8ՀgpBΨ{: |cN%,i!p`\Z8Լm ݍU-V*2)ci8ӡIlڅ|JR n-^hJ^Ǹw!*]*Bū\ŐC/Rd @:"]1I?4xyQkv"a$<ԥ Y?L7{Qo[wy-ū_wmhQF ,z0ni 0@V"^DjfB7Q^(UQĠGzϘvhIͷtrK pޜ^F!ƺ.rVrZ"W,G|0s΀ -Iix~RiJي?^> ;k!/wC"d.e>vz+Xl+'E~o_#:HE). v_,LC%p^^SBʱnW*T9v $ w]xS-P% D7{MHu1$xQxj/W@'x׶3QR(q<]G$Q^9ɗ@Hjv򦾵}58Z)9-Q>u_~z6c~k[E_s-_ᆬ+[K^oAP7?սnw~QQZucQoզukU_=B@D~v}l7*VL.pkg-cǂg# cZȇQs1$uێ0/y˸E~~t%vo ld)C ``r:h!Km95 N&Ife~HcU5ǞjsYPK0 X=g<6 'A%"CuiߴΝ|Eib̹d.F) nQ6Y08l`qDd≲Ɂe& mT1s\P8bk}_rҘjߟD^aksĕTfO, I (nI01jo){әc5qD2^ %osbYZk~(ͪaSJB@ưeElE[mWNwPo斖V ȚeЗ`_cM1F7N0LRRvk@ 3S'úqTrBU|B>6Oº6 m2JQObami#eEN JFOd4'@ 0<F޺} 7v6=B❡8c"'t~x-; &ZRC-bA>YUIƒꟙX_wn(QPFW[]2"*<7SQ; &6s&u/uxS.:Ήdzv%NHIde粗@e=8U=y-N'ثDOKN #ncTUǥ,,IQ:btOov~ a,HCpƞjW3Y7M)pz m[[Mܶxn8*& >.ِ"eޱ7`=/I(xƖF܀K( lq9.gf .'I}Su=@׮E>׷{NaNBTaao}r)=$"1gI!\\D]o՛ }WS;љze=bx|X|Z`9xy-2]̜W13xp,4.2Mw&S\TiJQ@Z|S?9w@:]ܳxO%t+Sa/Z3 TB7޳K CpMV'q;mZ;6H"J_-t9S۷ltDᏇi\lEikM85Tx{jSs?Uqܹ潬d"?8:{YCkVBHn,K =-%`DRJ9J%ݯc1|#*6W[.GF0ƨڡg٭Fw ^<0ab)w{Z~ /{TX)}}oO2IPe:[FH]$8m. It%c|Q Iuf/JuĤSQ8s} P.54tg2'<=lԥȨ^1;&N aj̦K^4[&> `HRXSk hM)Dr6#D 1xpfNjUڻy2xb.RC"?щKPS`tbh]t?|IQa@?^BΈHKfAhsky*Ṛӧ zFw2_o9=C{;6e>AG">Ľ|W3 `}?2-"PI6+ 嵠&:3P>pbb@hr HG_|MU.$"P @NB)p*Rda_*)F"Mm޷0C|y"|Jj\YE!M(KOxB0[,dwi`@Jܱ +AV \Fg%9[ۻ;&̱e#9'0ސL ?s\gK5_L|Z 1? n'sSk60ѯS}Kc_:}o_Ve1Jʐdܹ\aD1:qa}ދ%okL>,3B[1F!K;7bixOQdE}'L*ځ$]Hދ)}0@S3S[,~l\J4SڕoTFޝZgǰ{bUtK̒ = ]7si'cMPWSx9Bi9o>d!`; $iD̆70EWrUrgx'ziTϖ:vV /e2W~9ӑDmcpLOƬ+s7!َC%@ 4"O9cOͧ0>,?U sq!φB1=h6X ˜Bjݥ@ R ElRCɠP;IxWNfR+:l38L*70 /~i{}]eb| ^k-.ܸ$#}ZűL*ߧq㯺..2Ϭ I5֊Yi~ C9=jJW)8O( $w9_Ig *ҘKҜt?羘F)K NÓA5}^HBUEpg%wׇ@A! >s`.TRfy3v"2@ E#<2V܄덨%$U?C?P Ua}#E  ` Wd{$ B%K$N;,i=@d7O2T4F4DR)~jC7`=e%* c/4TU?" "ȼ`zْ t}ټ,(!,"{\SXY^̕NnN~~tߦQP4,kf4dzͽ!B]?,΋K?]2 }H~Z`_* Y`w!fT_`9(A8\K([;t8‘&=UUUUUUUUH^~رjGԂZQ~ (l)^vOF]e} armPpj[YDqƻJUUOa al{{Ă9wA>-b-1Ur'G^?v=G+w_ksݶcWnMhgGɇ#RZ+ n/`jCij?_NfzN&'dmT^IhN%x`~6 |:aߺp=IRpA=`)= 5O Bs]" :*GTLO("g G D##u c. v>Xo̕F>Ur3w;%yF1D2blށʼnk-ٚf3"l!I,\"B]>-CLPXN7eƭr,G+ n ÍȃC@uU&O6G r\Vn=GظS3)Q^'ѩ+AH:2iJ jB>?ܳ-^%xPjND LS,b- C 2{1P?*S{r .ea1b ZOQ MZlQ+5ñ.koRFxy6H4RLǭ7z4VA`f**d.`Z[SdJ04 9:.? qR6p@Fdk,|$Qq-]mH͠eE̸@7V~ ?kK|NxUga* 55)UUUUUUUUSZS&:DbImU8ku_oxk֧[>5d΢D\0 ZǍ˨!ga.ʆH8q5OY 砨Og4r-+z&uD1r9QxʆNWW R&P_f-P]9b#QKfNAzV-|pnJq@.̎?#BJ#0k ; )jn]6_})$7{`LmH(ZuSl].σ\n| h陫 /m]JP<:)rp%&*é\\LdYGJrӚǸ`s8}epXV-]?CR|)XlPV@| pʫr%(fFÞjH|{ܪ:wHRd 7ʛVTVtH>40z뻬<Ұ|#s Bج&IpTLjyۤӗ.[Й¯kH$̔SA4tCJSAd} w,ݥ0DOCPft_i;<`6Iw5]xeej cz7J?d]TaP7ǐH1}#*4O$@$z)HLpVslʔ2UR9 ΁EG?QxTE ,[@^x|;=HVܵr'V@J1T>Cê{c/4`I0  n|#@JXhtoYo@_yDLfUֶHS]~u93k̇wMskT ,8qU_/)PmؓBKəuU?9-,C1ܺ+DImW1dŤظ"sY^ [>pf2'ӱVOQ5;6Ur%]js" nt` g*%eycKʌK""XIuˡ,MԖʺW,ThDo^@.m^lċQtD5wʶD۫dNYZhLDNN<2@cC $\J.,P+ڔCVb==lįNO s#Hqۆtl" !obF6X7n\>4Xsd˜,wխV_mxENo /Os)o1OfG/3uiIgoM&eq8$5PY>Qߜs^\=.v>.n58xϨorp6xW&Th_LzБBǕw+Jߗ3z[UD|tct ʈL$ᙌ}"j:\8jSk1vmРNZdVnO6*J'0ԤU:tmg3KE=}Xna.&rĸj+~ eF:{+*8Y'jO7t<iṠapZ2a'>b28/L*$$dtΟ|,,5Vqk$X MN~8bdj^~SȦGF[ɭq&f0Dq[(R3X]@vd39.Ez\Ot%;J&_SjȑD⩎:Q=gˆ'%Զ;}K›s<}BG#:X%5W1nq&ZgY\alm~|< Z!io{v v?R3=RvXu}:4ʙu+})B0og>$g5Q2ݭ𱐃Pi(zR6]Tf$y^yDN]? Qvxtֱ;oa,ξ^[[šd@~kb عe)gF5H̀?|?=+@V~}9|Mz?R]Zosk7ߟWɠc%,Mr_"/Ϲ;Oۿ^,pK_VNNu~. &M 3"Y*6ȇi]7p\#rA\NgH1k[̓ԥ,kA92>Đ̋8K #ڧm^~^t{ yrΌ*e}rl͋P#}xK2#W3abͥ[tXrN) ^"-U5;‰;wRyJ-.H9At| ~Ht+}oYcI̾- qmXY+`%بO>q*p ]r}Ǥ2|M,v-Vmʯ'H)rr7F_@H!ܩFeW(Iψ[[|bkE! G *j4_5t<>h4gwm>q; Du87m-XACdԙ0sL%ݳ,EZpLUD~~rUÿ@FB.1%(4(sTegj<+pJc40$EyjB "Wz7^` |eA~18Ŷ0$U2۷Ѐ*Z)1],֐J$swis-[/pio)>y1"4iަ 5{uh\RbKnÿħbY &]_2"o8U%R:P%WF3~yw>J( ۫Akpq3w/ LPJs+ =Yxz7,-ANxyW$jLcd r&!D{31 B|{kyx"f;es}bca66y4s, "Zu'?:Rk34B.0!lsYnAYq:VvB[%A7Kی!N‟zB,]u{/b.QOGU;Z[@8jԧ+ RZK۟k7MqUR t{hN8gwSlmoh@![ %oXMqd#WJv&@ YNNf2]ڍiaeF…0B FeZz_ĴIBQGppB޼yR|uR#`ZM˜!Θ-PԾԔPٱKe, 6TG+#sGjJxmu0 R1.2Mb_fyK=^峯!N ugM螉*8m/m8Xx2>GkH~ RJ@㡯%8YTdik&{DؙuqyI«~܋sZ;7w[I%Bݡ+D 4?h"2mVb\Rp.iK!9~C;c H[q:ٴA-MWYp"69&4MS6;XYoՅM>G + p"e[H7!ex[BB.0 je)0GT&[Ecv=//vNT7/\Q8lP_E]9Jr4}7`{;d9Z,FKXހB7nA+K;26I H Ӗƞk/QB,}BG4>(#>ʧgx{}(j HoWuLWJ؆:.OEòRfG+ۭ9l .9~ItdJi0}/nI)A+ Ww(kUW;"=KU5tW qO' Dނ[Ȫ9_z(@}@4GR;ZԔ> xzJ&8M=k]fv,8V6!Et?=>\MOkD?upg0i;3L}"  1[ֻn gJܸ M,)k@}j^jzE22uګs"nDW}lizޥDh?l7Oi;?r?2Uh^n?50΃~q5 B)͍, ػ/en'k⮨^`NŽds=H2|\ra2@ߑo I%Rh$lDeo3dcʹ2siAKVZA;WkT~r if8G,Wi_{+F''aaJtVI|~8J |WJEV23 6B!)Q YVp] YSv B;ze׸ = ,ߟAI6mmmm۬ON>Ѫ.HPR9mqg*ZDmܹD̤\=<}n35[E:;2 Ui++ItEӔԖ_6ɨ8rG vϧ3>iSIhF`mؖ(:zwy`/8:WHuȧa8">(<;vWzΤɨε4-7AsF޴_'T>bDڸD@4gh5^@zU2^҉f.X:,7";uĚ }"NyեNȶ͔tWz-C; Dt, {Rv~.2XY~Rmmq/jx˺k>yGZDZd:e[~~OiYf+M9}25I\h>dpDc:Dyz.-טn."Q$I$I$I$I$x(|uqФ8š=u5Kb*iq%|t]pmvU(uZ򷩯sI@ URu=\a_K|my()D<>DH&&Ŵ*5xUCMII6mnWd=C>W1e,d cFb{H_2g)XMfL.TyYT &y:(`xbkAS;5LQW@ЁO=uI$I$I=EO4gv- q5}[IL EWmqbE+߭шױaxAը=yB֛ENbMc Ys!cLn4l0ꙓm6»3nBͭ.nڮVbg"Or"TS>pզ af KL$ݟza]©l%  nz /# Xvzx==ٍlTUG8s5|D=6dq]ּ|^QFvnL7hZ9'!)ΦSm ,0A #Fe&Y]kRVb5P} odhz< Kxb/(Բ@.$6jF35(a!ZXT]zEo[DvWQC8,MtdAs6{GsC{QfŐ*r; 0K֍L ;]7N378dL5 'a*L L&M(<2x{_(5n_W]A"e½X8X5{N DiR7P֛b+M3[fSJwo 㨐!XXl,j`8@ ߮X|G@7NRz `|Rh>r#/|tˌbY^NV Nz8SQ4{饇OsbPM*S59NQ:g]ڠ5H-ʃi%>qm"%{WV]N:j|iwliHEMDt͊`8i#ȍmgl| uxlc2un'Yi. -aX9ವzq> ȾJj|X-XK/3>N냻ԉ +T$:2">meR`cʦE(>SP8dCFJ Aa2Qo8W51wLj?5zӠ k3𿚈/ r#Jk)/5m?:| ڀ1f&}ȴ-y1ubd4\aM9ܰ:qKIe9 Эf4`ntd?:?P@L`%twv(Zt')4*aȸr) % !Cy&k];Xh}ण<~(O[ 繽]V/4ꇓG2"iHy!~,> Z2rĴrAeAq^<+jU)BU^N4A@(bz+ r`P%ZOڀ}`HxJ m Uor._=Y_HC&KT lUA2 A nһs dN$I#A]"3O1R2imIF~iY;>RL1;S+jR.E% A<{7R02ؓd g!6C}~r?ыwZ=onF%7,j 3N"Kḽ.x/:=r6x/e uuB&a Ds0cYYQc*/&0JӚK\Ļ+2dEswC)33Fb~aFIN<'v2/NtUN(Z{hs؞ C ~:yz7b@kF"*pxTP*L+;wicnV Bssr-0.4.2/data/MacOSX/dylibbundler/000077500000000000000000000000001236416011200170025ustar00rootroot00000000000000ssr-0.4.2/data/MacOSX/dylibbundler/dylibbundler.png000066400000000000000000000502061236416011200221720ustar00rootroot00000000000000PNG  IHDR:*vTq pHYs  tIME  IDATxidu&v{fef{wIvTdE$40^!',`>F1di`Ɛ9l.M^kʭr=np"޸dv-YU=nsc dYf!, eY2Yfep,2fY2,kfe52fYf\3, e52Yfep, eY2,kfep,2fYf\3o<;=6TB!O&pif2>I$I"PJBr\P'w'c191TZ Ð1fy5g}q/!R*8eVJqDZR#1FK)ǥl6]%JiJ8 q(CG̞F)RI!ZM B |'qi7|>宙e7VnR9_|J!QVyBL'1s] b@BƸ8RJ?9'$I}S)D">{!|"yBbLl%eGPJcII|. 1Z+ чJ)JI1ژvXk.  ;۸hϡ;0,( *Ù=|8|Ʃ Q%R6:mVT7-wqzn)Oh3fs97?XպNcڱm8d@穌1=w[U2CnsR ${R%}!>uBr_O;(OUxB1i'cRĴQB*n? n< Z8\>MoJM}0 r?2)eLDk PZk%T)LJ^/;TMR[6}i-->ؘ$;W*'ޔjn?DDZ$Qqg.[pkRJ+e[,[CO}'Bq)<"l;lSl;$9>)*\~AQj50tۇOtJῲ}I+сN{O}Qq;fidZ$ &qRSf}<} q0 QVZugoIl"j#JO-Tz#|ӎ|lap_fkIDu(O׫WoA^?/>!4ciT1!iLe;U i}v'X͖y,=wC3ۮl;-S*vyZɉ@_2 ??>c;b(gfos{{{rrR*h6KManJɤnl.tg5i`Pd0- q\o^3rLC{5zoڪ__~_kjևkHJ)6Tp?~#6)Bףv1}7/$-d^!34tqK&Ib5!$V?\z-yP`vX677Uz-/a GOt f{"uqB(e(ՁظWӢ'Bؕ@4v̶AQ0Hހ㸮h4i*5at411`v611Q.먚/E@޹sgcck_Z4e;' q$jRf_} vL:ζ i0)Ц]2ԄvX9jhW֤ [GlXJ}OM09wqHV}(wꫯo:"(:(#ѽ.Le )@9^gA{Xa]WUĞPJ=kZ}j{ݰUy(jڵgΝNx}*t,]lJ _*; 0Z:^+naҁw B@tmѢ>6E:$1N[ = 'V)=}p 9s8a*a40@t =ζ6~nqyhf;@Oә%'w+ORJ+Ç SE8J@ȀT{zc1fh۟g\E:=-VBuR@a6]Bw+ qi~jmwER3*mm/KfvFk-|GQ S!ymֿr"&/3oZzpS+w$b1HӲ y3gΌ8RkZku>u5}N}.(yWd2| 5y<[\\pE)VL=5]&]L 3Բ1ieEqS꺞U*O<~dAGw;\JbJsȅ0"Sa Rmxcn=V*Tg~d8NZVgϞ)31)y2ԕ^R9"5Sro@o.!R8KK)m<0JMv I_ =X#Ax@i?7 /_>sL.5tڀֆa ]SAЊAeT7zCߙq}ھVĘ9Q w1c.q9YuB_f_7y!Wܥ$`.?`:B%y.C܏w oݺuȑI+Гc "n0Đk훦*RCQ}0°Kun lFzB<]{`ܵ){hKoD ?Z dkH unY7~BT0j  @yv^?vu[.YH Q1娡@ҙmLK#xXAuO* #WHZ:- ”f|R05NVdKM͔HCȖv X$ _zٳa]w!y&L#W)jt9/0lS!#fƀF5և/D{WHh{Z=$b{wӋϡC [>DtNqk׮AX9p4c#) K)`}5(qZVױ+[<B5((m a)Q ?#R-zS~-SbznGHm2&8rI%,{=wha:CUP 'GԷ>;I )vb{Po?HߟBg](۷gff:.2u7Hy"lupR'QNtkuhqp9jXf;P" c|h$q6\.ÏŻίq zBT=.^#Vt.}'hĖIǏ LW) }Hc <8HD=&Heh7Ю!dڱ yj7=w91Ty*Ga} 5챃yƷ=y4@|O'/\[[[XXPJa S'cB֦# M[jCREvB eNԤR&IlE=~RS2U[>0I.ʆvt3eݺuɓ1ݎKꋕ󷟽[BevAT;"EAlAژN 16B$ƄEQOt}W̩[|TM*fC}ScWi£ |ܼyQZXkm\^򁦓B!Ԧ?=^!nTlQ ƆKzF$R ua (}|pŊ=ma>QҔ tl*122266h إqV]tHl0 6 ]!NH\ MW6GC UB'Bj}jFW'Z+bSAާ4GA}V977]c5(e` 1P$)hś$ lޚqgz"5iR~}v^In2I f' G=5pM_c>um1Olsgα0/tt*kjےZ[Rpva&!w #H e"Hmp N@cL @ʏ9δ kަi^W7_|1啕B7qhWcbx"Cێ8$mW[k RA/rTvd]L-k7AB@8H6pi)dM7`c9YZ:~X)vy/RJ5hJ(t˳ҀS~o)AuI=()13mCbhUwwJ05Əf_ n$WkrN?Y0L 67LLO c (3> Хuz5 eam0ݻ1io3`!˧gLs6w< I~@ Jh KfJ;d 7uf\XY3 =m?hptE!޶N_q]ı(~os8:I~JM)+5=ji@ ӯxfۑX԰htZ t<۪iW4F{\yᅗ 役c~wMMRƈVl9 MùRFZ|}BB6\AFe+y杖br{ByOϵ;7i N2ӵAo{i126ʀ1R^{`R0zKAIRI?/;?"@A{!4 '庮&TJDZiǺ.\NWtTjc Kh8`Kt ^ :.jJXQJUR?_nMO{ cZ_n'+;oXopiFièsOܸQ9chBr7aq1igC+5e%A+{{QwNV-~I׶h }ݪ@( Jk)凗^Frx\cs+W6y_{=t{W~3/gx3SQU.;j涚sD$J |E'ߙ>GKM}O(1fssѨc:8u 3ƨMnk3U-5u|%6S3iZۋwMLmnRΟ.roť{fF=󜞟𥋮rLM9Gʝ]p}LMjH8掋OE?yzhai|!V").z2!sێPJRۈiЎvG/FS(ftU-Z8 IDAT.Z}u|$`%NJaňBf{ M@&ֵۍ(gTk$qh8SGFFhኬ̻\ z}cc/v繾{vq8tV7mDjND,;q8thSu!ZZNld @ZIuSҧl~To|GWFgggs|G~TRnWb`&~?/L_$׫QRJr;70AzGUT:RS|~.6Yrk pT,"߄0 +88\Ѕt=epkkkZkXM):q Qg[+\]QYھ+(.L|Ri Y[K?YgN\8q4gLJ;/9 8K{. ]PJ”j9=sta> }z8c^wΟOipJ]^MyR$Ic<\Ngi5epيcd0~(j޽{(l8mܾql 9hc͖){G3~n8\1.Ononލջ`o쪛c}Zq`)j_'YjJGsxptRΝ;ۓnAXE3Up U,ε_A)uy!+/q9|a,z^\Z+:ai2>Fqĥ PqerEnjVTwU׶t_zJwsF)#Pc5Rw;vsOŠQY7._tA+Ic\.gL7JM>5"Mټz*cu]ƘVڱFwF{Ȝ ع{kH.s3G?'7#뱾Ƃ/us908a}QSkZB.mVn.Ŋ\:A{^RR|td!Pcurk1iEh(tH5݃sa=͹D2KKM?PJ)q{2`쀵qx}1 -Қ1fccRK)M# Hז0稊N)m+3Mv}lji*2Rr㰤Qo*aỈɱ/F81ؑAT]E"Nd͕fSQތНu"ڸrϮ?vc&8gN{;hJ5w<JQ0VB<5~{M}}>fBu)%{8wBAm5= ύR`4hmV~v|Jnc'B@" N/jovbsrٌDH)ʽa=sL'OךQrѹz.TBV;ﰚvg;zd$wl1; [C ŒQwVRUEHo `gg 2ä/ YeAUx H)Y^~ȑ#[8_>@"V-P1YtP+oH[h!ڝVp)ʙaPN\EdleRG,ˁ3 [ |9c$R$buvn3;Jr LNOHy]ƽscrys"f&*ǎWn(J %Tj)WD:D81|1?l+G ıZ8*Ւi@~d6}'NZnAK) 0]f"p'ʠ֩4%juœP EcDH%2N[.qBh4kG @Qخ+F8Bȕ坖-z3' M<\;6]4άؔ77h>zkT٫6-^ѻ5m+V@(%V7LJQބjx>3[jϧ =uiWy*{<̻>ME+a@ud0lݻ!jn4]^UZV`'SMǩB{!@A`\) L|DB+faXv+bzj%*jirsy;Dca8x!pC-漂0r"J&Zs(edܒjҙVVҡ$I1ِYniPb 4ڮ"k/=2&Օ{&#S/}K/d,$QY[;rRtOwDϖv I)766R6ê#\(B7F.@[ՠSE;dr\F3Bb8BxHƯ׎CDn)YYl4Z-!T+wr?I1)\XN (Pt]F' 1kʈDJ 'B^Cd H񩍥5CaZvZ͕?ӿNLp秦9} ֬>:~X9ӻ>"\֤3|yW F!di5b9NqƪUn;B; K ;!ef 'nkprlFB^Sqw|4uy~ (( 0*)'J#5P(-5ƧI(:W7ה֔jH꥽gA!d(ݠɉKa9wո՟J¹:7w؃"XM:C`!j.@=:S:~_TT*KϗcC)e.kZСuj;Kx p24J ˝' ;gFNhD )Kk;kB+KŅW(_83=1S=R \誀RЌ:hMQR!b\iM88hy>2!ThyKZ|;qn- x;7;*aN>29~F΃pԫ^ؽK38}#"3ʦ8ODȃV֙R,õ[[[qUj_[5( BѰTnoW: n3@\ J0P-oN?qH(pvv[(n %BlVWw8g?]=yDZzp'ϟ=Sf sh)2 V$ pe >I"U>2٥&yAHXf3@|b&FG6͉]'7-l>\(ʼbx(5»UJή0!6N3-:rRI!AQX ;.Ph\Z 㛭b#D K;;rܗq.HDGqԪ.nipb٣s8Hc#}_Ii4J(0WϥT.519[x$NG&|)t. {24YډdkRVF(lܾzg/>㠬t%4$rk´_LTVֵZpVel\AEH @U=XG^Rz" g] k]lFI,t"en7N=~~y.RfQZcZ+)J*!J )RFqyXzs?q/jř_83]/)7U I˦0ry!N,3cn_|qp<'(0B)jRI,cI#}F@_-} '=~̻>k8Πh}>~Jt!:aԔB ^ւH ;Bg:ؽ U3T۾֢bU$ -[f"b!DY//&_?u|.~;9%igJZ Ji)R)sGR{?Dgf JQTՒ߿'Oח[&jġJ(g!5TZ̃&~~Iȼc5}W8x ~l6Ţ+Ӯ5]N[WyR*" Tg/ᬕZI)o]hm'PB8D;{o~+a W3MiVƀ'ZIBJJHuXR6ZQKү݉Ր&>LS;o|ʝɺɁi€P%n5CP`r{%R`GA:y?F_cǒ$ m LiN}5CӑP*jzq6_s]6#%*IT";%_?s\ C:iŦj)F3 Jh.i O]J)-R*R\.'=qx"DVo4wvƥ3_|~߽}֥/ h0F0 ZD)idB @V(Rfc+2•R6*8kH)n6idϯZ&ފh@NRۗ/ҮX8Z!b뻉vc~Ws\y9;ehJB30B5B)mVOŘYIr{sGUJkrŕzBnƍzR633 b.MD4MJ50f(g|tWUfU|wVc* I\x1$Il#>JZ}XM8lDl6"M(êm+zq{ynRĔM@ljX\mƾy3A~.YXSw10_b 5[m 0Ì1L¥JQhKbq]zfsvfRBx|bh ?U{_}Gh.S%d@s[/}i!&u>»$yժjccc¶O cccj.c1^ђrdO{[ׅ1btêݎ"׮)jQozhC6k[rљK^yTlw[v{sgI,uB hMP;w猱aI$Il68X\\\__S"豓k Fb?|Džόx~SGNOH@ɊpZe(Q΢VF?\YYӠ̣!o} m8iaΙF~VaؒxX {!DǾJ N+kTjmݽn^i4w7˨'?O}̅C sWXbk:<0.sqhgNV7o\\\,ZkL1R NkF?so%%8ryc"A6g'ҥqO9n&k"' .,..nmm/ ţ <}f#I +ASpzv ]k+K 8$VU,mz0V;cFJzY.vխj-J(ĉ ӳA縎 TsgP rH8u0 #G)Lk?~jNx[=5ʕ~$ qSxOtZfY.]z㯮}kaz+&Q7jtt4I-\_ ᩷AYۛA`!Vl_PZZ5Q`8PaJ)%kÝ*[F+#(K{V֊cDx~ktll4 wZ?ev΍㺜s˓Q+%$Rܸqc IDATii\.cr\..t.㸴{B2R0T=û3ϏXݹf:>AJ\i=*ke8j,( &뢮ʃ"oNHz,W1 ~V> ǪhJ?>W+4r:Qq|t4:~ui5j,RF[ĸ:N}6еk׮mllBP(,>|_-IJkՂdAW8i(bqRl <5a&&+=p^R=cNyU9O;ӷ"$ +J$AJ :UhVF+ݻЛճ scSSs绝=3֮Z]8ai7MZnTܹS* \.g5;/3#aLbǩUE3tMb4JkNņ y>za$ҿ'; ogv.`$(t>FTj9Ii ?W6qܡsR7޼~횡lO%1%L.NkGV ]__wVVV:ꉎ8q!  ICO{5ק}21ft|re8Ն8+U9<.QB3CJǖc (@MDSx`sTGBlYTJ7oƏ׾ѣ 9&z>I ]_UQPq8 Szu7_(p7}ovpz: -лݖqufij%UHZi8s<|? s\> m)J)/jRZ.U֔RA`(yta-k\ Ӧ~28l{[˳': lIZpo\3~ڇؾxN_1̳a[am5vpΟ=qDʸzAڪYhժT*V+AY[[ja"P j鮕-֎aת-mЄiTWp\(}Jk<(]r]];҇U@.%R8uNcu9Fj-//ommZ-BH###?yG iP0 CLStQ=5G40W׸obC) u\-voƩ%"}hSpWSJQb j38q(:3ڏզVՑdبB cڵk7o*6~n@sԑ#'^rE)5::Nc`u?zt"BXcqOf]f}$\1}ww\.Hݽ{nݺSӨ!tzz:sFJ"ntttll ({{{|&JiG<fU } !cJq8;;+١`O{^RQJas~~Bࠈ뺳X-v/xlټvڍ7\׵ U }dI鉿D}JA(w$IEesϛ?vXp}ߟ N-Wֲ|lr|>_*pHKKK>ʎ#yжg҅߾bSZ͎ =3$V<8UG3fvdbsp^CeKxh4Z\.! ñ1*!dggwrT>(P%wX, 6=٬`eTsT̞,8SqiQ(Kj4fX=rNk@ټz9ccc֣<UQFNfOhGvqePXYVؘ(!4qmMz4D`0Q A?(zfBJiE>D2q۔)W49=z|pvףz9׾=А tW/iuŮUؠ͓abqǎ_s+-:Cv/ ,+&L&9 @wQq, WB(7PMH$FcLji/^VRetNJ)1Lij0 F~J'nb~i0\PP@tj.$h1a kcIv1GEexb|9{TDoHꪋ̔J%]l ]|]gI+ZBJRCLU&Z7BGhOK"ĺ4='+@Wݯ%-itvDi;XB8&]|Hd zW;XBń>KH)yE3"Ȫuٺ^X(tM 96.M>)/]&*bR-l"DiUCW Ot@W++] `miۏw Bz{{}>t|>}ݽH$Bxn/**:q"tpimmCX?޸q̙3>f066B[p"e,100s^/ŋUUUHaH$z:K9D>} ]7v-ݻhE>;v,<kkk^zICOOOcccaa={A6Ãd2NgUUUJ1\.qvݻwuyMuuuJ#0_|`&///ĭ{$eF;մL&ZXTҖ\!`0L2 r:;;SRR255HP}>=}:Okjj ɹr q_-tI"Zvaa!`FBP(8Vv߾}rgb˗/ +++6?N$,f8j߾}^wyyѣG4Bb*++$!dii n8ϟ8yѣGvөP(ӵ~jLr=ٳgjuWWWss^'\|h4Z]ve>\zUAj;wvuuz0;h%ĕeu:q~~h4g8vnn@4 ?xj:>,Ųq֭o߾xo=귓}}}eee& W] &] t@Wt+]#z?IENDB`ssr-0.4.2/data/MacOSX/dylibbundler/index.html000066400000000000000000000161231236416011200210020ustar00rootroot00000000000000 SourceForge.net Logo

New! Captain MacLib QuickLook plugin


 
     Use this simple Quicklook plugin to instantly get info about any library/executable, including architectures and dependencies.
  • Download CaptainMacLib v0.1 now
  • Get the source code
  • mac dylib bundler

    About

    Mac OS X intoduced an innovative and very useful way to package applications : app bundles. While their design has all that is needed to ease distribution of resources and frameworks, it seems like dynamic libraries (.dylib) are very complicated to distribute. Sure, applications developped specifically for OS X won't make use of them, however applications ported from Linux or other Unices may have dependencies that will only compile as dylibs. By default, there exists no mecanism to bundle them but some command-line utilities provided by Apple - however it turns out that for a single program it is often necessary to issue dozens of commands! This often leads each porter to develop their own "home solution" wich are often hacky, poorly portable and/or unoptimal.

    dylibbundler is a small command-line programs that aims to make bundling .dylibs as easy as possible. It automatically determines which dylibs are needed by your program, copies these libraries inside the app bundle, and fixes both them and the executable to be ready for distribution... all this with a single command on the teminal! It will also work if your program uses plug-ins that have dependencies too.

    It usually involves 2 actions :
    • Creating a directory (by default called libs) that can be placed inside the Contents folder of the app bundle.
    • Fixing the executable file so that it is aware of the new location of its dependencies.

    Download

    Download version 0.4.4 now
    dylibbundler is not yet considered stable but already usable.
    Note that version 0.3 is not compatible with version 0.25, as it makes no more use of config files and the terminal syntax has changed a little.

    Installation

    In the terminal, cd to the main directory of dylibbundler and type "make". You can install with "sudo make install".

    Feedback / Contact

    You can contact me at auria@users.sourceforge.net or on the sourceforge project page

    Using dylibbundler on the terminal

    Here is a list of flags you can pass to dylibbundler on the terminal.

    -h, --help
    displays a summary of options
    -x, --fix-file (executable or plug-in filepath)
    Fixes given executable or plug-in file (a .dylib can work too. anything on which 'otool -L' works is accepted by -x). Dylibbundler will walk through the dependencies of the specified file to build a dependency list. It will also fix the said files' dependencies so that it expects to find the libraries relative to itself (e.g. in the app bundle) instead of at an absolute path (e.g. /usr/local/lib). To pass multiple files to fix, simply specify multiple -x flags.
    -b, --bundle-deps
    Copies libaries to a local directory, fixes their internal name so that they are aware of their new location, fixes dependencies where bundled libraries depend on each other. If this option is not passed, no libraries will be prepared for distribution.
    -i, --ignore (path)
    Dylibs in (path) will be ignored. By default, dylibbundler will ignore libraries installed in /usr/lib since they are assumed to be present by default on all OS X installations. (It is usually recommend not to install additional stuff in /usr/, always use /usr/local/ or another prefix to avoid confusion between system libs and libs you added yourself)
    -d, --dest-dir (directory)
    Sets the name of the directory in wich distribution-ready dylibs will be placed, relative to the current working directory. (Default is ./libs) For an app bundle, it is often conveniant to set it to something like ./MyApp.app/Contents/libs.
    -p, --install-path (libraries install path)
    Sets the "inner" installation path of libraries, usually inside the bundle and relative to executable. (Default is @executable_path/../libs/, which points to a directory named libs inside the Contents directory of the bundle.)
    The difference between -d and -p is that -d is the location dylibbundler will put files at, while -p is the location where the libraries will be expected to be found when you launch the app. Both are often related.

    -of, --overwrite-files
    When copying libraries to the output directory, allow overwriting files when one with the same name already exists.
    -od, --overwrite-dir
    If the output directory already exists, completely erase its current content before adding anything to it. (This option implies --create-dir)
    -cd, --create-dir
    If the output directory does not exist, create it.
    A command may look like
    % dylibbundler -od -b -x ./HelloWorld.app/Contents/MacOS/helloworld -d ./HelloWorld.app/Contents/libs/
    
    If you want to create a universal binary by merging toghether two builds from PPC and Intel machines, you can ease it up by putting the ppc and intel libs in different directories, then to create the universal binary you only have to lipo the executable.
    % dylibbundler -od -b -x ./HelloWorld.app/Contents/MacOS/helloworld
        -d ./HelloWorld.app/Contents/libs-ppc/ -p @executable_path/../libs-ppc/
    
    % dylibbundler -od -b -x ./HelloWorld.app/Contents/MacOS/helloworld
        -d ./HelloWorld.app/Contents/libs-intel/ -p @executable_path/../libs-intel/
    








    ssr-0.4.2/data/MacOSX/dylibbundler/maclib.jpg000066400000000000000000000243161236416011200207410ustar00rootroot00000000000000JFIF,,LEAD Technologies Inc. V1.01C  !"$"$C B!1A"Qaq2B#R$b%3CScrs ?]@(7VVoݼ-mN B$Lc7MxMͫ,)n#o?'GǦ[e1kD6ڦ'h.L\C%H2<E:@(PP @t@${  -ߩǿf6`HOmD}J .L7Ԑ}( f\UÎ)${o?΂W;cӣ1|M lH'qAG.eV}=\:)VSv BDrǤ:+8\m-[zFTm$1P ڀP @(P 3B\N]\mnKBFX>W+vo]VЅ)N 'n$ kp%Djؒ@%@j$(OYxc ObN?CoGĥ@P7$zkh h5nne'J2=4|j,>e8- =F#P+h}f^}ӥVMGזI6s-BM򂏪! j q׏qV_HZcj+DRPf(3ށBH#x4'ASο~ k#~rh`P<#@Ao=vUZs[v j Ʋx*$(:y\b9)R܂6=M)+q-W ~m0&}fVܕtnIPPVGuO[IXٷMǔe AI'Ijzyi*N Ls@znq͡BR.P{4dʱXBRe$0$܊ =|S&Wm-^qh%’w3At~xt&Riwypp4tO) =<#oo" 4P(@@o@h86pY lڹIlzZ_:G;9Lu?k7P{9Om'Pd> MmۅLZcR9&gf=ol1܂ soWunFⒽ)IQJ{#0$%p-GJXYh;zk> i 77)ӤjBH},y8z2oe[X4$=M?'8$ #HU<̟^fuR9~= nm@埈Ukl2< v::pZLw;#x U$kais{zե.ؤ4Pթ-FJB3>Ŝxo 7,7~Z Pt*@204lXՆE0j -S &UyDϵށhڀ'z |U]ve%KopD`yd@`gA7/w3kvڒR.60O}wPe:WW|CIոAfۛhImY&edz6tm ձʃ%Kmب$GP>Au:ˮ~YXԕ,i9# yc0wMڮI %cPd==d˝?kqnAq gAmj g+CRWxz?-%nOj:nuai<^Sn$8<v0΁)#rh6$o@ P9?j Ǟ鎆v.2Tԥ A v՘ˆ-֖R$`+TvDo3f.1)ƶ%lT̥$ʽpX%%KQ6o[g d)#RX/Yy*"S; "꜕vRwgh= éHY&,Þ}TA0͟81W9)KA$ ѽ]8ΗKIC,%+Rj %˟ْ )\pD1}ַH e$ae-E<qT$H(;-#Ş$WL\:TCp-w8cCg-ӌaRSjB\̒"6397+CY'cy6fD;teWy0ͱԙw~, عA J:cmO'f)(3v G;Gcn9Pj6mܣDIP'؊_ vA74s@57sA俀YEK/:A J@*΃ ֲY7$|3xY+mlBqP_8L؆YjRvP)~cmع?EŴI$ #&3mʲt>/P Q+A z3exmUA<םPA AI.8PAjWRtO֯Xt6-\-ʣrT n1qkV^yujC %6 $n(+>\ @JE~[n[ aPBA4m-WisdYYK>դ3'z,XH@Jq&Ju; [:/fFB)}^+şMG<;,EaԂI'{u ۾a@ w?izun+'Wkn啸m:.Q DzӡX1k cBԇ< dL@ :0]37Lj۹J CJJJL3&GK )6z {=wrRl-I}?== -az*ᴼm &FldsΔ>N"쳒Krm|–r N}*1>Q $u7Vu27.w .[$-N#6o.ܼ.2=P9aPVneM낞!Co~ ۥ}ud!)icWT8Ć<*;vE-қq2 yqAPJo4گ U. ~,r,[4ę?*9+uLWQGqC{lo ish8I0b`j "6 AX޽- 庋I>7Kvnf]((d-STx\PRlkKw\PR/3xie9ѵ@ qOz תqv m` cx=:[w(RAg @ͼYS%B*$h y:m\7!æZҡ rF_jN_ݽŠv݈=w?RtKscrUŹ%*RD=c%yLC'\2/ĕu@#*Cv%j֘iy!$+$BkJ$~+27 )5"]B ,4Bb~>@@gނ?5do-YXny=R~κt)YNN}A-J[ҟȾRx#A98/7pma*2AEխy8WbZ@QRfAp p%.͐`=hI@ j?(:tO`^p.Bt7nҖP?$ ~Htb|,L۱fWO(%I÷.PIp)'T-+AvAeaV7[}MydI H~'|®1WJCwIm!D ނQxQmQ:9Nl@Jj↨H$)!B9 }}uRۈ2@A UxRJtD""h"/QswjRKA H)R ܈PGa͋ql,-GQqnH)$VcAԎlڼ~ .XiP2(QAu&G[4V@R-hV2Qca«YI-_h%*( Ҁ3/ì Tūi׷0̀͵VQfF@Lc}OOڼmѤҒx``4k,;[6o\;oxĄRI jHJ;T;A{R_ WIh u Re*؈I.m)1xdd9'ۭ%NDm> m7?ƃ+Q(%* WX(#iPHҁ,cKANq҂Q,)ARV[#v{y.:#}AKպ/hA bJ[uv*qAuN^^)焝+Q0vv#-nl:~.Kn:C,$-؍CDGA/cӷ)J}} ! O?PQN(%-X%0J'n6&?'h9aLJ ڃ%CpRQ}!a%JC3;) 蟐~@ w|뜚B 6̭gh)PdȰIPS=Z@#w!t­1=Zw} ,kcśЗi->-; q$!t-oa<QAeW% $=pb튐Z-)&eI1`#o@ݣǗmhU-#Z"Lv46J5oXzTHyRd|h 7m,nUhi! J?#Pt^dinGM\ Ti&?IqZ7jC JBʌ;A]l8TBF@ ٲlWҀÀ2q v3@ BU4-@zzh-0fD{,o@ ABALGCa"q1@t`EīDFhu@w? >ݨ2Fжm| !\O;9*lS2P䍴 H~FS[%ZG?z 4Ըܞ}P>@&@ހm@$G((3 ހPPz5FP4 #ƁDށ]j(y8P @;s@F <ssr-0.4.2/data/MacOSX/dylibbundler/makefile000066400000000000000000000007701236416011200205060ustar00rootroot00000000000000dylibbundler: g++ -c -I./src ./src/Settings.cpp -o ./Settings.o g++ -c -I./src ./src/DylibBundler.cpp -o ./DylibBundler.o g++ -c -I./src ./src/Dependency.cpp -o ./Dependency.o g++ -c -I./src ./src/main.cpp -o ./main.o g++ -c -I./src ./src/Utils.cpp -o ./Utils.o g++ -o ./dylibbundler ./Settings.o ./DylibBundler.o ./Dependency.o ./main.o ./Utils.o clean: rm -f *.o rm -f ./dylibbundler install: dylibbundler cp ./dylibbundler /usr/local/bin/dylibbundler chmod 775 /usr/local/bin/dylibbundlerssr-0.4.2/data/MacOSX/dylibbundler/src/000077500000000000000000000000001236416011200175715ustar00rootroot00000000000000ssr-0.4.2/data/MacOSX/dylibbundler/src/Dependency.cpp000066400000000000000000000210411236416011200223510ustar00rootroot00000000000000/* 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 2 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "Dependency.h" #include #include #include #include "Utils.h" #include "Settings.h" #include #include #include std::string stripPrefix(std::string in) { return in.substr(in.rfind("/")+1); } //the pathes to search for dylibs, store it globally to parse the environment variables only once std::vector pathes; //initialize the dylib search pathes void initSearchPathes(){ //Check the same pathes the system would search for dylibs std::string searchPathes; char *dyldLibPath = std::getenv("DYLD_LIBRARY_PATH"); if( dyldLibPath!=0 ) searchPathes = dyldLibPath; dyldLibPath = std::getenv("DYLD_FALLBACK_FRAMEWORK_PATH"); if (dyldLibPath != 0) { if (!searchPathes.empty() && searchPathes[ searchPathes.size()-1 ] != ':') searchPathes += ":"; searchPathes += dyldLibPath; } dyldLibPath = std::getenv("DYLD_FALLBACK_LIBRARY_PATH"); if (dyldLibPath!=0 ) { if (!searchPathes.empty() && searchPathes[ searchPathes.size()-1 ] != ':') searchPathes += ":"; searchPathes += dyldLibPath; } if (!searchPathes.empty()) { std::stringstream ss(searchPathes); std::string item; while(std::getline(ss, item, ':')) { if (item[ item.size()-1 ] != '/') item += "/"; pathes.push_back(item); } } } // if some libs are missing prefixes, this will be set to true // more stuff will then be necessary to do bool missing_prefixes = false; Dependency::Dependency(std::string path) { // check if given path is a symlink std::string cmd = "readlink -n " + path; const bool is_symlink = system( (cmd+" > /dev/null").c_str())==0; if (is_symlink) { char original_file_buffer[PATH_MAX]; std::string original_file; if (not realpath(path.c_str(), original_file_buffer)) { std::cerr << "\n/!\\ WARNING : Cannot resolve symlink '" << path.c_str() << "'" << std::endl; original_file = path; } else { original_file = original_file_buffer; } //original_file = original_file.substr(0, original_file.find("\n") ); filename = stripPrefix(original_file); prefix = path.substr(0, path.rfind("/")+1); addSymlink(path); } else { filename = stripPrefix(path); prefix = path.substr(0, path.rfind("/")+1); } //check if the lib is in a known location if( !prefix.empty() && prefix[ prefix.size()-1 ] != '/' ) prefix += "/"; if( prefix.empty() || !fileExists( prefix+filename ) ) { //the pathes contains at least /usr/lib so if it is empty we have not initilazed it if( pathes.empty() ) initSearchPathes(); //check if file is contained in one of the pathes for( size_t i=0; i> buffer; prefix = buffer; std::cout << std::endl; if(prefix.compare("quit")==0) exit(1); if( !prefix.empty() && prefix[ prefix.size()-1 ] != '/' ) prefix += "/"; if( !fileExists( prefix+filename ) ) { std::cerr << (prefix+filename) << " does not exist. Try again" << std::endl; continue; } else { pathes.push_back( prefix ); std::cerr << (prefix+filename) << " was found. /!\\MANUALLY CHECK THE EXECUTABLE WITH 'otool -L', DYLIBBUNDLDER MAY NOT HANDLE CORRECTLY THIS UNSTANDARD/ILL-FORMED DEPENDENCY" << std::endl; break; } } } //new_name = filename.substr(0, filename.find(".")) + ".dylib"; new_name = filename; } void Dependency::print() { std::cout << std::endl; std::cout << " * " << filename.c_str() << " from " << prefix.c_str() << std::endl; const int symamount = symlinks.size(); for(int n=0; n " << symlinks[n].c_str() << std::endl;; } std::string Dependency::getInstallPath() { return Settings::destFolder() + new_name; } std::string Dependency::getInnerPath() { return Settings::inside_lib_path() + new_name; } void Dependency::addSymlink(std::string s){ symlinks.push_back(stripPrefix(s)); } // comapres the given Dependency with this one. If both refer to the same file, // it returns true and merges both entries into one. bool Dependency::mergeIfSameAs(Dependency& dep2) { if(dep2.getOriginalFileName().compare(filename) == 0) { const int samount = dep2.getSymlinkAmount(); for(int n=0; n #include class Dependency { // origin std::string filename; std::string prefix; std::vector symlinks; // installation std::string new_name; public: Dependency(std::string path); void print(); std::string getOriginalFileName() const{ return filename; } std::string getOriginalPath() const{ return prefix+filename; } std::string getInstallPath(); std::string getInnerPath(); void addSymlink(std::string s); int getSymlinkAmount() const{ return symlinks.size(); } std::string getSymlink(const int i) const{ return symlinks[i]; } std::string getPrefix() const{ return prefix; } void copyYourself(); void fixFileThatDependsOnMe(std::string file); // comapres the given Dependency with this one. If both refer to the same file, // it returns true and merges both entries into one. bool mergeIfSameAs(Dependency& dep2); }; #endifssr-0.4.2/data/MacOSX/dylibbundler/src/DylibBundler.cpp000066400000000000000000000136561236416011200226670ustar00rootroot00000000000000/* 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 2 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "DylibBundler.h" #include #include #include "Utils.h" #include "Settings.h" #include "Dependency.h" std::vector deps; void changeLibPathsOnFile(std::string file_to_fix) { std::cout << "\n* Fixing dependencies on " << file_to_fix.c_str() << std::endl; const int dep_amount = deps.size(); for(int n=0; n& lines) { // execute "otool -L" on the given file and collect the command's output std::string cmd = "otool -L " + filename; std::string output = system_get_output(cmd); if(output.find("can't open file")!=std::string::npos or output.find("No such file")!=std::string::npos or output.size()<1) { std::cerr << "Cannot find file " << filename << " to read its dependencies" << std::endl; exit(1); } // split output tokenize(output, "\n", &lines); } void collectDependencies(std::string filename) { std::vector lines; collectDependencies(filename, lines); std::cout << "."; fflush(stdout); const int line_amount = lines.size(); for(int n=0; n lines; collectDependencies(deps[n].getOriginalPath(), lines); const int line_amount = lines.size(); for(int n=0; n void collectDependencies(std::string filename); void collectSubDependencies(); void doneWithDeps_go(); #endifssr-0.4.2/data/MacOSX/dylibbundler/src/Settings.cpp000066400000000000000000000053141236416011200221000ustar00rootroot00000000000000/* 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 2 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "Settings.h" #include namespace Settings { bool overwrite_files = false; bool overwrite_dir = false; bool create_dir = false; bool canOverwriteFiles(){ return overwrite_files; } bool canOverwriteDir(){ return overwrite_dir; } bool canCreateDir(){ return create_dir; } void canOverwriteFiles(bool permission){ overwrite_files = permission; } void canOverwriteDir(bool permission){ overwrite_dir = permission; } void canCreateDir(bool permission){ create_dir = permission; } bool bundleLibs_bool = false; bool bundleLibs(){ return bundleLibs_bool; } void bundleLibs(bool on){ bundleLibs_bool = on; } std::string dest_folder_str = "./libs/"; std::string destFolder(){ return dest_folder_str; } void destFolder(std::string path) { dest_folder_str = path; // fix path if needed so it ends with '/' if( dest_folder_str[ dest_folder_str.size()-1 ] != '/' ) dest_folder_str += "/"; } std::vector files; void addFileToFix(std::string path){ files.push_back(path); } int fileToFixAmount(){ return files.size(); } std::string fileToFix(const int n){ return files[n]; } std::string inside_path_str = "@executable_path/../libs/"; std::string inside_lib_path(){ return inside_path_str; } void inside_lib_path(std::string p) { inside_path_str = p; // fix path if needed so it ends with '/' if( inside_path_str[ inside_path_str.size()-1 ] != '/' ) inside_path_str += "/"; } std::vector prefixes_to_ignore; void ignore_prefix(std::string prefix) { if( prefix[ prefix.size()-1 ] != '/' ) prefix += "/"; prefixes_to_ignore.push_back(prefix); } bool isPrefixBundled(std::string prefix) { if(prefix.find(".framework") != std::string::npos) return false; if(prefix.find("@executable_path") != std::string::npos) return false; if(prefix.compare("/usr/lib/") == 0) return false; const int prefix_amount = prefixes_to_ignore.size(); for(int n=0; n namespace Settings { bool isPrefixBundled(std::string prefix); void ignore_prefix(std::string prefix); bool canOverwriteFiles(); void canOverwriteFiles(bool permission); bool canOverwriteDir(); void canOverwriteDir(bool permission); bool canCreateDir(); void canCreateDir(bool permission); bool bundleLibs(); void bundleLibs(bool on); std::string destFolder(); void destFolder(std::string path); void addFileToFix(std::string path); int fileToFixAmount(); std::string fileToFix(const int n); std::string inside_lib_path(); void inside_lib_path(std::string p); } #endifssr-0.4.2/data/MacOSX/dylibbundler/src/Utils.cpp000066400000000000000000000114641236416011200214030ustar00rootroot00000000000000/* 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 2 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "Utils.h" #include "Dependency.h" #include "Settings.h" #include #include #include #include #include #include using namespace std; /* void setInstallPath(string loc) { path_to_libs_folder = loc; }*/ void tokenize(const string& str, const char* delim, vector* vectorarg) { vector& tokens = *vectorarg; string delimiters(delim); // skip delimiters at beginning. string::size_type lastPos = str.find_first_not_of( delimiters , 0); // find first "non-delimiter". string::size_type pos = str.find_first_of(delimiters, lastPos); while (string::npos != pos || string::npos != lastPos) { // found a token, add it to the vector. tokens.push_back(str.substr(lastPos, pos - lastPos)); // skip delimiters. Note the "not_of" lastPos = str.find_first_not_of(delimiters, pos); // find next "non-delimiter" pos = str.find_first_of(delimiters, lastPos); } } bool fileExists( std::string filename ) { if (access( filename.c_str(), F_OK ) != -1) { return true; // file exists } else { //std::cout << "access(filename) returned -1 on filename [" << filename << "] I will try trimming." << std::endl; std::string delims = " \f\n\r\t\v"; std::string rtrimmed = filename.substr(0, filename.find_last_not_of(delims) + 1); std::string ftrimmed = rtrimmed.substr(rtrimmed.find_first_not_of(delims)); if (access( ftrimmed.c_str(), F_OK ) != -1) { return true; } else { //std::cout << "Still failed. Cannot find the specified file." << std::endl; return false;// file doesn't exist } } } void fixLibDependency(string old_lib_path, string new_lib_name, string target_file_name) { string command = string("install_name_tool -change ") + old_lib_path + string(" ") + Settings::inside_lib_path() + new_lib_name + string(" ") + target_file_name; if( systemp( command ) != 0 ) { cerr << "\n\nError : An error occured while trying to fix depency of " << old_lib_path << " in " << target_file_name << endl; exit(1); } } void copyFile(string from, string to) { bool override = Settings::canOverwriteFiles(); if(!override) { if(fileExists( to )) { cerr << "\n\nError : File " << to.c_str() << " already exists. Remove it or enable overriding." << endl; exit(1); } } string override_permission = string(override ? "-f " : "-n "); // copy file to local directory string command = string("cp ") + override_permission + from + string(" ") + to; if( systemp( command ) != 0 ) { cerr << "\n\nError : An error occured while trying to copy file " << from << " to " << to << endl; exit(1); } // give it write permission string command2 = string("chmod +w ") + to; if( systemp( command2 ) != 0 ) { cerr << "\n\nError : An error occured while trying to set write permissions on file " << to << endl; exit(1); } } std::string system_get_output(std::string cmd) { FILE * command_output; char output[128]; int amount_read = 1; std::string full_output; try { command_output = popen(cmd.c_str(), "r"); if(command_output == NULL) throw; while(amount_read > 0) { amount_read = fread(output, 1, 127, command_output); if(amount_read <= 0) break; else { output[amount_read] = '\0'; full_output += output; } } } catch(...) { std::cerr << "An error occured while executing command " << cmd.c_str() << std::endl; pclose(command_output); return ""; } int return_value = pclose(command_output); if(return_value != 0) return ""; return full_output; } int systemp(std::string& cmd) { std::cout << " " << cmd.c_str() << std::endl; return system(cmd.c_str()); } ssr-0.4.2/data/MacOSX/dylibbundler/src/Utils.h000066400000000000000000000023111236416011200210370ustar00rootroot00000000000000/* 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 2 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef _utils_h_ #define _utils_h_ #include #include class Library; void tokenize(const std::string& str, const char* delimiters, std::vector*); bool fileExists( std::string filename ); void copyFile(std::string from, std::string to); // executes a command in the native shell and returns output in string std::string system_get_output(std::string cmd); // like 'system', runs a command on the system shell, but also prints the command to stdout. int systemp(std::string& cmd); #endifssr-0.4.2/data/MacOSX/dylibbundler/src/main.cpp000066400000000000000000000114021236416011200212170ustar00rootroot00000000000000/* 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 2 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include "Settings.h" #include "Utils.h" #include "DylibBundler.h" /* TODO - what happens if a library is not remembered by full path but only name? (support improved, still not perfect) - could get mixed up if symlink and original are not in the same location (won't happen for UNIX prefixes like /usr/, but in random directories?) FIXME: why does it copy plugins i try to fix to the libs directory? */ const std::string VERSION = "0.4.1"; // FIXME - no memory management is done at all (anyway the program closes immediately so who cares?) std::string installPath = ""; void showHelp() { std::cout << "dylibbundler " << VERSION << std::endl; std::cout << "dylibbundler is a utility that helps bundle dynamic libraries inside mac OS X app bundles.\n" << std::endl; std::cout << "-x, --fix-file " << std::endl; std::cout << "-b, --bundle-deps" << std::endl; std::cout << "-d, --dest-dir " << std::endl; std::cout << "-p, --install-path <'inner' path of bundled libraries (usually relative to executable, by default '@executable_path/../libs/')>" << std::endl; std::cout << "-of, --overwrite-files (allow overwriting files in output directory)" << std::endl; std::cout << "-od, --overwrite-dir (totally overwrite output directory if it already exists. implies --create-dir)" << std::endl; std::cout << "-cd, --create-dir (creates output directory if necessary)" << std::endl; std::cout << "-i, --ignore (will ignore libraries in this directory)" << std::endl; std::cout << "-h, --help" << std::endl; } int main (int argc, char * const argv[]) { // parse arguments for(int i=0; i0) { // if we meet an unknown flag, abort // ignore first one cause it's usually the path to the executable std::cerr << "Unknown flag " << argv[i] << std::endl << std::endl; showHelp(); exit(1); } } if(not Settings::bundleLibs() and Settings::fileToFixAmount()<1) { showHelp(); exit(0); } std::cout << "* Collecting dependencies"; fflush(stdout); const int amount = Settings::fileToFixAmount(); for(int n=0; n This is the first draft for a XML format to describe a virtual audio scene and (in the most cases alternatively) a reproduction setup. The reproduction setup can either be a loudspeaker setup or some kind of headphone setup. The first emphasis is on loudspeakers. Author: Matthias Geier Date: April-November 2007 root element, ASDF means "Audio Scene Description Format" The header can contain the name of the scene and a description. There can also be a version string and a date/time. This section is for the setup of the reproduction system. This can be a loudspeaker setup or headphones or ... The static content of the scene is saved here. That can be information about sources an the reference point and their initial position and orientation. This holds the dynamic content of the scene, i.e. movement of sources/reference, start/end/length/loop information of soundfiles, ... Right now, it's not really defined and just to get an idea that such functionalities will be implemented sometime (hopefully). The usage of events is not defined yet! This is just a very raw draft! The usage of this event is not completely defined yet! The usage of this event is not completely defined yet! Circular loudspeaker array. Position (and orientation) is relative to the reference point (given in the scene_setup/score section). If no "center" element is present, the center of the circle is the reference point. All positions and orientations refer to the coordinate system given by the reference point. It is possible to define segments of circles with a "second" or a "last" element (compare the "linear_array" section). Linear loudspeaker array. Position (and orientation) is relative to the reference point (given in the scene_setup/score section). A local filename or (theoretically) an URL to an audio file/stream. This can be a multichannel file. The optional attribute "channel" can specify a channel in a multichannel file. It defaults to 1. Instead of a filename also a port can be specified. This is preferably a port number, but it can also be a port name. It is still unclear how to specify a port in a general manner so that it is independent of the audio server and the hardware. ssr-0.4.2/data/images/000077500000000000000000000000001236416011200144765ustar00rootroot00000000000000ssr-0.4.2/data/images/listener.png000066400000000000000000000176241236416011200170430ustar00rootroot00000000000000PNG  IHDR>asBIT|dtEXtSoftwarewww.inkscape.org<&IDATxkxŹUsh4[W_d[ I|9!p!HMvy69&I`Bqbc066`cYm, _dIHOOwW퇱 "[MwWLꭷ"B|ƘjnB_Ӓa4 4y˘ tc...^vg+,,\vkGp`s[[D&i)J)X,ʏQQQqR! a}>ߞG{zz^4-9~?̇0-p]gϾW|rUB^gφB,CWW|>bPssϚcL+PJI~kV=n_V'QVVJdɒkjjZ$=ki8W]]m%%%KFLJ^vPyWk^^^$Ecf]mmMRiٲeX~zb @aaa n]A r-Z[vR+WM&ׯc 1ee+)QI6`]wJKKӖwZ޺[3mM19)|wܹs@)UV+fϞjkkaAUUU˧O+s$͙SBihh@UUUZdc N+nFSU$1jJnllD]]!Ҍ3V͚5 cΛW .DYYٔMAuu5@${AcҜ>;wJdRz:,sb nBH]]m^oA,)"g@)%.k!vCU1ŋl%IbSZI&g(R]]un/0NsBhEE?: eeek(2555)Gqq1v13ɉK*++x< ! ydeYƚ5k ,˶,+)$eY)..^8kڵ-̙3T*++`FF t\.nB(!$ힿa\RRzeQQչ d})\i9Cs1 Y,ښe@.{[|LS[[ 9(juݞLRzvGQQ|J*3]$UCNgjt:Ep -QXXBdϋ`n/x<$I.҄SJɊW7l(**cwi!0ƐH$0:: ˃pK.޽{6jO$@(Ee&~?Z[[oF$Ie1B@)BYYQ]]e˖t.*+SK !k((x.OY-I䢢uRb|Ov<رczzzpb8MDCCChii9}4MkgvMWeYKN,Jig.m"j=z!e_#<&qJ"`P%E5?I )iرcv!~zi[d޼y8x ,K~~~<ث Y-Um xOBӴ(`SU>U~ p깤Ē@0!4R㡇Baa!^,]`ɅzEQ,!d@J)5^GQ9=Gv [T +.!@4CJ < `T?m{t40:HYȲ,s.JyۋH$ &:U8TTCB69q@3ēBv'iIILXDɺTXr=bF]V,8]$H "kX􁱱n]׳+*PJ\OI|$I,TSu`0p]; C=GY#ŢxU*2<u⻼BR-@H@4;87?7n%V(Ǐ^ ,g%@$WiXT`#ȷ97zR|m#$Iכ^DWJp?݄5׮x^C(i!Yz8x;3LNɲ|="VcA\|P -xyW tÀaɤa{2Gb`ܭ|!VZ|;v;"wݹX:0gf̤pZUXK)}[ ?ǂ!PUUO~b-_{8ً[:.8&cl]###Ci*RJfά{]7"ؼܪʊ2zxp:rͬB0Gs{74mŌevG"mTU"8s !V-Uq1,!%W8p8ك6t: ҁL8>fтr-! 쀜|!^-z KeW8^TWaNe1!clftMJ$6zvc |ˣ~~6~᳨!=ލG ` -m'sd0|4 Ni$Igt8,-}s;+X0_HTo SDZb[!*r?pzض}e%<_KCc^!𣢢RUuD6PJ JJ(t+Cp<${WX|t]?[n"0 %I~] I$&SUbƘ0Fױ/_V:KͅEQ06UUc9;Œvy ˒,IlB0۳<7v~~L$$*xKTU]7;ӖZ=SE-B+A)͋bTVVԨ/-$9zJڊ5y{ ^ ?WufG¹}g#x37x[?88,_܍"KLk~̜i 죠c_OS-?unUO]BEQɲyẜ2MFӳe+ٱ tu.|,Y4[syz|ۡvn@@ud^^3_09B].ttttNtp(qL{KbQpwńP\T6g}H>4FZ*6躞 9aLEњX,0oޅO5<B?a+=6Vi}@*tFQz N_UqcAD"l"A)}y&=DZ>E_sGeZU8vay?چ@<<B$dY~9<NNE<8 A49d<FP- ( $49ap$_CN蠚N౧FJK@ jUƫ{+{r8Dj&!E__p8ʦظS?Hf{ۢ⛒$]3-۲/!Rx|^zuGA1Lg* . tTsqb,RTT+$IuJP̞ր1G@#8Ҿ3"Nl۞K( fw.2~hEQ !1j{cˬ 3gl !p%%?7 |#kZqNu (^XQh"&Ł`Hyyy?>JKK?ڒ]:Y4MSe˖cɒ%E5Na0H3!Bq R?񏨭!Dh3RȺ`!`FYnغu+ Pr)"UQ8s L=4R&RzjA4py$9 ޽ wϙ37([oŊ+/} 7oFWWmۆQaxNBR/3@aٙS &'Hj!cX|9qwRM6A45w$F G"yHtuuŸ뮻 mܹGA{{;ZZZ OM A)-̙3Q[[9s`gEy&D"BGKAZޭ.V$BxalذDZo>b|ÈD"u(rXhl2l6+aÐ$ #HV{>Za 6WTTC^^^ŜlZzj^LoϪT!% <4 L8 q4Mu} /)"81H7Rhnn!i&O:iZέvztwww|?q΍D"L4$P(؞k?uC:aFZZZ2]$8˞uݟH$NZLCCC{#:;;g@588ti!3L p(<@@ "a޽MB}}Mx,x,;97!سgO'NFFF FNA atvv#[~Td9|l,\m\ z!L93 !m6a񞞞mɤsBŢ}}Md2 ;w^}BOO|>ߩ2EN&Ϲ89#0^αpܺSŸ'e\3@`ȑ#aСCSh4:ٺb)b,۷#9;:::@w|2O9%ι|B1227xc?x cH&5)@2HHSܽ{7:;;!0GFFӞs";Lċ/b#x<[Z?HN ԓO%!ŬTN[};O8j.[g{zzvwwwci+/I  [[" ^౜!'h4H__Ig˖-4 ǎ;looټg8碻{c6%yIcttv̵U"g%:m;m].xGD"cǎߝ.}89--{#G.+X,H&?oSI TQⷿ-^{KJ/H੧Boo/8f[[s$]"&:;;wر`"vڅc" 6oތǏCxgg_w}o,F&붇_$uk׮}rU(B*\{p:}/2 Èwtt<{ }ӭ?i%eY̘1c%!DRUVBMM RLX mmm|[ M}wu_$ΕLC)TTTZ|}0 1vHv|iǛ~FszbRFvg7/\p#˲Rp*BE"{D"1c `$Iq83{z{0'O>66vL%FfP$ݖ-IENDB`ssr-0.4.2/data/images/listener_background.png000066400000000000000000000164241236416011200212370ustar00rootroot00000000000000PNG  IHDR>abKGD pHYs  tIME /BaIDATx]v Z%IIگ>'n㤉`R~Əz~s4z JT_"ʌހ}? ?ӽK5xCG6T7`׼L^xW ^.)%I9%w0 fpg|->/WO@$IIo _ @< ԟUD /~ ~C F7Qq}$xU y ( Fǁ(Fi|C@SJg`xFu]!iO$r'p.~0 -AR#L =a 6c 3zPP . ӈRFh~B)`✒dէEZDy|0ן`|ox bC@T?$]5et5'3qȥO;SJFL. +e =@O4tF9MU tZ*PB~էE<R)Ֆ4} q]_L: 4z JV'FPϢ5@03 N(L )S:Bez@Zhލeߑ& @Z'>n%`4_I ?!P(D--S +&wJ1 ?}hReK-B} _Fe(ְ]SS2@z |a R(kٞ1AbA*%ܿE@[*p3*"B|3hVKVF>Dǐ?cIH5 X$S 4:cWī= S?(Ъj:E )|.3{=ie@04_i K 茿G= CRWXIu>sF)aHWC%mcJ7KA3 |cMx-^6/I_,U|z$ zf#= A1%1 g6}\ 8FyvL2ngА5ЗӁܵ~O^o =?Hu7"Uдf  rCDlKd狪8^V]II"$!2w !A~}ӕ>ܩ >gw), NU4\1ᜄqKot=?P6^@?@ (fD@P_  T{ޟ Bzw(Q dq9!KÓÛl}~e HFqe_4P s`dc9G/FuR:yQ!2%u_+yAϯ߷@*8^@pBۺ,,.{QIs $NiK>\ypH J6'o ~u-YUQWÝyUgK(O~;!'9NeyE"uڸQq'OLN_u25'?ʤ:|t z/3GNcjٶ7xE fWrO#" !g~q߷kۼI9 JU\ȵ,)cʇ.`V+ 40= 7#NɐSU5#O[|_H*{/WFF75ݡeH{ مxCRVs&9|k럮AW[8. c?DcqT \ᎄ 2r8-D" NK~d_ rnIy2>^O@^`{0~$!GcfQ琉C$A_H0{1~(o-%]AT>{"WA[\Q 4xg3s;D !Gcf;CIdJar~38型:4 gwW@kU0dp·4HM$d:fT ~vJp_8I$]|'x SR@.%(!=6¿l?_WKBR0ff$6mv;| :|tPڿM(ys4ˍW` 4:g϶uy٨~dZP=>B*m7 uFvJe y: # 3:qh 7Vl8XY7Ǻξy~8UUK=OSt'*F0U3l suעgeFūyY$FV=QE$*ɦAB*V 6]T?߂y~ a[L*FJ/> T@&M͋43i:wNPu:TwI$8ɁxPaHJv~}0=7I+_N''!MswW7#]Mlk7rL&ByVު$%hY?M|gϫ,ZA7+ ~ˡ&Vt3l_iu4~E'ݵ`O^5ԐޒIZ秚8@gƞ @zEE_7bfEyD|]׿?oCPK%Ǐ@玬M_d/}KR3r>G?+_'n}W>5~#oC$ֹtUE~[ ,"M./ka=##X~kkcl:#pe J`]PvY_&&h5_Gx,<ְ<6_%_p| .nhӻ}^{>pH`ٍH#?sdaKE#<>vSZqgkgpm) rA7PGK ^ĉ xitƩ.B+.)QwrnJ<{Mg?d:`k_-Yh6,RBޫYȿ)YS_?UD"Rp- |`z~mIsZx`DiF -11Hwa#w\#^C{E$32Dˤ@!#A9fK2HXZz`+E!}HнRE5:0k#'W,8@?S3= w # P]GZxx^T<E1@20NAY,HT$Zr$>s%N0L9^hcA)|cb1x.Լ2顑CC>D4})Zo YJ G(0D  $bڝ#5 k2YvyARں/` ¼Đ6֍!u_*x^&jsօt=7Uoײ2( |BN><g\<$o~*jh*N:!ZtpMVKw*shw;(1v0.FcI@U{b앚@1\)($0pfrtd9VH;*WsCpꝑtMfz~\_5kVntz^`0ӱHB>FDHFAhR/U4mA DKkM3hp y]H]J7$]2tVcח~mk'B)?-)DK`94#!"8FUWio HYt#iF(N\`1L/b<_"b ǮA@0.$crHo(^iWԈEgbP&}PYDt?HYxJ;U$.l׮$uyC&fƽ%fq6 095Q'rrj !|5tMŖ~WLHwi@ _KLB.Y#:~ؗ39{W ioU0tuG ]l?\ xw0dFӷ 2J.ВO ɢ];|xUBr';Qo?\m!On/ 4Q0OO*ōo_]=o]߶yڼ{XvE~K쀐m<Y'0)\FߤWSB3=re 8)h~ 5I O# HNKx1C5c~~;v?ZJ !}DeB (0V])UPТsx2~bUҬj9]}J=P|˚װn"WnHiy͋De;)H N9Kp2l^ Hti$ ?v FJ)n29im\n"fP})ŋCc9#}] gNsiFB?TEdaC,%:5rP|m@82(R08ĎOxHg@aulzc&QZxH]w L]9* ^_WG # A0l &dcG 0xW n+GeC(ME.O7A @//߿XظuH@`aExO' [BeXd 7Z]Ï>>]) īJ  U5IENDB`ssr-0.4.2/data/images/listener_shadow.png000066400000000000000000000105141236416011200203770ustar00rootroot00000000000000PNG  IHDR@@iqsBIT|dtEXtSoftwarewww.inkscape.org<IDATx[nHIts\3$/W0 ~ b]jͬK&fdlG^4`P"ꫪIUyfMS Jr||y֗zf)۷|~~y*H$,r Ls"`6۷o6:猪$Ik,3znaL&XV!<So6i[UUfIT5mc("j&S'I &ų ' z-0n6t\ʲ(\UiPUs1s_DzI${tJ}}ׯ_~޽ifcF"cB2oeY23osO?1`(;f]\\Ї [u}UCcU7D4fƘ\D oɌ1IQ&GGGI{wsx"ȈrFaIU= 3PU%KDFQyEH1h9 /(Dlv{Bp,߿:^Sg3UUMӌDdFTPUǝx"F+KU+"-PzMz |>ri4511"A/j8#J3gY"bٷ/ؗ l6#|uuesC9T1o|KDS"Q Fs U-1}0TYsOӽvoNTEaD2s"uz#CU0~>>۹gQy$IL4󈽠` t~~>|0ιLU:?"< @KD/Ti\UGƘ~!Q4{A@|.}kID$>Z30|7SUtczS{TUee_5ZV iiODCf:!;#B?CdNӴއbfwxOj'xvvF9z&`h8^|S7hǸ@}"\{̼j޿ CWf_cq9gmtLDrgHޙ&z#IB8z("Ӣ(lL:;;jz= 5d"l^DQ{}U"->>DAa7EHD$?<OEV+Nz$3 U5'&z?hrKhUPUGھ1&sXlᳳ|6Pg޽1sg1;@tz;܇mzQh.D ##fsCH<6eyZ偈T;fkZ;`;߽p'ETuMDX?Uuc"vcBpIls{ 99]שG]ΏVSsNDtN␈E䐈u7dEQ$s3\Az l6-fBDSxet#"s_lvѠ~ID//]+!{;::WWWvG_EQ6aTD1ޏD䀙sUE p]At(A ~n DT0s4fYVVf<c>l6 `kSUUeY&"_~yk>xW(f$M kF"DdCR%yJ}gl6|N~45M$Ezw@U:?a'"A_vPZlkN*dBeYZfT'"]:{mn{ i]Π*7fZ1fk,N%N\.IUZkZ"J̜x1xX5]nб[>.ڤu]'ʃ...KM$snHDӘ }[#oD(]!dsû- EwUW"rex?iZu]!:}< kew !T̼&{BU2.}}[U- /U `a]u7lgYt ?. af4-."|5Z6RU9OBηQ|TU|Ɔo$m]>f:ʛ7o͛7<&By7%417ɋjs.}K\|6mhIDKU a+f^HAD[.p8 o޼yT}Jm2^/9P՗<?- KWC';*"mWc̯zmYip<|ԍfaZzi4`4MV#U5X"5*=]ɪ~ f;Tuc^§@Ps T+0s6<@K"ensw>`75h6V}J"*D!DT1,ө@Q;Czrr\_zB(:IZ[tqӺFo"Y}|Q X'IR4MS':U'''X,42}Bp"⬵eз̐\Lva:PGlw*f11ƅ|e>e;E۷O?%u]]wW-!~?-DD!l'If_+BbYB]HKBElh:Pխcac1c\$M&2'",@=Um;!"sm-D{\N{=3zrr:,:bSljMh0pgV+*$)UUD#jy-"kfNy@WV3ƬXui9N]ٷc2LQ5m$)" > eaKDF+wQQ]dA5d^3Z$Ӏ,31S",=m{$/d$5ڧ66EsuWqd('YhY#Yt뵢zHwM9sO,'F UIENDB`ssr-0.4.2/data/images/skip_back_button.png000066400000000000000000000006071236416011200205300ustar00rootroot00000000000000PNG  IHDR7FJgAMA a>IDATXGA@=у,[\\ұIhfM\*h_: xUyn_(kvNjT;)9ڶ6S$xPIOll]V+05J:zQ֘IIc.\.ߍ1\zlWMR.oEQ0 9<8.2=DQidzK@eCQ{(Ğ[K鉺ʐ ?u'*VzRfY=ؗ|w%'u̿rT~=az9N Hr9Ff56& 5N g6\c2C>bq Xt'v'-1KWd'dBq@(i@E0\MT2W  cK"!%:NHŚ57#%?5_1 0>uL-x4+{r8]ui>b45_AjC!V2<%\ 'xJet,@$ >doI;צc zXщf"F5*:"4r"DY~%s?yx#&5gWQLF hCU}"ru5 /$9[2.}"k)f-ԤcDc@z+𡀬yP^ƢC>xˡ-[W#xTjT+g;DډK; bǁp=+ I>'J@l7Qf7cw*#!bL@Ev6oHVINyGgLJ C$6@&䊒'ЏYжoB"FMⵢXԨ:^.Ō,Pˡ\F!74s!]I9VI.eR$)<{JԘ,P~_ϱ.dJTMtӹߵ@0(ƄDVmt*8*.hFD@tDI?ᖛ5Pc%e!?T2K? " -?fM\C[k nRZm8: ڽr1^nxR&Bm9Zхs7WG9=%q.vC?tfVH̱I-J$>x\6]x;Ocl8I[6o?]=ѣﷶz8w}lnFB2ssW'bj!'W[BAXVg?_!Jjh40>Q1'>yB!1qaW2 (vw @^^#,6}Dc߻FI疖,qQpL45B2hhp?Bc%vBà!lܨ/--q*ݮ.]=cbQo+*SS5G**OKt$ &999cl?1jvdqҶ$%t؇%͡444z^Pe s !M ^{wvzzzQMln"VfRRR 1/^`iM) >BRegfffgJQ&''јoN8gءHvK|'&&ցrΜi4"++118yxX?ʕSSS4$:t7k333 %쯨x%Q˾!.mϞM5djjԩLᝋZݢ㼼;wCoWZӥ楗^j {rΜiz?r#=.[bbBjjBD()ٖK I|WJo(e2/sjkO篿绗kii| 3R?~ԩ|>BQ]]U]{Eo\N)1!D 4744|`ґVG)=|oQV˜6cBx`!uRKI]z!TRRl0T*ʑߍ:Άwc\YYL !6N߯P( ڄ%IcXGGln'Bſ߼y4Ex&B0,˳]ǎ !ջ?||ceKQ}}] up/ȺoKz{/>'狎Oy4y>66hV)s q\mId ݮ];45 &SCWXX9|Ћ/pqz!Jr >G]u𰭭SJe4֨THtN8!!|{y0>'r2~T@2##5I :ATUF!6V,?I.<9_Pq|?2rKEZm4֪T>$'''B۶mݼySD>m16kJ4=9kjjD4.;w.>>.*G6q55U]=b*z'fv#=]T*Ate7E/ 2 +VX]eIENDB`ssr-0.4.2/data/images/ssr_logo_large.png000066400000000000000000000215631236416011200202140ustar00rootroot00000000000000PNG  IHDR6[obKGD X pHYs|b vpAg6˓"IDATxg|UelH $)c/"M::*Jig,38qƊ"bAHSPl V)NJ $H3a'({uߑ$pJ^SWmG5k4}[+Ԗ-~%HHG(oS~4\-@Q=8aI=iOmK:ޗ(." ̰ʩ{x Ncֳ VP'LRk}d@YR_`>ɮluRf'zx*Wߘc}DPu2 J-(pI@o-sFU>>2 q}k;|y#RtzGꖫUJGCow)^Kz:^iZ1~áGT겿NKߤy0 'l.V_˺OE®'7߶PMԘmr ABj؄Zdfuv vڛSTKD oدW[Sd$_r}B ) Ϩg `]T[Ե'fU|Qsz=^=blV-}VR4{w#I[p2`ZT>Q[RGhe m3iEv\NE'Mp>KVo=w6V[Q[>Jވ3lT-#;R.}6ɶ=A lgFO\Wzo5@ܰƧ=se\)lQ+xB zfmkG lҕkl3Ofmu~vS z@\H WY{U&1jj'jgX߻֞Œݤ>6JmHL jf55ۛ1#6)ʐj3kW y@dfϬ5Usl!VfNU= !X-l>{$ X8H^^Oyg uVLvZxZm c H>iJռ}cO%2ٶ n-ځjQ#6 zKk}=!cY |fwH@a H|5|G^Ø7S= !c[MNVYݽG3l@^ռ>1o:99M-@ zfB=jxjg'HԼw 0߭:ҎSb1~ZcՔW/ P+MՕUr Pr/߫m//U햋{H,;:@;R=ԃP3zZM۟l~H(Vvյ/Pn=F`_5WQcA_ }I0*M^ Fyj궳)ήen<^=~ 3R-g;y#j Y'[偵'eU7TJgG δPUcOϬuS= D<f#%A554wd/5lSm`MT7FJ>VSS@Uz=Mn9/9om^g6/2_>2 ~tֳ> ؕgԢsg֪Y=Vv4J_V{ͪfP~S- &Q![6cDsfP(wfܡF ۽#lfmO#._g)- Hk>ؑ}cf lg-sK,1{Dg).Fλj_ՠA^{$t4xFFi"9jGn:Ma՘>6M\= umFFi"fhKh̚vfl/l72Js;u.$):Wm399yߨ/}= 7gxz2fPjZg+lp/c'HѓSTI`zj8;Q6T]ab{\b3+ eq"=jK}nWOz(Jse}jNgfV2= >jOw>S/RWNZ?_ͻF?^M=ı\Tdx~i#<SjW"Ovկ:w}=ZMs@b;Hf'JOgR3K6!911{0]=bDrE2~3k=>RRe*m 9vYwۊ,{H& a@ |f.e>= >nPW,QO&cOzI,מ+r?1xV|HHq!Qp:ZpTVƖϬCcGg;!Zg'^'j/PGؿGDr{vT3kO=>ƫg5'x Dl]G1[ 79ߩxvºm56?H{DvgV/ iRf ֪-6p;QZ93yl;7+R#v{Z| j##/0UY{E5H?졂vӖAs[/= >:Sd|ߨc6ey.v(Yy{$|}NpZձ5ҽG@I> n{a̵y_~{@|tl>ʼGl7U5𙵡jGG9TuYC:#~F? j{e&ӈ]VKl'{[oßG$MiAܲajGGݫ7io3kEtVoQ+޵gNQ3[z.tpzMWS |fsHH͉]T\5{Dv)vj3kYjfޥZb=~T;t e=}v6UJ/KO^%IvTmo?[{ZQ'U.egTx.'jaj3kK-G@b c)c gs(?F}ʶ*Klkj>բ#;ejF2I-i>Vv_£[cuqoD,OCB8K<<6A~=" rNuٯ_{.%g_=$꥿Q9ϨUu-sH[NKn3kQQHj[krDlo{.v)5Iz)Ǫg/Wg۽H ZUW؉ GGmfN?x xsꤑz9>1۱vB2{}tJ>f'z 䀕QW+nU_j GGͨMjͬQ?zUc~{ͳki)o!إhh9QzQmvznzKjjʭ/Up#qb>OQ+y"H$2YyZ+MM{Sa (k%edB`7e܅l[g&iMl@p!}٥wWX.evJj~}D>[Ϝ^qԊ]%dG꡶0'W۲6ľB//aZ0{ݺb:?6qU/JW{ì-W/Qd3y|tsKUgmھU3lyz=D?uѭ SJT/Q2N{ 4%]i?$I^1< Z=޿ B_&[Ǩ_ϖ<}EM9;wZƂQb"K5DDީ8}Zev?{$[ӇSy?zQe6 #{?WQ-{~~GH$4u43&N{l:v=Vi7_{Q>rȞY@i"vE{mu{w ]#5*;alO{uV+nU(~d4vpJsG]74H1P 4WKmi:G-qz }=~ףLשef=_g&BeMHɫlg.,W,Q4P|}T5VsS_ܷ'yPvyPH#F{==gC}A6co~M{X̚-nHKC~,?Xz4;v਻"g.pJoKՇ 67U[`T= :9G|I]&]ɯW_w&bcv6f"nS[}p J=ZT-xF5Cmb bt{JJՠ?+cj9#Mz?n;GJ۽ +#-?06׮, :c[Nꅝ̼Tttr7>Nx,dtu]"4~lHjzp+۲*VNm.Mn9H]uZh'f=!oM{UslBHde;Mâ=HBw/ #G존}tUo&'LW2lĔê>3[do8eo`Ruuku\e7hOAxn[GG ܙUugͤU8 VKm]gR%x5We>uj^3TGGѻGǹ~ ZUܢޑ=R$nv3x¸+_V%@NQOЪZ\\M=R$d{mf͖o>=">cU5w={k#"ݦA3ky{#ɇ73j3oU=2 >\Ufֺѱ#c1[/9#f+V+Ϭ=KSN VV-Gl~:.AͬىJi1xT zKlٔ7{I7{.YGm :zy.rr̚mq5z3knv8мHt?R-glˢGGvZpoLY{yx3U L=%=">zT[ mOҩ˼G9jj##f6 K |f.o 1`K|ߨ>=Rhm>=u##v}Zz̚ami^=aB5f9'=m|g鍽G}jmyZKY-=RY+yI? |5M^=">KjП4h#vrmڽ>O |fVo{Hȶj3k4n#vR{kfm:& ۵1[ 85[߮#~6BlwR>Qnh}ZJ |fHtLWϬ>F=">]lglFg{خY;uSg ԶuGG_g:y_}dlut v vjQq`w{ݩ9= [`3jc"MÉ歹Uzj##{Zx̚_{$>f؀uh { R{ 4 R&^.:mh|(ƪyF  ޢa}h?yͤ/V=R>)BZGu 5}Bu}V;!W1C=OmޠR Fi?;t~#m UYkz{$H@tjO{zKm}3Gאj3kǨҼGޫ}>RU>"$ ZmSȞlY/RO/>6K[w/+{:`ǏӸl5GKsj;mr]ȼWM>R$J_{^m6z? =vǫD!rַ0 U[Nq lr>UW2ޗ(MB͜Ij66=kv ؅+P(MV[md(CUcvュj;Hͫn Dk-޺-'[-,R|:{$T頓Ixqս")G$I6veYCm0 A=4񐝰y8P٥cu9BA Mz#`S> ӝwܨ-V>}d!Q5fb-9 'v=5ߖM𞉠4h3K6قϬٕ $8_|1HMZuGRv鱗=djaܮh= R:%Q JU[$ͱ oR[DrTZAFՁ{P7ܣz!Rl ` mdR|~Gmx:-umIghFml KcVA^ש G-bꛟonA-"vG{j*j^VY;Imx@5i~Du]Kb K[6bfgu-zF"؏rU'ƁϬ-U{$Dwe4.LPxLM/ 3j}n^~oVW)6ózZ^w LP^VK|q[8E5m?6xX9Z]hn%6w~^c="aHZƦA_ }>~_{@VOEO߳:gp!jlj dr^S>:0A/R=kU[9W3ɱe~TY+R8- 0 Y@[Ƈ ՙ drŶe3k^ RMQcjOT= !S>f {$¦2l DmjV7?G919      ##  56C/*6      -+6%!,13M]dH#)3&+0   '#3`Pqwq|{ll]HI+$M<RgX++8%!+    %3(#m>hSQc]XP[EGD>EJBOMOVO[WatwPBr.0D8D& +    )(/YL=S*Og<0>77cd!GGrd[2M w2t{%8(, 2+)({UjU:a1!~$Z;5DV FgCBg6N?@%*C2A9 'Xr\:}2`{]px"2      7"3Ihy? iELq ;?a0Js_r Bi5W a0,c)IU g\ct #$   ".&]&_D.^ZT }xXI`%%f6)>6~86EOgNUk2i=- >8j9j4+HKn@'@   12/:A'YLgP&@n.)iM,,,` { q&KkXAa'pC/^-NvF<$(    %+E>-zq6Mckm*A{N^51[`K V D L -Fk^8_lkB&PL     / 68Xt `m0/  7Z1!&(*/-'3:5578899g8s9 6851,5 *e-/(%nc ivimS7ba< |uv+'0e^4 +    $3Sz,Q~7:LP&u33X~Nx"/$:,EFCEULXXW][^\b]\~[\Zb[ZYZhXXXWX:W$XWUV1SSP@MwNDF@7v;)+x$ {^aYp8KE &{\qTp|G&:   +cO+%:pT_ ",-/85;;:;|;99u75|4 2>0.-,*'r(( &p*()z-A+/2K/2p9q4vA>KBK FQPeQXUUlYRNaT=Dr8"0V E AGG4'~2B\Y9l-! !   `8!`{+LB\jU=Z2U +^" gq3r06cK;@O,RMRSLHNE8D^'-&|{ hr E 7;m<Y g*|E&s"1324??<@C?>@26-I&[4 4 @c683G)A    !ZOFkU'/! 7%''/{-**.(L!${nw ^(}nzyVEU9#4 @fnaK 0G,3|MAtSVO-UV[AI:#>2 Q3Fu s6@m+#G "    )QI X~mN/@m` Dp.,/Cd;CQHaA@{Dq.5O)=#cJ:XSkvF"Dq*%/U_mu:h5i'od"C ,%151B45 &*F!2ZsmO2P+I! <o23X{8;/|J%. $w(j!v &yV |Ua3Z9=qD `A RYdgg_8vP'+9i W$ u(G9KQEHXJN298+Tv:P4Q:%cxFz1KFX& /= 6qB g]lw W Yn F!:.:c@77<%," q;=uX|_#Sq$/`?Do)=%-H0y1+1L%dy4y-R+%)K/&"&! 0/rI1  e<d ]$ f! j [ ^\2zFm)1(]aK:hoInjN+q,/G?@gIA;|.8PvKLmD^1{bXV C%" 5  {{ o5A"g \$ 14F/:N:)2%g:p^tMYEn#Q_i$@~h.Xd{QR8DYlXBE`1xOf/C:]?r i #5&&wY Q#GmBe0`#   U8aW!w<TFy> + VPD;f!V?Q8dlvA^Qbwf)A %b`4p: f|B@)ay"BrJ&33y/:p-8=5;A>-&u.!$tS*a5 cpe L3zf |{ |o5Q)'2_;M-%/ s  bzpy_:osyeZ7,`ijt2pA  l -$o> K?)/=3ky~G$6Z<!#G` NjMYp@^  G/s}es"g0h+2 tuyFCIb Y\Poaa  wH]+el \ 8 w 5;o / 3   5  c|sg>Vz&.]Fp6@+${$<%9P3b-6'7#}AXPoT%G4" T{ZnoB](@ )^ LJ<''$73$M0  WbK_kWrj̻Xt b &E" .%tb!p}F H0 QuTpl"U@.-UA&  fi dzoWg,S21-e C `w  9n< $m60  !B # ed c QAs0RRJ^W\9`nh  #i &p{?  XP[ |%#')--2F45h78389b989Z69)616*-y)t :'WC D f = CU&(cF NtjAY e-"w,V4'"*  UnigYFI bL;v7x3e\ #`dK! ?:S7 ,3&.G7VORVj_UqpPq/{t|{{~{|{wyxwqqm2gf]C]WNP@BU;!.2T pm  6=W x  { ,(\ZU !KQmz5t G O 6Z# @9t > H= H  /gK5QtT:%D D ^VM< . -2B=KL5OXQUU\\]`a_clbDcecdfie"f=h?fiitikjA); 4r8() %GBg( rvR3GFh+e X 2C!O&$$'b-)4m18B';OhLQbYcg``clNTIc4?z$#!H6 Q D ? e -P[d$7Fpr C$ 6`S"ILa,DCW$Go> aZx@7ɓ%ش׸ Cl {# xJ%!#| g , O!z$j6S+rDBqFVO5Z\&Y\\RVAP9FVM^;>8+3{ =$?ZoI9!C], @2|!U)7BRW =T eSRM.Q~2)@3C@AHLJ>E`7$/ <  xBIX"v$y2Qig=;guobx " f d ?c7# }^! } _n`1o\;$~fNKVq/&37256),2%:f  "SA( MJ \('X>ipQ9o sF~  x~"~!Q%6.(a8U5;HAQQ5@;2 98"`&n4)t#tEPE Pl !Q 6x d! (z%Zf7վhWj/Pەyү35-D4-  < gX 9{0 $G&3W# -,(91?o5O;OKaMG\XJUC~,> T# mu[},FgNJq a@T\PH\`x m Z YB%+`+dl~gRx T  ,.Q*W33 j(8kcEB  U; @D R Qw#ri5BHy7v}p3 Y jg uX y &wpN X&*$cq#]%  |"c=G o(  8 ]br    w + < /:oQzPwTQ-^:0k@P)r+&/ p< !h6.m6{8$KDHM+ HEHH>F?< 59H+;."( "h@ \Ow., ?`ph^ : u $z. t CJGD3/ <  "!z.X 'B3 7F9SVO[YZ<I6d*XBzhTLxmEMl + P . [ Q @ f  Q C 9Cc I n   d @ q8qS*_ Lwmr4M>K`Fs-' ~s#C;${"-/+ &  w  JT@n*q$rmbas8z= "nGz< a  *" O=DD!3Sm bN/C 6Qe5S e&  Q '  t 5 ]* l H @ =lFna*6mLK.unl?n}"2:.QG' 3I`*&'$z* D=A:xEHJ 1DTB E:<|I9115 +lj-c'#m 3U  S j^vnB f? ^T r6KLPZ&2I^ !:-3#+6_M#@R Y L }L0@S*? "5 $O|:l%OfUleIkj\B*XkG <@KgM+X\$SUh[Eg(,ng@!XJV'X,[R13kl 4  [Xpf$;q 4  b, c: a ]     A 89 O z=[&+%!!!8E DBi\ -e1ag]7;ks js OfzR! jAM!RC;   M# Jpr a oH! P  x K 0  T"S;bkU K(ii )#(}~o|t:oJWcSZ*YC͇Mʝڪ;fP^~ w ?4d6[:5,"?D2>ZBC m8t:E7.H2)+q&v~ۂ$/I :^";  )S pDy&!\T6/8PEDmMjXFa;uI3~"&k s  zj t\ƗTJ9 ^/lo/;7b j)2f= M X o B*WT,x= Zu][nxzD=O#nvJ45/ V B Wr \%o' O B  Ki( %h.QL& # %#Xe!t&z7?5P+[>v 4a/!dt+GT |  _  8; {9 ^9  +b`Kn;m{Ml qo^ImXpwH  6R YCWf@ w&]q94H{jOZ@&j+.N=P-2/tQ q:Ϣ ռ z ,eZ_+#Y ZW,74<?7V?@?&N4m7 3).Z _&~& $1A E-i"%!]; Tu "%u< *985=9O_F#HCAzO;O)9H bB-YuRCހݘU_ޏ V k fSV~i['ZN~u8}[^ Z 8 O g  8d f Q = e] 2 m u >wS6Re( E"# #f! WV\a16wuaK A4?& >   '^Z? RQ+ p/ @p NZ~OWudi29)*y=,V$ GiP2Yh/'qVfx{e=~qy&E'_y^m$j@ d1=G) 1B M*kBߒ QB-ʑ<$SVQiHև= %e$* L}6x A s#dx2 51S = ; 3I 6 3I%&7/6!/+%"*;":":7 JF8D19$*1 v<߂d/UۣݰU]ܹv?  ͎(A= N 'AcqXR,z}z)D R U&)rX5D:rCSO>@^{upon-bdd.U."idZC`MF.q4NJc)W>.@+m @c> 1W k+Ta1"d"w%t)'!!%oXjK=t6l;L/ V 1 < U i Iw FEN^Kf9f>/< T| &v/C o0@z {G=BbA)XkJ1H!   f  | o m 4 9/(Y>I `] Y3X {޴C )Kb0-VӍֲkz}'n\POpy;5> 'R b Qta +N-k*<7" ;r+6:#Z5'o@6?2/+,a5g1%M,a @#'FiޤVݮ-Kۆی B5d!aI;cڹ#vv֑ͨ?̰|Vi˘B!hњ%/C^NdjRMU*'{oN}  / 8UoAP=J,>,=#JTemgOWc2 c!B/Sal{!0s2$Ai%(.! !q%; [ $  5   Mh  BP5sA,FB[u :GAX0c ;It2:{?F6YMgcB:Xjt!$JZJ+OgPa} @ `e u!. Ri ;)Rz=AX]$]P9bgP2: ]Xe6`:AE(݃"ރ,ݿ9LyF dJ *;A "uIn5G-?8o(/n![3 j)#r""ޫ1-)ۃ-ݓ(R Y܎$/a C5mΠ3#2$EK~ԖMWJN^ -M~  G7_  !~=; ,  !3;<7[b 4ZO98{~  \  B A     V ,61Xa.l:v+xDc?#Kr2h7hvCA^$ cgM'PmEkvqShw18&\ 2 ; c|4n8Tf dQ [ : # 9 $ ' b#[ ^ &Q m FZ7+& T117b8 ~+9)bQ3ua!f(1K-"^, ^NP[0e o)SJQ*rKSSLd |FBS ezm/qINjF  rI FN *a ~ )O$[e_.W}uR (vr;G޾ޛ x 7x wKx)Z"_35.16 U1[߸C:_6S5,(%)ކ$H !\ΈC߭ Dv, Twhp 4t E1BrTMq6H [c 2 =.^#94>Q<\JpOBEr +f<C2Se&bE0X?AJ.G_a?~ ;3is5kU .! o J^j+{o2KCx& Y= &#pBrUv{8k" D p I > h g ^3! elF""%qegq $ b9W=.l(P@R@SJ5rB (+Q4 jd"GCx\Cz.[Z) \3K O O2GY o#vxK>'m>'R[heZa3L>x ?} M :, 02ENPJ x  G5Yww0s]):pYoFalԇzT\׽'$F!!L$/~ oEzKtc( b   l,X9Me g i f U 4 k klC\ Z+'V XxM#f/!r ZHf= Z}7w:7x Bc5`[;Ie> ~U*2 '1R9GU~+M%Nq"dI>8z}%TO ~z$<[H2=g@xB / 6 x ,_7P /  c2,m*Lsk s3R#A, ,Ayg|sDB, }?sV\uJhl9+3L+X# 1 I]5#h$1f0[U8m+[WdD08J S"Ty~}|HTy;?[h$GF~|Gj.GBXT/ +=az  3LW>rVi: |s-a|J ` db =g1'(=Y9I?-%B\),+& '*ݰn-ZeJI&SqRVM7i)cgn>3^٨}؋z_thwQ# a. , 8'<"  I   5 V w3.UoT k T(< L#|'**pglGO#gJm-9=>: dJhB&eWkqDT:[A#--m4=iJ33 ZT16*~&&` {RnHEXXc-?&%: cJU9zM|&SwIk S7b- KS kOQ5r62d 5=INN L0DX} 0eHA[Ft7/A, b-TN]a&^_IU)2=g;Xjd71Y,?x~8tl0;#KDniBlPnSdg Hwp?='WO!I4B S- &.K}0`)K u# * ~ U 1 DK.1lv &"] $'$L!x $#~dMM G1R =rۥtt zSݴ^'`V,>2JR@RZ '|I.<$g Qj6: [v 9UGn   B"%w h9!o=2+'a7"aK8]uEj<( UB' |i GE$ ecyih_}.>L r~LQad;|C'X:[1<llqaVYGe.}Uu pS (]+lLakFcS&O$[~S ` nvUNXKu}Wrkc  u FF E   S & r  1 # F Bj o l [ A ux cssrv&ob9T٫a޸&>B.n34Ae B-PZ9 MwP W  huZ p Nc Lvx X VpB)q/C9] f,]&| ^%E ~.Z"ig{ET{f8;U4THk`ze r[  h y@kbSjku} U y8A mh}\jbG2Gu>:j33@T  +=y&e*OQ mVca} UPw 2E8!nRpU"'X$>z|: KbzeKkgf!#{:}>S1w E!` ,e10~_o[;A^f4x A56-iWk[~@h nW9W x  G  qW(  _ + Y R{  M & W ;  4d H $*Y"yN}V.\*VN.$$L: X L&Rc\ C  E VZ }I U[t P K  fx6&DX[lG8@47LR#<3v3Non<5-_d% D' D q V 0  = l {W ) 2L i M 5 :_=)]LvdQ/jO3 _{+ $5%/5'mX*zW db  g fcC~ vk > \4kDLFc:3>0~`4E{LZ0MCzh|tS'* _!=T$S= }/) j1 X b wWo$ zE 7 TAR8x sQu- }DCST5_)/9 zPXLOo`  A [  ~  :   i 1  &w F m 0 ` R U - [' 9   ww 1 s iA qAlN QDe,~"{ v'x(7hs; t!. ^ SP!v ^"x: q  yMo 2 x m rl \ T  C b 6. i .gh ;t1 heU~RO-', kGiKh  ]3 6 gpB T{.7=>8+>S!9 mq=\oU  < /W16!=Pv$#~Dr`3x{ ~i vZ bJ5z *' #5  k_ !>c/<|:$T  l3y1 \& *k % ~ k{ \}]d2u5 c~O%j kMkVok SBK/ E* 4 -j@<e3!Lk3se-;\YU-+lHi|ZCV?n#rtyq ~K[%reFCnq1AZm L/   6%;n&eQ T V93RuZA t Sy Q ] 7 _P(K]sbO`}Zv:oC8 ~b5Q8aYrHH<HJ#pIJ0TNc2*F ]!U(( ony+[l5_Y  0 KL:  : R$a$UjYX:s.'9 - V M N   ruv - S? l v qd  T mq 8 e(Q p J >MC4=$2dP|gz7Um3d4MJcn \!Yo9& n 2v Z.OC2 I _ ]  ;@  B  \H$`3W(ji,d)#:BV:)5 /VzGR:S $VRv@&:tHePIf yDHt#.5:'t:i \ \RZ 7$r >HY   bUx>.I~,KU7u&)VH_y3M;z}w~ .x'1ar<q0l(+Kd2@uT"%iQ!]B e:z : Y C @` n60UikuP  qs UjUe}y?E]n~"ALVcD]oVhaq%]+,W*xPLqr) >xrVXbDL>|Sk{  $ugI6O%hZ % # t\eT _ !z`Db1de O7?*8Q_@~sF0n c  S  k  aA7 . ao * p[[s  |b  tD& 9oct\FE yk~"VAsLi;hdGj P> +[d4N,6   5MJj*@|zFbJnS Qx4@2p9t/-``tFc+g:K54_qY7E5E dnb|xG`c5vF6|fT> / F=%e  ; 8+c h.ZHk;Gw`nx.3GM~Bg(Hr6I.6nZn_gt7W)lo_e8xI._>O +WusE\Ep,Zez43_-bZ$d NR  Q}  gH?)}WjDL c : "u'b i'@0n >i t#W&o qMF}`0ob |}>DWeQO`kV8 ]GXz93={{z2G)J4 p [tJ $ ")GswIBsz#j2yzvs\ p?J+Vhl[HpkNjz!z]3 a*      1g [ 6 0 F"C>K)`%C)2c#9{* *\~>_h4,"Mq+]5Z;B U)7RRk   G."  5" C 6=^~>&He_@r"?J}4c 5c?|;6.1{MQ5t`Sbjq)KF&)cqCVBRlz w| x  o  = [_v(7Ck ZD4')1  c6<l0Wb*AS.J8gH?2_jU2nR( i _C5Ca8X+WvH16$ Q 8  +U 3L | P; s @3 #g+ht0g!0t`wmb"9[.t&z/ [,;}a/J<hYPb"fg~;L<@^tTjJLz2~Z1  +Ya}fO? E nqp#mV0kem+ :KVty|woD{5u[:4 : +% 4w _TE,v K e^ ~ InL ]  tx+hhw5NW87~z[P9dP9%a&VML07&<l O  4 - " '  gRjkj;z+oV!U y07%3 S!&eO#  ? [e+-@t + 3Rn\;N 8 'E u   * Eo  ;+-]:4>zc?U"!j{ (p y,]nJ)iavkwKqJyG{Q-k(vY==MjMz\,;iEy@5_-XT^ mr(( wA5xTHTlY06/;ae 0 ^ M f c 'W 9u/nZ5zHM|. 5fv/3V}8O^8*[jr`UtJtP8[5^e^r  9 vY p 1 G S_Q5Mgd >+}` iB[GapPsBS7 p w 6 0Y G| B R O oh..YqJm X] qLqEeP^;2A9@%(9e>Py2;_Idf^ hG0d9KDZh.-  x Y # X . Hh  rSnm!=]0x BtHOK%Hqry bGgaIr\ 0xb & = ?n > - 6! !O+p y  +@9dnfkoqaKo?(jM 2(M &KDpy  0rtUY.vL'J]v[)mTqR>Zr~A5Gsg6-iV8Aj,Ub{#u(zhE97 !TQ4LQZl@SRfp/MZl s N 3 ` V+ q LC 8 F$* W7C6<'AJ.Yd7N 46-,3!w|<0jQH]itLmKpU%5  iU L) J  IB ~ +z} l::|(Qba^7Y~TL5e,QAX >  9WhOF|d5ioI V$  @g6rnk4 z }A%` wzM np*eWCWuP;M'Z ,2Q>e%T`VB,]i% L$(hmh fy = x - g^ H;  [<u@h c2I'n6t)fNmG}(O0r(sb-  0  &WuH#zio|KC6UoKlZYzu(99mm,yHg3H@i8)+%I\E6)TLm~@TX5&ZXp,=Td=CMSooU+glJ>l')4L]lE<](s?3o L d   !Ew W0Kg|N$W6nKdj= 8^!Yz ) Q  q I h / `! 8 "svIq?<8Oi - <[  O /rj20ejh%m7evEI!oBt5>]/q @ s 2 | c#rc,MRl:%2t#u aua))Z5c.cJA|1TT6u~&90M-B-(lz)XEhH9k+ug)cAu`]D2Vq*#r#R7Rt?j-J|)AgpHPC3_ >r~cRZ%312tz6Z  _ EbAbP76? jjLw7cV&hGfo>yE.1yb\Wg6 $BG z^E Z _ " ~ ~z8mYr   f  k TC ![4qX8i{~}i.-RrxGR~Q]/~'+q?K V<  4 HX & smw(w*xu1yP0p JKioh`,>TR&c2mjQWi< J6+%{`+.P(HG.KF<br|Q8~F9D3p/; Sp>(_oCHc^{0Me6Du F :;b.z\yWj11 E9[u|$>{%KLNjC{pC=pSv 4`E:i #.d5<70?R]j(sNc % o k  {B z$c@a<%f$<']KfHtEb V1Haks~R}oekF__DQP48:A20g 06IAjJnMq;$EV"M41kA9HO.Vi_ 4 v b  #j'}p?5rfzt9 | 03't 6S;"CSKCD rveC OJCl2$N`z.(6 +3 @ | Jl S i?}}*i Bnl/(dt J# )/0Yy#@~8W"27E1lBA+ Dz*.p/SvO& 9:6>aHsJZRO9q6#.^.5<Z%=OK}'Y^YN,C)a1pG P_''Vh*kN8c7.tG.\9k&}E9=sXz}1rkddBUQe#La4.f17/a) m]  5B    $B jK 6dY 6xAps%4 9 J u >-p tsY?bDMZ"&u Er[V&nU b @a;00W;K6Aw}%588jz')_(8[FjL@x[#/\u1zVr~5#ps@=>S/_>/ bbLkH0, | {H !o;O%JXn4U`l 0};Fmwy0&Q):*H=y IIG77cP2 S pL  RJ 1SlC  .Z  r ZV ^AQwGlKw` HU,eO~6|8pMeQk>bhO P $Zw0E4-,C" Cg-#58<@ 562"?g SwbQ.Pk3 +%rQ`8v,fE2?QA,iT6d#ds`O&Wl^A}98IL *\h=Zvfk|}`[E!cuL,>:\8JZ d g  M WB >E N c + x ~ h'  ]Z ]X, 7D(lAz$0cAFv :U7"+?=1xi3#HCTe?~1G!?JxT:zx%[rr1eT|z7*>pC2hF!rRO!.P\b|eb&eUD)x +m)+_?W1K|+nFybk_p$G-LUN+M=d$ib10yn&y:$z: l)IR,XXgdphGU j S e ) ] :  f  ? m '\ tb`_Us8Ns-#?'ZLBd3`iy+4pddQY+6]> ,] CbaV jK` %]i11TSC)E4wGiWx n'&\-~?oWzM"4e@?a.&7`hM Iq -F$JSS:|X@ZB8,3G B5[J71:{T ;'g,J%K;'#Wx?#:{A\ul'IxF-<Ua;v:6ui-N>YLc ?`$a ux/-X3L:eoSZ{So(]o*8.'SjZVx:gG&)J]M&#<}o<J(gTQKzyV5D*.i)D:.W6ia!hZsq%]W{"s=Vj (76O ^ C t  :9 L V B[OA25:V(a&NohYRWE*:vo3r^p`zU IZ>ce]'.)CIU< `2hN_8>Q46scZd2M5&}pa#?%az@?#Z |SoLYV>KYu?wQbNS"t/\FL0|L]dvP$rrTTT#<ec[ ~GYEV,4 Q& . S+]W[A&r:mJ%L@r 7R<G` 2B#Ip4`8>umazOL )u<"YX   8}.+|%]/sB] Es~r{,)ca9Pbj:rx `R KtATF i90CF \qtG5;XKrX}9# O.nCJ^ 9 @hd=l "{M>js5[q<$toRQ1x":c; :H$ydz/c} ai{Ml@%z'G>9R~PqfyseC'! X)B_k#X?8PX+:02.FB]jnau*zz~*F[R.K}Af)26za{cD=T )pKcw UE3<+n}B4Zgj)p|  (k;Q!/ = }JzguAP;x1`[|]0~FuT ,EB0*MYMjw I7 t+MFo(Wj2GI[uA<\7um[dSkR9' 9PsGV:9 %&M\u577D B?2]:0)TT'i.es`zR.qH?G/{7R;|Y z *v|!p9{jE `X$i(V)K$ l$s26h Oc8g-3oY(8=fT$">9G5Ns)amqH"c-RiU8_ ;r|NM`KX4!bCD+e'&(:]s_x(K^8}dQ=R^-vZ;?# "+;_XI.</wYd)k RWH4C)aow[HPY+eU4lBS M\,KBRU 7,~,Hh6t]i(t7FYojX#$3iiI4hCuw#Z(N^bY8JBiZV` }Ebi0W#'e/bm >l7O;Cjc7y*o;!o% 0fezkl1 ]_pj=HevWnLr' 4tFtMEI.H/U%}w&$cMc;B># %X"V4S;|tf6A:5[QvgT;= Q]vt?|z??VyDv `t@<55*-N6'eSA&RrZ,1h~r+z8t ;bx@C J L Tx?XE z'tzE;dKys}>&H$N@u4Q,dsnQ[Y=a7Jf{UN5mNE>?(1 Q9D*lsOUL(*U, Lr 6^k`^uhP! ~PER(4=#.)F"=,qf;y&S ^).\h\4yv<``0ym[,DyQ$=y+6Ue_'+<zvd%~ZkY|xB+, 0|~bGLO,&KC:jdWe @y/l  5\Fs%*'~!R,d'5>+p B.}*(Ct]7E${^3$p*\GH<NwA.zuP9qttPU 0'zVR*4S<NK2u-W,qHLmv'TuZ&B;_@}%y@xaoO-Z7lOtIv#?X]"}jhFa.08$k|U%dX>f Bn-HXu/?@c:MDlm-wKE?Wg$r{&=JkcP)g9l:c|n,!_KFKZLh9cfAAJ~3dkD3p:[dxp "]LsX$744]pB!;3ky[1?wL"-+fQ0B#h@;Kdt_vO $iC#eXljzT6(YDxb_IrVFUVW[/_aYKXbKs./LEgAq9g12M/#'R%xT%3bZ[ 1?:SV@:}`x ;u%jUXQh)>+ -q+<-n-!p{}u*yF"4nDI#:m7#Dm| jtUV'~%M1$X.=4`)U8gq m2?>2:Wetzzr u//Z0 ` Hy<\? Lu6_JQdFZ?1j9hId8vA|)de!2(HSlQGHfT+\hp/$Fd; J!C| gk<  :J<H d1[#  IvD(S@`IYZM*m$ pa6N&a:HX!Tq u'S/ k0l'VD:qTVM$<=`M Ybc 7 e /h -M.Z;:.bC`YlH1 A LvQ_[|%}uWNws DTunlDs\02nv3g*>iXa $uKtt? }> 0L!|Z~ qj;q=%1;Jx/I[$%00@U]/V"gECPb6BHc kK>gT2 V|#*.+ nN{f:P+[6Dg5J ZW dHU%9lO;F>m^dGP?{"NktDU*I,T{+Kn4_k> , +A <*l8 r+>./jXiX<4#=%QA]'xL2PHE#s(,?y*;JLZ*q @_GJ`SGd sAgNGWu`k5iTMN[$s,k%ciB_I(NP%"tSK#fIFv^\K0D r4#j*e 510E-ly$L oGSB#-K5X)+1)4nkKZ6L$ztY^"83gx(A(r bF2shg*]z.+*,l:d'`e;G*@-D}EPD>s_$_x]`5~T`xyh82n]5}mm(~2`h|jo1,5 F <z<)jlOlDRZx-p o/5sMzt;N ta+%;1|/6mqag;V(8a?qoO7AFxO_]x/k,AI+!%fC ,|2[Dr0aXs-v%hT.P 7$.'YZ,O iklAMS {C!?5+4D};w~.]8 e* 'AgSMs K^/t/2*c".rHoQ e h{v8'qz8OpC4zwg{3D3mtQVHIhT X98fL[ _"=*^"WYBOn'%Hj5FW3`!D*lAE$Iz*r/u+> bB8pYh}V[rmzC\?Si%N IRUxpIsgAz)te1t$Q4T'Wqjg`6SyUJ4jx 2c ;F4g'l/^ 6 g"mJ-G.?RKK'T$GN""$osp8 N>qnPGOw'#;++ Ty$TvoF!!+,dN)xiVPWe].kX c P}@{m/re`\41o 1{f4{)aGgBaZ`q @e{)Z4KF;Q}txBun UHeE3P+(6)87N#y@YXJ^/ JF,4 g$5_kog{7f3c+W8qHOQ|#/!!.673  D4 wWtt.,WnZ^w5 lLq78*{[R,B\;Y+e1uow%MA1tfJA^-nf-Ul ;1Epl>_ Oh#Nj*lN WmQ TnP!%'>$OO@A `-!;VaRkgRB :k>g-8`TQoBfS'<0-%7'TdGr?;nJK h!EZ6|GjX58j' # #}b+ALVhaUK+q_G^82,~^7_K>)H9qaEeQ9\rYy@-#2(hvsX>dyPAH3uR e0:-  U[|12+1 TK<16 ;o! )Q^sXxesIISGq=! 9WUv8 ~ 2cmr6]#$'9S`;Ds+dK!bn~}sfC}"%d"K'ax#LG-/\9;@R r+=#L54D2)AX9 2&_K&_-s>[&ZwZ ZxB2fQzRueVIcDWN`p*&P) EbcbXFi>v-5iizwLQC} w|g ere@c0?tO 7ttxySw1wmz|oN)#@cr'S")<z6$k/em/O\9 ]~^2_^'{~$.p@]cfilYQ^ZRev\;NrLh6er'y=\D :WK+& >~Ce:d+I,XCUmjZgM tyP*@0/Bl4V*H{Z >7*ms_v>BP"=-ikwv3}gqF$} W2.Uv c"3.MTmDkO'mIvqxc &8:)ifqrn)''Ql,im^C *< uI1@C$4%]TTH7TM6~XM oAwXqkB9:D*cX^@?H2lT\O?VYJKcWj}b*pFw'y b^& u:zE $5ZWKsDO[ `Nr ;'bL B]`T4FW LfJfg:S5D:cXP/=(- o `(Qtsr `8w=J)9Gr}h7* s~ K^hbL 2=jcq_:a% >`<JWGl<AJeK hrPpC +wIAb.dT|RyUha u3C:BIZE$ w7I=F*` @@:0T`QMl,SBaE97MCm7uj2U&G4!b`7";i{&<0^ua&bAkN2;v;iB3k3I'Pf,R)f {E$a X|MSf%QI=WeP|*`,{9%/c!Ov"@*`P{9`0N%iNKMl7sC8Uyq] ZF @cnf)p;_=F*@Q4Y1XHhu0,OGq3.YsW~72Q -s4_U1vvv 'FZ'"6 u<u"8,%D#~hRU AZ|{{{PT _~cy`o1]7>#xHpYp'([V+G%,pKR]\(Y \$E=u!O ~c2mVfzETrNp  xqxQ>D?)LDX*)n*huYD% 0f:xX@7Mh7AUN|\U0vlcO|f0P<Maf{w9]oI^f)Guya6c+B?>v,RUZd3i/W4o@;o0]RF^g:!NjMZhL1T5 gzP`yBO O92BKHIC3[|lq~ )@/4axLoE[C>iF5y5a ;|z{Y x!fg;Mr*: <Ky_!u#hR #('EnC'73@E6GL  r2tzvu`^( 6-htyX e{+ai|}8Hv8Y%j !S K dj2_[c#+R)?,T}P$, -.amCxR&0h/0 u1ehG5)/$zz=3Dr1P\td^5x pjw+5_*U-ET7 B%Gq.E4x|v{Fq21v)cX]L12e>!8qQrlV1Eu2P|" -C"(V3$[?r0P[b~v(,6lHpjpuvy2>k".AR:' T$.R6!^ =\oYB/Q VhT3f_"Z#ORIoK~t"q#b5 HTx) 4'UXWt1d@@nY_%QO]y8TSwC]0*"Uv BjmM1J-q*2UqGq(QPr  laE48wUt.Gt7KcuX~Kydq@xK}*lz9JT5+W}=  :~m8bh`VNL6)p =G+VO'~3uLD< %c|s#fEq =T l?yC4gmW ~H9`Rsr4%j[VaPA]WDEX=ktRwvXdnAmi~<kaL,c;os;kh^N{K.qJIvUG_i1Pc6BBxOWZU]r*Hlh!F/ef i.*ES5;!dw=w~'($.  CY$LW~1K2b1p=(f Dwj.te|Gc<hKF& 7w ^1IWJ^c&TrEFTDY8hXfOhvH /,Ah&M/e-JECI# sy::!g!6m}] /1ucm*,2f*Y(l ro,ZVC$N#&Z4t[gZ("pq'ocs;m8yfexX\zb-Qvo?&Z^|)4uGeDwF q>MMOC?U/{wtWze!tMRw4H fWdO"Q}*|rp|5`j+mf'GQ3iJ9urG]t!3nU[2\[ =yrL]i/.MJLp 0[nr@80dO6v22/O-zP[VZxBtXma(LW; +T-`$OFmBOFx -Tch.'Tl\+g[l=c{Z)B? 5z&7f3q#ziL"Cjy$"Dw%P4 27 5;Y 8#$nqYiU_%(r|'54lltSYSh?=~/NSSsL3nD<P8'L1@A ?X !Ug#D'h{l=Un^,{g\2bd/1&N4hz fyG?D@WXh,4e@r2SX=A( )O7u7ik{ }R It Oa:M4[U!]4[-?>J#G\~X #r~4z* MK 58Ep(96F)DOE,6_&U/L-2U?W?U_T*A8 {,d+Ru^n479hz_^t&eO<CgBltV~ &}G^'tVD=)?Q]  H sA?37aJWV  # [NQB'cH$V"+8)KYe5 S^j;n l"31@n u42B=;4<K;6;)1o}m/q~(874LIZ}R.!LG*#HE-.rE"p&<-67_5);t1B.|z -A8}N4* :}2 ?'N|[qnKxbVR0Zwp>KR}<Neq^&rwgV/2| ,3TO2-,K.4V$R (lqh:QaE,*:Zbzp 1E$ZAq.@ 1&X.N%m*#u@$vMi}x^h-o"1T  A|!cYy6X{!!43tV'E,w!@Vp#g@`(o](I.a!1iGj ae=gYsZQNdDa_a /fep2OY~3R2Ljl#qQa.$3hT zL21~*#yG*[\T<XUr8E@>OvW_FT@ES*4+,KO=|+C<(QKVdTXn/}{t|eZj '9@W $JQDF)XKd-+k1%!@h5{Zflfjoy ,S=4Za|n"#>9k6 Io_I!SR)@SR.EA*Q^]o~9.W:*$4Ox06/e$NUy:c`g NJ&l,u1QZTInSpf\U|`utehMd0Gi|p&UUIiYlcx&/M"70o5":*_>jE3_K)b 2&r]p3 vtu*#O L3%F$=m-1D.x@qB2 O<bSIe)vu_7+6|;z~gO&g8W @_x=;b|"QH sKE bD*m7#'="-&! 2sSt|lT0KH:t's3vl%]kVyZm8d!GEg^uT]OV36?P'}0,l^R)%o ^B1C I%KqeQONvB 2 w`1f$KR%*eT;$7:||z;DoC+~ kMs>=Z,Ns:F7HE\ghi:5=yp\t \BJ>0KM'^k/Z~ bN dTg,v'l`EZR%6=Yj~ tysiy-!2Mu32LS8I%yO!w(w@fifD.X"Ja 3g`)ZnKbWj!47Z(93TTdlZPK )h? EzW`B8K3?xB3U3A4IxFokhD_%VdxkcD7H3Q,}zT-K1JH8@Hly RSSD%}|qGO^2Q)D''ii}#-Qry}ND`Q SYKor/ *Y #!~=E~=mv` H:uC/'<-!?`H'E< M6 IN;SFsgE]7|v9?m&P,R;Hr:T~9{X\42p_yLlUn:U6[oXsdWWeE&StJ7Ib< Q\U  :m)='WUB8 #++#eSX|bV:Lw@5e@U  GoeR?Q&!'_NwXD\R1H9G!^93JM+}IpqDb@k:XTTB|} bjf%bqJA5F3[s[!%<3&UN.,B.z`/^<\5VQ(`uj#2=;It"g1a@4@T7/(F-.zn|nTQh?i]h+?r]d/e?J"L8Cn &bFo_*>ZZfsG`6YC6,~xbV w[$?0#A?E\+dW6_Wb.u \[/ g `M!qPL7"Be{;]1E-38Y !H%MJ9F@]$"u@Muc-6a' ^-pJKl, TV[x)5~j-> 4o{[ =q T2`ro$J&[fGLq|oE@\Ub 4Ax.TV Y T9MhegM3 :  yU}30J]1}dY~ %GM>4(Nb)r vtk EPJ@)>BJFnH_:_b(gW{Q-,%Z#Fi(Gh[v.S;X ?VW|FAs`~exn)IGm!4emR\.j.HNXxd8>k_'n5AuF:nfEJ+GtPtpapg>].?HN 9FJDxO$CB1u */?V3e#7OAn4b3I+8U`!YCpi&)\^NjPo!=5XF 7  {9d<gc-K]X)C;4~nX TbP}7Fm4X?6( RpY)[&\}d+GL<$Ua{yLBqD3L3 _i0 ;UcZH{x+1Z_P7i_zI7L. E2}4bs{`+U#.YE*977jXjz6.ifa[<s;&T$]x%SSUETHAiFz6^=?3z,>R= u*k?W"T1B4Ef$4$"CEu$ kwiRB{3 e|sTYsB> li{2<\_%SbjFDnFH2Z]O?&~Um3>M' lU$VN`am |>A,u< r)sgzQpb:&g-,#J,+/2*&W"- !^5O*fF!k!E fyc>3#<k0*t\V5KuemRU<O!UJZul_u5WC4@Qb j Vn`/ D$O/E@UE'`FzE.8AAK-x4`5/5o +ipwmtyUXJD[C){wGr6pkd?a(S"yn;(Fwe bEA|!H T?z"2@L$O!nG q8&LSFgkmD `7&_)sA&>fLQCktkr[oy{+]65r[kC/jQ]l&Lp)tzyN%*&fMIIzAmOWzCnh|by_rVm@j}]V >)P#<lADP: g|k,lVx[2"_ xk?6++G]?iXE=pCh pYd8\X?%i,M= {-(CVLR8&:,)]aX#LU>myg~$t|o#M4d<^4E~q^%XfG rG>F nK,^!x%B6`u!j8rj-GEfbBQz8n|(JW?/hB&Xbav#wuxw}$f*K@\eugp/U9pW 1-itcPcwJ/#)30/1@U*~/N0FZ-._;ewegX&/ 0fRV1~9P'+%Ecv`1C$ 5Vj1tOZx!P+o^;Xp TQ\J3[ @C%[f[~-u:XDD2e FvGGfzKKyj7XIKr<[oV}d5_'0 fOq]:]6odp/5@!2TtJ^\.G: -.}ME<&3Lz:GhWb+d1d!i-dnvaYi'0a$]F F*ZvH!n2'mJ<Tx:Z2h&vdc%Z66#-*=5"<"9n5M*ryK~7r@v8,Ctu];o~qX~k{N.2 ' EDr wj Dq,>B|-|*qz[#? +46!uYsW]VW\xw%n( wn c`r^vzCok 1I>9^x !#*CY"1^l.m^x0||hd&It8bPtv&o 'qR 8c'raW~]w`q g"^$CNCU0.!a8.Xx{sck(3V\qj$# ,@1.&b{'#"$Gr-Q<J'@;?M^)(6/P.m{P   8 6/KUJ Q|+m[UYJK Xi SOr/"1?0"==8?9+B23+)$/=<2+*6%K 7%5cALMT])W;R4TES=="<,/ +67y>"-$egMrD wPj*wcC\.~!e feoyR` DO&_"6ynPclBm[U'd/E5o/Dc1X)*9E:<$kz40 X"z1@' /VTAXiZQU+< {XypO_av`j[VAU08-l1n&*#v->/!og+O"rK,s'&%K^7C,9#!u H~bV[VAQI*:<42BHK :,,4mqNv_pFs+ waoPaQNV1f`$ U`T-uWM(@E$&qEgz$NM"?3?7{3m?8@@Ce@?AD>~-hDek0(-[96;/W9 <(4'54. LM[R^\Wt0nifoeJ bwi2/9Z:/Kz *-Q\2y<B`DI*f6Q3*xloD~5HA`s*L8:+FbX=Y8>_G, 9 d1R[ 1bw'&Gl9+~jq&iUZ[ C\gIup~IS@]%&!VSpLD!M;@DFD?^Z^avp n8~teQ|ir"L_!zO{p_|}):9jaSyV 30{w9+Y356~`p$TTBpK>>@-O 5 yI}E{bB1OT"=-6S%r>{s8CB5F4(U]WwHP BoQ CWiHr|d%Xs>dM eRYWk}H=G@C:JRO`eww y Qh(QH8EGX:%;3M8QD/neAe(!4oU!1Q\Xz%1o;Ni^4_U 5"w^R,y6FT5D/OrQ 9 ]z6|d7V%OSxl1tT3`l| @3 cmV?Y aWJ0sg^LXe0Q+lN5(]_"c+<jO3LJ`?BKN|I(GNVVR>K30 ['&f1q"'  r 67 PM- ># %k}|{Pm=2 ; Yd[)(alN7?1{8dwq X&YAsLvX:*;DVYSra,J :1r7WQeUthw"VtD6]z;[IQv4U70`hh=@Y+_g^acDsdyctG>zcHz<5I[ %# /'` "K)a+(B~W}_ !\)Fzu'D$*x^+#4Ksc goTY XPrJst&D3f?*c>53N/- AwN9y, DE+o:Y?&ee:2 Ox7%QD  x&&G:tk?Q`.syf?>WA*5 L3y "yW"Yc>Dva]bb rkm1y<'rxkjkJej-W\\kR@uHT$x+ V  6^|H"i :a(}J296bE>hTp1r D rj@5aU-3Mc("<eL<m ;)?ky.r%/5*{l?|Ux~~krVxCtlrjtLc[4?Un=$KJxCva c*dRtl\<0)! * !K Ih v'Lj}j3" 6;{|F &qo<hwns /zr(C^P-xUWqdg$(e?#}}{Tb^|/^_RIowd-74v_ )t_I rZ-/zd07`r+1'+@5KMG6Vr.YB}skYC`?:tF`S6kw>txs~*|KuU+R{BiiN0n2}GH7}"'zt"tixjtx:M50PV7+=+ ##l@|IkSn9' 60^xu5^2`9>!!.Rh)9fA~9& Cj-b>uE $8S8b%&/eCa*@@mC;Q *,%3|=>V,HaB3Fi">5{qe4nf8bSV>>%*  1'Q?S{Z#9<?SzP@;GEEU0 7XWG`}u  M 4, '~X#U`HB3}jt[:ve*9EZpDBVWb>a ('dPpCT3C "VlW??(QpASya @u2m/U="nr279e]yh7dh)u=Cq5E3$"P t \t%|&3 4ha`QE@9VJ,W76 AlGJEE4(6Evz+ `^V|kx=d27k= 0qyr`nEO,('JLAYX BaSG1r+@#R <_].Mgx#k8ndfpX:{hrp]A\G Q4-,29E 0Wf JLCX$ogUpL1n"'&K*j7]s.kiU!s6t'-Y!)  _OCA)DM1iw!k #_tfs$@0620x1+== # G: JLQ%QnVUeWbV `{c]] B )UWahf]#4)eb8dd;[f AV:jrUwtlyX%s8!cH'v:* '3 SFD:iWWS?W.]Ra:XP$K;3 u%$5 E_CdtE% 8$/iU-j]H>6\k i]w`ZqiMQK*LAG'5,E W {dj)$,S;1)Q Cfhg/m]mb PD?(a#M2/8 kr& ?aDWNOdoSw,l<k>app(6C V EZN]9+>^[\9H_zZ:?]rBi#LH/W|?kIw,7 ORnZ}u3J+/<"<9b9VV 6\(A)N*R@Ht:PoW,5TSccoj!tgv8G{7cxf#D \dE\)kw6~}BGP*!f=daGFT'}[t')%rLlGHk D)?4;]QY*[CX/L8,2jujJY4=$,Ba&8C%B,b!W(QFAO{ad]Lvzw+/6:&@P$|7iFp 6"oX:(F -Q}kvdfv }^k$scQpP~s98oF(["M6QhO: 1 .5-=gUBu  ZZl0;+'2"*'647@"q7}P).jOak VmM< `/JWb2WC(Q&J:Ws_yHZc{b=}z@7N%~+RrFe]O4wjZ]g6Fo)x))% "I&2c5gs< Kt~vQ8 _ Rxf M4OQgFo"N]'/7`-G|M"U0<z3N~tWW)7h g,_`il`X'dOKo85asS,a$Bt )v. =]4IH@;BC>MDPdSqxipi^nxVP#YV}QaX_CnjvHo:yC?<g&4#7T )9t#kq v3XM{k>>oQDXuU9Ne}ge6W2swO^\p0r%j?Qgz8 ifx PB|DC"n:b iOO ,lR]&/ R<g;U |\Df'w.E4|aW3Y`L3|U:2~r}gkYh'f^5gEn0kRePi'Fz&`i<QQPBN50?/+781LQ A#D-QS5X(rIS7}tb'oB !G_ =F50B~A<:=b;:[ZSUFWs4@"2J*Dzj%3*L\bgct8W}}Y`{:o)|}b5=`Ej0[42S3ZYE8=L77![pw??+#~nm[ g afur\r\iXINV]0F-Q4R"@"J@@9B5HHDFP9w4XCC8nQd zZ?K2O| oTx^: B-X[l,?'rk F"* D)DGl*kaV ty\";-SuSrA iBqn< RCPkDO5I~!Aqzt#}Przznn?5:~- /HGZ r:u15Mu[[ v=i P`nm=p4-GnYs@^27i Dwqq`jWC-MQ@U+ ##'#20+2F@ 89M/QI'NR74G>|28}1h jr?rg8E5\m_W"zzYfFAY@XJvh!#=(F 2 ~rcc5fTN0xj T>zLgaej0jYl I@UF~[G8p + o_PuKfXRSJb9AGrmx=sqRu1^iU 2pe_Vn#Okjznk`XeB@RFLAuyn(~y 5+#rC?h43) (p# pO!R?a/\}m|a'Uv 32x;=9>/efg ecB|%4t#! dclsybmL8(}C<iS@.8^OkUO*<m 'Fu))H  =>2>>h\9wE$YeJjcROZJ!0[=()z&X7{6o9)JtFI>IAQ THM&{@mL?n`ksqaprQ`-O1W[7U"5}XsCqjmUd}iujbo;:\ /^/}zm|aYj\uxBeFO~xyssKzH hZ xd.[p)R5?Nu \^m5k Bh.-5&+ri)(jneUa]5.OJ@ }n ?vS0w~-%*YQ`l )<acDwuf_R6>E!HABZPk_duez5>ku>P&j "A 5!#E}gbyF B!A>1I T%G,Y80YIm]l7aX^l&Iy"V>!ef,m"TQmoaR'\{jxP-/mw]Qz[+=ED]vd,g+su q{q+GJW'nI]Ydd".:=3,7:$CjqOSuBvtqw s%m#oxxdgeSS_]Z`bU UT?JK?P3H,TC^,R+zr ys7i nP[/Wwj`r&]3PtjrXre]d+wogp3U@UmZ+a?.(  $-{h^.L<=2U&J&)S. )4k% :}A0o v2.6GI8@KF44=Ce_E'pYKy}\hZ 9;sh',?:;WVY(V/fQp`0=E\Yb ffhi_j[N^Cbeft2GG;%'E2rS >|42'O+@.*<KA9s(n w0gvxCMVN#`)hPL8u>bpEVC {k},A/QBu8M\dH[^Ami^3#e[SfH.<3w -[nL"slqUP39%t CT_{Wvj~=Y6@FA  D%de^hq6A8?76".>z*C5lV`niuQvw|nHsB?O/N~BG} 2m5CVckt & F6IwXgO!D~iJn3mcx}gfi\4xe Rel3~vkpk}fY!Bu _[)d(\46jBr<,Mf;$"U8=Y}/KS:7J ;]UGWv03$Q<_o[ZhW _ ! Tgh?j+}*  HDYbm3@GV?'h6'}Qn{?c{uPjj3>_ 15M||3%M#7 i4smn,"6,Jc%U1#:vNe]S7 R"mzeFYb2Okan-a:GbpN}?"a@];30b4h-rxaZ{{TM MyNC16 (XtOhnHR3F56[N7O'q  - 0$BP66[/I8|/9tn5r)t6dMT:_`vFiphJ Cd,t>_o0 ' "eM7&}L~Sx{|nooFyrw,n} $q=>#7> F ,jm_, 'BL5n>c[_A +-W!2Qc(8 p@Qs/]/?weAi"tbe`li;p3i-"Kw"}r?NHeCbJsz?M]w_\y<&62G)45_]g8u~2 P1 e;R5']tr]t:Yoay S2!FM<9)q$qURgx9o.^926%#q$\EQo4I<l]Y8Iu[%Qed%DF!w#UX*h%xcPlf$HjFe 2%<+0vilj-)n2Z5OlL&G(y6w}tv"^ _dR\Qu  oAa~nvgMD}] \6ed]r]`nZ'P\f[VqhMs-mG6 1Df#Fkv+@&#1o,jp7^DjY D#,Jv3cv\}OH36S[t1 uy}Q hkh{Jk{g66TWvjGLjep1,{)~*znyR|'&\tT5a>/=vT|CxnzEZXN~<E\!G*(^mB {u_? 7#F/mE>e(&>7EWfWB gt=wjwR5CU)6e!BDsa5mcU]_w}@{7b$8[9 A)>4<35&\{Yrb!Vq}~iBIF; 0Cv*0='Fp[pP5KX:|f +0SAVNs S&k~{` g<xDEhO96$EQ93 `;)4$#EYmRv _xeX2g7|(1*(78<9DMPPJMq'(q][B(x X`km\V84hm9S<!c@E% WRRvhivaQf:[6J?E983Ahy2PKwzkiQk] DYE 1<1Gq[^pfpL~j!Y_.y-, }_A7\Jva"}iVw-/EZ-m!_a:1MPPb"b,{&E6P!{Cay`RA<~c,jZ~vs]cUe[2{v~o?q/]&-  BD=QP]Ub}LqllN^~I8J<30?;_]X~mzrXt\ekcyoyxo~ek[AL{/LH)fn \sy:9IZF$ 5-(f7N=:!U 5"2~^Pspw69f 'mzoy7oIVKOKe)Z'+L< B6eXX zXA]>3m*XY:* _s@\KBPpdDaC$ 3+ )QEb*)P&J(Ls]-TM+i0_n7R lIWXEp,tDn99ea>"" 2G)?n>})P,C@BLn`e0yT\Bq[<)a6*O@an5h?")!8/<^G'XMY[E5QVz4m%Z p)^fePW~j_2p2wV If1#Rt E(1kNU7YpvKK4k]JdU([Kot\|kd4(357 ""I =2>bVRX_SX?G:;.=)#.KALrVyqvy1oSX%kxnuTb|Z"H X!u=#)"A+2.%(LxGQ.j]Vzyw?~GcyZ+s&'5 UBt102$aLT-B,rz]Z48WL` dq^Rx7r3n_md>]At<5!433XEAH/+#:#V LOJ8{C7JJW;TTZ\x}|U :.(A9R4/+1L;jV(`yyj93i0GX1 :&//N\iz}Y;%+)83>PQEmV`bNG S"%!$1!X= T\Les{M},iI\ M! v/ $"CN~ T4omfF 9@a/J"q( 7o[]|';m+f@U5"B`duC^v % GIZ&)nxrjG4GVYF_wjbAv*Y Bezj}vaQo9*x70*J*4/^+ #z0;+-972s/9g@6IT>o6(D w|aG NTYN^ojemYNOd('6 G7-0i?TB#r,r:jkm|}rlNQX8f}k47?D.CD-0cZ:.@C.!4D=C_>l"h6 !W@}5AMCDOeUaRDV\C~=iS|,qlNbE!9"+m *c)#7D*agtxm b_}bm?O ?- 6)%U!M?Eo4[*]k*VHY&. `XT=EDBFJMXMi!g9;?W LV)]1OH8P2,*+,p%.X@:^Qbk|X% JvP*4Ae(!6D#V/c4KxJ$vRCp^LE6}W{CK 9hs3!Dv}2/]: y!?"BS6>?I.{=SU;`yyZ?UKr0Otwu^n7gM{^E?R7C802m#"b*%m!<>6=AG*-.#  %'/F!GcK`I|?ZZ>D FV:6;&3.8!:cH$=2!'>\D*O)IhTX>]!O=@|I[bU9zW=(@!,(y!p& ^c# mW, '.F:K]J:Hzw'?G?(;=9faTXWO t YH)Z5 VKV1 %hF+O:!_zAlUvuUiMkhdgGZE= p' >$+B<:RF8D3) yxIb1A)517WKLRNFIRPV0^[Vk[`Lvc |(~EL'!\lLe-& RIZcSyVlJ7t*o9Ztt+]K +4G^a &/0,6>D7Q+z:2;W7|q=2R"NI[Rj/8/E7.T$?\jU8K[2H`<%>MM --C T vhT}fr*e &9,F,N [xdko NF"L;&56?&  4(73> MAeiZg~p\kwNSa`S]r1i Ypl]:S). ( 3+Cj1w8;C,:%/8010,4(*,&,()\&K.Z3.mLFQlc|#yag-slvnTjdpQ1*HjH)aUkgl]R)fWIdiy,!` V!1N6%~\[F'k!\?P# r{ 7)ZhVr [Txwc{Iv%; c  J0SP\UeXUlci6 zl8tl[`AV{\IK9$H]f/ |'&x+).u50{58436$+"*& /~)@S:lxtoyq~] k*xRXM(6A:?-J2@z>_PGIQf3Znqe5lSm-YR&HzJ@&pNk#Jt{"MK/# }#ovvjd}s_3.I#79RWR1W>FmdKO-5FL5AJ-4857<55_6;7-/>q8b;IFGGKJD_G[FUknAVyEf367%*,:GN\/m)z0wEz=MdMmQ`]>VFW%`%SEQ@:GIEe\o|;Eh75'o@p|\[aKNFO9!3/(0p^4muEqIfueE/}A&G W]b";b$H8h T(6~'K0XF:=4 u|*gLquQE l2zL*- `%LC5POcXYUXUgebooqUVY[MO9PS:KB`I?9DJCRWMMS<@:'1'"+{0*W3[6B53)4G6JfNIEH!*F8dk}HEGz%P.:-7OC/>LB'PNApnd~|Ba/E1. qx(6.<,5keEbM1.3^[QSiM) U+DAUPGTU^Vw8cC/si .W7>N=.B=*} +v3)'*21''!%KEHSMapi|\obE}WjIiG[JRHNN>I;KS$U1R[9^-\/[M];eM^GgYwnoZ}D}lkmGm2b:]aE0CII@;[C)N3""8qG.OBIb#zNR`z r!7\ 7d@>F}d"B VWui# S oa}) PT8TBe%sO(\J^d +4tMx] pVR  *O s!#"#|Gb:U:AHA:O:NJO8^[henv}uUfzrDFRGQE3-P6.39/<8+>A96NAMPLUXlWTRYYOBh2_ajpznDW6q06C7 9$5%0VLz>AE/MOGF<;AbTV+{ s/GNb#9 & FPq7$=qU./ @C/KL1-uU}\0 ,/< NnmWkt^4;^E!9Li7~(r6U'##&)y| M&#.Jc;fPfOhjV\yy{z}v|qco ZY_Z\ifpwN~td~Tn|IYpLFEFvJ#.32V#3##@ +**$/j&+'|!ic(>-"N?p ' xzf?A0lCp9=]tB+*n38<iD8B)H2dyqe O9.FF4dWvM _OD87[83-,O^$b*=A1n ,l%)E@S@>SIQpJ9eg.eognnonoq ijg _ca_bfbacaYXwd}YRdx2k|lqdy=,+:// 0C`'**93GCF\Sc7aPY2 3Nmft"7]p&. # iA?_>)   P_Eu:nWyj/mt1j"^2 %ur2BAIhKuU~rd~f}il[}h{|{zv{infU^IEC65932:6$ 'A [ wn# ZC>&,%!68lrNev5+lOl^t+MQo9-Yq;]Q<rl Y3sd8m1)H_ ~:T{@s (U\hJD +H9":XL[`  8OjGg#= "5 ;>9RNMHGKENMTT Y_[qpvz|ozhvunuyp~uFu~EVC-a^^wY,>gV IgfbgbiFOr1@3A.(o*ng   P|2<-d;PZAW^#%= %ZP;(&L9QOUdWmmmyruyoinOUD'6 i>F+0+  ]6&FeM#aI]P&D\`diI{4t23(If=uQbMky<>\= !S|&$LbO c<D!J (^b'%~glc!0[S^12N(T'7-,@P{/; H + g,,5/141=6'DbX*PngYjYlwXuzuzj~{e}ZP7~~_l\9O)):+ '$x QT&j|V^k UnFp" p^mUqv<<{%' y{)'Pn:&-. 54rF.:$"&DE Dtb1}%s0r[Hr[zcl[l^maVN|Q^Ih7=V!!r hy`+k y=<|)_T) U"Ue9F\HcqvQtL4Wd5f_C={u`*Zh)cE(5g1G}Nrx? n;FPU}Hd 3z2;Q:b%57*#&60 ~!]jUI.Izo]B )1.3586?GBZY\okdqbPa8=:@\=(x4#!( G_8P&qh0^*"~pQ |%  m -;5 @U&e'oan'a:f TU8   O HZX`,_3A"|NtKNe! {%e/ .R#5- Xqy7pBg }t48O>jbP>B `x.a gkZw1,lVAR;8(-0eB}S!wwIY9| Uw,4A5^*q.;s3/CBAg%, ~a[5s!i"n#.v*-40,3..l1S3R4.4-#+e!w#W& H!B4N7-nC[:Zbq "{4+b @&{r &  2 )N' 0 .E>^u>pLOF#kkB/:4&QBes HBs &~+bAcQAxr~=tpf 9a7 k lAP' )6T/(<&'m 9[62l| 0ztIKFo[taDj}dOxIW0!SJM~O/> rEJwGb+@"v_=vGMk?{$_5Q>*,?-;!u78Grzmv-u^naS   +E"S% LKB^=Eo+(( [vW{ux\)   %r ` )z0 ;?DI}l/7a2*[Gxe/h\R@?!]8s5d(;~}!2-T8qf S \F)[UCq500K7HS#h.8,[ * 44[suKh9@api2"5,!nF t]w}bU&Fno(>]Ymeu*Z%Sl>%VA#9WMRdH.N0n0U u&~3b&Pq^X(Jh(>%*^)dNGf=G4{8>-$'G}y>(8Q9qXYkrz   HBfl2'}T6}~T]voFD; :h3 n ;(%'[E @HXjvvdu|{j [] ]U k @ 3j=Q7}CoJd'3, P-$ >4EuN2c|7\9E cyjF{Ykt2) fi!g?\K{0 ]V{v\1  sd"[FpE;A62;*+$ s"I QzJ7rbsrFqMVcUdot&,HTEcC BU$>p`RY>;\fi!K~_1jsw %.!+HA3H#xezkovr_""FI\!uqi(/b7r !~M(g2~"a$J0M y^I(  " 9i(-"v*5)s#  s &p/T#% 4=^+3K - _xXjNZ/?y@^nP=*r$7 1 T:NXiO E,]naCm`W @1er  `'(.,n#=>  %b( %    Z2YrCU@AuErl^n[JF/ 5: *)S]i<G(  Q WcRz I\-5G~ofjkHzs~LhzgF;_.#wj`}^b[54l:v   .to9Fh*)<)$/z_{ph&I &4"f%6D&DHEJUXHbYFi=TR7b> I`6V,hNSNW UQJKFLAOG~AeC=]/0]=66D=$&*&U$" UrXo3F|Pzf! B \I>AT[74.b-K ^ 27% "={r4P]PmXB;(C <+R%wwc'ay?[H- !v1/,:8i0(\7BB1RVJ4OkR2G#DLEHE0D4^=x3(5A::[DORGEC( } k 7A ^  s; j ap0Dtaz-TPg~ek{ ZggZQ~`jh",Q_O  o ,cO=O  !n {v   1V %+$",* ,I;2X,FI4^3 TJ ,a A , 1049FDbKMJDEBqUXF6OohRzS WE--t/]N.J}|'9[ \J:2Fc j[V%&.&G%hL8YM('\7nQ/fI\lH_'!\ i?eQtojW+h5 seCD(@Dd <\y*K:9d)S*w5D2<,M}JHGXTXSX$b\8]7d!YEI1PDA?=?,0(v[ x q;q4FJ)O=DVXk  d nK --DanT{heHwiDTT4aEmymcw pZz"shB9A~`GH5 s.P% 6d j~"$ a _  ^$'0267;wA`ADLLhDb<>Et0H-\3c.J,f8N>Z7]6R=n'o$m&")*&a55?QINUH9U>5)-52*J#<} W_6j&nIw6^as4?HAonJ%7sGt*%AdChTOR<%7D`SxtoMtM?Vel*u7S]! ~ [ Xv\ :r fY f6/0-BC?>H&PuJV\VRRVIX2SCV1`#\]S8[VO=^E651'+& 0' % $#'$')!!vuvo G=Gc]?*-5lqJ{D6cp{`w)CyB}~jnvs~ PqR3UK%Fi&s)=g<;i(Iq ~vsB| u"%Ti(|9#HECVLPQPQPQ VR PWG LJ@I>D<-7 4]$y#I(|nJOXtu~gX6;J1jiVmT>1Y4[L4+bdi5KtRpXwutRJ:GwH|DLl?jzfIIYpwG w E/ U1o >J<';G9x5@HF/H;S/N\FWS/J[P3NHBR<>?15B;@{NH}NNMQMMRH?F111 ( ,#$( %F<#DCntq%/ >U4?Pgtt[lixrpwqs ?X~DW%1Y)C>!S3: (/hVH6 5m{ !s,@&/'3'C3@]Dfe_okw_faU\RTRNVFHG?B7RB25O"2 1  b j f,;.mv)_ p\ik gJ[e2>8nMDbJ33,J0rmP6N4B }}iP Dmn$md&'>:[~]Q[fX6 I(' %e0)8#C;s6@Q7Y7W<>>@7 91;)3=3LB?@VDoRXQPSTTUZ]YX]RROJN@H;)7 "jiB`1 "&fG<PE 6!hBn~}mhm~wmX&84(H Id;\1?IgJ8!  #'h"V3.4@6LGL\O__X[XMKHA>FoFBhH(Of7B>+:)n/R+pf!LWM#; GtCC|IS;Z[0rk+*Q 3K[jr)W1EKc3 F"33|e.c@l_"i<f. > juP!<`"0-(.,  !1)0C;DGJLGYWUx]YvSTTzRRKTG7E)((" BuR@Ut!'&3&7&!-"'@t1Axu|`}|s. - $PR=Z$!6`26)&'YP4 B-x~maZ-$) !'r%x3%g<47|F6HB>G?372' ,*~0X*&1$!#eByv(.!C FSP2+=Bt^8*" uE@&0g\/E5!vpI0Vpo=$}5oB!H\[ChA~Qm C i # E M a[ :"# :39/K>?I/E9DZH9=bBVCS7i2R6Y&\#Q'N%T#E 7*A9 X@Wq+dms_Q:81f k*6V.?-K2# 63 '\I#= z} .1$%{i/09_.?,5/%(#0J$1_rE7P3Wp`ks7;z`_r 10 C`LkvbapK YP(5L*..>(5**<!!   \Rq,y*XBJ4.007 xuI{AK@}EEZQNB)]aO 2&yCo@wGYrl.z2713aDCP{s/Cq%|G0blZ!EGRs %#(/,&v2-q(%(h'm'v)~||`LP'GDe y%<(+&* ):GU aY4a+]d;]>!!>- $  " I=5-;.H9y/i"^Ft%A+"^Z3a4SGE~bq u X HJ  y qa: 8. ##)1% B 1 C\<cj!zw]|f<8wN*vML8CipQ,F HW([ eEWonn@F6?U52]9bYX<t$86dg/<3yz 887uOp]2_8MP cF.70$6[,Z^k~SF/22$;HDeH,:406E,=T  ',1ct_N%!;3.\LejfNF Q,o8#s=-2t{^d'duG7i}Sq5TSsC5E:H'DI_%)@S}[F*vnEusdta-9LK<'[bUwc ~?8;=_/Em2m%'] S$AWqV7"<#I/]%u[ |ux |pyfd1urw}iu6(+@,,$ 4#(+u_y i ?!<-P72jP78. KA?#rV-+ $&NeN[9(  $5#P$1;/.'8G% rlvN`~z )9Vgkp_G@x|r[dkLpZ`{\jk J!1J6)|#l#o=.m.{C^Es:T3 D.CA3&K BSJ*F3E &4p)S8b h 1(A{k&z#b7) Ng:w3Slv!-R)I8h zWU >TWRGMQJX,?@ QgR=&ABv  !A<< !#_3 N E , % %L,tnN6/<(:{LX`VLHcd]_>3%5_0Q(6\osUrt&C5 >B:(/0  )+,=A>H>RC0nM&m  55 $C` ? 04&69F bf| h\SfN| 81DL{=:Jz<8d+ % O<svq  n%g#E81UJ*[p!=v&t0,]<qZKoH>*:;S&7MV$Q5X(/8 T^C=v<$#=J4+B`@'5Pk y T *@ uUgzIZf G# * NUs*T3'wUYU<ZAE+ :zbc6F:1#IHIA\(< GYiVB'p'gR[ S?OVOGOJ?#%T>^kX[#kAH%A*@ srq  , > @5 D9.3" :*W^^wtPi4  e!U4p $]b r1t{ 1(  zji8;Dq4-CI3`L`1tC ;{ 9Y`F"^Yk)_/wTuE;/$RsXG.hF .qFkF!f,!,$ OU] 2.i&S duXos2JMY> *1:3##>D.0G?#1 /; W = _{%$&$ $%A"$!$N%&"*85(w9+sH%m ]D#,P \!L$Atl%&*,4 %x5:2a/X>6<=G17;)RGOE-;:J5_ cc4syAi:s7/+,X#(,7"#7*>iO_vYB^*04K)%0%b\ G;;9;QT f{v   kt9d(). 2?t<ZU , %v]1 p`!%(3%7R=Eb4 ($c:0;};co;d$Q >J?Cf4?jv3HO^<K!>%oD,Hs.#. &s{ l".  ,l%^7m~"&< caE !$&y,l(h-];a4jA|MB?R6B9(A|ch{r .'$Z_ <)+() 3 , .& /#: $% "O4$E<B)4/&==[3eBWhY]lA9D?.(D"& #a!B1 +9j$k++'%&B :%Df^K_I$=gj  8[XJibPOE.$ 10_;jq`u5j34P lZx";"84>B+7$+*2%!@I95="*6e}!8}]Z8t*.z&lUVRY jD kJW! [K2C+(C{23D %8+?R">*57t* y)N5)?yKXJusGhD =XZG' Mo \ n!#+;$!,>%7>LGuHU=4GHTA/7a1;%w)-z# 8!-%JI;  ,,49YAM#)0-1 !7'8 +30@3oki1-3-* 3)+<..y5]3*+o>50? <"- ~ON/$,$CGM[Vqwv|zooVv,E2(-eKC yh%!%1"+XS-k<5HSGANKl40 -6-:9-k6/ Yr=lmsm9|KjcQeh"dK0 >]!of|TYf, f qmdvlwhcj[cVW[C^MK\BMF71 ryb )A'+#593A;K?w7o;91<@;DP F9@2Ap%`)] ~3 5 3( ?WEy! Ol4~0>u/ $} ze3>7%G_KI;@L=07 ( @NUm{us=c1&%\0P^ L&" ?E4 *!{V' W | hKw}~ObgbgqYlWN[>rK\rSTS\FL R}#yRxrCu@ti-wJ3.6S(mb7$N* RT_ [ { l }q/Joq*>)(6tQ %!'o0&T*a-iIt!s &de^/]1G ?vkVq}_3tqd8 UtG  Zk'"#)62KTQZ`JUM8E%9(6X2k}zSR}<E(j>$Md`:$?89 nmGDql{YCM1s+ 63 :LXTb:y (,:/KO&48}Mli)*'4m--P  |}q=  Q=( F\c=_J G^PN{|z\]@=)reG6?-9PB{~ yaoayair[} #&(y}xQisr  yppi|aLdn[=:TWSUMM@<<)&'$2?8i_s~_sD`YW60;sgbWxm0kYF~w~}($#m+$k9T# g- Q`IG|Eb<L"#)Nd!m(!:-P &BC(! (st>gCy XOHsn_8j lu*P& N /3 @ ((@-:%JHBbqe."$S)4#jq\HblU 4 "{ s~|S_$pkx~  ,wt%,(#y{rDXH>GKJV\cijy||zmokdc]NO246E-`tYTO{pN2y SdZ?h3UJv_V 6%(A2| {rD SAf?Y22d0 K1 E& :x +>S-!y:d ag  r$wh|=JbV8[sO F0?de}dCfQ[L3cB\Wnsw&}/~ Yf(i\ ].nq ^a k\X l  +tu& %} FF_xqt}~*GqsB`nDEJ|WyDv%&@ -C(V!d4~M`z'ru899^"3A*zy o W  # 3,G+,m_i V +eCMo[wSM~XR^aD`BUUEiNlqhD3<eASK4+~~|8/"8xicks{! xpslZZw K!-G}~1@UFM^/C1!}}e_bURoXwwT~vfHRm-e.[!T*"3RcoR29plfPk|pFIh_z8~ -xJ l N 8/P$&>(H w |qEgr}K=V {t=r |1yr,uem-hd{\h?wM>k}y$ITAXd%{jyLtS}#  I4#iC*8HMDme8M&DH~ &!9@5&<0)}sG `$:M ;&@w@i0 0C_Yl%h ub4Y L@@ru tZ!]v2Yk5 ng ]BjM.m \ Z,.m,d? ZF\}Txl!_ &9Va{vh{C6s+yivr.lgZ}U}g]/R  (":W; {x   # OycCrE3!$ $hEd}[H_4*E{nX&6 @s'N 'qR_wR' NXXraJR5KS p4ca|w~^DU\kW L^A3 5T>@r ".\`~#!"0",##', /1&(8%7;+.3VpR)(0X0L^:W{Le&b<cIj+ZzT&/LyCrSl Qj_n>eX WpU*~gugE #w :`p )' !Yv %fJj     Xy/ 2'&NCXldE3;<5h,~s{xuU=9 KTMt.pTP5:  + )X!9flSPP8#5C-RpUR}*?A )7$L }c  l"60 6]JnnV+4 $oZs v n8(\?D2 Ihm`I\KuU"\iUhhZkv _6v+CH/tBJH=s]+y#,J-G10{k(+(VuS 1    I fNYJ y {ylhcM?8EWrhSU9$T5ExOzbaw/, 1:Hw7sdG%  O>L\PAIA-D[>\uJ<m #:3M\g b h!c - *T_p)~qH\"?"n_o  y9VE% aJ vz=MAPnc-Q  z 4k>tsKD>3Vw1$- {5wX@AO&1b(eqqQCQvP]4D-Id7$'''--'N$   Od. *      !QHa ]x{7c[Z  cvC5E>:aoawDsZE3&79C- #I DVFAKOKTc`bwO0Y .-PbV`~, J7( .=O^ o_9A A ", 9YDz]W3    B(~}V-'!ytQk_'1F}tdMbF 0jqD[`)uM'NNe%p*m s9VqRp"vW ~%Q3hm?z"Z4 (2J{}($G-m#N'20 )+B*TQYq] w {||_lVKO0:-*.7&3R>wz  ofz`_w?Yxisg|TH38>  A AJ8BFH>RlZ]oU?Z"/>0KJgR:a   GA:`BO ."! met_|)*! (]C     V 0%\8Ylyg:) ?`?ifx\_~%`vP5($9 ezyd6e"ui[D{EP5$+tOy X.Q+j u8 2dl%I/ !'j*E **^>NC>   ;*@jF|}xDR 9  * $ERMRX 731 (Sv^}snlaOlvtQoC4KM9AI'H9+D9:<GVX_mYEY:=@;L:\D!U960OR)>4 $";_K IU; 7%rk~acF) |yr omh r ctj6&, tTdZ?b}}iX:fwXPZU(Mg k.MH:q8DTPRRp@FzNTqH JOIWVT Am\dXQ 74>E4vXxOa|d}L:4Kuff J " '&&   .SXkz}S` _ QXWM_PiPctzic!mb"`W0d*H1$I:HRi\Of7CE4K:,( 28'9 D+&5hKmy[NS+# D=Kzjto]^9( I;ty {u q gb dW ` YOa@YH+T  o}"!>t,I;&q|qUjX.! XINS$naaltkC} {5 3jt_Fd] zqAS+'[|A{ r7=n9M. J57 4 csET J8_~=F,  T ww"''%%_'v!` 1$W' !+%IIVjr   ztRM;"9_cwuARH! >9 +G9 2%7A. &*,[7*0w<-r=cWUhXLrlq8t>Y<9%5<'2)  J!E+8 (  $CA2K? C2>xTsua`H*+ }pp`^\W_Yfih}q d-|{Qu gQbSK\9/} n{2 JKJTcUWb a3,{ ;n>oOO=G}S{s1Eo*l GlPt[pt}T^%Q%= l EB2 V"yRR+>9?xJe0Co2%2C#11<'W(L&fv      ~Q$^`Pd[ 9G*!,-:^]^ n`LY(6$?A&zp [  A eJ8+.*,07+=D-<NbSuuMrasAWX<T.hFv4 m/}{\D<&, 83 * ( =6H''!e>fpQLP 8 (Q[ d~   (*'u)`,c$0)%|XdR-L !`bT'{CO U EO$%;->4,:KA&ZPq|}p$Q 8lEvLJEb%?,$HR.u^IP}J3bD79}]bnhx_go%:r| >)! 6XJN&rn##0m[t npKg. @3= !  # 6;>Q^^wr o}s cfO13)@T@I[ 6 *"% '+%4<'grglqE4 @. L!3$:%u+]+^3~.iDLUn=UfUt^dfruCZeuV{R)]6MZ6K\57Ncl\!YY j"Q.h feST;,2+-,,00  1P*/%3"A;9dQ:B% 7:>OG>Q/9 , "# ( y w|l  1 VXTzeZ-M@>?oa{"P+!EI16]6BF3+P6E5I7\IHX3YBmqjaWe&LX+}gQixjdD)]92 U(M5+hhAfwJmYB:"L#3 vn}DA9cQ55n&{lgvihh[hNAWebIOZ\ "'^c aSyG CttkeZGrXS[?EOBPg\      y ~ } n   % "#8 3*8T$<!"B  B&%T'&$4(,"1;*&X Q>HndkeySb`qXqoZuvoo@hGH5d4T:,1T(5408?3E/K.;#I H,&3[)2!OMx|WMP4,2!)F%2 8F"B- 9*I*BQ3(9 -( 1SCUYWZ(V[$Z%[\$WaVY aOg^ZfUQU<.;  ?/5dcyI:>.8Y>JRHZ8G2p2i141:9-INI TQ>)@ G]+U1LM~6p6>`i6@W*B &J  6FO$?!5TW[ X1=dsZAo6<jS{*%m`XsPJP|=--eXSLE&YFW*1,!9YjOm o3 B$vmoqAujjgRZ#NIKFCKQPYi"_sskh{oXx2H0  $!#+).'  #"/$B@ ?_P8J9B`PBRKHX;V7j]aVq~DKRS3>1J:93G-h,S'Z,uZB,e54G&I3S,4= G$1 j'7LT=00$!!L6 6>) "%,$  P.mlayjY\> ( (*&+-%  $f9oX|N B /- 9/.IU.XB;<0 /-!#925CA39H3;R46N =49sqq #@vD&;b='?@ N$4kR4 xETD^y=3$?+T3Y94kPd]shIwAF8'OJ<ePMU--^:#8! 7*1+|t"+ %mOPhG*FfdnZfw]r t EIVvnum hK D1#% 19 2, .6!<8PS-B"/11? =;>ADILXR*QhD2F'L9!6B>27K,z"XE`O+IH)3K m[`y^F i<N L6Z!-2,&CB.77!(%&, #!+ " #'3%=/ 7 6 6'?+    zu}  ( :0F K ISV QY YVa[jf_sQcS4R   N - 0FX*D8,,(*?0"J%l"=$%P=/>Y=HQ0$?* T!WN f )!6?)('e"!#?S4P7F;fUh8zFZDI6nZh)_$l?eLAC9,AG7<O&+7 ?D+N>'4B;-3!*'Ohad|  er[2 ,) >?;T@*<i/k!Y"i]D?7%'5 2Jng} j%*5!P!P*+?'A"G#B MUCMO<>E:0-!  < *)C=#4R;Odad@d<&E%F8(Q&!$')$" ,%A/>E=>,G;A%A?M0F63  < 8 @ nU mmXTN/,  ,SAOe~ N W2 -(7E1O@8@.&7Z cV.{Ui@)$)B/A-F.1))S/P=&U =>;0 XAN T%;2:Xy5h"C*}s1>. 1B9 1P^:yFUEG`cV9X\#=S6%918-3A2+H$. ;1.8[1Q8Q,i ]pP2Q vzi hlXnbou #526H<?73r+r+[{q#  K&f!c$q96S.\4W9)5(4>:*4+.K.>(/ ")111-TF*Ltg!TcU5C>:68='!*'#:;'/S2MRDG N>-@4?*:??@%3.$     H ; W `_hhfmf]dOOI79'* bvkX{ l-0%C]<V=CHx2bB0/G-; ,&ql+!'*}  ;> ("wTH?.N_D 7G%6si&+z=HZr(;'@%&F0*!=>L,0=57NSH@H No/w-]0)~+8'S8/-8)"$  3!>W}w~uw~n }qnuae\G@9  %(,51800.0 )&& * ' #+'%!$!-)"$"N96 E: <h\EVP3<<12="8!.<%+  ))+* ECF9A@7:2+3 EH]u ppZ >F" /ICIj[euZOj#,&07%&7*#4 Ua Kcb('6  ynsnb# l^8Xf"7T E<4f4; Gf*. /jEU|aF>+q95.6B! 51^*5@7&X 35*AH6c0p9#30$@ pzw_ (A)|  $#$)).9/Y-U0U1x)E,_LD + ! $%! %-(&0& #3 4."       768V+T%LQ'N>9C92>8-% (2<H1+6?.)433.,*# $";GEMUFQM?M5G;&F"    H2cr] iw&@! $$.H$0$D(;$0",:)*B B-<X`!<8@b(9d<6%!D\1/m<,yN'GF_p=:L,.av ;% % C&a1/(+F9'w7-ZC;$=0'#'+3b"g18^#Gl"     Q vkl~$ !& #)(&q&q#pYfi cuydy;/1" <"*/3, )76)*%  ' '  1,#": -%;8!  !94;VKJJD94<2(12 % %?*1( 05//'94c,U$m,ypvhxiJa5;3. & /7KIaxm *  {0RM0T-p'-&5x0c(*< %='#bn"Y h r8L-4!?C5JI::J!IM?Nmd;_ylNZN+f<O[AyJ<r?O)NfM\X[7 D2U&$Wk> %6S^-^3sTT /??fDF)u?3*:. %,U) $x4$$08(3*!(kUs  }t-d  # " %+?9HG?B>!,    ?. 5//(()B730>$4U"/0]$m%N"L^ !  5+   ( 1 B%F88-O'[>(<:'&(%&2&%: 5++)&23J1)1 ',$  $"FMQcaX_M:B"( "  />MZcuxcn]AV&     88C$#2 &&,}8a+#6%'M<!Z1l6MG,M.1*0A7!O[CQ6_TGvFBS^kKwr)j!}sfcead_7}/zT| brdw1UzDc!,m<`rYJv(6I-`S9bnG2=N/#0k!EIJ '+.4"7D2)1;m(@ @ $ "#$t e(G F3\j "&60"0z|^qOPG. 2   ydmn2]$*<3+@/35/:"'! *-, R F,8 1 3+, A;! ')26$=34;3Q 132+:2,K  ?A; X,Y37D.>$$3%) ##@04GZHBY%27+9 8 Jca\rY#<W <G[w   vskga[[RJK H?QDL^H#fa\#ohTi M +J  &#F& @S"/& 5,)B@,G8)s>@S;3P3D;BUDghgt kjWaQiUsfZkIw61L`PjcWdR/^&)kU!TKa)2T6nhOjZKX*;N~gu;msL?@6 2$h+5!:<J  F.'-M #]  4J3 "$1#X$ #  IfO7~#KBNfy  lUa <99 " %?-@ c%Lk#p*a&d+c3;->7(:7 @083% " $  760<E81V7G;E8aIOS91?F@G(- C?>4D-CHBDO*&;9*)+,,/$&'#:!-:@_:O.J6`1I"(&>$$+",1/$MPFZ^+@& )57-E7!@  !      '5]>(%3/&7$%v3*R:,")AR5=*!7=&Q97M&DW0K5PR8:9[D[;R@\NXCU@IP[+i S#xn||[hm=p<jYp:T<lZ,48>J>r =xP)`*9>FR7Rk N4Om5@N;U/`fnC!:E<C?'-\ W=   485 VK .G#   ;+<q R   p?C6  B,aa^| s \k"T!1*H)$15+5#9";A2:3E0c%V$V'`R:*<$4(9 &>E16F+-"0AD=J136G8F(,%:[:A=?;f@NMD,B:9>*($8583;+796>D$!&.&C0+5% 82#,84:,#)%0#,5*#: '  *+4NGVYRTUAB5#   5!bUj   - # ! /  {;*G*:'7$+-%0=/4"F 4gq$iQl."&2; ' M6(TYVbLYZiVdVWLOWJa=N4qJs'_Ciov@KUWt<GO2MLI*PP(7-T)%3T%6[&K#&7Y(+;V _nEd3OIF@<CR#8-"B:SD[;3(5 { ),     %G%`VUu _i k \W [ C D: *2  59Hdaq sdBK3)2/,W(S,f-6;-0._!i]-?8%<U@ T `F 9H*!FK"J*^6_=4.>@7;(A+4&6*846&= ,!0 &76.9+7EA=H"-%2#5   10*"5#5 #  ,./$ 0**14*%8,4-@(#!9"gakw{IN7    +;79"F0#8%1&)#!   +6d*@#%&( ,ERC$"$!6-$e ln2 ?g93gNEWJHOUKa _T[&[<!G,:!;*3/$6J + <vP|H%  H3 5$ 4 [wDJNo&potkZR^4_[ J.^> ( -8A'4" 8  1KFNwlryuss\fV9 K 1( 6 2)R<R{ U  '*%%(ivNRK-6+ -0 $OAMrZx"}'o(v0w8O(V3G/#0%!)2,Y X`zk{gCSF4HZJhq]!`-n7=(E7K21 :2Q,U/K.V1d=6!@.E1 " /+ .>>+1+!3$)     . &#070DF+57*&'C3//K0v=X0<7x/E}V*V'F"'  *U F]y   %)'(z)y!tw !ssm EPR A ZpY$-S%VQ3)/-?0(3O75gAC===)-(uS$`K(=JS'LXeF;R[*2]6%P715A /2E6BJIBObV/D/[9OF""$-=-C lNpv 2J A!( ^Om oy uZt UadPa QRSJLCBB=;98312 !) 61:A75<#$311;4(1 (#$5%) 8F94A%$ '.L?DPB /5+*6 NRB U'`17@*G(2<"J%L"K"L09;  '!!  +# $  4,;UC2O&'8* +ND Op\ZaE7)21, ?8 )>. 46(.H79%[&_&\id`a\SOI ? 83'$#"&.*8 <<B B=??: 766 + %)   2 ^Y ]~ pVw+ " &-!6:)Y,\%52R+5,E1EWsqqPKUICuyc4 ~ &%5 6[Qb?7> %"N 6T;OQA3bZ"/9H 5C[C2G  } v}5*De      ciV.C      +*052 043"/ . /* #  "9<-76 ))+@?0 23 !-%+. $   6,.I4> "+0((+ !!)(.<=4@<0<442-1! "("0M:f ] gm     ay ZH   H L Tc_$S!    <; SDK.h?X<9# [Ai%@ /9 "y)~+:%55HBttr-qmq OmK 7 x _d a:R99E?F^Yr  + " ; p S   # " 1 -3867;334}1.&s(  OTA      66 +-&#'      "+'  '4$/&<5g4T,i7q3e&e1g*I(K';# !  2/<REoa t| $+! D B6" k   ! pREM6?5;2+ 6z|z n,z D)D JN>{%H+"  nHNoB=& 4jyhC    8) 7^Eyo{   p,%P.P2K2,665.5+7327-81I$J'L WWOYM?N()/"@4Mub fxX&@  $'&89*15 ,+!!  /DtZ  `kXJU!W&Lu#g%y $)(()&"     zKA@*,R0#rrh$ [mR 6   Q2S~co{n} zbEW f[n|Nk %$ /C/8 Y`P-H5"P:) 7      ez=C3)$%,11/c0P2/2-&+ cdV6?& !%%$,)   #(55/2 F<UrXpvNMA(+  ( ;A,5;   ' $36*12& .2,f_z   !!%$!+&!# [h(0}   s@  MQV!2H2JqYrx|2@% #!:84) *B&Uo_$">..,LXubP?S?a(S+)IA %~A_"{$GxP|2WCL6e]%4!}H`b#O>6Vc\$449E>PJS`Qlbbr`a \J =2 $ "#0*x0z2b.'.80$( $*L@r uz tLNE .%+5/GICNN*5" *%!D81630;9D&3 %/#0%&,?3;3.)280-+2,"#,7 Q:uo`rwhzYaI.   >-vl" :%CH>FI670# (  '('-~(\ 6Ii  hzQ 4IH ,k{nrlSeZ]_ ahE`5- ,#-$8 &t!Q7HD=QF:@#.1MHm @h=I,M|~-b9SwQ" K83~~AVCB3)!<yrs Q )PaHJ\"8'$'9;KO Va`hqrx$"$#$&$&&$" ~fr? A1  )*&<5!* / /"    #  +9B*-; & - )./3311/('$"5 >7p*R01/v3t0%91D6 ,46 NJFO @/8   ]u VQ^X[z  6 RUTpljqnekjeotm   D_D $ "gCq~egk*,$ A>?`TZec]cl`{$ -! 9YD]oR,17N7;*1C'=2 bDOAr4o0Pk6vnw> "E ! & )   taXE5.#   #;.=_Hwtw higPW[T^l bn r _ Z`!(   !'4-*4$$&'#* (-Q6UbA4C    -0 0. 71NFd|            x gr`Rcndxh+N  &(3IV9%@R60 r}'4 t '*Q `qd]f\QPfev i9CwUz.p M>3EA&-S6gnt?a~]J_&(#   5 o[iuj3] ZyNWQ6M +1, " "9 .J oZ 90,. ~{  T cI .'#   '!).%#.  &("&41 ) TDNq$4"&*  +   nZj bF`9F?#<*  ?-JsX  $ 2 $; =   p{}fuZ # ? & 5!<&1h8W'Q0"}6he"q]PQ[a[`2L9Qjm^p`apG*o;<* 6kwhs|k k:TmQe{(F7!B& xViF'! S']R%T%t#\(y#v's#vqo^OH #m{hZmthB>,@!Hk3+;00AJ <5(D'& 6')&$$1 )&/ -181765@#2* '$8[A! 04<ZV`lgdo^"df["a `_bdc ecc fe eg ecd S`K5L  Ag@*LT? |   R@YJ 9"g,s LJ'a %J= ^HV/,;WVMc@l42.9)A<8.jh?bdF]G ! sTBp F %=EJ'Q&4m ,6hL   %62CD@T;IB+ B#. * 2  TXK0 5/Go^XiP5 ]Vd &"z/qq%k t b>4!$#", #$ *+-(*1+,YG k$'%$ }RTE"'1S>~}wwoUXTBO\Hxkv *!#{Vp8<>-1&;M?p hqq1C+80A}ls >KE + 6WA {  ,%%%qk8V\=V|ry{+WE=?RS5W3tEb3 %/%m(+&#a|TV:[As*\X'S;*!!$" '#UZ2$BG<PN%.' > ),[7WUQ] Q _W]j]|v   gcg6=WHRq bck _R,]^'U0lz& %-%'}1p%f+I"^*(20$<A9nXv 3'*7"  .^)b$h8A0L3`5T/`0|3pn,eIb6/C=<up WlOJSKKe]r %#$1(/,*,&( ' !!  h~v_./C& 7     , 6\ lwi &!!-t \9XGurily@oAtG$VA`OEfgqGeAnsh/S,q/%'B=&7wbbt^EQ;9/S$|1/( 8\?c !}r}6h[Y w n]i(3! {enY\\NW\Zk{|D>CS?0@    *-1&')}$L!g..!211)5&7288/:78E=C_UDPZUbY^|nrs}FJC%2xwnr`3M#]3H;=0T(\(N #)()2-656BB1D 2#5    !"  }J_,)"! ,+) 703#,Z3G,i#*v6a[So[pA1`[Llrc  .('+(& $- #*)% 8)=`G[gOBO qg%w i5,,SZdn2z3>OWO|:Eti#}nUiQ\`F(iI~z5TO8]O2X_POoLIN=5E  1*Cz_  zn7` !*,07613,"#  p~@B3 3aHzu#pq   ! q|#p'!%/!)y(Y@X5[<<V<H'l3]*b!k(~ /uYp2lw[KnRVAgOpKaHKH^>%X3C'fh "$"+%+"8'#2*%?<@RKGXE15K&6":&, ''  #   (&(+) (*! (    Ri@9G84dLr  aih,*+((#/r',q9.,P8.55779/(Iy<lGtdMS\@(Y3{Q5jj-%C+NyBtOsSUWPjoZqLV ?LHms0N+Sw>SU[gZ]rSheGY>ry(<*53 )(#*-.*<3WD>KX=<vG!IiE9"+!./(((%!%6,%)J.R/P1b3b.L8c5P216M1("'2,/3)#$     'C:GR B9 B   $#vK`[0I/h3Bv>IGN\Wy\~_UzMRZGp?YD%CC=%QM%P/[ Z>Am?HBONDJN5#3$))$*),2.6 6 6:9@@EQPN^sM}=Vu#0+ &  dsNKH3:=-@O7 z_{     9= ;#_Z#L#Z#@*"%8303 ?9@%CF"GGF;[.YC[ omVkU,qC xC7BfLMn%i l=>x'wkop*icS\gB0VN]y;uVtr:UqCM?B@wM A9OcX?JE~a?q5C UC,$)9( +| 48/?C*21 ("#$&!2$*%.>8@G?;B484+5$&("&$"#"H;Vq    "&(-0,30(1$$+($4915?%)1&(;4?SH\XYeg~_hb_ji`tN~KtK@zNcWv@licfqh|xrm{fFT:085/:58?5KHAQT-D.#apO=$"(/)575:>> @DJLFSF7H$)( -GIS#of  $l G A D " 4 8\@|ww{gm#}r(85CEHON_ddqyYl\7T:3.4I&RFB@u r'vzpFd8{V3nvZpq`pv{I<UG5|~J^7_ypo/|gIi*&>,&a]\-pz]hS/@!' %!H395::+2%?%E/ 8 7+8)1.- ,>%8":%O'@'SK%R`Rtjv    p}dafZXh`lr  &++/61h,s5b)](f,e*c)r2z2m+j.y5\!X$m*l e!84;MIYY]f[eg`_dYT]k^sYtwiillyrrfvdHaPEU`V_gVKV=@B9C7_6i=^914EL3G1J-a(\0F ZD7O WZ ms/Rc2Z4S+n03v./2483<5*7!Q TB &  %/&+<,QDQpW} `oT ==nk$p-..4537>o@hBuIkMc;HA*:@0OlSyxnwVpr}qys&7PZOX*7&SAHsmZkovpjxs*v<"lTn%, O6<(T3\vJPZMEN-=+$ %%);9=B>z=Ak+i0s'afw   {}pQ[;81!     4(WRb{  $ }'w+}$q"m(~##+*&,*[[!Y!;BA,>#501H88 Q"Q(Vg[Rc8gZ[Vu^KGTSLmhi}{n~qhozwfxjMvc{O`IhRTV4VFP]RARABEA,G ?)A$C0=6=)LmHXJyYTAO<.   #'")*)(-0b,p3a0]-k-n%l$  vxv]g\Y Z VU QRM IO BFD=HHCT_Uz %5r/6V>M7W2?:<,b1J,k%2x" .7`yAk~hxlbeqt}rwyx|x^pgtgsLiVxi ywz~Uscxcv} pF7RG4LuCyYd1|4Q@aM=B.R$ !+('2-,=OHB[hQK[ ,$65:G@EL?6@)~%)${!%%""#R#O%R##y'}x` { .A+   %'##-(+0(!(z zxm#sk%h oo&j'y%! # i$k!wg~+",C7wJ}LoIEPTO<Q7R.K#F(I@<CA:]S\2wdLVvWm{ttnlV}^qNgRp?y(u:m}o!WiY;S4W>YYZLFjQ]Gv/=x&%% $  -&+ -61/@ 1> 86@68;2.7v*w j ET4+, # '-(@8F\J{j    #,%*>x4~=E~=8?/1,")i l%\H+_Ml8S_aW`YOLH>;Gn9tEqRM>]pYcOqVbZLWDu-QCK\]C;fodcUr{f]t+jj]nh~63C/&A%%:!QP J&cc~:lD~;(  !?G;DN$ ''|$y +*.=<$3#D&1]O2>^ 5 ;nRT^4 F L?TiX  (%7"r8o6f0W9Y2F/E4>,/$5,,+/!**9%3"@!S&J!e%e#k"}&t'#$}#!"eiY<F5"& 8?.c[ _ ru ~j!V;a-L@?EV@cDWC;B:-8" -w(}"yDt9oGrhtYGwYvBx*s"{.X0b3c5K:RXJPS_K;sHu4/qC[IpAThQkUsRWFMF097437=%/(  "#'"} |{6uy>v=v7{C}=~;>607$(#   tQYN":# ,+$ D=$J4f,W;zBz<y=B1|51u$^+pC@<"',)"1+P:B%O@XDG3>1G2)&(-C#6-N@z4_TRXqias_E]@~:lO{RN}ufvlN~<ZcnTN\e01ChqX0js9QNF0dm0W;{c1KIO..ID=JJ347.+/6+!,q|i $A%<B39ED0Kw2v<w8'5$+|&]w$)D*rr|x mqtc rp$m'z(,+/1/.0-'()"&&"! {#|%^:d4Y9ODY>a8WBa3j6Y4O+\4A+<*P4P4L1O{JQg`oqw|{qS_apDeS>MAQAR2P8\R"Sbik#+L;KWOEKRMP\XM{QM{C8GTCHE4DECWHI5S:[1L B*H.&/ *";85D>', %+ / 154C >G'SH%d&Z"c)x)c'-~)%.($)y|$)/).6'i0o-l!J-RXG!_w`+*5JIGWI5vE&X"S)\JI/Z+\1SCV>]NCTJQ>[(d6McV9V=>7+KFQ}O`evkjn~yYafp`k?>r )=+<eN\<|'n6eB!\}ok\Kgo[ellDLLHA>n@/+0.*29.Q&62ViM"E!W3"1-:41.,-)<+%:rUkzc[j4B(}yw]ceVj{h !" 2!&,J$2[)S&^${+g&%&$"#!   :/;D><C567012u-8=|3YQZwfuyx~y}el~zu~__`RY`_^ahjetxWbV,>F6D\P\Zbg`]^/]Gk2g0g2q0m8ei)aUZWTUYYJ0T8K-83B=,0"+# $ & 7YMguiitMR J.9&!#!" #%(.33 ==DLG[ X_m#d}  uqx!cezk##&129IJArRiDp-VCU(u"`.|+(::=JHLUPNZ@KH3DA<M]VebtpdY\r0U#Ob#cM]&MZB19XNx)kS6<L3RMGEPVnP{vyU]xdW`o^c|U zhmi!T^[Qe[YSCTf7y6f>C7^:F;7EQ9[-K;} v|  !w][P6 >, $- - *JERr2g'n/~4k.Z*i-B%@#C%0!6M&CZ$|.l!E9DcP{hsggrhxwqxzhpgR_XUZ]^jgtrn~xsuhVd:66r"&t)m$p%s*s+j)h'k/d1[/kYaO``pf|\bXGLJAAFA9<2'.   qLW80 0"") )!4)&I#>&M-j%W.+*/*+-&#) rf$D$//2;5.U7*/h)o5\;o2mGDISF9P P&LTPJVA=J8E?2aBS=j:pEkMoLsUj^_UyMYlQL_lduu:tL_@su#i \ s!WN c=p9I_EXwPbqFrHRk_]>w,Jst#QJ#ZwQIM}krplmt}yOeGeWjMdGesden`RcBFJ<E:@5#-         \ ] J /91Dq\  3%l/q5d,>(O,B 9 LXRsxy!}2*y4aMo?_WUZ\\`fZ_`jih[fPm]jC^@eLdDZA\mXZXk\\\dao|p|fzba_SYGFI>:KGHQLZQfo||glli_eVQRJCPMGON9@;&,+#)/%/0+)&'!      Uq)3$&(%"($!!.!$"@%m&Q%&%%(#($&("1AD7eR:U.P>Y7X2QXRQO[J~OqCrIGi<KEeB#> EKJLUM EQM-G2X$g.\3o h^w1H: :_"2"7VA\`^mVF4MJlBSPcACZ+OBG\g UIWuIk:dSdQt}srHxP^_<Zcd[`Ujmehl^XacYhi^]`LJH@A6;1 +apd !@!6!$D%b$=)+1.-89"B)8(ea!NgV7U' 2.  ,    #!bq!HEA(!+'* &)),7#0E5:<I2i+X2%$$ "$e)|Z9V3c9ZNYC~TtWX`acccfh^c^Q[TNSzT~PS`XYSf0z6pJ<E^R_o[vassV^a]Y^dQSWLLaV^tltx{gm~yzrzp^jVZXOZHHNJM[]\goYf_I[IIMIlK{EXHQEX=L@K9a9[3h,.x  &"%'$$(%)*'-2.9E=ORPUVROTWRY\YX}[YrYkYzXyXwTXULOV{NUqdZZd^NlAVMENYD%j"g-a|x<50Q>-dQfhowfn4l4b+cIVXVD\`gyO[oHf}6)RYE|s|u|yXbVtNw?WYFwtlxngrwhuwx}gdshKfrdh pgalZ\]W]6GPAG0c<0,00155:K@}>G!IFHH>;82.+(# %4*A;@PEYTWdYfchqi}/{04A@@G@:C033*0*+/,,=8>OIQVVX\X^\YaPZUCQEFMLNVU`lg}~wxbq{fzntii_qnMTYZedPUXWttq|oxaZaWNthmd{IC~Fw14n<q9v:oDuC~-5( o3)`KiSgNRZf\QEXK[AO*[3V\YT]> F> #1  "3$T$B)(),)**('&$%#!$&'*/1-y4~0y)]1j'e*Y,n'|/j42=HATWRU S MGLMDSQQYV_]aiejkhebgbfpicmYDS)&4'$K>GikYrqozqkC)=KzCy3eXanqY`}Sqwlp|sb~xugfStEfY|PwFhim\bkAK;-zh rUYFR[ZXvV[^ZVW\XZXe~b1Y/`4X IPC B!@7:?W5I9L>t=^CoFuMkLkKsSdFhLiGa=jEn0l6x3$~-$%&!#& "$"/ *03114c-e1^08)J-D/:,N/]2V2B=DUNW][\```ade^c^T\NNPKNVS^kc|wnvuychjggnn^flbfrabfGIcS_ol~ovrymxu}veM`_kIe]6}  ;<;JHFHNLKieuwNMO{1:hCw<sIS[oSKcTeWcChThIcPbOdHdUd?rGnDt0:x1+11+93~4=4~;97;6=<;B A=%C"=(5E?53@4I87518:?>FOKUlYrU^UY6ONQMJR8N*NGR~McPLQWQb^bmiew_rTrggwar|jUcH=M}Lh?vn\hSofh~]~lxpFUjzei0LE/EqDxyemjVwxv~tk~LVS 5/]AdpZ[i=7F91{^Pizsvsnbjtdomb\XXKP\RX\[Z\fffoqemg}YdRS]XRU I:UII"NLK*O+R-N5L3N3@:E6A167<#(+1!' *  $*-#",?+m$V-%)*#)-)4=5PIOdT|gxgdsjthlmmquksl`k][_YXc^cnezxuzjqobggddhia`hqgewp|v{~}vy\YYwHyDPtMyHLF785#"?,BlUpovhguv}tzgqtqwvye|R}^zX{;iQl3i6h<d-c3f>T9\>UJDCSG@HEBM=H>P8`4e8q81D<CUHXXSVvSOXL]NRP2LBW$V X%a_b0d!a7`T`;V{SvSNJPMLNKOHPY[Nmbg`Warlgvoonvwkum[g[N_gW}uLMO-5XZ\KY[,6yM^5zOc{*^DW8x[r@c4exh|~kn\KxbnfX6p2wAibgdbEXPiy}jt~qzPmZ|Os>eXgN~y|utosl`ed]dpilpkefkgipmhkibekhkrn"op!nTn8nOeYhEa?UG["P%JNL FNKFGDC@>?@=>96=,30"-%%(~#%p)m&o.d5h/rDp>yEVKa^]g`ifdvmk[cckYe5ZGd7W1V@[EYEXodn`fqk||yryo\zia[y`wdvchfbfnili_i~~{vrubR]QiQa<[<I}=@PGMHRYMstzsxxp}_abNR`SZk\lebj^hd_daXYXOO]V[lgeoh`(l\)d*^(Q-`*E1K1L096B7E&?2HSKXYW^\UWYXUMKP4L"HN*S0N$^]_N_biiwhlkjofffa`_i]mZaV3UET1M&U9ZBM8i_b^ceujyowmitjaeq[I^FVKH3P4P`HD[oj]}e{IHT>?e}Tdv$vGK-F~t_U}m\%u[CZgdOaW-jBr!^JS\`=,LTo7UOXKN{HPJJ<VHvRTDw\~lnkcpvo{ws|morpvkx|tFe^s9o5m:t+{3|Ev8{Xz{rbsvutxzqtrjmjlkvi4gch3b3bK`@^CalXf]zVJSA@B;?DCFmKJ=LMQ9QO+QJKJFJFFJHDQLOXO [ YX^Y,^+[,\6_/Z2[1](V"P$SN J"O#Q%M^$X^neuyxw vIe6q^chc`ggdihhcdfrl}isgwtz~}mosq~kucy`^SIIG:5N=G`MoltctrW^qogm|}mrudjjojhpsoT}]tZxCN}LsFxO`WEQN_*_a'h cpPpBmSsqjikluemdpsPkMqL9u:H?{L|a{Wvn|rsqjzvzZ{b}`LXMNPKPOOSVqV[J]\\?^ `1^^^^]^]ZXVWUTTRVLQOXU_Uto{kjrz`rpp>rCl:gh'__Z!N,Q#OFCHSH`\PYxAuSt:(  x=7vFamq[OtNYqKkKup^iWroqdjnanhnWggZSYRS@o[Rs Z/)V1i}blwQIl|qRe`Qd{kyuht]amWSei5_vxu{x{-F?BbzO~Oz_{=|#{?vxxrvy|yzlsn@^huYmYoWLPMIOUQ\ZY]WYVRQNKKKJLNOSUU~Z]q_\bcdFdKfFh4hAf4f:f?d9bB`N\J_\ZmRcYQMSTO_Z`netwrywzz{jyqym|`zkgao``dHcVfIgCkIoMqKtH}L}D|>F0w.~:x5i5s]k]gab|_|cuK{P|Mo4o7P{@uMj~Xuxv{{us}hwnkonpys{|UgB4>6*<}<1|7w7muo?j@n<oXmQo5r@r2suz||'|J}H~Nxlwb}fxoy]~NzY5|5}3}%#z&t-~&e<i4hBYXbHZyYq\Z[[[\][\ZXWTSOOPNSPVy]NWglCk<kGx>u>o[uQpjgmy]b]OZRN\c\vyucjvvyfw|mojhebea\e{ibap}a|Z~`cbfp[My`- hKmeyZN\clOo\qq}aNnwk)KV*<qjk\\p"\#O*bqhx zCt'zjpglesw|t}z~y{syv|\bO2C"~(#wzUy>yYtrptdge[`Z]\jX[$U8T!VVVXXZYW[}XZb`dddd`da`a ]2c6]?bZiV]cioghehkwe\jhi]fCgVg6`5a2[U%]OO TQS^]bki'vv+uc|=yuqwrkphuh_iffZfAgKcNjChQifo_mqzy|z}~{|jwceawU\HGHGAH:*<&T I)W4y9vH=K[Ekh\nnUT^VsSwxr|xwr}ZkV.M!)"#05249+)0+(H>Nq]u~rqr]X^qV|Qoymnnikifkniwlvuw^-}B|}{zzz{zy|zz}}|{yipkXbU%U$V)P8T3Q=R@UBUHWIYQYSZU]Y\Z`]aZ`XcYeW[ObS^EV8aAT[$[S_ XZcgju}y{J3thwjviilxgobxbi_OZXeV[IdUtbeYtqy{||xrnL`L5@GS;^Tl#ph#jpF(z)sM{@rD[P{O>pA~`knGy>M24yE;wECThG(u$k:x&#_S^hwcYelirrszyrjlkvrormlrktmsco`rkqkkalqporpgedn\XO^WWOV(Y;R)YQ+W,cXQk?hLhyq_homimiab`ZZ~XWuY_ZgWJ]H[B_,f-_*f!c&_*`%\?\<ZKYeZZW|VYYVWVUY}[[|gvdiu~q}~|w|xpknp{jif|jjgmjotmzyyvgv]zbsZg[l_d`b\_]\b`VMPMbMf?Z@TEWwbo~{i~5. y| x=lp`lbn[j{lwrfMjWkQ]2]Bp>i3nHY|Koto{|vv}}yyuyus{|vvoz}iimampbtdjkdhriknhucWdfb=`8^<]-^1Z@X=[G\ZYVdfdnamfpfu[p^pZsQpWmQvTnSrQ|YsP}U{UzOY{V{Y|[w[s`uYl_j_lZibiYv`q`x[f~`ehhpdjg[vcZjYoZnW[YhWbXa_icicgpxsurwz|n{sq}fvlxizgti}ifkfjqlvwstuqmsuxqqjvvscl_}=LHo;kI|VvIiad^beh}bk~l|p`v9PA5?NL2?;rsR-Ustz0Y@e.kPae;WL*)/"F7L{zguucqaWadXmcndv_e]iXbw[Zd^YYYURZVTvX{WxUuOxT{WzN{[ZxXu^\ZYg[]\;\M_6\4`4`,[3^&b'])` e!a#f c"b$db(c'c'a/_._-]3Z1[-\8X4\5Y<W>X@QYSQQaR|TnQYZ_hhlq|rWsmv7l6r7ma"j.e'b.g?i9e5l=j2m#q.k#yv/u4}-xdt]xkqglea`_]WUZVQkckwzZXeUvTyurnutyvntzwuZpmyMpRqFo*k:r^f&b'O\NfIaLrma`cc_\efdece^cjbgl`Yu_wGEwG;9C<<D=9y9z:s5[2cFa:WCdWsEjX~UQXPPpPsLjJVIZEKCEGEH@C<S@PCN>V?QFN3M=J2FG0GIILR PX[Y`_cdch d hihmh(n!m-h>r0mVoKsSspv\s|uzw{tuxyvxvkxrmJmPlBdd/jdeiej'g"j+pAk6q?tGs?s6u;s?p6vDzQsB}jbwou{~]^YAFQKTigeng[f^r\{fxnOlWowlpuarly|uqs{ElYQEHYZd2xFl5-JeEk-\;~|y^u9>?k8{i8:eWG$l u3i$j"kQfObRcqemcc_x]b`H]cW#i0b%bnggeikbrn johg`'^1`)YQWBYRTxN`SPPRSTTWWVYXZ\YY\Y]a[ca`zc~_kdmbecRgTd?g:i9h,j(m5k*n:jPfBox_od|`S\RPSQSYVdUV[!_9[$kh*k-v*o7l:r2k4b8ha `cde`c#e9^/`IuMjNuXXc\pz|~x{~kvKEM=:XPSg^gdaf`ZxZvS{KwMwK|C}J|SKXu^{RpQ`[j@VCUEU3M6Q@L:L<HDE>F>1<6?';1H8?T;VPK+WJLCRiRmNtOQMOQPRSVWWYTWWRVVXZ\a_bee}f}l{kymvptlusonoontmnpufrmqjuapjwUuWsVvHuLmVmJjZdleZfd~dggedeeqa|hGhJeBgk)k.lq3vOs?v[bqWf\y_XJ[P`DP.T=f+b&h3}6}2sLK|FhTzTl@f<nOqHo:ps{ohmtizVZypvtrgwb{\bpJyPv}hb$G# +=#`]bjr'0,cqg2:LPMU=DUYob\~4!~xvy0rmO}OzOym`d|i^{Yy\~GpJx@q1a<m\!Z]XVUTROQQQRQRRPQ+RNJUEVRTwUlUTRTVTZYZ_^abczee]`faY^5YJ]TTVRQYVY1^)ZTd]bZbnjthUbbfSa2XC^)U X.Y+U#[OXGYM[pVfWdiqafmVcsTQ\^yX|\{_vQEK7)@H5|q|e|efyRkUz[aR`Zhc`U^efcf`aicec`Wf[bWYM`V\OUQaTkT`UvN|WtKz;Hg,n1l1X"`/Z4V3UFTXVUJmLsLnDwFyK[FfJRP0ICQQP SRSWWVZ]^d iirrrv*t#o+n.l*g*d+f"c)b&dc(fcgjdm!jk$o+i%p@n6mBr\oMlpltjrffhkg~fijGi``&d)c(\_ aacf/h hMlLnIpbu\o<zMq7d tU XaS!WrSmGt^{~t~v|+798/z8+|'qD5\<U?sGqFd=o{f|`w/FQ_; )"<>0YVEOV_}|jj:@Dl Fi._Hjxj]drqvum{vpqNZZjBY;_:]0R.`-U'V,[/Y%[>Z0]=ZVV@ZiR`SdT}QhTU|S|VYY]_]\^\Z]^[ldmdcePkVj<j>l7i%f-k^a"]SZ$P Q(R4K1NEPENFRTVSWL^U^I`8gGf+c'g2d*]&aB]<[@[U[LZHWRXC\2Z=Wg^jp~8}2>|FvIzKQ|iz`}vvnusnt~|]]_7<_QZ{sy|}mzhkg[tfRVzYQ~XnTm[mYeRi`mWgVnbvimg|ywy{u}zqf|xjpgko|ml~{l~cikmhiVJhYRTXP[]OfYeW~W~\^^gdirlxyw{vu{cwhv^wXv\t[tWuluetosswnmlihhhgggfmfyfkfNgde5f>d8`d+_```c\;_-aD]n`TfeksrrwwvhzwvE|CvEl)u.bJ`>iLcr_fxbrxv]?Z~!}tv}$S5~tr|oezuCLL[~'hS_<hVoouNeP95@eC~oiiN]o`HX9YQ`QaGex^uyO{KxPaBhFgK\FkNvWfObeckicab^Z^[\^Zd^blcmkghdu^{^l\JX_Y2W7V0Z\&Y cbbi hj ih'ih>e<hDf\aTfa_ic_a_[gbI]T]C`&_<_ bc bcd`d^V ^0R-T2SHM?SJHHKOOWKOOh]cZlcutnz~yx{mwmqdsPwTqKrFwAr?h@j*k1d,nv"j);[L|]jgIpWIiKeGk;fFfGg;lOge_[ca_}cdavspprunlnsmjs\deV_[p\n_q^_b`fochos}v~xjyp}sxbhkrulsgwt}w~wmtp\iWVXR|TSnUiThR_W^T]U]VZUXVYXNXWWIX9ZHUW%YWY ]__cf`db^f \3a*a6[VbA]i_cbeb|fndzhhsfrkylPo\qGru7l qpjojl i!dKj6eg_mhdlxdx|NzZxP'5{=z-|Bx^qNx~|vav['E<?4DK)"FE-t`#Ts{7RSHL^aAmQGq!+8FME/-UB_jMchcc`KsTjPu-J1M~xabg\CRx0/z5W"d"g@Y4fBzdpW^f[RUDDBo7:\:Y4]?JJLAXYIXYVnbZ_`ccdhbge^cbabddfffffhhggibdbn_xdCXF^2[ Q\RPTQOTSW\Vadc@e*f\hYg]fygjeya|bo_l]m`YVW[XZNQMXW`Q[Wect\ogvi{dwgsjuWo^k^nOnTiVjSlXf_`]b^baYaearcf_yay`x^_PYF-?~({%m.nSfLfKicgdgbfhhofs_r`iasWccKpXd>~3zIN=d]Zj\`[Y\fTx[LZHUQVCVFN^LTK_IyIiAtFwCk8f?hC]=WI\W]NWahee[h`rfkMtRvUoCmFoSbJcRbbZW]iah\hfqpihnmihea]ZUPOIIICKEJRHXUV_W\ZZ]Y_]^caggkqop{wawey_}K~VF>HF?f\m|~turjnecg~fRbjlCl@jOoGnHimgnmple~hv~ze5R <%>xVzletD?TMEmzZZu+Q?rqgY9I'Z<U*V<j\mLGbY[Vl2Dg_Pe|vvku8/YQ&5&0;,JFgO[\fXPWVQ[fgyaViVi_gReU_fdbbeVvWs\Z>l>XA5.I.H=JgZmq nAs$pugjlwcU^UNX]VihfuolOk]nPk h;ndeg bcigjr%q/r0y:wEvBVuX`{pproqtkpmmmlkjlgdi``caacca`_]\\[[gak}up~b|eyh{Uzbxc{fzhtnprt^pij[rAxQp101!}!3{'AwdgMrzh`rhuqkn?iHqLi1`3q:b9^8h;i@fK}KzW}idl{hXnNfMgWZK=KHZ5Y)X:]AY3VZT^WUXcSf]NYWZP`9UDb;[4[;gA];Z?]EU<J5N;L,?%L*V&Ab8Z.U8dPWAZ^P]R^UnGi`kSl\ftbbc}W}V{OCC972}*+y'v$v%r&o$r+l*m.q4k1p<n;oAsHpAwQvOwP|YyU`{Y~`iy^p~}z|y|}w|vxvqlqtn~ep}TdR{w,|,<G50z=&}:N/|9 \PYRpknK[[=wL_pXoah}[ya\RnFYX+YAEA HxSk_dlW`a+f<bCa/i?gZ^Hc`idfXj]tar<mGq>wt(s+.D1orawtuckpiq}v|t9\y~| ~3!=5<P?eZfzn{tsspqqrtuwxxz|zzyzz[zjwW{Tz^w\w^vvouk|khepin~}s`|fb{FyULtLsOvNrSrOoTrNoFiMl;h9c@k9p6gZzNz[x}j|||~}q|tk|^r^<r@n0v xsu}wDlJorqokjr}||uwtuwpfqlwaoQ]_fSIRI\Ea1f6h7y.o9bEz:ZOYRbGYK[Ph2e0g2q$o$r,u,w.y4{4|1|43-{193CWOpzzzx`mOII:9;57=8E{E}I|TuQ[^cjgwu~  |x|vIqgu9n/r=z1o,~Kz?zNnyZy~{}t|l{sfQ~\B@G?AXUWjhRZQ:HI:OeT{UpJBROLXgKu8PKI T%5&L<fW^Q>"M)_SXD]UspkQ_W1<U;Yjee}ncU|XD=zI30_L[swt_jXN]\Pt~{}zzyw|w{Rh]w]fOmZphf[rlmmoirntmzntp{p{rsszpwuupyg|m{`_~]WYPRMFL>C>3?1|8|9y3v>z7m:rAqBjGsUgSn\llbikxdc{dado`{\k`Pbb_5l7g5n{(t)#5E>bgdst{a{jyasMoWnAl?fDi;l:eFpEo>iBkGn.h4f3jj%f1h'l:gRcFhm]q^uca^afdag~hd{mK|A}7~17z8v0{EcGfL`TPOWoCbDl@/u390w6eCv9UCRGZ;T7Y=^,d*h4j1k+jKjIqJt^p[{\za{a^}bpl{|cniaoyovsmaaZUQNLFDB=>?=BHDMNLPOBF:*5zs+vm;l1n6mPmAoPoXpKpHoQy4s<z7z+%0:.e[k^hodmx|zu|bdcSY`Vdwizmzu[pNd\|]Pupujri\mqqw|]dbDLJejYSPbW\t[qWRd_@dSYF'LpKzfqvnERT>QiW|~~{wruc{g}cySiYmRlOlKhIgMl9\C_:Z%L8S*K#F6K;I2D[ZQR`]sig{}|~z{roq|omkvkudwU}Z|G}C>/~.'"!|z}tvuot'j$n)n5f/kAn@nFqUvQw\xa|YyXx_|CuLu;xw0uv xtoq4n1j=nZrTmexoxivkzty]qar`pPjWkag]kben`jjgWn[e_[Wd]WXQY\W`SXYiXmTe\genaeefpmaoQuZEGsHt:y8T?R7N8;>@64<.819*A$:?:/;B9e1K.k:t.c;dPk@X]Z^\XWf_c]ScR_VYNfK\_\Z`_^s`mddcsfcjLh^n<m5n=p2l0mEo>iCdVjMdM^UhDo4e>x"zu|q"vx)p@v1z]}W~fxh~o|WxWuMs<pFn,l1i-jj+dhbZc[\a#a3d$kNmHsSxstb~|mtkPZQCYgVptfM|WSlDfXsmo[hz{ofzhWoNujX{~a:qFjRU;\4kjaafdyn~\ldvWn7X<^M[4\OX|WZawLKhQLFiB(eb)_ w v0]f/a\EAKUZ`OU`OzQnIED@=<~;1z1s4~]ccOW[[X[bRSXSR]W \Ic Yohncndncjfdg`ylhibtznbtcvlsevkx{oztqinfehc`iddletj{gvh`lnf[l[j[lSoZkPySwP}IN?@7',  5u'}DpIkCqJqIk;u;s:o2r3r9l8m@kKeIhXf`fYfZgbj@gFf>f#e0h!ej&i'd$h0e.d.j1m.g=h1p:bNU;`I@N>@B64973A)<@:O>89i9l5b6s9s5g;m:i6_4e3R=U6M?7MAADR6RGI]ILFd=k5^:^:d/PHNDU@PNMIX;U=U3^"[&V\XS\PU!WP V[X_hagkfacbXah:Z3v;nBn<}8n;{3p0p/y*f*y/o*m,z2k,s.p.h&g!e#]ZZ VR[SYeZj jgm kf3h-d5`Ke;_WbRaU]edXbj_eedipfiohslqbt[x_tD{Lz=v!1sx yn w w,u!w2zW{Gwexn{byeuhK}GM=4ZNTrcrsgggjRpSgOL<WATSEDVWjs\at}nnzX\~dU[nomwlUan`eyvnkZ6`@U@: AK*?G(b:X3?<Z72@L$A^aWdjCHKB55;2I>H3L])b/K!W&P991H!A1>GMMUUZa]qkfrn^[bA_XRiTfJcbhZc=bM_.__#\a]]a^dcadbX\X NS6Q.M5TeVMPhem^fcmrhe[s\sRoDtGr3n/r+pkpnloojvnst &7)QG~T}uc}zpr{]WVNLOOLL{N{EvGv@r7j<l)b'b0`,Y'Z=Z9[5\CZ>Y*_2Y _jahmb^gNOQDGIHD&A4C*<9;/?>-;&?A#B6B+?/F=A:B6KECF=ECO5X#U-RZP>EC.5-C*R;@8Y#Y*R)YSQNL G*B N&I+LVO_[^ h^g#eb*cC]7eIZO_EjFYIk*e0a'h^h ^ eq a|ww{ ~~w{usvosomoAl+fdj_di\dyZZ\Z]^accee{ejumhlrxRyW|Q:C4/620K@Qra{uvIWHz*}}#yIyFwEqcq[nNkRgMe?d@XGW;WGNUNH\tRp]mscqm}|je_Cn[L/L!R9C8H'[QZS[HiRhSYS_Ic[WmQb7j3\KISPDN}I~CrIJ_)neASB?@661.(;D*3opf$^l"aA?5PJ1DILIJKMMN,]Z1awmOdprypMSL(y9}.~ z5|>~-{kW{iz{yuyuntqqwsJvoy wzzx{wz{y|{}~}2&Afs|]ZP;A1,1-*A;H_Rioef{h|Q|R{Ix6v<v+q&n+o'm"j2p1o4k@o;p<k<lBnEm@m\jWp_fz\pgiSUfWFNfS/Q6S3NI/O A@EEGNONRVNPMH J&=$? 5,%*,'#14"+$0!I-A2O(m'a/iskchg1^'f/pKh=tDuKqBw9z?k:p3oCdOjEjfhkniqzn|{hxohS[RKZ`U{w}bh~Xu3zCtwvszqvvqxqvvrxtwxuysvws%x xDv?HjV|t~^i[';&+5#]W_wzmvW~ei4k.c6S!\C;B5C96P9IKOCWOKfE[P`*l,[4L!\#DE@4JMIxF[PUPvQkV|GLGAOMJDI<dP_W_KxD9,0L3I.;.?2J)&93-c\gF-BU:(>r ~&i0EaF(P>1F#PW&HTlv{w@vstznyquz~suuqvo{t}r}l|sspykxwnuxymITH3,*O*bVVu~Zh~g~WR}P~:70 +*.;5=>=@?AB~GKITTQTTG}H~B{4u8v<s/u?pQkAplghdlfcz`}feqdehihNhHiHl:m6mCq=t=nGj@m9]9^5`+Z+\5[1b:[KTD_GNRMKTAOLON\K_VYe[_^UM`TLR0F=K;<9,/+.&"0:<.27D6J5F@HBJ;>=ADD@=A>]KTIcM[uWTZYPR}\}U_mctwooq`]`XUgclztxmSZKCGD@VQ\{rhtwzovuvwy{uwvsvsrwxv}{cpVYXJUCIG9D98?:8KBTl\qpqvV}\rdtZpiluubhfVa]\Z`_Ze7eEZ=d6WDCKQB?e8e@`CnBkKVT^JME1T@<0AF6:OB4K\MgMPSMWVB5I-G8,7 _*5@6//DO/Ae?KP_$n.[1JU$S:>0ZDwcXVotms^y(t-0oMh/'!4-hIqA9qDpn{iara}byStWx_mXu\rkggt[jigZmBnTl@t:uFsGtFrYtXp^vl~iwpsr{rvt}smsmyo{i{mikmlopvsq{lnofhkeiogronurtvrmo{seickhh^c_hq]dbx\P}XOLPQORTSRURUWVZ_abdh_cd_ac~a~bodte{bvhfdmbkaThKPRCOGMLENEFLHIVWTZ\wKOxKa<q?{;u8w5/-h1y&d/;=R0/FE/@,GH0J+E%K,U Q-d)f'`/i)n,[*b._0P*YGW?TMWi\X[pUvZkQlFqPf<b9eAd<_9hO_G`Lh]^PdRcR]F^>\>P5U/O3C0M*CEG9IJHiRTIUUM`V_ffrlwwrpx{r}yz~t^mJQJ2B%&&#- J@Pu`}|}y}|~|{owlklimfkjbyii_gVeagXhSVn_nUm@|O|Bu;{FuKkGsQg[bPmKo]h@I}L>LDOI@Q)m5f/rg"ff mbIy6/tYfC~WC`>ZEU+U-kC[@n8G}L(q#Oo ( 3&,$UKMCNRvwkg[lTv1lJ'9 <)>&&+*1PG_gfoplvj[jPQS#GLF&IGBKIER[Sdechdhe egbd^`d"[&h#e-a6h0dG_DaL\_UUZkRjRjRuLmMpOnIjNiRgJ\X`SXTM\TU=\@W:Z(c0[0f(i:eHg@hsbqdd`d_a`]bVZ{YLOeU7T4S8V%Y.X-\+^._/c0g4`4h3e4_5j6[3`=bFXA^]Zc\_XiTp[]HeKfMXDdFmF^DuAA|C664,.-+,+(5+3uA2vLtLpFjNsLUG_CUJ4M@C=P,R,L1L%K'DADA;IHDKI482""!E%)O+S+O$\*V(TVPIHA><5(2%9,014)<&.+;54 9.@59G5F@=E<E<C:EAN]KQRgVfWh]scm`{fxi{hn~opvywv~r`oyJxQvHq-q>n$n$h(dh!V.[%U6IKS<<eDa?f,:u/u,|6j:[9cBAE<G9J'J'N-O%R/W@X8TDXQTBM8RESNS[UX^SJVC E KFLLWNFY 3*>5<},Z vUYWBV9UVOdMLbX^vynXWo_L]88> LK;>J@G"2@J " D+kq`uvGP>#d-t%"i+u<|2CDMG<E J IJEKQFSTR9TUHYDT?YTaH[BfDd:d/j1c#i$i!dee[YYS PWP&T'\-R5`6[>\HeG]Zg[gceqhphgdilgqopuotutkvEz]v&w)z'ywytv(s@m0qjiijsiehcddbew`scr^fWe]dR^PcShOaMhXmP`SZ]bRDZGXEX4[;V2]9^5X/Y;[L NMCD CA@1??S=P7R?yEu=IFBHF<;;x66c=i<e6O7a:D.J)O->*B'@5>2:.7663$'&("&''6+ ? 9 ?M>[NX%oY:|6y:uQEPvP|LqOcJjHYD[D]AX:_I`<a@iOo>qQxH{HwRyB{V`JhMO\-G>bVVdNbTTaKcWXiUfc_ g5abKcH_L^ha\[h[j_f]gZfb^a]bWhOePiFfDg>k7g:f&g&_ Y\O NM HJE FD'>LA4>j;i<m=<<;@@s<QfHcRkieZclxqvewdh}TwUyUjM^RdL>S<I:=$N)014+4-$=,<%%-&""'!*3(K@EEC8a[bnGlLaAg@nJ5;L34HM;~n"]4^^G,GKHAAB0 .,0#("*# . `5=<:YN;C8%c$Zj"R+Q!yFl<Lp\yvxyomjddq^z\lZ=TTU1S-Q.RS$SSSWY Wecfojrootpyvw~x~ {v|momchbacbaedg lg'm!o,kPjBnWe^dYe^a_`QbWbNa>`H^6]5Y;Y8X=UE\K[IYJ[SXENINIM>EEDFIFHDDIDNG,B9?)DFCSTPTN>CF)> AW?QC]?;~B367i.2[4b8g4S1c7]-c/a5Z3d5ZJaFZRRha`?qEsDv.|:u:79=<290"+tv-'w1E:OPPYVYZ[[Ydf_leaqBjQw8y4y9/}099>FCKPOOOJM|Hz@xBt2r3m,ik!aa`Z\ \ \_ aa c cd ee hhhhhphq|n" (~{ qnk bah^ a!mGc0edhf_`Xv^sRiPlQmMeNfJmPfFo<{Gp+--}")b!j(_2@(O@M6AAMXbG\iOdbdIw'kCll i$h&sr3:V88HHBfj\$fr!J0>Y ZF |w&R{X}Fu\fdh<gBdFb+b2c_[EVpVUOBJ9".a~_OOChKo~ag|wz|eoeK[pPzIqXLcc]LpHrRqMzKzby[}dy{wp~qqski~s|jnw~kx|rzeDW)-(   +%%)%~ xx u n qlkigi)c!e-f@c-eTgNfSflg_eoeodrf{fvdjh|c{f~hgXh[eXXK\NYJSETDWBP>J=N9E<8;@3:F6D6A7I8D+A,?)@A<G DBHID A#F8I!HfP[RcQVYUUYu[]mgmiokfpoqasbrhxe{gvmqns|azmhsQ^bgUTRJZR]P]GW_aYW[JmYdGaGgN[GMKT^BY<dAw:p4G<GZGkdjssyRSC%0~ syusttsttsrrtsu w/u|OyKOugl{fWi,:,||8|4:]PS]{QqExOe@h;eK[QcKQmWpKo8G~-r+}/q#^%m0N*L0Q9C/B@P9GAQQfAY]^Zf[YhJ]QgNb>fSnjbSvrwkgkQ.{{i/nv2N8G2c.d,SF/PZifmZWsulllymzqtpeb_ZUPNeSgJjUAbQTQmIrU]dW_\]4n.X3C$\$8).%9RB__cuo nwqelqgsv 1!>A=IvF|>y@v=|36}2//-)&$ w~$vx*wFq7uVqUqTsdq]q\r_rUqMqRr8n9o/lh#ig ge d f"_`'_D[2^\\^]\[oYk\kOkRoOqDnGG}B~CE?B@CwGxA}KzI~FIGz?;s<^9h2YDP>T=XFW@SDZ?SGHNPD@b=\@`6t/jDv3wAzW<|ojo~yww~rpun~puec`re\@\H`<Y\-bhioxgs!p\ifb i rpmtqk rhilhik l o r t|~ "4.9}SD~`}cc}q~m|v{w{zywwtvwt}zy}zdqOUM3C%($  ~$uFz/s`t`tYntqppKn`m?o n0kniaf_]^_(_Y?_DX=LHVFICE:KELNKA[gY_Zmc]`\Z\XWWQ{KSMdLUCDLUUhHY[X\kNOS-XE)(1,0`X44-4<>R9=^hJvcVKq_<,FQBp~vjy_`i\WkhfopkPlGjOq:x<p7uAz/j[/aPCRYIjh]f`PLG:8A6@N@e_dfquyxg{rvpoopv.p3r+wGxA|/?-&{y urn:j*hccadoa^aY]VNVDED:>}98u7W5`6F7@6@:1>0<7C/A7ABE7EMIIILK[OSN]T^UYS]U\VJRSSAS*P9QST QRVGLH; C283938F3?8B6<4D8J:>7^CVB^@yEkBr>z>v>o:r9Az>=C@<;A`Ag@[[XP\R^i^^Tf]eXeGfR`Qi>dM`^dF^ab\ZS[[aUWa^\]bPrMkNy,y-w$ n {n Wk KMP @JSOVec[c^PVPJKLGDE@992.,'$%$58!8%B?5?/?38F2<3G,I$I)M+L#O1R/U2Y;]6`@c=fDjNjFk`k]kgk}jqkhjnhpmkozk|jgihb`_PaUSBTEP:F+J7CCA=@:<=9:<<=$>N>6AQ>]@GCB?I=?<7:>7CN Ab+a.b-tCn>x=xAv=z7v<t?u8nJfZgO]zZv[~[WQYF~5WBi_K`zeme(2;\ShAS_k?YTk/sg3_=d*l9XEv3%|/ +>#Wtdv\o30 +;x/|1z9|#yt!yzny}jtcTbf Tytm|rgj\KR;9;Q1&4n>o;f?NzPgHuQQN.CKKCDHABJCLXLdbaledd`[ZRQMEFCBB BB1A)A:=b:M={9z8|;;;AACFDKIMTPYXXv]z[nYoXlWbUhSfWeTjSnUnRtMxMsJtDwEZAb?Q@/@>@&AA(A,A@KBGCHA`BTDV:Z<T@N:O9S?J;M<XBP>M>L:M<G<A6XHQDQAhLjGZAcC`AL9Q:S9O7N5V3Z4?1M.N030:)R.F1O)i"X$\eWJRK#FL P;K,X:TAT:]9W=Q@U=JE@MGI%[(^+Y`c(P!U-OA<7DEFM?FSBfG\966.**""|$\r!5;0!3)/F5MHHTITQQ$X:Q0VPTPUWZjUaXpZrUoTvXwNlPqLkD_Kg9W:Y5V)L/R%H$D$LL!E[U[l aqr!in o#X ^#V&?"J&F%; KXL#po(n9+LoM{Mg^HYX_:c2b:b3e1a@`9^=ZKYC[KUOYE`BVHX5\8L8A)I.%I#<"Mq`$eu%b9I-X:Q>A:S=xuVzU8qUdwl\gu\dftPka^9{0N:3M08B1#6"/)@&,o%mu 9:9MrHvIKvGABD7FSr=l[bRnYKyCRHLXHXVMQN@B@22At9DWyIc``piioh`g[[_[\ogoekjPt[oL|LzO{JQ|RV]}`}c}op~v~~}{xz`wsvDrKsArl0lif c^Z=^7WFVeYXRwU}QxPRJ|S}M}NvUvL~UxQRXN\TvUf`vUS\O[XWLYHT`VWU]QuQiQgJmIbGUA\@QEQFL@FBMI:,;6=0/0#H;=PCPOMUTRc!Q!C a$.$2 ;- 6'/AR1P;UXoFhTjVrSoUlPrXqSoVw^U|]YY_X^{g~YTfDMM8IGBQa[jroqxmpslopoopnpljje`gZpZdU:KNZ+N+Q&^K_WU`RZWTWQSSRSDS&NsRiNyGMEHHCICDD@ADeAhBZD2>DE>>#D;?->"9/8K6:6L1T7J?J:PF:K:G8G*K-C3D)E4@BB3HKCHEEINBGNOHIIKSTLLKWQRDS9Z@T)m"g+f${t9V.e5RN(>;O4O!Q=XZQFk_emiYDw{rxnhyhUglD^UL5g!jxgwVMW WRU_PBP7/BP>:0hhp_jdzz}tr{~v{jyng{}|wyssvpxvsyvgkbT_NORLRRU[_al}lm~s|sqxvxf{V_1;%~{vvwsuzvyxvuq qmk-k&f'f0c$bd#`^_ \Y[WWW0UUESFRESYRTNQRWNRNHRKLPNINRMaKZHbOmL`MVSdLFNDNLP?Q=L`VTU]OTuXqAJqB[+r6P,R)T(<'3,"&#!# !#!, )59378DVRRHZihmemc|oyXv]z]zNuUyXV[dfkotx{q~tSqkwZxVqewk~lvx~wvshgbYZZV]|dayrw{tr}~jjrypj`Olec>bBf>d'e4fh"hhhcea[^YW[]]`bdfhmlnrnr#o$n'o?i6jBiJf<d9cC__$`ac^f^UaHKF4; .--%&(%&)&%'" "-# D;;)U(E"Q\MOPaWM}k`jgstl]{n{Jc1j>Y)g v roO i/[6H%d)}4j " ]yHxSxsutsxc|S}ax8t-z7}.v)B;K}fZq|rpelgekq{oixybMc(0)  z{yow tyw tykp(p;e2jUkVh]jwosp|svtyu{klvsooUahmH_M`Ea.[<`\!]abcffimkmliiieddcba`^]\\ZX.XW9ZBU7W>]CY#Z,\#ZXYWW VVV%S!W&R>L2T4H<L.H!A'K= @ B:><?<;B085%0-+ /:3+2J/R4S5\/_4}@o7n>M}ElTtPeXGgN\Mo;rCkSrEtZe^g`ihaibnhkyrptmsdsgp\iZnYrUkV}MwO|JB~F24.%{| ~|tx`r_\ecehmljqkpol)v#g+p&o*d0q'e?l7j?cWqK^^hdh\Z_hc\I^RdC_'a5ie ipjponpmuprzs{xtxtqqm ije!g!g+e3g,`Df?^GU\`QGZLaJU<QEX69:>64-5($*$# * /))/$ -.3 ICHHOR&K$lgk"wuD|3pHbzg]VoR}SmKX66/8*!-F?O*EIKxOYO@_vYyy}{z~wJc[vJP>OA]BU>T5b9l+e`&fX[THW#P RY0Y*`{#v|ZwPIXQNjakl{z{wy}vlslNz^PrHxWvZfRq}irhkkmkmlhhgebaa\ZYVTSOONJOKJMHKIHKKLKLMKMPMKONNQSTPSPKOJKKIMINLGL GJFCK3=;(4*(,-.,,0/. B=EJ\TZ`dbcjcsishikd_aSP{TcO^IeV]UVO]R]PL<N?X0PM$nf l x{vkqcab[[b\bi`xrrv}{|~v{{}wqalQRTIPJMOLQRUUTWRSTQSTV#WY\%W\]Y ^]]abahefleigba_XUSNLNIJNIKMFAE9 8:66==:<>6;5- 6!' " )0/4 935=<< T STgfU`S =>M..)0:.&(5 4(*4)DI>5a?UPk?&z25&9QAW_ddT[]0aaVf {mxpcbhbffkfdmOWQ>L.49'.): :HJ[9T3[=g/Z N*[B >E;4J9A\K`f]bgPWWGNKKNLMQNPURQQPNMMMKI IBB!?08)8=5=1@0N/I,M0R+I+B.D)#/(+-2*434<:@BADHDGGE IDGFAEACCAE<AA:@>>?@A&9;':@2,6M8N4E6T;U97<A:8?A$<XN Wrbounie[ ^ Y'RX'B)F/C8348I.H1H'QL%T N PVMV\PLW=AC3=D;FU*I!S.S3R-X.R1Q#Q'O%KH L AK%W,E,^-Y6V.b%Y.XZUOQROOUUIQH8B+, + $ !#'&0129889:9::;<;,;<B;B>DA\>TDXD`CVEODWD<CBC8C"B-CCAAB;?:18+*0/-97:D?BC ABA=>;59 96;@<DG%BAE$; 7= =9IJHRRBMC0@%)" *&4&#>TKX oic|\;9]$ 9B2.9*$3:/7@4'07,A.\L.^9m"^Z&lJMQ?A ?>;<6!9N8X7F5P7X;.265/2)24 1<IFIRG#>H-1+         y }{s{z  6$O P!R$m,h,c2p6`9N>]A-?1E$ECK@GC9C 687*.!2;-?)@+N+O(J1T/N1B7M4@8A9G6D3G5K6L6M8N;L;S;N:WCaFV?u]qWk\zutlbpb{XpFfIqA`<a:`3X'\EPJ5B(,! &  $ 4$+!0(J>?5MOTMMSOhT`;`@f=^)U.Z0N(L-M4H,H4L1I0G3L*J)@(C ;05) # * . )67 4885 4420*+ %      #!%   !#$%(+--253?@?EFADB<A;;<8:A :CQ4D'U*T:Q)WR&S M RWL ZZRV!X@$H)A",7!&( $# 4,.NB% 9((0 65 >s'DfA  / ( =B 8FF6G)I1B`DPMD'V1@$0 / HL?PCOk_[YT`b[LV4[GXTU!SQT)S#T$S:V4[(C3LE/;1265:6892/2'$.&@'?@"V1LU4X1V,V3U*W(V(U"XWRVMB I5,5+./9"4><@ GAL I LTNXXX^\[_YSYKOLAG=<;56826<5?=?E>JGFLGDE A;993 36 1517=4SJRmalr#hHd.kicl`iba_yZ`zXkIkHn9m=g9h.l4\#[!]%UU^0d+\3^Dk?@NMPGS$Z3Y&a#e%c!e!h"Y_(Y0I'P<K<C:ICP@J4M7O3I'E)F(:#8$7&/#.#5&0 3="9<@6 /4!!0,"*7152;4F8:6T9N9P8b9Y9[5`5Y0T+Y,H$J"D5:2+..( 0- +.)+)'(')*)*,( + )$8)0#9#K%6$'%</-0988=<;@<AA=C>=EGDQPOTQUQW^Wbd[Y\ EF@/5/ )))'(.2"(..2'7TBa/d!b)mSjHn5pIn:m<#KQD= G @ <?BA5B. !I@Bl)ZK;R:FA+U-IA]-bA_ckLotQy`iJux5KRJ()3)D"A(I,d(S&\3cV N%T7<=*.98<HIEIICF2R6L*WEf?\$s4mm{mknaUYLEKKETQTa\]a_4Z'[2X9V*W&W/U[YZ^[b_agcg}jc`dWZWMP#CD&@J589T0R.S/b,V+^.b+\-Z1]-S8Y7S8H@P=<>=?;@.?2<+C'?+B)H'C=K3I<LTQGMSc[^OcHvQm8n6w5o%e$k/`%`,`;[2\EUEYIRVETL`Bd<c?i=n9cDf;mBjMj?vew[q`v}xm^{b]xMzWyGrOuAk0`DfJMI 6 ?54544&)/+&A4K PK RWFN M AL ?BF?CEELPM`]]jgdf#gb_meh uktpqwo{xuz wr v pim]aZKRDAA:;852/-('&##&&*//9;<CF?(E*D5>lE\@nBCj@`Er@;@CE:FD&KLLN"P R8O;V=[KUJ`BaI]?`,`3U/V$S3I@K7MVJWK^OuNoHnJ{KoD_DiUMNMXKj:`@uAv?u9<~@|yv|Z aU I1C0>;/-?V+KFS$K)9HIPGGCNZeS_BCEUA?.0/J< Eg\R;d7K@,WDJ l eiuiz[6P# z+x%^?b,InD@SPKPO!JPO#QTY^R^[MZJsLMDxGHBCIEAF@67=<2C0:4C1F03<:90;B(>=?;7 9 44#586%:O5H<I9a4O?Y0Z7N6I-F77*513/(%*2/(-)2,=(>.D-K/J0K2T6E5O9G898I@$3,:"8,7.031738;8=HEN[T"` a-^D_=\S\[ZWY]\c[LRTWRQADLMMGOAPHUL[FHWQVK[3i1gv$rw y1k,p,e=Q3^88;<;4<9#=A = 8<779:<=@?=C@@DFIVVX_`ccbdehfhjglejpbuqjqm_b^TYXXWVXU V XXW]%^`#d5e)d=j=g<dHlEX<bDZ9I*Z4>EC1?0 20 (1 '7*5*9%X-O#I(Y(H"5+G), /063 7:8$<?6@;D5G@HDLJ+MSOSWQNSGEHCBPKO\Xa`elf|x|+/.DAx4~8w=j0t.XT]JSP;qFe4V-c4V7-72+E%O5@A.74P;X1J8JIT@484?<>./..CNF>@NOrV[l9}dVn?@C/9'&-$"B4EmVbt`MW2-0A&I$S J$V/Y*?2L98693D.95!0'"%'(+=,7,1.O1A325B5'8 <":BCELJwFtNyE[:cDr/b0}2(-015;<%="A)>O<A?W9^7^7h5k4c2l2a2T1`0:6D4/6 < :BBDJKLNJGL<?9)0&%('(++154GDK[T\bXS\OPQLNEGD::?6 @M?ZWZkctst90)y--|'r(*h-o)h%X,f(FO)D2)%;JF"DYUTZUNVPMQQNMOKGH>A?8; 2(2&:-7E3=N@FLQAi6ZAr!p$p | tvx piki!d&glh"n rifkUVSDIDBB@?@=<=;<:9:968403*,+ &<)2(3&<)-+!(+--,-/..--0)-+$+%&)(*.,162;=99;64:<8 FGJRP\Yakd{{}{muedc]bQROCHCADGG>F>$Z?V ]OKBW(::7F=73+0193?*-4'H5CBO?-?*#0508?7/301..4 &  44:W3P/V;\FW:V5[CMS#G6B&&  (3'1>%B-<(?"C&)0$!$$!!#! # !".(#+'M76)R>R<N9YAP9L<N9H6B5B/639-1-$0+)/,*- */+-2,4.2:/B=?J@JFCGAH@AH?HBCH=NDFTFKJEC>D=AK@GHDBAFBITOYYX]]+a/`'`.d/a'["_(Y/O3RFGFJ>IFCMH;9<>>:6.869(:(7(4"3&/'- /!8,6+//:>8:,62A-9%,09#$'%'+ %)''2$3)0,4'84)/0/(83"74 8= 8 NIL\U[_YW[ ILE7>,)-' $+)&($(&     .(."B4+L'E(E7U/H3Q5S1K/J0K,3(=(-'"'$ %)$&0%.+&'##! #/*/?6FFN\*U%n"p$n zvmshV_NIE=>743 .. 30/5406/%//)6LK'$// !$ ) )%  ! "$"6$1#4%@$>!8#?!7 <7/7) .1/;7-1%  . ( - I<GOEDL.9,$  %$/,(*!;&$)0"-.!! !&'(73;I@OMLRLTNNSIMHA>:@$5=J8O KLXN'Y$[$Z2`+a*U)]$SCP:9 9.0( )$ &#"*(66,,?J:J1K6]1Y%S YOAH;475//1 (0"%; 66F:DDBHE=D:-9           $&+.*0,%!,!1!0$4 E!;!J K!G!N L =D5'  * (,7 3='<*B&K*E(TTQWVL$KE:19%8,.01&6"*&40-2,--**+ ""  )$/@8GQG:(   -4!+=?4;:0557,&.* ,.9;.;3, *!17<"//O9D)H \'GS T@;A" !2 +5J< ^X Y pefl ] RY 79 /   $"%% #'# #$!##"!$,&#!6)4(;)J.C,Y6W2W6e?a:YHaGOJ<VJQM UMBM8<;09.33,,"5*2,2':%@"@'K"M P'U$VX[[]`SYN?K432' -$&"  ( ' + 5 1>A= EI,1'931D5)SRLZXLMG> B533,/11246574//&&   $%  !33 : JE TWS YZKLG9=6-25.9859613/)*("(+$;3?&V"H)f-h)h(w-s#k&r$l_$gc` df#b#h(i*_-\5_.F2J6B3-746$96"2 6'.)((*91 '.%#!!"#&(+390=5% 5"  -#8% %  .;;8F!ADIEFPEFD4:@TFW>M?jLiJ?;TAA;(.. .&, 3*+96/:UH:@kHjRjKFxPBAGBA~NGuOl]sQQfZdKe.sAmrt qqropqnpmkli e4p4e7jOvFhI{OtGrA|Hp3r7o4g&c0])S*O*J(A+<%:&."15 (@18L6TM"O1b,S:j?e7f6u:koqjef^\XSVOTQ KUKN NKP@KC/?()-$'&''$% ( $ (0!/86%5(<$;46::434-:3&(!&!! &( (1::EPJT[SOR;>6"*# '0*/?+5$A+F2>)A$K*4?B5ECIO&P4W+UIYH\MZb\[^c]iabb`]ekPbUgJs5b@up kqfha^]Q_UVbU] [VV!SP6M4L9IMEBDN@Q@LAN;NCB@B@;B.=1D+<!>&F):F8A0A2FF<:G=@CB8I1>7K#E$H#SHSTLN#Q=$A&<!.6 +-( "*    ')$+&"  05.@&H!2) #%( 3/%3Y>B3\AmBVDPMaF$8/?)<+.;6?9R&NTVX[Y]p^k]lcsZj\c`mV[l^_`dX{]l\y^[uZs`}PeUjTgFZJhIZD^EbF[A`MeDcMhZnGkco^pXlek\kQaWdH^5U@\!J"N"H:C133(/*,., 0-2537;@?BK3?2J4DG9@LE1G<@8;%=9/ -(*-!!,.,:EBYW`pfwtt~roraRY: 9 7'*!!#%)/+ (&/%')$/*+*(#0$)&)("!      6'!4Y ="e e[$k*h%Z2b3[.O2[3L#W(V K[T Vadf uxz!2,-3-&+ xoy`ebR]X#U],b?_2k]mVmetxuqyxq{otslshml{daiibMdK`FY6^:S0S.R,M(Q(LN#PMMTPT\V^9_/[D[l[UUUPIJ><:3l1x3Y2U.S-D/F#G&B"AED5;, $!#"!#!##   (%  )!8(I NALO&/)+& ;,*- 6(,V D%DSA+!9!,,)2%>@L[@\K^uocpkp{ujr[kjpLsJqPvDyGuUyPx\vnuenwi~ltew^d]Ui]Z`:[Ng0_-l/j#_&n$^#e'd'Y#e1X0[0[:R:V6Q=Q;Q4N<R7M8Q<N=H=Q=C@D<B88;=5613746230J3D1L5b8W3dBj=]BYN^E9Q?N0PX QYWSTRQO MI G3H,C5FQJ@C^HYH^DsEeGx?xEvB;yDw4z<o5e%k5F'H$=- -)+768 = 7A==D=;@4+2 ## $- % -=0F&H?%A8A*>>9;;8>A:;F?I:H@NHT<VIXLdDnBmC~665,-~1/|5p<s9gAaDa@[@UBb8X7^8k1\1k>n9iAoQrHcTkYiP`OmT^;e>h7a#j)]eaW c P UWK RQ P T WVYYZ[ X"W#V&W&U#R#Z%W"Y$_(Z'b(])_(g%_$e)b!`,b6[(bN\D_Ogn_[evg|cocsdw`LaX]HXZ1LL H> @7F7=2L-v1c |gofIRLCLTN"Y!\!U)W%Y&?&F%9"'$& !  8#*2&) &!!8D092%A/'@ []Pf_@D9$A!3)DQ(M?K.MJCMHCCN8Q?07;11>F;OO$J6V$VZ>VIXAz+p@i4}2c<I=g< G*= COBSMJRJ T MP[R/^*Z1XC^8XJ\FYHYQ\FU\\SWZVn[^Tv]tXqX~`yYt]{^p[eZoYPVUUHS-P9OOL MNINMLNLL LK'LK-G,E(D.@(?)A%;$@&F"=+N&I.L:V1JIXHRMP^XZMjLoJoBv=x;q0v-r(i"q#qrqr viok_g_Z$djb#ilXMW 1.. $  2& @=H`Vs'{#u'~5-q3x5r2d/o/P6Z/O45@K80E1E6@,B1A3613476520;;49:7E;;7M2J2M-Z%R&^%a]%a-d"Y:^7W<KPTGCFDMD>;/>6?@=< ? :;=:39$?N=J@TEr?fL~JGLHDzC@v;c<o6K7K7D3,445*5#7,9.9(9F9>8K7e8U7w5z9z;7G}AHwW^Ms]8[A[2b[_[Y[TS OM L/FI/G6A(@$@)35 1%)+%)/(-!-!)")7(5#(!.(!  #)(OE\ o   ~e " 0$z|Eb1.20*';=&")#1aUA<mC}Pc?]0i8:9:#4" +F3.)7#&&835310+- .,K+1/R,Y-I4P5S3$I3HKZURXYQPVOQTF"S"K$IBS;JAGIJEBE:L?=4F7A823>8/7073;-<1;,D/@-B)J-D&I#J&I"LL*Q$T*X7]0`DaDcGcTdQfV_YcU^UUY\GPLRCO1F<L(?%D'B9A$> =#C*G%G'R*T#SX#YSXSLS>E<)8!"%   !&.%' 4( /3' $ ( ,-RG`rh(ukM.Z$/34.(09- 61-)/(A1I+G.U7S/R5T8S.O(N,HJD;:,/)#  "!-7!2N/K/P,c8Y3b'`*[\ ULKA57, $))'!36,619AG6?GKHHIESIPERBVBT=S8W:Q0W1U-N%V(O#Q!Q$L$P#H$H&I$E#D%MHLVPZ \[_ `\_\V2Y$O<O:K:DIGC@<AFA4>"@/AEFGMFLMHPJKJHMAD?4:.,*#$ "! $ ) '(-#  686A BNC]&z#e%*$ 3l5G.a2=%&1&$UaGjXS,tEwm Oj6* 7+E'1:H6P3E=B6E'9)9!6 23#/F;A*@9_bQJFmXnAi#t9nv s nokncjvk '$*$ |q tm"`j0X/Y2V?M8TAA?DA@G2@7J.H,G+L'G(G&E!A%>'=!504+1.*8*2$?$>%@!I!E*H%K,J7H/G>E<D:E?D;B;E7D5B5C0@9B6@2>5@3>(:,>'8/ 4+(-,(//,.0),) # *%)%&   %/0*103(',1*W$M'm#r#p!}em[:C+ &#4+.B 9KHQe#^qtq{t.w(s.k>n6`>bB];P9U:O*I-O'RLYY]geo st y*{%r)z(q&a'k&Y'Y(Y'O(R+L&L+L-H)H.I0H/H5I:G:KNIHIULkJaKKLMLNQNNSIuM{MpFYKeICIIL?M%N5NSTSYUWWTYPTRKQ E0F.F-<B=<A3:<?-F>&GEBEBC?>?; 9"7"40)/(*-$&# )"#$*. 0 ?ACMKB!I;+ 7 ('1]*$%/$4+T/PT1t\mJ\ald\e>pOkEr8|Mh]^Lrc<nD\BV`+=0=&<1&A*:2J(K1TC]8U=qCq7m0x4smnlb_e]cl_hdg qhimf*ae3Q0T3S?K6XBQ?XBZJUC_U]RYT\_^Y[]f^`YeYpYfMuRqJp>zGr.n4r.mf*liillkol!l%o#j8m3m@hUhIjj_gam_XxZTSQKJyKEtGbInASNWIQM>WIQ3[5[0Y ^*\#W[*Q0G)ON;J<N<g0]4a,g-])U"Y%G!CG"A"= G'D%B#H&C#7 80!$$$' - == :G C?? =89654350 1/<-)/SS$Wpflvf [ i ILN@FDHKH#MT7N1Z6fJY@hLhM`GbF^FR?S?J;?6F7.,3+/)!(" &)$"@(7'B+[2I-jAc=dAyRnJrRvUoOkMoKlAl@l=l6o5l3p1r1q1v/v0w1{-},~.#"{{iu^!`\%N0X*G>J=I=@KHI>@AJB>=,C:?ACCH?DB9'?9+8*9$8(7(4 5"3/1-.-),")#%$+; 3,A'?.A=J6A>IFF?H:NBJ2X3T3\+m/d+o)y.g.\*i4E0?2O9J1A:^:b3Q1LFCju\oYdfm`qCHEM@J$-#4>7&396b7F1^;j-R6HEV36U-R:P6g.cCO@`?SL7EK:/A,1- '$'  $0';:6BE/8,$ ,+% -706+6*>+4 ?":BL?ZS Yk \qo o zu{}*:3<RAZUS`T[[UVWIOG:C+.*# #}2w'xBr?qGk\eTfnXoWrRG}IA<{=s;y7gDd?iBcLaFpSkNnP}[yRyQUtJhAtDV.U0[,NN!ibmy(51-;622/+,r.q,p-U0J1>-G1:/(+50) ))!"(*9 ,?@#;8?.<A4K3=684B1%8$86 97131++-*)*' #"( # !#&,.$.07"6=528>2V-?0ogp %658J~BNQOT{TN|S~OHzOADD;@B@EKIOTNNV=J<~ 4{ { xuwo nk e fZ8[3V8KQNDHZD[E[EhDeDdFiDdB^Eb@TAU@S=J@L7L9K<I7G7JDF>ABGPKHFJUOPER>YEQ3h1_6g/}+o5~-2{;.E~<{=NBLJHvM^HqLIF@KRZTXpFCJFIL;B8=SYETHPc_T[FRQR:OK)MBH>2>34)88.,, '!%)&#'"  ,) %)-J=74B@J>=<5A=>+A)=(@F DRIS"fY*l%p'f0k'v4b0k2h;X2cEZ?WA[OXGXQ]PXQ[Y`UXX^^XYWW\aTPYXWRUCWOU3T9S2RS)RMLKED#IA.HBQ4Gc[^Yi\h|anlltpotnxfll[o^jY\LgRWE[CY?P7Z9K/R,L-=)H'4)2(6%-#0%A%9#J+a0R,t=z:p<zH~BiFmGoGbKeJqKjPwHAI242#)$ eljT[="@ 3)53 =C><C<<?<:DBAFCA@?0> :&:/;'6!2*4)) ("(+"&-1<@39ONPPUPgZaVoSoRoPwNuKlNtKgMVPcMHVGTIY<d?aFg=mDeNcBhXJRSS@a V2^]XWTPLNNINMKNLG G EAAC?BE?KF ISJ X'UT,\HV6XaY^VaW|YsTwVTtPjRvONOYMNK.MAD#F @'6;1(/$0+,9)4/@)C1=:?1BI1F3L3^(U)^/c(\.W7Z.QEN>M@ISGLJJFNDLEDAECO@BAMF_BRAjEkCg<p>o@p;lDqLyBsWuWQr[X.Z<84s9y:~Y$]\F K LK I"IJ.:5:*:-.2,#@9(7*E"?6*812(9:(,'% "+!>!2%G*J*F&N(L,>#E :(%*/#$34#2*9!73.32//7%:'17!6-50,58$1"J)B.G-[.O1Y-^+S-P+V(>+C(6+"//( 45 5;;;> >> ?;=1?B@k>T?zBzDsEH|LfGoMWL9EJLDFIFIGMKFMFJK GMNLQUS \Z\$a[)\"^(U5N)OA>;:?5Q)E)R#P ORLWMVgWr-t+l/u:u4]<f:Y@@HOA3L4N4G(J2L#8'=#7'1(%'%#%%)%(&)%.$--,'1'/.+(0(-")%2&:76A3G4DEFCJ;?FE<C-;=B(6);05)(,1759"C>H.J(F0ICL6DGJKIDBEJGF=G>M=O6O:O9R9O<L?L@EFEBDHAN@D7S:N3L&S+LMJIJFNL O*XV7]6d8_F_@gCPI[AQ<9BH0+1,.)#(-&/F:WXWig\gWBQ*.& "$ + ) 5"8:#@7?-C3F:G/I(K1ILNLPT VV(X<Z/KTRTJT8iCd4^-c3Y2M.U:G8D:IAF?GIWGVKXSfMcPUR`MQK8MEH2D$H4K:F)IQKODL?_AY5Y2U4^2p4a A?MNDgh`,k h&T2U$TF#GMOD&BL/,9,*1&$5&21!078)&$-0%G>> L : &6 8 ' :A316 %A;?^PY`VQXBGD5> 76 = >> PNWhdnxqp"y[+f*X-:5M1)<,8+=G'=!MJ&I(T&P2T.Y0T7Q1W8L6N:M?F6ILCBDMCb?RC{Ar@@??;>939./2-175<DBKKNSOzSzRqJ^HbJI:D;>7/,00(+(+ )%( "'#$3/5D?FIIKHCGA :B&7(8+;D7>7G;J=G7I3I8<*>(8&,1"1*2=67 C 4&4   ,,%(%&&$#$$!"( -'689 @ ><=<:<;;=?@>ACBCCDCBB>?9242, /3-#9&6*65>798:@<=691B33&8)7!.6/ 2 0 +3%+(+"!"?!0UT[sjw}wuzknpee u!l(y33~<AAHM=EA1;74;@?|DJFBE?:;=l8w;P<P=J>/=7D8B,F8NIL>OMTUPHK@QHF>D1ECBR@?=b=g5V(V.,Y#U*N:T4W*J5F,NN$G"UV&K5M,U47>9.<"0243 8/$+687I K<#LA$'A<932*?:2D:,VXV fcM\K)=#((L>My`y~O#f!N$,;),,+,.2.5>60G(B.DLO;GJQOPCLANCK,E/F'EAC B B EED KJ JO M(OM,MGN5JbQ\MhOVvNTSRSPWST[W[][~[]tXuYrYdUkU`U_V]UWR\TUQYQUPONYOHJQOIM9HFO-G.G3L)J+I5R2Q4O8U3R6G/M/I2=)B6>.<+<5<,>#<'<<:;9:8)6,9+=29(@H$ALO JKN ; A :+4 $0%* 0'$ *$)(+. +<=@MMMVRLTLLR!QQ\Z\ eb^ cYNW)<%>.8A'8/JMNVRTUPL$KA,?.:116330?*:-@/K(E:Y2X8]Hl;gPsMvLrVuNxSmPrOkQ`LjSUNWPQWCOLY@X>WA\>[AWAYBUEQDTEFRHLEX:i?^5v8y8w382m4{5j5M:a157565-7%4)49&>2@&@6G6D1>4G.>+@%F'E'IO1R&S0SBT1TTUQQKO\PUJHKLG@?.C4<&8<(?-9#=+A28)2 8)$%$(+H0E*3/2,5#2&-29)<49H<<?E4L5=;28;7.@'C7=?<3BQ2Z4L3N*W/8+;1;&',+:3 @ XNUdTFU 0"6!2,%,#',+"</$X[ Yom\4i.V4<IO<JGIRITW UX YGP>VHPrG]Pt;{Dn@p.s=S.`/L0**A/+--, / ,.,)-7$2(C%bU$~  }w ] k MOG2;"# )(-54ABKVPdbbmgihec`[\RKT>B@1;97>'ED=J9PAQXQLTeVeYcTsPpWbIjK`MDD:C0?/?2:24152)1%/$/024259$+%!-$= <;HCC ECAB!BB?0?#B.3-5(/*!'''#$"%"-"&!89 8$BB+7'<)63)003$6#1(,$/"'/'+%3B 9$N"L!O%\$U"Z&\"US%SGHD;>553,/)&'%$,(0!:3)K)H*P1c.Z.m0o-n(y*w$m#r$j"["`'R$R-G5<-EB%A+DRLPVNJQ9?: &'/)."2*)/.'153 4< 6@?CJDQP PV TVXUVZPYSHTADD :*@77:>42*70:"0)$6!=2,G+K*B3A2D%=-;@L8K79?<6:(.25-3(852B+=3F&S"B)8(K$ ;6&8IE-A)E+E=A8C*>6E%6 '5  4- 7YJV)b(O0EAV8%@,J!<3 =#$# A&TXPffI[F#>      +-Z7l#g!g%/r+27v7v9v<a9f9X:B9N8+<-9&9=8;;:;<695.1,)*)&.)+1*4 /3:0==7<=164,5/586>9<?<=BAC IJFIIDEFEBBE::82 4;566C3r/p-z&(v't!{fe`QTCA@7$8>0>.>,B6C->2@/@2<7<0B9>8@7H=D;C2I5D3=)C,:3:.855A6;$C*I"?:?&&$#%&--(,(! '! !("30/$AAB>DDNXLNMaRbNdJoOjCqGsDt;wBv;y<y;x8z<x8u:w:p8k:o:Y<^:Q9<=I4&5,3!+.+*+,.+ ./ /20'4)6$4*8)='=%B'J(K'E3N0F8;EG?4N7R6Q-X6X+Q-Y/O*C.N08-4277/2/<3:/90=39/<0;6;.=(;.=>:9>251*1% )&&"I2E9#1)%)-H.4P,PUg^mocbdJIH$7,5A><C A887033L/O2N+e&a,ZeX ES:490,8/7C6X M S o] mpf feLQ C( 1"+ & ='8=,:=9^DG<|HwG~GMIRLPXN[YWj\zSUY\URT6XHR'X*X(UXXTUURR'O"P'O6L+L5K7K1N0O/N#O$P MKMEFFAC4C-C7BRAECQBY@PDLFR?>L<G;E0J0C7?0>78C291G.K.C+D*I.:'<)A-=*>*H3I2K2U9T8D2O6F4/.;2//&/',&+",!!  %:AEXU`%b#^"b*c$T+_(V+D/R)699834!:-:86;=: OK Qf `aldZ cURXUS^\^gffg hhg hf#h-j&h=i;j@kOnHpUmUtUoXiPrW]SeRZTELSS1M1P0UH!Y#RT%a2T(`D_AZD^W\MVKVRRDM6N>F FFAB F BGMG Y9W-Y?fibQaue|_sY}\~PoNrMlD_CgGW?XCWJM@RIIGIDKGGDHHJDIHLPNKNUUXVTSVUWXPLRPMMFEIK4@7F,?3 ?-./'0 )) -* +65;%HD'=%I#;&#".'"!'0&(>> <JA.9;:I:->@++9/'" ,-5)&4!N0=YaPR\798<!1$P&S R `"^]]^`^#^!])a#c_%olq|v $#,>;FMI~JP}AuB@5|962562?<:B?x:?m:F3Y9*+(,")        "&$-0 .04/1/546I4C8L5R9L6K1O8C*C/C);>'D?EN HT SQWTN-P+K8@GC@FU<SETS`IX_`ea^\b_g^UNXSULG;LBH1G2G/G%L,CF"B#68!4$1%)'&)$'''**'324:5958<1A@=9B%>:=;97><?588O8K2M2^5X Y&_ ZTZSPVXV%\%^,]6_2aCYAZF]QYKXUcS_PcVmSgLqNqFq>uDo4w3t4u,y/r/y,w+w.{.x*,*%)$## ! [r%2#2''.* //-.+-(+ ,,.N+=4^0`1]9n4k7b7h6`7R6Z8Q8O8T9T9R=Y=Z?\D`B]BaGbC_=_B`<P:U;J987A6%5$0 ,,% "!"(7$:&:/I)I6A5J6A@3;>>&D(=$7;$%  0#EECVR G#MI!6F204403F; Hc'U,anXIX 1 + 5(# D ?AW)RPJSHSEM\IQMPFNHKMICCKCNDF;C3H5F.B&M2V9Q/fKdJiH{WvPnC}Kg>M)c2)/'   ) *H9RUT__[ac_ddhlo!q!s#w%r&p'v)])d*Y*A*M*7)4)4(+%-%0!.!5;8JHN\U_"b$]#^%_%P#R#L#? D5$5"5$.'1#2-0*4/8861?A<?A@HJBFPGLIODX?N?]7X7[/f&`(kkltolnf ]` X%TV&W<V)VD]BXBVRaIGILLDG0B:D*?)=):"7%8 1/1.!-(<(5(91F5='J.G#JT!KTTLJKD>@?9G@@ML=FRA\AY?_8c6U8]3U4H8U2@6@5D1:/<.E+@)B+K,D(B/F1<.42:4 "$)"     !  &6+IDGZO[ZVWUVQSZV\][__ WZ TI PEEH#D!DO KTaYrsr }+s'x*l6\-f:L8N7G<57>?,;*=.E&>&D2C-?5?B=;9N6J5L4Y0R.P,S'G!;B,(/) &@<BXTXbY'QY5A2A9DH7=;IJG@GHO\HOLLNZGBC)F;563&+,*,23F=86VA3QK-HSH6@< -B T?pki'ys+q3o)k%d.nga"f `SAU:KD;fBV>f0d>_Ia6WXRTQOEb;Z=J*W#D!(="< 0>_Immiyr`jX?N&( /0.=#55(7%0&*/-*#0#0#-.!.#,$+%)(&*%+#.",*-%%&!-(659A<GDC IC@ B 5) 3  &'C+8 A;D8A<@N@FLWIWOWa`]]O\Y^R[@YK\@T<W@OCEDL:<F99=)9;5C BCJDJHEC?B>;#>9=2=/<-T'K#J-R%D(73@)5624310221@<FVN^c_acZ^YQWDJB3?%)# ."NNZ y q #o$}'K)S'A&(, "  " 3 06I>PPNVTORJBI686).%,+!1-6 6-8+@'6+-&5# "#!$ !"*0!(3;0>74G:@=N T(O#R YK RQ$HBR5F?LPHD@6LC5;:4<?(OU%EDP-AF)l/Y+gw+_;Q'aM)O/K(ZWXW\_[pqg m%pS6WAS5=8ACG)=,F0T"G&M2U0A:5G?>MN KQOCM? *72-0U IMV ME"L1+7)0&2&.)*'# +#') 2)+ /52/13/C06/D.@/;/=.62906459856<;=;<=<B<C>D<HBHGECGSGRFVFcF_FdEhDbF`FcBSESBPBCCC?BE<B?CAH:CCFBDAAEAA?C=C;C9D8D8F5H5F0H.J1?!G$> 1<"$" .'*%*,681089496485/5=-5,/*8#0!!!##  -!3 22 <;4?,?0=F>G>?<N:I@7EDD:P7T8Q8V=^6O=U>S9I?R:B?F4B)74@ ./ /$',&) 0*&,-%(B"/!DG<A C+*.%%(4* -, $   "/-1A:!E"G&E-H(G3H1G2H9I3H;O5M6O;W2U?V9W;QEM=ND@D>A<E4E4D5E0D4D9F5DAC=CBBMBHCX@YB[Dd?`H]F`GYOQLTMIOEKGHFJE?HBG<E1F5G%G%G$JJGUQX#g_(n(s(j0k/r*X0^+V$?,KB"<&E K#I-W+Z1X<^<`?XH^DVALKW8>A>6F >0=JKGRU%6"C'34 /89>DBPO!O"Z"3"P=MeSho`Z*] HJAOBJ<e6a7]:c*Y"Q.^< E;*&.'J$4]'h]diYXYRV[X[aa#h'hm#u$rw| rjq^] _V V)\$W*_Ah2^9m=o1q*|-zoxo` l UZ [N YVWX!Y]"V'["VN#V@C> - 2(&$ -$8:;D A HH IMMUUYa!`d$i#d#b'h%S(Z(T)B+O*F2C/M2Q9O2a6a4d2q1m+p*s(m"hmddijn u{ }!/&/D5LKIUMrHyKsAb7l=\+]*W'A5 (6/*.92YAG<c@cCbAoAiCg>mDd>[5b?P-M0O0G'F0S/O0M1V3R7B)E1@/1!4(5%*#6*B/4,U8N;W8q;b=y-|4x.'p yp&\,h'U9U9W6P=T;S,W1X&W[U XVMPKGJH DNIJP!IL+J*I.J;E7J<IAH;K8I=D-F/A-: >%3"32#,&.$,,*++0,8*5*A*B*B,H,G.D.D/E2B2A3H2E6L9X4S<`8c7a;f5g6Z3`0X0H+R(C&=#ED@!SQ$T.c']:`;f?^MVH\IPPMJQEMKL7]=U5a$w1j#)(#3109601/))/)-5\-v665749< TXy\QuGdMoAB<P><> ;%2 <,)H6 H}[ XtK@MG?7U6S<V_dU`[bfh^dVa_eVVQY]W`MVSuPpOrV]]ZcZvP~\ZA`GU>7)D9/(2-*J?Mk"Wutp{s usnmldba][_^]aa\`\V[JMG7$<!1 , 10-?;CVO^d_b gU ] U DR =??5>; <<= @/ 8 , $    +%-</GA= KBBD ?';%>E8D:R7l3c7{*}-+!')%,3-?>BQOTVOzQyXb<^CO<9'=4/"(!1!4-EBF U%N [8\4Z;`NbE]T^U`X^`\\bkch[o[_zIOE0}=/q)s4u5i0lIrEpMv`Vzild}lypPc`iP`.GFS H C#KMJYZ"[%e+c(d.g,c'b.d'S)X/P/>0E71909,=A#:"=<!6(:"6351729<697<:;6;3;89+@/;*@ I)@WR[ rj{|~jseIY576") & .,,4/10/0-/,..*7/7D5NKFQLD.F/A39E;@7?6A9C9<6=<c8P<m@:~NIKbF0.e.[,c's&j"{~ vlGg40-!C=/23%,$DM4DGMltT`}i~vuaQ`o:r7m;a+g-g7c5n9xBs>NPNVWqH|RpES0e@C,E*F13,<.B5;4J;YDP<rFoFuACD>8@vD:dNgGdHOXXNKTFTGNAL>JAB@@@=B6B3?8C1@7:B@99G9G=H<O;JBPBMCPJTHN@[GU<Z/e;[i!gelghhb^_TRNGEB<>@:A?<=:;89: 5"9!52%4 /**(# $& %+#/+!@ ;>MCD G A;>9 69'<";,?/?3?8B5AFDEEGBWB`Go=hBlCs<gAA|?yBFDkJtJcNFTSR9_.a._&e#f"^ a#c%] `0h/d2j>t;n;sAvDpEkGmJhOdJiIkQe6s@q4r}'{ v{ rhm WVS"DF;L9C?NZ\RRVj^jWkNxSsFvBuGtCs<oJoFoGkRjKjLfQhMdI^McCTCZERAB@KC3A7D1HD*HEE"F!@ J0C0F6PDFBTJPPQIZITOV3W<T-QOLIDA@55/%'  ))+97:><< @9%;):$4&4%3%,!0#2'*":#3%3 =4;8;>5 GADRIWUXb[g hd jkU[ U4X 4"$F9I*B?LbKHBq;|GgHl:yrJdTnK*;!${:}(~AnnqPvvrtqws~cTfR[MJ4R8567,329%/#F=!E#[ N,Y(Y-S<Q7O@QGIDQGZNO=kFe?i,}:q%y%.y*v*}Ai<mGj\]Rco]l]n_^y_ba~__\vTzRtMkErD_Ad<]<N=X8A9F:?4-192%'%($&##$% %'++042@@ E!PN@S7XBTgRRWrJtLnH{>xDv8v8t9q3q3n8m5n:mBl=oHmHmGpMlLhFhHg@a:]C`,Y4X.^V'NSD!5'@"437 A;HG @DF7<8(,0*1 :2FE$M;\+T^iWiXjwwjtdumv_wKxSw=~=~6x%{-}"bj#_(EP::667;H5B2C:E7D5@:?6J2E3L/Y*P,_+a*_-e.c,e2c2h4k9f7v:m>x94u6-*,)'3.4s>x7XLYGTO?^FQ@l;f;j;}:q9:503$&"pwk U a < > 4 #  $( ))/-101316,3(77=I8>?j@f<r=?554--1+19j0tCm@eDnRvKmU|Z{UvVz[vKqOqNh@_EcFU@QDSHODNOWMWPPYRUSZ7`=Y3V]$AI@(4$!$ C&<L%9:#,2>E<&:(@!;126C/N+A1n3m$n7O>wdn{dcmqua\`gfZd@fRj)o-i)i sW_Z)FCQ+AYAYAS8f;\6R6U3N/@0A(?&6);(?#4-L,F*I/]0Q,O0W.J)>,J&-'1)-(((.-*-/"(# "    '%"$!*!((#"*%'$*$-&.#2*1&5)4/2&66+/.4)B6&KIHSOMPJ B"E%<)7(9.6643D<>>E@TELE^F_HaDjAfFl7l9n4r(o0u&u%w'z"y%(%)-%2+|.r:q1j:f=d;b<`:_2a6]/Y&\-IJD384)01)/?%5L!EEUJ V WRWZN SQ0IPEMEPIP_NWSeGfK^Hb;cCHCQ>AE#N1KS VSUV7J+N<Jb?NEt:w<w915-+/-*{51x7fDn>UHUKMI:K?L0J*J,F)B&C.2-3--5 3$,6,- '#/C9 Q T Q\\QWM =E &)    0*4 O >ZYX*f$^6_8c:[EU@ZGIGIILMCICMQNJMTNfN^JsOuJsD|J|8m=v6j%T/_#KF&K)F"DBS<ICN\^RR`\j^^R\OgSBHOJCP$M;O\Z[heP[Q 8E; 5 =FF'O*D*W(n,c6l,v>bTUCegDmEbFk;oBQ(X7P&4A?8=F F>%D?'5E>6-H2R3J(K0R+5.;,3(.!##)$%7%'SQPh_+]+b*U1J2R'6+9)6%!-,&*/')) %'"#)('(% $ $ " "###%&&.) /8/>;;D=EAC HBNKNX#T![-]+Z0Z:Z4SCSAODGOGH?U<T?T=Z8WEUAVJPWJPKd?d=f7r,o,w*y#{'+%619H=QPO[WvM}SwHh:vC_,e/a+P\&L LN$F#H!O*I&P([1R*\2Z1_1g5`3u:r:q;@|@y?E{Bo<nA_6f:^:P3Y7918/420!-976><"48#52.%3C3?0H8^AQ?fDgLhJtHrPoEuJnIfAkIW<\?X?F7N=C4@6A8>3>6::=9=<9A>@<@>C=>;9>;359/1/#/+)-)*/ +97;FALLMTPQ TMIM 5 ;-! & J BNwb~qwiP\ A ?#?&3#8)5(3'7+8)5)C*>*F(R(F,a+]._4q3g:k<n<kAjCj=gDh>h4e>h1n0m7n6t6t@l@q?gCYBb:L<L1J&<-BC=ENFQTKMR6<:$* <3<T JJ QK <2]4I._;IH-CAP*[!N.I/X'2367:2%.,.291-/45I8>>'L60   . # <;=OK,@+O1?C(=:DLB>H)+1&02H%<%R!S(O.W)T5C4F5@<075579-36-A17-S*N+O)_'S*^&^([,\'X*a*]&_%g&ajnl pskpi_gRSPAF>:9'64722/:.O,C)^.[+a-t1k-|97{9@=uE~EtFdLtIRMZNPN6PGO'N)O%IDF<=:37,,,%'4.8IBSVQX]&M(V+M0>1O3,42:2<7)B'C#;,:3=00<+<4<4B.@H@DAF>Z;Z;^4Y8^5c/X6j*g,_+g%g+R V T%D IJ0H,F1F>D8>D>EAE=I;HKGIHNH[HTIYA]GXDU;UCJ=N=BC4E?F$R&S&U]]V\U"KR'A$A&B.:'>5?3?4@=A:C<C?C=B=A?A>;?<?7</;0=%8'>"E9LHK XNZYUZXS S NGJC!>#A#A%;'G$C&B(J%G%E0F,E7CEC;EVDVITM`L[VWWWXS_L^K]I`E^GZI]EWDYGU@P;V@A+H,@).;$('!!+(#)*4&-<1@3?1K1N2A7L7@9->>>@DCAE=A>495 1:>6TSS%fd+R'V(U5F0G1S5M1Q*ga0e.u `-R?g398?D?7%'21&(-##)0)2* 1-$- *+A/.@1G.9/5-<2!+#5> 2UQQ fa djd)^<e9Z>YLaC`5]>q.m.r.%~+}0-|9rE{>dUgXcVR`XaTTMZQVVIQP]LZI[QdU_P]\aZ[YRaU_E^D_?]5Y8Y0Z+S2W3Z+QG^AXIX`aUYj[l\kUuQsSoEsGlCc:i@X4Y7U7K1Q5D-H0E-;'B,7 8$7#03"... +,,''#*+,4%.0D+B)E0V)R)X']$Z!W[#VU#U.U"T<L6P:KMAAGF>H>??9=8@0B*@)F(K%H&V'X#WbgTaV BW 6 = ;( 7 +,1#.(/I3=1A3Z7P5^9a8_9e<h9`FiEdGYPeNSKTNTKHFIICF@F6G-E0F#DEEDEMIQ#_Z)h#m*k:n/q<fBh:e9]>b/]0[-`#d'a'i"k(h-k)l7c5e<aIYD_XYWZZ\g\a^g_hce_e\ca^O[SYIT7P@S%M&O(RKZ(R"U+b8U1cD`D_CfL`G_H`K[FUAUEU<M<T<Y8O9f9]6_9o:a8lHjEdMf^cX]f[m[dXbVf\MZN]Gd4a:e0f+d3d3d,_?`;\@UOXEPNMPLLIJGIEGID@I9MBH(S/R(T\YW YVQS!NQ%L5E0M<ACF<F=>EF-;5>/@;(?<;BD@%W$S.Q:_6XNFNKS@l A)&(%" )$(E#>9C:+4?,6,>(_$P*T ^V NVUQZc]hkde"hR!U$TEJMF P\ Ri&jc(lGo;TQ`TSP6ZJ[1P,T5P0E/KCF=BFFZHOB_KeM]C`CdIL0Y7J10G)#%(  %, #>:=QKJRKAJ8::/220 3 7 79 : ; < >A?EIFSQ V!^ W$c"`#b&l'f'k*m,h*h*j*^!`#]TZZX\"a.^(a?bC^G\U^SVYS_W]S^Oc]UX[\SiEaS`EgGZKNGYL;E?K;J%B.F$G#E'I&N(K-R,S,O.Q.T-H.M.G*9%A"4#7%:'3)7.A0=1C4O7H+O0R.P#O(O%Q#Q#O#N%P&K%L&H'B$FA$?AA@> B =6 ;1 /0,-*0(/01C6;5E8M8F8F9M769<779$;.: DB!GSLOVKBI. / *   #97:OICM>.;  " %3!.59 42 6&%$%'&,)-,1!-2,J29$a*a(c{$rpv k a g.Q,Q4QCD>HKMOGINKYMT<a<b9b-j-h+e$k&h)b"g.d*d+f5f1h4k:k3k/m8m&k+n/j(e.g1Y3Y7R<F?M?@B>E<G8J<L"P*O&PTD NF#%1671.56#&H MK eeR^S<MF@GPRLUN CN:<?5; E.D1C3KCK<<AED8>%<5@)1)  %%)($$?"?Ba)Y ]6i/a7ZIg=YN[O^JXO[N[J[KZJXFYEVLVGUISPVKYOXLZI]I]E\C_@[?U>X9P>O:O:J?I9KAI@K?NDKBQAPBTA[?WAbAcAaBeEeD\>_BX9L0Q8GC$ECBFFG KKGKN#K?O/_H\KgGyPpO~JJ}I|FEq=q=s8j0g2i.f)[,X-X)=9@289KGYTTb^X aXLX@D>-7"$#0!0 /$7&1%,'7*$'*($**!+ + 0 *%*     # #( .-*'2.515181715:945;/H2A,Q)R/Q/X,W<T7V<PII@NI=H>E<J2H5B6E1A8:?>97H7K8I5Q5Q4H2M3I3?2F7:587?;=89>N?G@REgGY@nIsCj9nEp+Y/a/V?'M&2$0+30&,*835/98AE:?DNDO>M?S@R5S5R3R+S,S-Y,V.]1h0b8h7q?hHcBnUJVNSL]3\:ICR>E@.M;L<FD+ [.CO.HK479S<Z5H7I?P:*(,4)(!  1 ,0-8*3.,:04/>-?5>:E6FB3CA?1FG-/7. %$!&$0#-$2E>!G IEEE9;0!&& '?/FE@+HED@@>A?Z?P;HETD@H-O>L QQ NPNHIE<=>79 =8=<";2<*8:9:7<5F5D2M8L5N7U>S:]>^?_@f@h?f@nAg?`>j?M9U=J:13A92231 36889=:==9=88876461-0!% !!#- +/25#7)7#8%;+;+;+>6A7A8A<D6D*<.?*:!1(6*!'"&!## '/)-!1)"#.)'/0/00*3&3'438.9091=.9-5*;!2%29::JIMYWX^XQYI NG:9#EM+I-G/\!R)W1W-R5SBR>NBMJL@J9HAI"F)FGFHCDE<IAAI?C A; 97,,)!%"!!$&#"$ #'8.CC C!NJ(F&L)B390A4 :(4/6('+$"/*/=6?A>AE8A;/;(**#*%+4.:9:?;>?99 ;&4./"4 XJSXTSSUTRTKRC^8MB@YOQ)S+V.VQN-]&V)V6c0\.U1Y*N A$E)7 :(87//67+;0411*41'5'4'5:8+##*)7 -;;:B@7;90$45;294:6Q7J5A5I9<:,86FBL] Rghhtrcm`K W=:< /17/ 5 A 9A A<: 830 0/.547=>CGEGK7A2 ,#&$%)!  *& 8 8>MIRRSYXOWL=J,-0!&+&,52DCGUPMVI2:@CU,S4[0ke,wtr}xh pc'K3V.C<>?<?3E5D,I1I(LQ*POS!OH!LFF GE!F%K M*L5P0S?KEQCQHIKOAMDNCN<M>N;F<I:E8:;>8@;;;@;HAD:KCN@J9JFO.L7L0QS+RZ\_ dcag`W]FI?,7 $'8/LIOdZdl]Wc.<&  '"))(,-), *'(!/8!5(:$JE!JPKHP @ G? 1 ?"* "   %9 6 ;X/J$'+ ! 6-1%3/EL>?<U?U?P;W>UHXGZI\U`T^FRSZDK*8:H )) #!  *%( ) (''*'$+ 0!*&02.05 &*-#)81>SH]a[bdPWM7C61:<9JIP^Yahcag\^YQW<E4 ,##'0.24102*+( " &#+ ', 4+:6 7A 9A1B->1@DA99=:A8832653%5&4#25220-"1///%304B2/;P8M>MG_=VEXD[:R7N7R%:(@"4), )?2RNQ$ia3c7l8dK]KdITRZGV:IDS$@&F#@/;. *13/)9$8):9@2<?CBDACFEFD?CEG>@7<?D$+-1!, $!%!254:9;;=?>;@= 7:421-.1/154668;9=>::<,1%   ')*;: 9><9=242+0+*. 01 6 6 7; :8:810$4#-'2/;-29><A;>ACBE89@@:=.178&,%''-- %)3"6-*;+03L KMb]R_T1,$O*<'A%S,H-LI#JKBXQN\ UFI>+*1 -<!0)93U+D8U9a8Q=J=S9.;3;-64%=<9? ?/8 ,%( ' /'", /*- : .$4 ! 0'10-1/0.***%#&#!+("'+-((+,0***&-,)+!*+-).- **'"! *$-/-.-++(%("!   ""#/*5#D8,M(L/H;O2K>C?D><B3>8H/F/H3Q0O1Q7Z5V8R=[9S@U@Y>W?Y<UB]:U>JGU=ANDLCO/[$U][\b_`^\YTWOMSL IG)CH?6=U:V6P6Y8U4T6P8N4O2K5K0N/F3A5F2.1660.&),   "      $"!/") ,%,'),*0+.&?(<%@!O$IL O"G!C#E -"0"' ! #($/ 0/43+)0,+-!8'9/7*%%!"+$"%   &&!' " !3)5-8*4/7?6;0=2B+<#7';-0* # 0JAJlWux o{"{`8j<\5>BME71-3639 ^&[Y WSNSQLO%N9=;*-7 +7 J: MOH#I-H#GCE9EGHgGS@hHo:c/b@e NRI6;  $ ,+.838;659.*0/(<7< I? LJ K SMRSNKNNHOVP^\]faikhjkfid_gY^YN YF G D9 > .0 + % )%)%:-2*9>>=7B6Q<M0Q1U4P/M5O=C=BB?I7G6K/L/K*L"N&L!OO%L*O"P?R8SBVZ[JTZ[^ZTRVYYO@PHM?E&K0>#D@'5)A!6@584D1^6M*m0k/i#y,s;q0s7mGh;jI]P_GWFMLV9?5C0>#,5.*/0.$74)4:907@7C6;4<5>21$1+3-*()" %)'(.(636= 7E@ CNE PPPSP][`ketst|w {}zwvpopkiolnuruxsq'ufi$dUZPMMIGGDCD)B>0?6?-;0:1=; >B=DAAE>DA ?B$? >,=/:-71624)0+/--%($'0#)"2 A8*S M*R:i*^IdBjE]XUJ\ZBYBU=Z-V4N*K*I&>8H9EZDd!b!Z!k.JV%WQP+S"F C F!A<P2K,M,]>W7P'Y/L)=L 8795<)4'" "=*EFC MHII =7;$!%'&7(GBJaR gk dgfX \WMTIIIFKDID>F-3,#  "# $$$$&&#&'$#%!#-&;:>LGOQPSS O S LEL8>;,7**/+*$+.6./-:3J2@1Z5Y6Y5i9f<^:i:^<O:];E9H:H<::BGGDBAEJIJCGHHELEMIMDWIZFXFZLYIFCHG8A"7*= - /*% 0&=:<JAKNJKMFG G ABICHR I TR QVRVWVY\T]TJ Z<EC0@,// ")$#$#%$$##.%%3$5 25 2.-+ '&( %%)' %)$',&"&$5,5L:ROMXORRKHH9=9,2 2,4;4H"EF$T/O$UG[>VGSdVSBbDh@b/d4d7W.X6XAP7NISFTFHNGGNN)J0K/X @Z9A5O?LC<=@[EYDY@mCiG].e7]6L S&H-E'B?$?'&0  $3,,2-"%',8-GDL^Reb_ha[_UGJ<86/. + ) '&&"  #     ,#+,+.+-+*,( ( (" ))'1.%)%& $448DA CFA!>"A788 2 7: 9=BD+D)G*H7H1K7P4O7O>T9SDOCU>NEDII;8?>A<80:78-5*//,..*"10)0)2%/E6>1B3X9K0V6]4R0Q.Z*?-H%?+'26&=9$6A;14*471N*@+U1V+T3\>Y4U?YDO?I@QD32<822"*+"  $,**2')( #  !%'&%)% !"$)!92<QC[_ZbbSZM;!H(#*#,&) '+5#2/8AF7?KLOPKFSCUHK8Q1M;F8N.@QDMDF3]J%\4Z7T5V:XAA4D5@=/7454M2J2K5]7]'V.Y$YSRJPF:C!'!#+ 4( /K=DFA>>$?="<;>/<97=7827,9,4+4)4$4%6)071)3 #  !$;%IFEYOTTQRSRNRVS]\^d _ b ` [YUSQOONQNPSQYVW\TVUQQOGHC=?:987:9;<>BBDILMRWUUWJOC-3%=/1!/10/ ,/1-;37.95DK>?DOERBOAS@R9K7M4G1?1D(6.5%5."/2 .042<57@9%?A#>5?)B7<>=7<99C;<2=4H1K(F-X.R*S4]=V5]J]IZK[VXNNQRNIM:M@E0R-L0J,R*K8K4J5B@<<;;6B.=164=+056141,864-:/;2;.@3?766>:/>;+GAIVH_\[g_ihceeY[VJLHCCC?B??B?A@@A@??=:8;69=6>;;>8?<;?;<=====>:79./0),114< =<E>7!B-#0!.% -('!33"3"990@+>1AAH9ECCGIB?C4E<<.='=093;.=89<<5@4=8E'B(F-V(@N6=5?6<3424::<;476;;A+$/5) ,A9 CW I&^+Y#Y)g._!g#m%]W e(A$B+E72/53;7:17+:-;&%!, !    1.5L@LQJJL=F>0@.03/8.86 - :%-&# (#"$)(B-0+M0I2I2Z5N7X4W5U4Z2W4W3Y3V1V0Z3P3W3S9I<R:EFEDFH@QELEXHVLVO]RVVTZX]K_@cHa1c1d/d!f&b$g f#a%c]+\(Y*S2R,N5M5K1J3K3G.L/H.>);**-0*0)',0-)1-026/<275B2C5@9E7F=F?GBJFLFKSJMPQG\?PJX4Z7Q6P+T2F-I.C/9.B1*%3(*%%.&4LA `\ ` thqsllmdgc\aSURINFFE A C BB@>@7:4+3&)(!( %%%""$"!$ ""  #/" (<(:*:-J-B,C/G+='5+9$$#%( (&5 17 E = JKHMLBHA6>)-)# )"%"'&0,(*4A0=2L9g0Z6p2{2o5n.x8c2g7fBW8]EVES>O@I,a0k2l'c"a)e L N M=A HD E%NM)C.L%B%2,D73#9$75,A*?)C1J.B&I,P&@5"="#!  !-//85;:630/,**(('&(*$(##    ' -1/21, --,))/)1(.)6*4)0)4*.'($.&"%!$#!,'+5,7404.%&2&A=CUGWWOQP=A7%,& BG L` _k0s/t1~??DEEvHIg@iDf<U0X7L)F%D*=)8&2.3/,-%1+3$-"/&1#/0%0"6"0'+$4,'!-7 -C >CUJ YY W]XOOJ BC=#9$7!3)/&1/,, )!%            ""$+(./.1,0(.".()&('$'6008.${/wt| }{ +%)<1;?757%'"" 5&;6:76=<]6S4]0a0c4i,9#9VBiia"r&o`%d ]OTLIJ!KK-=2E+:+&1.#&"  /#D@C[TPWMAH:9958 * /)  %#,";48K=LJISN,N!N+HED5GFBL?=E=HEDO$ONRPKJHD@> 9744$/>4D.:/B5G0*462&2 5021350;79B:CB?B@:<7/4**'#'",8/AA:><,.$ **-:9?BACE;A; 0;,/1#.+;G4;8@7X2L;X1]2R5N5T8A5@=;4/(21!    ."0I4TTVg` g gbc cWVMBG35%*   %!!/ %0.-2 ,/-!' $""$%'( ) .--1/0.--)-().&,(%&"   #)(,000454796::9<=3:5)4)(+'$)*)'' &!#!   !  )*,6336325).+$"%%(.)#   $#$#2$1&3+G0A&<1?$;2$476 :A C @ B=9?6@:/>$'-"246?>/9/+# &))0*#,%!$4/6D<NKKVOPSMFHFAH(NH;[?Y9[MhIe4jBn.jj&mced^a[\XQUFHB590--('*'(+(**('%(&(+)-/-,/+,/ / 0879 @ >9 >4%(1537H>GGD GE@>< 917*-C.D&DQNH NG9:$,)# *0;PN a de po grl`n[a]P[<F=#6 $   ! # #  "!4%:77B9CBBG BNI OZPe-a,e1sBk=xBvHv=}6x<|{ yzysungmUZR>I:9;7::=@DGB&K'F)<5F12.632(",'!  " $!$+'-.++-),(#'  "#.&4%41)9;8/(B4J$; 7!@!# -="-,%' # $#$12.44/589;;704,!( #$ >8<QFKQE<A&+#%"669FCKMQVU_]ajgtqu z }}vrqfa`ZV\VZaXgfekgl i koiqsquunslclUZSAK<;=57:89*<:<;8:;7N4B/S*Q(R"\U\[WY XNPG:%=)1%.#3C;GLEEH17-# %$005C%FF-H(H&J/M*=%H(="*; #''/06???E<5>)#   %  2&9><CECDILMV[Z\aUZXLROIQWPc^`kbrnltkpliiac^[ZRVONPGOLKPKOMNROZY\!g:f1g8l>j7i5m<_%d-[!K S@;=55;68?9?-@&<+=D@74C8G5?*>/@(/$1'/%#!&)'!$&"1%&%753; 6591*0!$    &  3!8 0/3'($%'))'/") !#+!-A1HGJ[VTXTPSE@E@=OLUicpspuscg^ NUAC;,6 #   ! #&+&;794=9UHJCPLTPMKJJLM?AAE:>0289'-)-&/* ,,+/3166586361 ,1++.-+ 0.1 71=<!;/A)>;5>:=.D!E)L K PYUVXTRXOMNKF> A , " &211>59=:;A384& -. (3>9Q PR^X^`_bb] d Y PZ 6=0 !" <29E 7. <  )/%%2:3:359?6>90869+:7&:6 792<::B>>@=;=565/02/1648;89='1% %    ()%.-($+/(8:8=>@A?;; G FG K J_Yarg } y1e(k&Z)\ TGP9?8'5*!+I8 HPDAF/4,    #.+-;6=??CBBDCAB @?@+@@6=9A5=B8A>57>4052// 1, ,.'/)).'/-+0,*,*&(&$(+*50;H<WQRbW\^WVWIKD6:+((! &#"%&"( , '(,/*, 0'('$ )*/%"(%" 4.;XH`f^bd_^\X WLOKA#C<7 ::475220 , + )'&&$&!%*$=$6%A&I%>$:'@ "" $++.1136 /32-4, / 1051884< 041&$-!#$&! !  $##+).2026353/3+)+&#1'0@1NLJZTRWM EK /2(+((7/13+&)  #"$-,.31;6=LGPURQUKE MR L'U$V*R?V8X?JKH@J;FJD'C2C/;6(;"'**)2'$BA?!M#H95C,53K.?KLFG HC C FE F NMQZW]_YWZRNPNHPMM1Q$L-P2N*L(M+KKJ KJIQMS\Tab`gf_d_V]PQOILFFGB@IDDKELHFKHDHA9?32866A?AH CIIGJ J? D:+5     *$ +>5ADGNMNPHBD 2%2#5$,4.,=233:3L6A4C2L4A-9'F*+.+   *C!=Fe(W"pt%t|~}zysskb dY#T$T)O?K9N=JAJ;M7H9M-K-K)P!K"LMJEE? : : 60 3.15.:8:B<AA@A>><;: 753 34345788B@FQN R X RNTBB>15+ &++&: 4"; N#AKOGE H 560 !&      0#05269/22,1*4$5,<=D4BC8ICD3HL*>E<-:%#)$%003>6?'6LEI9M(e;[`gYQ Z 9@:!/#!!"   ""4.7:9==<?;6;131)-$%! $%(,*233 99;=@)CC0I(I+L;Q+N<U6S6SAX5TBS>X:LAE:P104:+0$)   #&#),$!    !!/,)%2)4*3'4%0#6'7%7$:';$=#A ?>A/7-)   ;5:T F XUU`WYYOF#F7 3*1'&#&  "(- 2**-             ! $ $!!#)"'4!8'62<(=7;6=7>@>:B?@BD@FBEDI@DFGED@=DA?:?7>895;2757/6/333-2)1-1%00$)-&$    ! ('"2-'()'(-/"16727 2*1$%$  $#%#%%'%- ,*.+%)& &')0537978866985A9?MCONM5R(J2M4K-F,D-A%@&<";<:88 6320/0./5029 468657%/$0(+1$+)86:B<JFFO HH%J"C&>,D&70<0;-41=01*9*8)1%@'1$<$:&.(?)0,2+8-4/9+=6>1A3E=D5D7G:H3F.E0J!D#EM EE H< 1:''%'##0/.7- $1! !!"$&"(2 .?>BQOPQKEHC@ACA: @ 7-6!)%#    !%%!&&'#&1"003-*/"      "++/2368687362'0$0).5,-,:*;,9.?*?21.:20<6+9@70 :" & !         ! ;, FJFOPB#K$>)*38079:=;?<<@: + 3  C*4(& ++2&#*%('+1&+'" * , 4 /25$'+$$ "  * 4/1>6?<8=<;=;#:>)3+8)51*01),,)*-$/&18 99=><<:76 6 333/10,(+#*;%8#=$O!FV'VR$W.T J3L-@/2<82<<8;::99::;=>>@@@BCADD?>=25+$-//*1,$$"427> ;3 1 ,*$ "%!0 "03+-/!  " 518OCRYQQSAGD8@15)&  *-+04-31*0*,,(,- )/ 4.=<9@?>=>> ;@ @ = > > ; < ;8< ?:@F*> L:F6J8TJHAWCQHN7U,L8P NNRNSTUXXTYSJQC@@97955965;62;%+%       ;)BH> =0 +>20469/2-0.4*#").$32*.-      *"( + )-:5>>=@==;=> ;/C.F-DAD;D:?@@9@2<9;#A*@#BHFC FB=@<:=>> FFH%NN6Q4S3PDN>R9L>K0K%I.HE E@<?04-!,$$$%$%)& /-/7 4 : 8 ;? 8@?; = :0 4)$    &%)828=623'  /)14232978@#?B F ?;?-0 (&    # #50162-/' !(+%87:G BF J I KNLNNLQGJJDHKK!NSV"Y^acgg$k l)k4o-lClDkAhKhIb9`D]0VU*NNLILLLNQRSTTUTRTMDG;88424125 2 968>#8$<$<)8&8"6'+/(%  B6C4#/$.&##-   7/5RGRXNHK><=63:7?G?Q O S ] S c \cthwxsz}rvuntmpmh n+a+d/_GRBX?KIH<G0?<>B=@F@DFA?BA@DGFLMPUUVYZ Z [X [ XS VOQNHLEGD >A9 9 4, .%#"        ,&-;.C A=E?<?7.3%#  $" . N@PYJDI*-' #4$!<? ?KIMKJNKOQPSRLRJ=D;6AG@YY]mimnlljegaY]RPOJICC @:*:!;0548*=+5.=::>5?<:?;A?BHCL JL QJN M!HFC@:;<5<::?;=@97@*4+(     " % '*% &%1"=;8K (+4$;-,9#1+5= .&3 25/@9*/- %.+1=79:643,.,$"(!/@5IKI QOOML%M!MONJKQBJB4D,1-&   %#,,074*;(=*<3?0A8@9A9B<A:A=B=@>>@;>8D;@5?3C5</@.9+8'<#498/,-" , $ 63 3 ? 835,%#(<9?XRKSH>H & "3$45199572+1*'041FDIVLUVX][gcaieT\M6C#       #($$%$)&(#& *"%"$"$$#$,' 0201220../ **)%$&%  '!$1& 57021%%&9.#  1-&-   0#'1.,2('' !+(0@?AH? 7@#6*5!4"0*2 '    '0*0=1C=>I?JIIQNRUSSVSSTSTVTUYWVYT PT F GA49**'#!&&%'( "% "! !"'"$3&0'4,@-:+F,J.D.E.H577980<?$; 8 ;<5?=554'!%#0#)-!&    ) #23+%7-59D? 9C65;:9>@;: ?131(/%( $       %9,CDBJFMKNUPUVSSTSPSVPWUQSQGJA8?+-",!$''   !""!      *' * % $%"..1&>!:.;/@/77/643'7(3$,0#*- + "''-&% "*+)/1 "&%    145IE:D9'1   4)20+ ,,,(+1/6:76:.21)0)))$&" # *4-0?3DACMDOLOWPXYZ^]`_]\[WVTRRKLG>B;9863 , + (            )( *6./-&'6 0>E>BI- 7 $   (,$1.# ( /3378A>IUKXYONP?;:0,3..5.0/.,)+"%'*+(#$#!%+(224;::;:9820,%! &- #))&)*")!!     +(-A 6=F;5>%(( #  - !4*=?;BA = ?@<:<<2)+('#,)  !!$ 2/57322''&!%!$!"            "#%%(./1798:;::872// $'%#   #(%$* #""  #1*+ =5-1" ':58NBWTR[PQPLIHIGEF C4 5*       #!",&+/*(-#$" "*&.5)!'   "&  #% #(#(404:* /   $'E3IK << ?+*' ",&*;1=>9<=312-+1+/7/<8;B8KDHWKZ[UY\SVTMRHHF@A=;;:8762.-(&!   3&A=>PGN M J J EGDACA691$)&%$ )"$#  '!(' #(# "   !#     %3#< ;5 A< .6/!*  !$"  $#&/+477;=<==::;543))$     (()3 03677885 9<7B'?'@*I4C'D3I,A:/@132( ,,(*.+$'& ! , *#! (+&! $    # &!) $"  %'"(-$ #/,0=4:;756<57<7IBL]Pjhfqkkjg ea b ` ^_ ]UUQJJIAGK?LGFNFLKGHGAB<(5<,6.9,9261;,7.9*:$7(98:99> 19 4( 2 "    %')720)4*2+-221.4/5/..(2',./+*()( ( )$!&!    )++1-8$6'5)<0835+:4-.$$// %'')0+705>/K?F[Ed [ XiX eaahalljonfhgbcbabbaibckace\X\KQJ< L8 ? A 6 A 67 823,-&"#,$4-";%$       (/ ,/<4?@:=@292.6+$-!   * '%2/#*%!(&")')+"    (#.@/QKQiYljjsgokmsk~w~~{xwprldi]^ZR WGKJ?EA @BA?<=;7:7776:46515433320/,((" & ("#(!#     ' '$'1$0%#),$%#(     "(++40,8'-*"&   C-TRUj]vruy~~wypngff]ed`ccX[VKN@?=593740:,560?0;:.?-12&+-$*0#50,5-*) !  54+  ("=*81-0!- # (!". !,!1PBacanjlonmnjmkiochaWeJSH 0C %*!*.''+   $!(+")*"*)+./33566876420,,(""!    % '%""-%B!A"<%N$F%:!=1")    1"A:E _ O ihbje]\a_[pkn}yynzVaT9P$.) !     !""'!$,##$$%$5.6D6JGBJD:>3&+        '#02.2$166)-,3'2 8 3/.) * // -7:448%#$ '(:4OMOc]Q^R=H+." /(.E4QKN `Rie gwnwyssuhhcYZPKHB@F>DNATOKRKKIEB?;:3..!#     $"# (#  )%,!%1*+"     #!$ & ,'2>!7"XS"XriqzmfsW[Q;D * -+2?;JJO ZXag"dgo[e`P`KPODO@GD7B566/ 5215 63 =;=D?AA;98,*# !&$&)$ %%,-)',$( *($   &!!+  ) 25/25:/9F9WVXi^]hVBI!)   5$0_;hb`yivurzyxyw(tv$u/q op!ojgd^Y_W[dZjfckegged`\[\ZU][\b`ada^a\[ZX[ Z XY Z V T T M GI< ; 4+/     !6.8<9?B * 5 -*&'"'$$&/$#( !$     I 7 X\ [ if eifflXbS>N"  &41:J:gXdi #xvr!mi cgZ`hRm\\nUhc\c*Z[1]7\/^2d5c"d%gf eibcdXd[ZbT\\VZ\MXL=P*4+$ &-** -+)*(#'$!#&'+(,+!' &' (0 $1 ' *    =# BKD LL:B5"1 1WUbt}{^l^A] - 8 0  -   +%+.+-- '  '&349C@IIMURUVQSW?J<#7  !$)+ .--.*'$  !..-98/7. * #-*'&)$   (KFVk|zjZb26,         5&2=57E)81,  ($$50)/,%,+,-/1')')-4*!'   "!%/+!2!=)c_gzufl6<$ &616K=UQQ`Xcffllmsqowhqj^oU\[ KX EKJ>J:=;.4.+./,+,'!!  +IBNq`nye]i9?7"        #)--667; 8<<997&,&0lRjr\3K    &#336B ?AEB?"D?A@< ? ;:98867877240*.##   6/6XG]`XZTA?3"''"##*#& -))75#-$+O<dj`hcMY=5#..,)+6>K854Z>P0P"f0VaaXXUEG=0:$$   %"*+*1569=??GEHMGILB;?,1% '?8<YHDM7#1         :6CgWpwu}{vxsmlYZI5<"" "#').!.9/>7B-M$G+JH A=:4-&    #*(1329<5 A :0 A $.,  '9%@DCRSAP>": '%'6.18.%/)(()+%325HE7C2,$"$$# (?">)C4a*S=_:a8UBN<J7/;9-$ )    !%& +!&)%,%%" #%%+()!*&(#@#4LNM[W P\M9G!'#&)&)("!! ./2>-2,$ 25<XO_c[\^DQ8,   3FGE YM L!RD6.=( ,    !&-498D?=P1H>!E    )!254<;03 8+3C)%.M%C$@#XFH RA8$H &'($%) 81PYPVW-4 )    ;:;UP @ O7(#*(.*%(" ! !%!#'!#'! !7)5Z:nbju  (n#%W)[$N /%A  ?K D!:Q#D$6#G-54.6@ ><EC?A;5 :#'  &  $78>RMRYMCK01&  *' 2 QOVrdz~k~ijh\"c XV!UOMD B; 23)*""   $%(467B6C:'7! *,(65 +.-$( )('&*-5)"+ .(2UDZbMN\$' )   0[?uvidaVOSGJQBXNQb Sdgfnjmor tsvx{uz\mQOM<?:4+% / 9:/56!@4=_Vnst}xpseX\8;, `@zwohhbbbXjY_qZ}q uy sxvkoffb[^ORL@H;A;2A0$) .*'4&"#  5'!,bK ^l\Yd8F4& " LC[nvjsmTh@LC'@ -'3I6ZSYtexx| y ~veiRPH:B3>5$9'$   2-Byb ~jrkTgHQI2E)$ ( '@/JJGSQSWVX]Y^`\b[\YSXOSRNSIS H5A#$  /#+0%% %'=.SN\wj^\P9I',$  &&1438:35/&+      6,7Q:QPLXR L \ RE] @CG69;31%2$&8#4AUJ idh}pwzibf5@$kYS_V[yt  !7'XOdz  ~[_M&7)%=7>OAXVVc]`^^aZULD<.-#    6 %  < SNNlctq}~Xt68, f}ZY`ON^T\l^skqq~& +F/RHGYFUPLRJRKIOFHHFFC@A<50   "'@)'D!8'13@*.>.8+? O@QS H KL .:)    6&?` '('~ivNXH)A ycnhI`TOaid~ ")45?EEJREOI9A$+& ,!+8 8($.% ""   !02*0"!*+ (Y;sqLgI -oumCTK?P\Ty||wjo\]XKODA@:88057+E5AX>rdpy '0-&.'&0("4)# 3#@A?NKDN>.=    $ %?- =9:D;IBGSH^YZi_ci_X] JIF9 :B; ? KF LRFDO%7 irljllptxwz}ltiP]EAC87:25;0774533/7;2K=I`Itdtw-%,?+C62<&/#  .'=:>PG A N6  1 B #'& ( 4*/TFOYOLS3E(4]]atzuy]eWBV%9(zh~hjphnuoy|   "#%!$% "!61'> RD$c ^d ykz~njnGQ8* ~rtxr}    '.@G=KE$  ) 2-% 4 #MOI\[HVJ5E(F?Rs~o.9scfhR\_Zgun   &!   ) "-1,#-+++,!40- <'5-*   x]\V530,$2)2E;]]m~ !''&/,56159!+ !/51(3 / '1# {xwX`a_q}-.2)*-%tzsioqds} }"$"$$i]UF=H8@P>h_o"%  %-D6SPOa ZQ(Y!I*5GA2%N#F"BM787674&4%5'.3+--+0 ]kT</@g*JNUump}_;P]tPJTHFQTT]ganpltx{ufn]a_T[ZW_d_vo{{ V g \ 0 Q */7%2=5H[Qy'# '62%*C5L5B2G4H3//1-+.//&.3# $%# }hAQ :VJRwZz}">.7a=ugmr{gs=D/0!,1$yx|pxuytoxfnijfgZTcXYRZL^S\U`Pgpgfnvt  : 9?cZ _!bVS"U&.Yo 764 !9 pZyOk{V$Dx8?.7%.S7dYdg)!$>,?@8?=,5'#~zNiswIXD[YXNCHL{KdFzR[Vqruz|~ " .$,)'$>$r{e;V !?*t;"H=AV?bNUpM{hh^{met\`[LGD3/'  (1*7ZGdjc~mnoaekh^TLV\X*T4_ga}u} ! wjspT_^W`kl{xEGFnf\vS4[LSD5.EkQ1CBCVI\SZgSmfcqeoidib[[WOOIBCB;C;>H?JJLTSW_ZZcF\J+M  cbfxjx awmFOWJ`ubyn6X),1[A@{v $%1)11*4$,$ $%WcN={}vy}~ *,.==:I<0D'  }    %/)<<5?@* g|QGM3;):aQ * JQ@ 1 \Pr6ZSf}%D6[b`qpj{qcxR]R7P'2+"kFK=%  *& , 2#*"  r4=- O9_p&'1L<d_m{~a\T??12-$*":2][io{sq}}|VcR#?  &'1614:!/"  ##  yn~yo rxd9N"  $9&n\z .#J<NqW|~vgnab[RZILG:A.40"/&,10>;FKKWKYRDV3?3+(!->4-<$,**  4,1H76;" xi~IJP7@SIWnd} ='OQFRP;<2#8--8$",0a?qpeztdqdSi:K>;# #% #$,8OQg hfldYUA/0y qzok" v{{{yx}x{zxqwvw1(M@TvW|r|YbQ3D y   , /8IEEX@NH/>!  #$4:@IKJQLFL40+sxVZf`^| :>:RSDS:9 . !=">#7(D/B+)83<?ILNTUSY[W^aXib_ g [W T F;;#&  346-L"F/R-V$W*](a_fa_ fHXF(I/2&qXWX>EM?Q!e'S*&~0"%[zLJTDJZP`|v )3pIwwWv0E2  !2+09,&2 !%1EKdo{muc<R*!+) )=-6N/WI?R<44^r&&02+3=, ! "( B 5IIQa]|~ {hj`KV==:,2 #!!   )"33187.5*&  #!-=6\Xcstzoseqtjx]l\?G4'mq k<K<-<@.[MRr_twov}qwyot~{< _Uf}euKEH78F9AUFKIGF=G?@KCLKFHIJBOYKjip ~ zs ud'g$a'S@Y1P=O>O2N/P/LQJCM 3:6#,  .K:jdjm|yli]I@1xxtWdeZm &#!  2D>A]Sjfjuj qr e^a`X][[`_ffnwt $z%%3n0~/g7l0j([1eX YX SVGLD5?)&# !)-$<37I= UPU,eYj(j`hlP\H*@   /(FFNaXrnpzl}vv~n~ss~m{|sttjglia~znojVYPF?;86,15)>9@SK\`fqo   ezY]X?F."   /!)Q8 S*K!G%W<E,;+82$ lhrgn~&$,&"*+((&$++<'XQJe_W_ZPWBID2;%*   !,-.32/4,03299==@J =C C4:*F3?4@1D1C.F*J*IJLJP KMQHQJPZLe\^n\baVQNGBEGCOMLQM/J(H.ETB??P9[9L.I$S((3   *+->6@<690( %C:9WONRMGKFEC><:7;;7K?J`Logkx|~v\h];Q.  #(&,5/?<@KCWP\o ^~! )',-+2,1.(s1w i&\&WE<-", !35/43!' "&'7(+ 0?L6'6  +LAGt\`t[FZ# #*.(4A8GMEJS6A 8 1  #"*%"%% $4'75 7A:@@==76//0'-$ 0*1J?MTP%QR%J+EB< 88431.=1;qM9jTfSoGgNiK|5z7( jxpQjSOgh/a")1'#,% '(THQ]QOgIQT>CNDRcUyps}yozQ\P4M&3&|tx,32BC=BB=CDDEEDGB??:=;<?<<<:76/-,'"   (150*=AC56@@6d)K?nm$i$wjl i[ TQ?>7), |{nrcPX8645!.' 7*+EfT  L @#kem {Zy+  '533E@FJEBGECC C@39)  %$ D.#S!N&O<d4U6Z?Z6X/V:P$Y'V(T["Z\X]b[ kd hr ewptx  uzjRaKKSMR_\`jcqbr\lWIUSMHD>CB1A!:(UF R mW| wx  &z!l1q"\%W"YMJ]L_z`"" @#1mN}x}ms_DM-)(z~h~  7 #RRUj[k`^fT^ TQ VG PIIODWOR`Tdbahbgceg%]h+`+[(^0R,Q(K+B<3- !%(#*.%0+#4u| '.#&n-|c#]c[Yqgx    )'$/+))-)"6,; UB`^`peplh np #+.(23%-&%uq Tm9 >8)   .)6_Odkfpijnajcwbs]l\YUTSPSQNRQGPG8A&& H2[bXfh NY Q6B7,69*C::K;BE3    0)2D8bSd{qqcIO .)"  (&  +-/%'% ) *V 1 am}b  #9#3 ;'^'GZfYT ZE?IID\Z`snxvow r kwt yVw 6(=8,  .%#?+B><IA@?<8545,!#$"/"16*%,4 * % =.BgMwzq7-j.y3g%I`,00   $-70GF9@>-08 * :N= g\pz fjZ6G&)+'( %       $!$   4(8 _HZfT#B   :('5 )!0)1.+5R5?+k3r*bh+lRWK5 @$ $  z ikTWQ>C=8?C@XR^vk  !%:0KNO\YT[NAL)/ #  3 HFCXPNNLHFO-G'R+_MR9rKmNn??u?myaK` -!  4 3-K?60V R%Ma WHQ: .$+ [ Oc  zpGZ..%{kj`Q[; B?)75.<F>`]g~q  %    !//' "$)""(;;9LI.B'  *-+%(5$FM;EP- "$(""& dbaJPHFC?D@;CG@c^g~xwt jlljgff[^WLRCAC;:=:<@=?>BD@WI`}b      0#6;148#+ DD<XR :D.= !TXMb_=M3 $ ) `K`t]P d/6- " !' $= FCNW8I6$ d tM O B $ 2    !(%(!-+332&<*+4+.#(+'$!&2+/664;;: DIF\R_ue+11C+9/' !., '3:.%)N+U#L[^=I7# ( 77 57965 <&D(B-<,L279 48==7:;&,# p}a^e[Zrpt }sxfcbWT[PU_Pd^\f]b_`gdgkggmYf[H`EGODI\Tezk    !#$ 1 >,<Fk ]_lU@O ,& %&"!/ %%'"7:8V T5Q)#N fG6?;"1B9!;I>J L E HL @IG=KB H K H RTV ]cfjoj f i TSN??9466104/,4.7 ;:!IIW%[\m\mj\uVddQhS[^Uc]ampu' #4, 3B/"2/.*62&-$  # !6+'*54/?$ $%*'-3$,%'   !!% $vvz|    3'4Z,D"e+k8]1e.i9@"O$9 & ( 3"94<9. 2+ % $*&&% (#  m|igtsq {zx|wu} $- *& 4-*,&     $9:9KJ=J;&8 '0+)-# $ /-!% !33GE;89$#  "    s~p!$# |lso\mR[VDWB A LFE^U_!vf"#} |!#|yfnU\T#?P*K*H(Z0j0m-31,303;=<NJVj_x|uiyfHb)11%40$(T*O0Q!pd#_iVAO*'( * 1"31$:)9/8,( z~uz|&y*~(/1'.)"~v~ptpaig^s~l<-G GJ YR]_[]\LTH4A',"xpqf[nZe} c&0,0=4NFNeWjpfhjPXN5C 4, 55 /G@LdVlslothljci[c[N\(A"J%@J4).<537.$&  #&)#')!  ~nkdb\Y]GQN=LEERVWplz ! ) % # $  5-8WDdc]je_b[TcZxCZIZEb7WA\+k6e1q"3|( *$!-6-' 0':B&8)?%@I+F/L)f` _eXR\ '2 #538KEmHlPoGZ>cFg/_1j+th!~   $ #    x[gFKM=IHIS[^lsz !r~^ZYH O ]Od{c"88<VOV_R L X* 0.& 0KN@RU!0!! "!     pwkM_DBC:B=;ACC^Tmu 2955G9@@7 58'))"+%-9.A?CMAVJL [H'O R)DJ=7?N$Q,I PP@G9%1 %%b}um.(+,()+!'   #&:39@93;*,(!  oqc9H&!&1-A\Py   xejnYjd`lok  % '3(893:8#* 0 - ",$(%3#)  xvxnoxu # +aBmmbpg_[YUQYYTZ_ KVL7H.$0/ $ (",P?V[PVX5C-    %)&D1"HMEJI8{<2c'}wv#&("&.m SIO78 P=Su*_$413?97> .! + jz\[\NV`Xfv n&'/>8VR`yk~uvsijd`^ZYTTOJL?@;24&"       $(#!+&())q)'i,t',n3*=:9EB>D;28 !"*-*.-)/,,..!#)-1.7-". -5 sPrmM^:@=)6'  $>)QHK/d'Rd)b]d]\dZWgCJH6G9@?6D5>6&8' !   $$<-EFDQJAJ>.9    .4"<1(  qnuV*_#/w3($)  uz wny!u =!QPMe\egfhcdh`^fLOM;BF;CM>QOKSNKNKINFJLIMMJNPLTWUY^R_VI]9B:- ! #  !        uk$A.kgpy`q03& @,aW`m {{*q r(hD\3aG@JDC3FD;;;42:49C;IGBIF8=,&0IRBTX6D2   #O$\$_Cjf /-4UGghZfmJQL3>/186>>ADGMKMQON\T[iZqjqs i|i?\  !!)&,/,-/ %   0!=gEU4 -&2U>X]RTVMMPOQ XZ\befflnl}u{v~xyyuyzx}~zvpm bd,eU6k8^4`?n>Z3w9e1i$|,_zletc`_OCC , $');'"8 #% 5 * " \mtX{ 2NS[wk ~tyr_f]WYZX]X]`Vkbj}mz~wnved]ORH?DD8JDDPHOPQRPWRW^Vhdk|t{ v xs[IC&  |u /%<G::@"  % MB Qv ^u~pcbUGK453'/$!'&"$!#'#-217@6DB9NDJSQZ_deflcegcdd`^YR _ QU` F\Q P^ MXRNUPJMG?B>>@@CEHLQTN]V~J{]CsNI9~N!8#%{z|Zaxb{ya 0   nl7\3 NFSu]}ksYXP9<0+)#   +(057C;IF>S3C A*C*+2$%6&4I0VHLcHkZWkO]UR[QZWYcaijlqkafUEQ*6) '~    #-# % ,\: |dhKAA9:CEDI OG%LG>F7:1'4 }mxply|s  *&59<CDFHLNN[^]dhWdYBS:;8,8(,4&26<%B>AL3A<(9&jnXV`WVr+pXfaIc)**  *9<9FGCG@7?5058688446''%#)$.  &).IFISPR`LbZCdGRZRcTbZJ_+>/, " 9^] [}sr }gSb889 '3-5KG?U@%?l}felfjpntyqz~ ~| 99Ac\$c$le/b'hMVK3D,1/$4#12%8, 0, & 1  -"7A328 )697HEMNLOJ=@6(-  $"%C?6I9!7  *,2<@FQI WV M ] JUPCV5B: 6     !%=5DRJJW:E>';        &"+-$ ;59XIUSKLF58(%   #D;ISJCH4;2% -.3NEHPF@J343%*!" |$!&.) *: 6!/#<"&#& '%I-TQL\PYVQVSFK<*7,50;<29,* ?52@:"+(aH[p^Ra9<=)1*',-'%2!  '-NB_mbfvN_T4O&-,  |}~{zzzfpjhn pq{}|w}v*k,v3g@g@nEjNkJH}LF=>1+$ !1[Mu~ugw]6R       }w|{x}uxp4b"g;c8[7cCi7b=|6y57*8.1>-;7-/($ )DGKfZZjWE\466 0 .G8SYHIQ*7!ju_d[ HT7 8/#     $"-;6GEGRNTXTU[W\XS_R[]Yi_ltvy}cpnfr}w & *&=+,B16I(AI9`EZZGe9H?6E2O\OPR*0!!A.KJCSO0;(zup|xbjaK^DKL?NCMOK\ K`^M"jO^!^KaNPVJKUFU`Ch}  /#3 *28.P1GI4S6DF6L1>=(9&%$T=,.!F4YU[uejzbUn*<% *"%~mvl|{}tq{ckiXb[ \aelelrs~v&#'-$ * % %   "&= 98@6Z/QUW.^LPHF*>Q4.(AAG~pvs`v4C,}u{wl|cqm\uUdbNgJVT@ V=AD7?::=>=8*> M;J=F3%*  visqWiPMXQOrcv%$*+0&(3)mpxe  s, >ACT MI NA28 $2 ;D=!C]I0MNVByxMq"g Jc{xv~z  &rkkn`crYse 7*1<5 +.  $ )!(- =64> )-   JgONQN\PWc`d|t $-0.:;9?@>DKLPVVWVSQNE:0% u" $4,|79599.5)+~| !_268A@|2=. ( #4_\y"P9_84KA6S';50!(5DJYhktrv_z9E; "{tvMxNXv3[eP v %'/"!   h|un,./$'(&!,40RNYvpzmbv^fwOy]dLh` Vh^J(G#8.,,",2+,."! uv077OO;N8! ;>*8&[*S78$R3/)+. *'5TJnuw|{]gXLRM=aHXwKsqdjUV07 }t)$/)$#* 80>[KcidjhW_S@N+7*'" ! &135 E GJNTUR^e_dnLdK#J0 )9!#+ - ) 0T6vksltV\K4H'#C#(r6jcN`R9C)  &"  673F>*0  -#0438<<E?8G498.9&0'!  ! 7?@NS?M;!93#EB=OI4<( +&% QJXzpwnN]86- 117]QO eSB_#5, %'#%' +$+. 3).#-/>CCIOBGI@CH;BJ5N;;H*E0.B'?6/;//0)$$!)'+5/<:@LHXZ]dafeci l^h^O \5 @. '!96:J@EG!;%488:5D2": 625RGITB5C $!,OCbh`moIXK&9 58#<'%'& " +' ":'>'6&>&C&--5*(195IFL[QX[OIK33- 8) &'.'++-)4?99I/99&1)%%     !"!-:3>TClil{~seo=E2+ 35GJ4D0-/=F=GI-%HJNxhgteUa9@5*  $   ""   /16RPSg]ToIc^Cl7LK)L,3;/</53+5$/(" "443"" 1-6;72)((B72F20++9HBV\V]fL\Q8O-7., wwy}}  .2=QRanhl{XjY9W&q|tmx |.%, =`a]{vjUo&2&  " $!(    %&5:9BG,9&~~}u '8:;ONHWL>Q3?;&=*-4.537747-10'+*(*.,&,"lrfw > MVISVOUJ@O$.  82 TPVmbvsrwltc P ]7@2+ #"    "2.::8H'>+)py#X[bORp]U=fxgjxN]O)<  #8 'D<<PCINMLJFMA3:#$#;8/;5-=#00 2'00' 5 '3/#7& ' *)$/  |~d  ! !),A0DDFOHXUYdVS^K9E yt|ogj|r       % /)(5' & &1 F-WYSaYQSJ>=#$yy   ! !+$6(, 9N!H TZJCM',/0E:CL<5?  'CG!Ffsdrd Pb$0   |upvcl]Z_L`PVfNtanu  &   '!* + ) ,$2Q&;+G*;$)%0#!!  8GO:GP,6!      %+714>'1+  $(-)'9!UI$f mb hl GQ<,v``Z:=8"3? [HXf )&-A.@A1I8?E=G@B?9?162(0(*+%)*'&'%'#)-$6,/;);9/75-#$)>,7+C0N1L+H%)+5-73)6%  uydt{ &"$ rssbingsz)& )/& #  rtrQZjRhb %634G>AL=4F*"   !!<39 B/'7  > 3D) .3.=<5H;FG?Q?JJ>N:EB2C3;=7G<JIAV@PQATCMH<N-;0,          ol`onb) "3%.0((-#!(%&)&"* )((4#/3)<4=?<H6D;*?" !   ! %!)%(0(545?;DHGMUHWUH\LWWNaERL7N/69&0,-025.40(2 '#&   ) 08249'+!  r{Xs`Zli_ !%) *%" %,09<BFJNOQRROLNFGJ@KBEQFROIOOCI<.< "(!'/(%."#""!## !   %(!#&     "*+',*  vu{iny    #,933>)1/'   $3#GRABdAe^XlceneaoM_S4R$++      &#$?6<H91F#-*.$ / !            /! .           !%/9@M^X^yJiV,\! 3B04S3PJ@I=94+!  gz" #;9ECCMJE IA;E(-##+$25*40(:)55,A,35(2)(--.689==>@=;<0/,    #)&#!   |iriPbIGNCEbP`~k , , - <=9EA< K6?B:I ?MD:S4% $--04563.0($" &*!& ~0 A0/Q3HD;B70/( lyfbkjx $$' (3.8I;BF2(/ !A6>K=3A(%!!*!+$"   ,*1;43? *#   A+GOKY[IWL8I%.%#   #/0<DBEN;F=,>"'" %/ 73-=817.#+  }tz~x''J9HLCEI*.  "%'52:<3./  %#$3.04764GAK]Rghdlg_]ZROPJIKDAC7,/#%6F,C6<     #1&>><JKFKJFJ7F8-%JSGFeLca[e_^`ZSRGE@750-+&#$"!#"$#!#$413P,H@A !%    # xyz}{   !    5$AE?JLFQA4E  /0# 2 (!*  1#(     *40-:202'"     /%-7.*2  !) 3,,9//2(     &#!1$$ !     +&  70;I9/>        0*.     "(&-4&40#5&!'   3+>B:CH&6   {~vywrzrxzy  '##+-&%*#!   |utrpuoxzy!! #  ($ # #$*#&3! zw}sr{ ))+2.:' * u~x       wuyjo~x-3&71#<,+0$&)!#& "&!!'!"%!  .?A9E?16/ *% (-(356:787774877:85961734557/4/(.    s~llrkp~ % " !&")*%,'*+*4,33.7.01++&"           '/05?5=:/8('& #'! %"   " (! 0")("    /&-"#   $ ~}u{~qz  "!#    ""!&:0=@=@A=BB?DADECFCEDBD<=8/3&%      +1;/091"$        "  ##)  *"2()1"1#&13&'9$830<46<20>0% *  ,*%.%' #/5(-?&G:;Q<PHDM@B;31* "  ! 2,5P@OQJKJCEA<@/6-(   '%)9/1;/%, @'[Y[wgtpjgW_LHN4?3+-% $)+!-*($+*"+' /"+*%9 2.5! 7$;D98:& #5/:?45>!ka_fUW_U[dYjbergxxx|     "@,GJAMP'6|vqp`c_U^TWVS[NZVL]FVTDaFXZKjZiph}y   7-$   p|][U>B?/;B/cM_d    gk\LNK:H@@G:MCM^Klfko}     :(10,-&(%%'!(&"#    {v        )!#7,9<;ACCFJLPUXWX\RPRLEKFGLDKJFJJAJA6B )  "&/"# 2CBHaUgkjrtvy~wpj_VNC7-               &465CB>GA=L2F<%G,( -   '% #%    }~u|yy|     & #   |z  ($&.(/&(,, *$!   % }   '#"=116"*#3)$;%4,+2#3(+7(B29M1UIG[CPHAE7:4**(#njdrmi.-:SIgheqping^hXYRJT8A6- pt'A5ZWb}kyqmdYRJD?722$"    )*  #"".15?>3?+){kmoY_g^iwj +$2:437(-$xwy~}zyy  #1suuRc\Yehf}    *++4-,-)#       ')""&+%$"    4 *&         !")"20*A';7%E50:#% *!      , - ,-$1"&  &20 92+  ! "7679/&& %''##   +AW!b_% ZL?2( %)&          ~{s{}s  {     |mc\X^g{  %"?"( %  #' :(,9 "      (, aprYm{VjwZ|em #    7/ =zvn~    .22vlhlMMY>J[5nS\\z{{#)%2'## $     ( jnuXj{MfmMquk    |t !"'  ''    pvt #"  %%")."$2 2.#**!$ 6 )'.    "-5 0+!.$6$  ir~  ,+'/*$% - /2+&9/+6*"9-% 3 #$+    ",564*% $*?$9?5;A*>/=%)0?OL H: /$'"-!#!$  & "    '&"*$$;4(< wir '(#    ')/4<KA>.@<#?)B)@ @ W'!|68<G?Jq*9G,    ${kX!!)= 92aJ:tA.x- qf#_.O ')#' (* 3 4CVWJ:`QGdL:lB5E'zH/6A{ '(4"vGe-..zmuz~[{gD~3UT'^'@?E'% tv1|{ zs5vuv vw{z}  "& -9 F&)E"BY&[Zl))C $.-9*R=R`Siqi|mz^lpDac;giQ!+  !((%' F(-P1"Y02X7ko X]MYz*Rb5nLRcSVjP`sE[dCfkLyyenwy   !-&73b9wuz.@ /)+3P*=n-]hVzy      # $g=|q~27R)2J2E/UKU`^ihlomrnonkpspxzh{NY.{$vw."!+ zUdVmaM~SiI '1 <=GL**WV>8U(H0(3 (  $    ,+$(44)  stu  ) #$:46$O&FC(\"B=I', . $;$B0&* !' $ #7 KO3/\.UPG\SQ]WR`ERP<L:85&$  ,284O2KD*L+) #  !$   $   ! #<Qc#qq2,r g?4g+[I@W5N?>J-N56I#L/,E:&/  6')'1(A ;) z w{nw ' @581A=7H>8U%P?bE4j>*o=ktgg  +{qalv~i}oqunrrotwtw    .D ^eE@s@jeVf]LWL=R7HC0M(>7D'#2("G68 \?GT*1@()3#"03A?7, *D8  9 %W)YOE^MFQ?.A'!       3"N+0H   !$M"`?Cd&h7?d s%0t}.*u4/vtA>|%rPDm8]KDYAUVLSWBSG9Z'K?` H:c*-F&(  $2O*4k6][@HF(ys|chrPtZ]sI}ZaG_bAZ]7acTD" &Y2>z1pHC`ZG: }ooq^keXnRedTv_o}v +'' ,<)>H,+B`,*GQT'9C/RJ^kr4)  3 !hnT9Z3$!{rWoH_]JvGriI(,$   |*2-'"+    ' 4,0+|u . KS,'WN">6+) !  x4*  {xz  +,1,( y~}new     zw-J,<O95Q7%utwwzyu   *.3-Q4SQ=n6\X3p3WW2l.NL%Y74 :ppt +T;B,04% ,/?EGWQ]\WkNeZEm0VHb@2Y("E (    &31)<$!06 78Y<<A  ~ /J5& *'29NI^d]pcjj\aYSOJC>30-)'$ !9!B !I*2S*/6    ugeSVx t@?z~=BFL,ojcv`Qkk.W+!'+   &# .'   1.*"2$)*(*,389@H<NF:Y-HA"O0. 0#%"F<7q+dY"xAD@  t`KHkoFNs{]r   #{oaP<6w}/@sNer,+ 9   ~ {w    t_wvJrMW[G[DOI8L0:6(@%::.W0V[BxQhubux~zw~m|w / A16X7SKFQ<J@:A27-(,!# '7 T `:4e&R</F"+& " 4#K#M@? U8THAO:85%   #"##!"   #0.% ,< b {BJ.a_Cqla|ucprVggNdbT}me|behUR^5M=H ,(;( ( '/& nyrLhGIM?KBB@766(.5$B6=S=\PTfOpbgi})6%$1 " ,+ ymlbZUOHB<83.,*--/;@JT\gtw   t]GBdoH\}u~ 6D4(9      m[}UQz|SX_ZRF{|?Ekn@B[am%W6-M99'4<#=/=     *0,(2'%!"  !, &&   ;.9a=mYZrFpQNd1Z33HL JY bk"!rk6+mYD1T:>M;8TD6 F% &.  xyrinndzmvo}}x~hzkn~h{#1"H vuqwfsjisk|z   qoy|ov~sz`|NaX9\,>=:!!)!%-(29,I:BY?dWYnTuno '>2GIGMJNKLOJSPW`Zinkqwnyxsr{nhtnYlOUP=K24-#    **'*&09>FJLQOTRPZQ\ZVkTlh[MqhC;bW'n?5D :=1(6(   "#63*/#'A7>V=WSO]TYVTTMVLNWJ\X[jeqy|slWwH>`[.q(OI[ .*/ )% }lliRhRViIo_]tXnf]dWVSMLLLOV\dpz   !!# *%*7$+& kri|Yk_W]HPE?C8@;>JH[dk}(.%"*   }   %#+/...''  ~ ~nZT``F[FHNADN?IU;dOXwU| " !)!#$    + %# .z{bSlkMx^ctjjt}v '   |rsnQiKK[1_DFiEnhir~    &*)03(4*.  ) { }.-+G,FF3Y5JJ3R.;=(=&52#>%01 2!t   &/(!    "!*0!,8C!>&   532O=GL@@J2B=+G+9:&9(&-$4*?O6:Z0]GHe?fXUjSfa_ldprswtwmidRU;1. , -! "0 74' *)8        (&0+}{told\VOD>:-//6$,@$L8=W4`HNi:sRW~Jts !  56)&1"  ./6H4GF8U;IL<PBFMHNORRPSQQQRSMSH>M$6')     %G%IEAVMC\C+P $ &.(?6<PCUXV``P[L:M!0!   ', {w !"+!!  &/49=@<@@4F36C&@,)60!17'%;&:95@?4B8-I ;/N6*K    ip\MSK6X:Fd6sVYTu|y (!( ! uohvw}`de6i:Ci!zHR;uvlw*%<=5R:II9O6@<*9"   &!&    #2 # }~wz '  y$!2)*    z} +!% " "  bc`wQq[_vSiup          &% -66,=53122*./&"%     +))/##      #%/(<.:4$1 wz% "0(%{zmtvmu~ !      $*    2(6744("  .4#*}s  $(  )#30(=!.,'"!'"/'0;'G=;O=AD4. 4   0E?f[[oY\O<. 1EGegquymiid_d_\a^RWJ7?)9.Bz !."6/#?-"sy#*0"2 {|{93?VAVP>8'&@G]lrtXn]1[& 4670=>-?0<    q[^Z?K<:=65=3;F8XHSoUvs{|qglY\XP^N]^Tm`lyu    {|XgY(H &":!K?B_Ndebjfjlkjklis}y ;4*R ;-2" ~eeim[icejimvr e^mr[uln| ~oXa_>R>N7]NWsWzwVm^;k0RO,g,LX8 f ^f    &'%@?2G-3    (.9A9?A9>:41,!    $')0.;2)?&    !'#  115D??A923$     &"". "    .D)3U![;9Z"J4'<&% ' 72=/O<EL?C?4-#   $'$%&      '$&'!* .H2^OSoOeaS[XFWE1S;1 C!". #    ! %  ~   ~px   :%:F9<L(;, &    ) 8.#3pn~fvv|~{ (&(C58;(!   0#01+*&!   %',4)*# p~dit  $  2%;8@M>aS^z_}}zr{jVg:H96j~xj7(=6,-r}rx#H+XMPkRi_\eQhSVeAePOfD^SJVECE4)1       '?1DE@D@;>813#)  ~xsmrz1@KjlkreFaP"P  ($C-MKK^VZ]Z[\MWH6Eg~GC=$" !-+,2/09-24+02+5:*R=LjCjtz  xpwhd}}/!&TqJBH60<,1=$>31A3=<49;-4/#.))473G?PfR}s~  xy|iwytsru{sx{        w +uykxut|oRj#<"+--)*441I:LdIp}     .2 -}           .62,,7J>MJEG<=1&"                 $3(-E/JCAQCJKDEI:C=1A)62$;-"(    x  &    # (*')&"&    "%'%0)22-:%/&!   "  }}|||#,5.58"- {    <<BfVZ`TLN/7${}z  *-%15* 68:OHMOKKLEG?6:!$    ) 0P?fbZnfHR6" % (  .!,9*!. .CECVNEK=.7     $ 4 0-"%  " A:E`JafQQU( +.% ..,+880B9+C!-*& + 1$#8!31+31+-*$&  *)    d{\_bT`_`dfjlnomkhbdd\g_eo_xoyy     "B88XH:G$z~nmi]_XRQNJNJLNIaYdwey     !-,"(  ;%EI8AC  ytttqwrwvszmnmejnoqwz  1)-2% )     7)"4 ""%:09:7:8.5**     $#   *#  .-$ )             %-)('-(233;>=E>8E*5((      )#       #   *&125;5=947/!%      #$% $"   ./$#6 3'&1*  &-&%,           +#,1*',   $$"   &.+"($    +**4-/.*'%$!#($/ 4,-7(6**0,#% '$&     * ;>.+>/&             }y  (!"'    !&      ##"               $"$                 !"!         $/#&9%<63?58=52:'3+1#!*  )             " "$! !          $)$ )                       !           !!!$     $!""                                                                                ssr-0.4.2/data/impulse_responses/wfs_prefilters/000077500000000000000000000000001236416011200220465ustar00rootroot00000000000000ssr-0.4.2/data/impulse_responses/wfs_prefilters/wfs_prefilter_100_1300_44100.wav000066400000000000000000000004541236416011200272160ustar00rootroot00000000000000RIFF$WAVEfmt DXdatazgS=% gK0}0Kg %=Sgzssr-0.4.2/data/impulse_responses/wfs_prefilters/wfs_prefilter_100_1300_48000.wav000066400000000000000000000004541236416011200272210ustar00rootroot00000000000000RIFF$WAVEfmt wdatatcP<(nWA,},AWn(2+)}+2>Oc|Ej8Skssr-0.4.2/data/local_ssr.sh000077500000000000000000000014331236416011200155520ustar00rootroot00000000000000#!/bin/sh # Shell-Script for starting the SSR locally in the tarball directory. # This is not needed for the installed version! # find the directory where the script is located # (in case it was started from somewhere else) DATA_DIR="$(dirname "$0")" SCRIPTNAME="$(basename "$0")" DEFAULT_EXECUTABLE=ssr-binaural if [ "$SCRIPTNAME" = local_ssr.sh ] then echo -n "Which SSR binary do you want to start? [$DEFAULT_EXECUTABLE] " read SCRIPTNAME fi SSR="$DATA_DIR/../src/${SCRIPTNAME:=$DEFAULT_EXECUTABLE}" CONF="$DATA_DIR/ssr.conf.local" if [ -x "$SSR" ] then "$SSR" -c "$CONF" "$@" else echo $0: Error: $SSR not found. Did you forget to \"make\" it? exit 1 fi # Settings for Vim (http://www.vim.org/), please do not remove: # vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80 ssr-0.4.2/data/matlab_scripts/000077500000000000000000000000001236416011200162405ustar00rootroot00000000000000ssr-0.4.2/data/matlab_scripts/make_wfs_prefilter.m000066400000000000000000000040071236416011200222670ustar00rootroot00000000000000% This script generates a sqrt(j k) pre-equalization filter % for Wave Field Synthesis % % S.Spors % Deutsche Telekom Laboratories % 9th Mai 2008 % ===== parameters ======================================================== % sampling frequency of filter f_s = 48000; % aliasing frequency of system (= upper frequency limit of equalization filter) f_al = 1500; % lower frequency limit of filter (= frequency when subwoofer is active) f_sw = 120; % number of coefficients for filter Nfilt=128; % ===== variables ========================================================= f = linspace(0,f_s/2,441*10); idx_al=max(find(f [Hz]'); ylabel('magnitude response -> [dB]'); legend('desired response','filter response','Location','SouthEast'); axis([0 2*f_al -20 2]); figure freqz(h,1,[],f_s) % ------------------------------------------------------------------------- % save filter coefficients % ------------------------------------------------------------------------- fname = sprintf('wfs_prefilter_%d_%d_%d.wav',f_sw,f_al,f_s); disp(['Wrote pre-equalization filter into file: ' fname]); wavwrite(h,f_s,fname); ssr-0.4.2/data/matlab_scripts/prepare_hrirs_kemar.m000066400000000000000000000033321236416011200224430ustar00rootroot00000000000000% This script is part of the SoundScape Renderer (SSR). It exemplarily % shows how to prepare a set of head-related impulse responses (HRIRs) in % a format readable by the SSR. % % The HRIRs used here are obtained from The CIPIC HRTF Database % (http://interface.cipic.ucdavis.edu/CIL_html/CIL_HRTF_database.htm) % Note that the above mentioned material is Copyright (c) 2001 The % Regents of the University of California. All Rights Reserved. % % Download the file % http://interface.cipic.ucdavis.edu/data/special_kemar_hrir.tar and % uncompress it in the folder where this Matlab script resides. Finally, % run the script. % % Author of this script: Jens Ahrens, 21.01.2008 % This script is Copyright (c) 2008 Deutsche Telekom Laboratories clear all; % load data set from file 'large_pinna_final.mat' load large_pinna_final.mat; % this is necessary for interpolation left = [left, left(:,end)]; right = [right, right(:,end)]; % time domain interpolation (note that time domain interpolation is not % most favourable in this context!) hrirs_left = interp2([0:5:360], [1:size(left,1)]', left(:,:), ... [0:1:359], [1:size(left,1)]','spline'); hrirs_right = interp2([0:5:360], [1:size(right,1)]', right(:,:), ... [0:1:359], [1:size(right,1)]','spline'); % prepare data for storage hrirs = zeros(size(hrirs_right,1), 720); for n = 1:2:720 % Something's wrong here. The distribution of left and right % channels should be the other way round!?! hrirs(:, n+1) = hrirs_left(:,(n+1)/2); hrirs(:, n) = hrirs_right(:,(n+1)/2); end % normalize hrirs = hrirs./max(abs(hrirs(:))) .* .99; % write data to file wavwrite(hrirs, 44100, 16, 'hrirs_kemar_large_pinna.wav'); disp('Done.'); ssr-0.4.2/data/reproduction_setups/000077500000000000000000000000001236416011200173515ustar00rootroot00000000000000ssr-0.4.2/data/reproduction_setups/2.0.asd000066400000000000000000000014531236416011200203440ustar00rootroot00000000000000
    2.0 stereo setup standard stereo setup at 1.5m distance output channel 1: left speaker output channel 2: right speaker
    ssr-0.4.2/data/reproduction_setups/2.1.asd000066400000000000000000000017221236416011200203440ustar00rootroot00000000000000
    2.1 stereo setup standard stereo setup at 1.5m distance plus subwoofer output channel 1: left speaker output channel 2: right speaker output channel 3: subwoofer
    ssr-0.4.2/data/reproduction_setups/5.1.asd000066400000000000000000000025451236416011200203530ustar00rootroot00000000000000
    5.1 surround setup standard 5.1 setup
    ssr-0.4.2/data/reproduction_setups/8channels.asd000066400000000000000000000022251236416011200217260ustar00rootroot00000000000000
    8-channel equiangular configuration
    ssr-0.4.2/data/reproduction_setups/asdf2html.xsl000066400000000000000000000126231236416011200217710ustar00rootroot00000000000000

    Audio Scene Description

    Header

    Name:

    Description:

    Version:

    Date:

    Reproduction Setup

    Single Loudspeaker

    Circular Loudspeaker Array with Loudspeakers

    Linear Loudspeaker Array with Loudspeakers

    Scene Setup

    Reference point

    No reference specified.

    Source

    Score

    The score section is still in a very, very unfinished state!

    Event

    Name:

    ID:

    Number:

    Model:

    Delay:

    Weight:

    File: (channel # )

    First Loudspeaker

    Second Loudspeaker

    Last Loudspeaker

    Position: (, , )

    Orientation: (azimuth) , (elevation) , (tilt)

    Angle: (azimuth) , (elevation) , (tilt)

    ssr-0.4.2/data/reproduction_setups/circle.asd000066400000000000000000000010011236416011200212730ustar00rootroot00000000000000
    Loudspeaker Ring
    ssr-0.4.2/data/reproduction_setups/loudspeaker_setup_with_nearly_all_features.asd000066400000000000000000000070521236416011200307370ustar00rootroot00000000000000
    Loudspeaker setup using all available features This arrangement doesn't represent a useful loudspeaker setup, its whole purpose is to illustrate all available features. 0.1 2010-05-15T12:00:00+02:00
    ssr-0.4.2/data/reproduction_setups/rostock_horizontal.asd000066400000000000000000000137531236416011200240100ustar00rootroot00000000000000
    Horizontal loudspeaker setup at INT, Uni Rostock A setup at Institut für Nachrichtentechnik in Warnemünde where all loudspeakers are positioned at ear height of a standing person.
    ssr-0.4.2/data/reproduction_setups/rounded_rectangle.asd000066400000000000000000000060101236416011200235230ustar00rootroot00000000000000
    A rounded rectangle array This is not an actually existing loudspeaker array. It is only used to show the possibility of constructing an array from linear and circle segment pieces.
    ssr-0.4.2/data/scenes/000077500000000000000000000000001236416011200145115ustar00rootroot00000000000000ssr-0.4.2/data/scenes/asdf2html.xsl000066400000000000000000000126231236416011200171310ustar00rootroot00000000000000

    Audio Scene Description

    Header

    Name:

    Description:

    Version:

    Date:

    Reproduction Setup

    Single Loudspeaker

    Circular Loudspeaker Array with Loudspeakers

    Linear Loudspeaker Array with Loudspeakers

    Scene Setup

    Reference point

    No reference specified.

    Source

    Score

    The score section is still in a very, very unfinished state!

    Event

    Name:

    ID:

    Number:

    Model:

    Delay:

    Weight:

    File: (channel # )

    First Loudspeaker

    Second Loudspeaker

    Last Loudspeaker

    Position: (, , )

    Orientation: (azimuth) , (elevation) , (tilt)

    Angle: (azimuth) , (elevation) , (tilt)

    ssr-0.4.2/data/scenes/live_input.asd000066400000000000000000000015531236416011200173640ustar00rootroot00000000000000
    4 live inputs This scene creates 4 sound sources and connects them to the first 4 inputs of your sound card (if available).
    1 2 3 4
    ssr-0.4.2/data/ssr000077500000000000000000000014741236416011200137740ustar00rootroot00000000000000#!/bin/bash # default executable SSR_EXECUTABLE=ssr-binaural # add all options except renderer type to $OPTIONS: while [[ $1 ]]; do case "$1" in --binaural) SSR_EXECUTABLE=ssr-binaural ;; --brs) SSR_EXECUTABLE=ssr-brs ;; --bpb) SSR_EXECUTABLE=ssr-bpb ;; --wfs) SSR_EXECUTABLE=ssr-wfs ;; --aap) SSR_EXECUTABLE=ssr-aap ;; --vbap) SSR_EXECUTABLE=ssr-vbap ;; --generic) SSR_EXECUTABLE=ssr-generic ;; --nfc-hoa) SSR_EXECUTABLE=ssr-nfc-hoa ;; *) OPTIONS+=("$1") ;; esac shift done SSR_EXECUTABLE=$(dirname $0)/$SSR_EXECUTABLE if [ ! -x $SSR_EXECUTABLE ] then echo "'$SSR_EXECUTABLE' isn't available. Did you compile and install it?" exit 1 fi $SSR_EXECUTABLE "${OPTIONS[@]}" ssr-0.4.2/data/ssr.conf.example000066400000000000000000000063621236416011200163500ustar00rootroot00000000000000################################################################################ # Example configuration file for the SoundScape Renderer (SSR) # # # # Configuration files are processed in this order: # # 1) /Library/SoundScapeRenderer/ssr.conf # # 2) /etc/ssr.conf # # 3) $HOME/Library/SoundScapeRenderer/ssr.conf # # 4) $HOME/.ssr/ssr.conf # # 5) anything specified on the command line with "ssr --config=my_conf.file" # # # # If a parameter is specified more than once, the last occurrence counts! # ################################################################################ # Correction of master volume in dB #MASTER_VOLUME_CORRECTION = 6 # Distance in m of equal level for plane waves and point sources #STANDARD_AMPLITUDE_REFERENCE_DISTANCE = 3 # Default Scene file name #SCENE_FILE_NAME = my_scene.asd # GUI scene shortcuts #SCENE_MENU = scene_menu.conf # Default playback setup file #PLAYBACK_SETUP_FILE_NAME = default_setup.asd # XML Schema file name to validate XML scene and reproduction setup files #XML_SCHEMA_FILE_NAME = asdf.xsd # audio recorder file name #AUDIO_RECORDER_FILE_NAME = test07.wav # renderer type: WFS, binaural, BRS, VBAP, AAP, generic #RENDERER_TYPE = WFS ########################## JACK settings ####################################### # alsa input port prefix #INPUT_PREFIX = "alsa_pcm:capture_" # alsa output port prefix #OUTPUT_PREFIX = "alsa_pcm:playback_" ########################## Renderer type settings ############################## # WFS: #WFS_PREFILTER = impulse_responses/wfs_prefilter_120_1500_44100.wav #DELAYLINE_SIZE = 100000 #INITIAL_DELAY = 1000 # binaural #HRIR_FILE_NAME = default_hrirs.wav #HRIR_SIZE = 512 # Ambisonics #AMBISONICS_ORDER = 3 #IN_PHASE_RENDERING = TRUE # "true" works as well ################################# GUI settings ################################# # location of images for GUI #PATH_TO_GUI_IMAGES = images # Enable GUI #GUI = on ############################# head tracker settings ############################ # select head tracker #TRACKER = polhemus #TRACKER = intersense #TRACKER = razor # space separated list of serial ports which are tried for Polhemus Fastrak #TRACKER_PORTS = "/dev/ttyUSB2 /dev/ttyS1" ############################ IP Interface configuration ######################## # ENABLE IP Server Interface #NETWORK_INTERFACE = on # Listening server port #SERVER_PORT = 8443 ############################## Verbosity Level ################################# # Set the level of system information # # Level 0: Only errors and warnings are shown. # Level 1: A few more messages are shown. # Level 2: Quite a lot messages are shown. # Level 3: Even messages which can repeat many times per second are shown. # This is a lot of messages! # #VERBOSE = "1" ################################################################################ # Loop audio files (individually, not synchronized!) #LOOP = yes ssr-0.4.2/data/ssr.conf.local000066400000000000000000000011641236416011200160020ustar00rootroot00000000000000# You can use this file if you run the SSR locally (without installation) # # It is automatically used if you start ./ssr-* within the data/ directory. # # See ssr.conf.example for a more extensive configuration file. # location of images for GUI PATH_TO_GUI_IMAGES = images # head related impulse responses HRIR_FILE_NAME = impulse_responses/hrirs/hrirs_fabian.wav # default reproduction setup PLAYBACK_SETUP_FILE_NAME = reproduction_setups/rounded_rectangle.asd # XML Schema file name to validate XML files XML_SCHEMA_FILE_NAME = asdf.xsd WFS_PREFILTER = impulse_responses/wfs_prefilters/wfs_prefilter_120_1500_44100.wav ssr-0.4.2/doc/000077500000000000000000000000001236416011200130655ustar00rootroot00000000000000ssr-0.4.2/doc/manual/000077500000000000000000000000001236416011200143425ustar00rootroot00000000000000ssr-0.4.2/doc/manual/SoundScapeRenderer.tex000066400000000000000000000032471236416011200206250ustar00rootroot00000000000000\documentclass[a4paper]{scrartcl} \usepackage[sumlimits,intlimits]{amsmath} %\usepackage{array} \usepackage{wasysym} \usepackage{graphicx} \graphicspath{{images/}} \usepackage{subfigure} \usepackage{psfrag} \usepackage{nicefrac} \usepackage{comment} \usepackage[colorlinks,urlcolor=black,linkcolor=black,citecolor=black]{hyperref} \newcommand{\contactadress}{\href{mailto:ssr@spatialaudio.net} {\texttt{ssr@spatialaudio.net}}} \begin{document} \sloppy \title{\Huge Introduction to the\\SoundScape Renderer (SSR)} \author{Jens Ahrens, Matthias Geier and Sascha Spors\\[2ex] \contactadress} \date{\today} \maketitle \centerline{\includegraphics[width=.4\linewidth]{ssr_logo.mps}} \begin{abstract} \noindent\textsc{\Large The SoundScape Renderer (SSR) comes with \mbox{ABSOLUTELY} NO WARRANTY. The SSR is free software and released under the GNU General Public License, either version 3 of the License, or (at your option) any later version. For details, see the enclosed file COPYING or \url{http://www.gnu.org/licenses/}. } \vspace{\baselineskip} \begin{tabular}{ll} Copyright \textcopyright\ 2012--2014 & Institut f\"ur Nachrichtentechnik,\\ & Universit\"at Rostock\\ Copyright \textcopyright\ 2006--2012 & Quality \& Usability Lab,\\ & Telekom Innovation Laboratories,\\ & Technische Universit\"at Berlin\\ \end{tabular} \end{abstract} \thispagestyle{empty} % no page number on first page % include your stuff here \newpage \tableofcontents \include{general} \include{operation} \include{renderers} \include{gui} \include{network} %\include{todo} \bibliographystyle{unsrt} \bibliography{references} \end{document} ssr-0.4.2/doc/manual/general.tex000066400000000000000000000266731236416011200165170ustar00rootroot00000000000000\section{General stuff} \subsection{Introduction} The SoundScape Renderer (SSR) is a software framework for real-time spatial audio reproduction running under GNU/Linux, Mac OS X and possibly some other UNIX variants. The current implementation provides Wave Field Synthesis (WFS), binaural (HRTF-based) reproduction, binaural room (re-)synthesis (BRTF-based reproduction), head-tracked binaural playback, Ambisonics Amplitude Panning (AAP), and Vector Base Amplitude Panning (VBAP). There are also the slightly exotic Generic Renderer. For each rendering algorithm there is a separate executable file. For more details see section~\ref{sec:renderers}. The SSR is intended as versatile framework for the state-of-the-art implementation of various spatial audio reproduction techniques. You may use it for your own academic research, teaching or demonstration activities or whatever else you like. However, it would be nice if you would mention the use of the SSR by e.g.\ referencing~\cite{Geier08:AES} or~\cite{geier2012ssr}. Note that so far, the SSR only supports two-dimensional reproduction for any type of renderer. For WFS principally any convex loudspeaker setup (e.g.\ circles, rectangles) can be used. The loudspeakers should be densely spaced. For VBAP circular setups are highly recommended. APA does require circular setups. The binaural renderer can handle only one listener at a time. \subsection{Quick Start} \label{sec:quick_start} After downloading the SSR package, open a shell and use following commands: \begin{verbatim} tar xvzf ssr-x.x.x.tar.gz cd ssr-x.x.x ./configure make make install qjackctl & ssr my_audio_file.wav \end{verbatim} You have to replace \texttt{x.x.x} with the current version number, e.g.\ \texttt{0.4.0}. With above commands you are performing the following steps: \begin{itemize} \item Unpack the downloaded tarball containing the source-code. \item Go to the extracted directory% \footnote{Note that most relative paths which are mentioned in this document are relative to this folder, which is the folder where the SSR tarball was extracted. Therefore, e.g.\ the \texttt{src/} directory could be something like \texttt{\$HOME/ssr-x.x.x/src/} where ``x'' stands for the version numbers.}. \item Configure the SSR. \item Install the SSR. \item Open the graphical user interface for JACK (\verb+qjackctl+). Please click ``Start'' to start the server. As alternative you can start JACK with \begin{quote} \texttt{jackd -d alsa -r 44100} \end{quote} See section~\ref{sec:running_ssr} and \texttt{man jackd} for further options. \item Open the SSR with an audio file of your choice. This can be a multichannel file. \end{itemize} % This will load the audio file \texttt{my\_audio\_file.wav} and create a virtual sound source for each channel in the audio file. By default, the SSR will start with the binaural renderer. Please use headphones to listen to the generated output! If you don't need a graphical user interface and you want to dedicate all your resources to audio processing, try \begin{quote} \texttt{ssr --no-gui my\_audio\_file.wav} \end{quote} % For further options, see section~\ref{sec:running_ssr} and \texttt{ssr --help}. \subsection{Audio Scenes} \label{sec:audio_scenes} \subsubsection{Format} The SSR can open \texttt{.asd} files (refer to section \ref{sec:asdf}) as well as normal audio files. If an audio file is opened, SSR creates an individual virtual sound source for each channel which the audio file contains. If a two-channel audio file is opened, the resulting virtual sound sources are positioned like a virtual stereo loudspeaker setup with respect to the location of the reference point. For audio files with more (or less) channels, SSR randomly arranges the resulting virtual sound sources. All types that ecasound and libsndfile can open can be used. In particular this includes \texttt{.wav}, \texttt{.aiff}, \texttt{.flac} and \texttt{.ogg} files. In the case of a scene being loaded from an \texttt{.asd} file, all audio files which are associated to virtual sound sources are replayed in parallel and replaying starts at the beginning of the scene. So far, a dynamic handling of audio files has not been implemented. \subsubsection{Coordinate System} \begin{figure} \psfrag{alpha}{$\alpha$} \psfrag{r}{$r$} \psfrag{x}{$x$} \psfrag{y}{$y$} \psfrag{bx}{${\bf x}$} \psfrag{alphaprime}{$\alpha'$}\psfrag{yprime}{$y'$} \psfrag{xprime}{$x'$} \begin{center} \subfigure[\label{fig:global_coordinate_system}{Global coordinate system.}] {\includegraphics[width=.45\linewidth]{images/coordinate_system.eps}} \hfill \subfigure[\label{fig:local_coordinate_system}{Local coordinate system relative to the reference. The latter is indicated by the rhomb.}] {\includegraphics[width=.45\linewidth]{images/local_coordinate_system.eps}} \caption{\label{fig:coordinate_system}{The coordinate system used in the SSR. In ASDF $\alpha$ and $\alpha'$ are referred to as azimuth (refer to section \ref{sec:asdf}).}} \end{center} \end{figure} Fig.~\ref{fig:global_coordinate_system} depicts the global coordinate system used in the SSR. Virtual sound sources as well as the reference are positioned and orientated with respect to this coordinate system. For loudspeakers, positioning is a bit more tricky since it is done with respect to a local coordinate system determined by the reference. Refer to Fig.~\ref{fig:local_coordinate_system}. The loudspeakers are positioned with respect to the primed coordinates ($x'$, $y'$, etc.). The motivation to do it like this is to have a means to virtually move the entire loudspeaker setup inside a scene by simply moving the reference. This enables arbitrary movement of the listener in a scene independent of the physical setup of the reproduction system. Please do not confuse the origin of the coordinate system with the reference. The coordinate system is static and specifies absolute positions. The reference is movable and is always taken with respect to the current reproduction setup. The loudspeaker-based methods do not consider the orientation of the reference point but its location influences the way loudspeakers are driven. E.g., the reference location corresponds to the \emph{sweet spot} in VBAP. It is therefore advisable to put the reference point to your preferred listening position. In the binaural methods the reference point represents the listener and indicates the position and orientation of the latter. It is therefore essential to set it properly in this case. Note that the reference position and orientation can of course be updated in real-time. For the loudspeaker-based methods this is only useful to a limited extent unless you want to move inside the scene. However, for the binaural methods it is essential that both the reference position and orientation (i.e.\ the listener's position and orientation) are tracked and updated in real-time. Refer also to Sec.~\ref{sec:head_tracking}. \subsection{Audio Scene Description Format (ASDF)} \label{sec:asdf} Besides pure audio files, SSR can also read the current development version of the \emph{Audio Scene Description Format (ASDF)}~\cite{Geier08:DAGA}. Note however that so far, we have only implemented descriptions of static features. That means in the current state it is not possible to describe e.g.~movements of a virtual sound source. As you can see in the example audio scene below, an audio file can be assigned to each virtual sound source. The replay of all involved audio files is synchronized to the replay of the entire scene. That means all audio files start at the beginning of the sound scene. If you fast forward or rewind the scene, all audio files fast forward or rewind. {\bf Note that it is sigificantly more efficient to read data from an interleaved multichannel file compared to reading all channels from individual files}. \subsubsection{Syntax} The format syntax is quite self-explanatory. See the examples below. Note that the paths to the audio files can be either absolute (not recommended) or relative to the directory where the scene file is stored. The exact format description of the ASDF can be found in the XML Schema file \texttt{asdf.xsd}. \noindent Find below a sample scene description: \begin{verbatim}
    Simple Example Scene
    audio/demo.wav audio/demo.wav
    \end{verbatim} \noindent The input channels of a soundcard can be used by specifying the channel number instead of an audio file, e.g. \verb|3| instead of \verb|my_audio.wav|. \subsubsection{Examples} We provide an audio scene example in ASDF with this release. You find it in \texttt{data/scenes/live\_input.asd}. If you load this file into the SSR it will create 4 sound sources which will be connected to the first four channels of your sound card. If your sound card happens to have less than four outputs, less sources will be created accordingly. More examples for audio scenes can be downloaded from the SSR website~\cite{ssr}. \subsection{IP Interface} \label{sec:ip_interface} One of the key features of the SSR is an interface which lets you remotely control the SSR via a TCP socket using XML messages. This interface enables you to straightforwardly connect any type of interaction tool from any type of operating system. The format of the messages sent over the network is still under development and may very likely change in future versions. Please find some brief information in section~\ref{sec:network}. %An example how the SSR can be controlled via its network interface is the %Python client located in the directory \verb|python_client/| and the provided %Pure Data patches. \subsection{Bug Reports, Feature Requests and Comments} %For a list of known problems have a look at the SSR development website% %\footnote{\url{https://dev.qu.tu-berlin.de/projects/ssr/wiki/Known_Issues}}. Please report any bugs, feature requests and comments to \contactadress. We will keep track of them and will try to fix them in a reasonable time. The more bugs you report the more we can fix. Of course, you are welcome to provide bug fixes.~\smiley \subsection{Contributors} \IfFileExists{authors.tex}{\input{authors}}{% For a list of contributors, please see the file \texttt{AUTHORS}.} \subsection{Your Own Contributions} The SSR is thought to provide a state of the art implementation of various spatial audio reproduction techniques. We therefore would like to encourage you to contribute to this project since we can not assure to be at the state of the art at all times ourselves. Everybody is welcome to contribute to the development of the SSR. However, if you are planning to do so, we kindly ask you to contact us beforehand (e.g.~via \contactadress). The SSR is in a rather temporary state and we might apply some changes to its architecture. We would like to ensure that your own implementations stay compatible with future versions. \begin{comment} \subsection{Version history} \begin{itemize} \item Initial release: 0.1 \end{itemize} \end{comment} ssr-0.4.2/doc/manual/gui.tex000066400000000000000000000302671236416011200156600ustar00rootroot00000000000000\section{Graphical User Interface} \label{sec:gui} % Our graphical user interface (GUI) is quite costly in terms of computation. So we emphatically recommend that you {\bf properly configure the hardware acceleration of your graphics card}. If you still have performance issues make the window as small as possible. The smaller the window is the less is the processing cost. The SSR GUI tries to enable samplebuffer support to enable anti-aliasing of the screen output. It will tell you if it didn't work out. Check Fig.~\ref{fig:anti_aliasing} to get an idea of the influence of anti-aliasing. % One day we will also implement a variable frequency for the screen update so that you can slow it down if CPU load is too high. Of course it won't look as nice then. \begin{figure}[htbp] \begin{center} \includegraphics[scale=1]{anti_aliasing} \caption{\label{fig:anti_aliasing}{No anti-aliasing on the left image.}} \end{center} \end{figure} \begin{figure}[!t]% place the figure on top of the page \begin{center} \includegraphics[width=\linewidth]{screenshot} \caption{\label{fig:screenshot}{Screen shot of the SSR GUI. %The current %loudspeaker setup is a ring of 56 loudspeakers plus a subwoofer. In this case, %WFS rendering was chosen. Both ``Ambiance'' sources emit plane waves, all %other sources are point sources. Source ``Vocals'' is selected and the corresponding %loudspeaker activity is indicated. Source ``Bass guitar'' is static and source %``Guitar \& Keys'' is muted. }} \end{center} \end{figure} % \subsection{General Layout} The graphical user interface (GUI) consists mainly of an illustration of the scene that you are hearing and some interaction tools. The renderer type is indicated in the window title. See a screen shot in Fig.~\ref{fig:screenshot}. On the top left you will find the file menu where you can open files, save scenes, and quit the application. So far only the \emph{save scene as\dots} option is available. That means every time to save the current scene you will be asked to specify the file name. This will be made more convenient in the future. Next to the file menu, there is a button which lets you activate and deactivate the audio processing. Deactivating the audio processing does not necessarily lower the CPU load. It means rather that the SSR won't give any audio output, neither for involved audio files nor for live inputs. Next to the processing button, you find the transport section with buttons to skip back to the beginning of a scene, pause replaying, and continue/start playing. Note that pausing a scene does not prevent live inputs from being processed. To prevent audio output switch off processing (see above). You may also replay while processing is switched off to navigate to a certain point in time in the respective scene. In the top middle section of the GUI there is the audio scene time line. By default, it shows a time interval of two minutes duration. Whenever the progress exceeds the displayed time interval the latter is shifted such that the progress is always properly indicated. Below the handle, there is a numerical indication of the elapsed time with respect to the beginning of the scene. See Sec.~\ref{sec:mouse_actions} for information on how to operate on the time line. To the right of the time line there's the CPU load gauge. It displays the average CPU load as estimated by the JACK audio server on a block-wise basis. Further right there's the label to indicate the current zoom factor in percent. And finally, on the top right you find the master level meter combined with the master volume fader. The colored bar indicates an estimation of the relative maximum audio level in dB, also updated block-wise. The left boundary of the meter is at -50~dB; the right boundary is at +12~dB. The black triangle below the colored bar indicates the master volume in dB. Click somewhere into the widget and the master volume gets additionally displayed as a number. Note that this meter displays full scale, i.e.~above 0~dB clipping and thus distortion of the output signal occurs! 0~dB is indicated by a thin vertical line. In the row below the transport section, you occasionally find some tabs giving fast access to a number of scenes. These tabs can be defined in a file. By default, the file \texttt{scene\_menu.conf} in the current working directory is assumed; there is also an option to specify the file name in the SSR configuration file. Refer to Sec.~\ref{sec:ssr_configuration_file}. The configuration file for the tabs may contain something like the following: % \begin{verbatim} # This file configures the menu for the scene selection. # scenes/dual_mono.asd Guitar######### comments are possible scenes/jazz.asd Jazz scenes/rock.asd Rock #scenes/speech.asd Speech scenes/live_conference.xml live conference \end{verbatim} % The syntax is as follows: \begin{itemize} \item[-] Everything after a hash symbol (\#) in a line is ignored. \item[-] A valid entry consists of the path (relative or absolute) to ASDF file (or pure audio file) followed by space and a short keyword that will be displayed on the respective tab on the screen. \end{itemize} % Of course, also audio files can be specified instead of \texttt{.asd}s. Note that so far, no syntax validation is performed, so watch your typing. We furthermore recommend that you keep the keywords short because space on the screen is limited. Note also that only those tabs are displayed which fit on the screen. The SSR always tries to find the file \texttt{scene\_menu.conf} in its current working directory (or at the location specified in the SSR configuration file). If is does not find it no tabs will be displayed in the GUI. So you can have several of such files at different locations. We have added an example in folder \texttt{data/}. The main part of the screen is occupied by the graphical illustration of the scene that you are hearing. The orientation of the coordinate system is exactly like depicted in Fig.~\ref{fig:coordinate_system}. I.e., the $x$-axis points to the right of the screen, the $y$-axis points to the top of the screen. The origin of the coordinate system is marked by a cross, the reference is marked by a rhomb. The direction ``straight in front'' is typically assumed to be vertically upwards on the screen, especially for binaural techniques. We do so as well. Note that in this case ``straight in front'' means $\alpha = 90^\circ$ and NOT $\alpha=0^\circ$. In Fig.~\ref{fig:screenshot} you see a number of sound sources with their individual audio level meters (combined with their individual volume sliders) underneath. The left hand boundary of the level meter is at -50~dB; the right hand boundary is at 0~dB. Spherical sources don't have any additional decoration. The wave front and propagation direction of plane waves are indicated. You also see icons for the loudspeakers of the current rendering setup (if the currently applied technique employs any). %In %Fig.~\ref{fig:screenshot} you see the system that is installed in our %Usability laboratory. It is a ring of a diameter of a bad 3 %meters hosting 56 equiangularly spaced loudspeakers plus a subwoofer. % %The rhombus inside the loudspeaker ring %indicates the location and orientation of the reference point of the %current setup. \subsection{Mouse Actions} \label{sec:mouse_actions} The GUI is designed such that the most important functionalities can be accessed via a touch screen. Thus, it mostly employs 'left clicks' with the mouse. The use of the file and transport section is rather intuitive so we won't further explain it here. The time line can be used to jump to a certain position within the sound scene and it also shows the progress of the scene. Click into the white/blue area of the time line in order to jump to a specific point in time, or drag the handle to fast forward or rewind. Left-clicking to the right of the time line skips forward by 5 seconds, left-clicking to the left of the time line skips back by 5 seconds. Double-clicking on the time line skips back to the beginning of the scene. Right-clicking on the time line opens an input window in order that you can numerically specify the time instant to jump to (refer to Sec.~\ref{sec:keyboard_actions}). You can change the zoom either by clicking into the zoom label and dragging up or down for zooming in or out. Alternatively, you can use the mouse wheel. Clicking and dragging on the background of the screen lets you move inside the scene. A double-click brings you back to the default position and also defaults the zoom. Clicking and dragging on a sound source lets you select and move it. Note that you cannot directly manipulate the propagation direction of plane waves. It's rather such that plane sources always face the reference point. To change their direction of incidence move the plane wave's origin point to the appropriate position. Right clicking on a sound source opens a window which lists the properties of the source such as position, volume, etc. Refer to Fig.~\ref{fig:screenshot_spd} and Sec.~\ref{sec:source_properties_dialog}. A right mouse click on the scene background lets you select multiple sound sources via a rubber band. If you hold the \texttt{Ctrl} key pressed during any mouse action then you operate on all selected sound sources at the same time (i.e.~mute, move, etc.~them). Click on the SSR logo and you'll see the \emph{About the SSR} information. \subsubsection{Source Properties Dialog} \label{sec:source_properties_dialog} \begin{figure} \begin{center} \includegraphics[scale=0.4]{screenshot_spd} \caption{\label{fig:screenshot_spd}{Source properties dialog}} \end{center} \end{figure} The source properties dialog can be accessed via a right click on a source and shows information about the actual state of the selected source. Its main purpose is to provide the possibility of an exact positioning of sources. The properties \texttt{fixed position}, \texttt{muted} and \texttt{model} can be changed. Please refer to figure \ref{fig:screenshot_spd} to see the complete list of properties this dialog shows. \subsection{Keyboard Actions} \label{sec:keyboard_actions} % A number of keyboard actions have been implemented as listed below. Recall that also some keyboard actions are available when the SSR is run without GUI (refer to Sec.~\ref{sec:running_ssr}). % \begin{itemize} \item[] \texttt{+/-}: if no sound source is selected: raise/lower master volume by 1dB,\\ otherwise raise/lower the selected sources' volume by 1dB \item[] \texttt{Arrow up/down/left/right}: navigate in scene \item[] \texttt{Space}: toggles the play/pause state \item[] \texttt{Backspace}: skip to beginning of scene \item[] \texttt{Return}: calibrate tracker (if present). When pressed, the instantaneous\\ orientation is assumed to be straight forward (i.e.~90$^\circ$ azimuth) \item[] \texttt{Ctrl}: when pressed, multiple sound sources can be selected via mouse clicks or operations can be performed on multiple sources simultaniously \item[] \texttt{Ctrl+Alt}: individual sound sources can be deselected from a larger selection via a mouse click or the rubber band \item[] \texttt{Ctrl+a}: select all sources \item[] \texttt{f}: toggles the position-fix-state of all selected sound sources (sources which can not be moved are marked with a little cross) \item[] \texttt{m}: toggles the mute state of all selected sound sources (muted sources are displayed with a grey frame instead of a black one) \item[] \texttt{p}: toggles the source model between \emph{plane wave} and \emph{point source} \item[] \texttt{s}: if no source selected: unsolos all potentially soloed sources,\\ otherwise: solos selected sound sources. \item[] \texttt{Ctrl+s}: opens the \emph{save scene as\dots} dialog \item[] \texttt{F11}: toggles window fullscreen state \item[] \texttt{1-9}: select source no.~1-9 \item[] \texttt{0}: deselect all sources \item[] \texttt{Ctrl+c}: quit \item[] \texttt{Ctrl+t}: open text edit for time line. The format is \texttt{hours:mins(2digits):secs(2digits)} whereby \texttt{hours:} and \texttt{hours:mins(2digits):} can be omitted if desired. \item[] \texttt{Esc}: quit \end{itemize} % % % ssr-0.4.2/doc/manual/images/000077500000000000000000000000001236416011200156075ustar00rootroot00000000000000ssr-0.4.2/doc/manual/images/anti_aliasing.eps000066400000000000000000001651641236416011200211370ustar00rootroot00000000000000%!PS-Adobe-3.0 EPSF-3.0 %%Creator: 0.45.1 %%Pages: 1 %%Orientation: Portrait %%BoundingBox: 0 0 138 55 %%HiResBoundingBox: 0 0 137.6 54.4 %%EndComments %%Page: 1 1 0 55 translate 0.8 -0.8 scale 0 0 0 setrgbcolor [] 0 setdash 1 setlinewidth 0 setlinejoin 0 setlinecap gsave [1 0 0 1 0 0] concat gsave [172 0 0 -68 0 68] concat << /ImageType 3 /InterleaveType 1 /MaskDict << /ImageType 1 /Width 172 /Height 68 /ImageMatrix [172 0 0 -68 0 68] /BitsPerComponent 8 /Decode [1 0] >> /DataDict << /ImageType 1 /Width 172 /Height 68 /ImageMatrix [172 0 0 -68 0 68] /DataSource currentfile /ASCII85Decode filter /BitsPerComponent 8 /Decode [0 1 0 1 0 1] >> >> image s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s%A+ls%A+ls%A+ls%A+ls%A+ls%A+ls%A+ls6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s2EKrs2EKr s.-`Ts)ar6s)ar6s)ar6s)ar6s)ar6s.-`Ts.-`Ts.-`Ts2EKrs6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s%A+ls%A+ls%A+ls%A+ls%A+ls%A+ls%A+ls%A+ls%A+ls%A+l s%A+ls%A+ls%A+ls%A+ls%A+ls%A+ls6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s2EKrs)ar6s%A+ls%A+ls%A+ls%A+ls)Xi3s)Xi3s)Xi3s)Xi3s)Xi3s)Xi3s%A+l s%A+ls%A+ls%A+ls.-`Ts.-`Ts6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s%A+ls%A+ls%A+ls%A+ls6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s%A+ls%A+ls%A+ls%A+l s%A+ls6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s%A+ls6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s%A+ls6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49s6]49 s6]49s6]49s6]49s6]49s6]49s6]49s2EKrs)ar6s%A+ls%A+ls)Xi3s2 grestore grestore showpage %%EOF ssr-0.4.2/doc/manual/images/coordinate_system.eps000066400000000000000000000074261236416011200220640ustar00rootroot00000000000000%!PS-Adobe-3.0 EPSF-3.0 %%Creator: 0.46 %%Pages: 1 %%Orientation: Portrait %%BoundingBox: 255 447 483 625 %%HiResBoundingBox: 255.6 447.5536 482.25472 624.45782 %%EndComments %%Page: 1 1 0 842 translate 0.8 -0.8 scale 0 0 0 setrgbcolor [] 0 setdash 1 setlinewidth 0 setlinejoin 0 setlinecap gsave [1 0 0 1 0 0] concat 0 0 0 setrgbcolor [] 0 setdash 1 setlinewidth 0 setlinejoin 0 setlinecap newpath 340 492.42018 moveto 340 295.76107 340 295.76107 340 295.76107 curveto stroke gsave [-2.4492127e-17 0.4 -0.4 -2.4492127e-17 340 299.76107] concat gsave 0 0 0 setrgbcolor newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 1.25 setlinewidth 0 setlinejoin 0 setlinecap newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath stroke grestore 0 0 0 setrgbcolor [] 0 setdash 1 setlinewidth 0 setlinejoin 0 setlinecap newpath 320 472.36218 moveto 580 472.36218 580 472.36218 580 472.36218 curveto stroke gsave [-0.4 0 0 -0.4 576 472.36218] concat gsave 0 0 0 setrgbcolor newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 1.25 setlinewidth 0 setlinejoin 0 setlinecap newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath stroke grestore 0 0 0 setrgbcolor [] 0 setdash 2 setlinewidth 0 setlinejoin 0 setlinecap newpath 340 472.36218 moveto 500 392.36218 500 392.36218 500 392.36218 curveto stroke gsave [-0.71554175 0.35777088 -0.35777088 -0.71554175 492.84458 395.93989] concat gsave 0 0 0 setrgbcolor newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 1.25 setlinewidth 0 setlinejoin 0 setlinecap newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath stroke grestore gsave [1 0 0 1 0 0] concat grestore gsave [0.9471784 0 0 0.9714573 -25.153489 42.650535] concat gsave 1 1 1 setrgbcolor newpath 460.86189 407.01604 moveto 467.18281 418.50019 470.02445 429.53457 469.99987 442.49966 curveto eofill grestore 0 0 0 setrgbcolor [] 0 setdash 1.0424908 setlinewidth 0 setlinejoin 0 setlinecap newpath 460.86189 407.01604 moveto 467.18281 418.50019 470.02445 429.53457 469.99987 442.49966 curveto stroke gsave [0.20107154 0.36531655 -0.36531655 0.20107154 462.8726 410.66921] concat gsave 0 0 0 setrgbcolor newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 1.25 setlinewidth 0 setlinejoin 0 setlinecap newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath stroke grestore grestore gsave [1 0 0 1 413.15239 217.18279] concat gsave [1 0 0 -1 179.8125 257.93022] concat gsave /newlatin1font {findfont dup length dict copy dup /Encoding ISOLatin1Encoding put definefont} def /Arial-ISOLatin1 /Arial newlatin1font 20 scalefont setfont 0 0 0 setrgbcolor newpath 0 0 moveto (x) show grestore grestore grestore gsave [1 0 0 1 180.81731 -85.862962] concat gsave [1 0 0 -1 155.5625 368.02397] concat gsave /Arial-ISOLatin1 findfont 20 scalefont setfont 0 0 0 setrgbcolor newpath 0 0 moveto (y) show grestore grestore grestore gsave [1 0 0 1 219.20311 -44.446712] concat gsave [1 0 0 -1 172.75 508.43022] concat gsave /Arial-ISOLatin1 findfont 20 scalefont setfont 0 0 0 setrgbcolor newpath 0 0 moveto (alpha) show grestore grestore grestore gsave [1 0 0 1 146.47212 -166.67517] concat gsave [1 0 0 -1 282.84375 581.18022] concat gsave /Arial-ISOLatin1 findfont 20 scalefont setfont 0 0 0 setrgbcolor newpath 0 0 moveto (r) show grestore grestore grestore gsave [1 0 0 1 -16.162441 -186.87822] concat gsave [1 0 0 -1 524.28125 575.11772] concat gsave /Arial-ISOLatin1 findfont 20 scalefont setfont 0 0 0 setrgbcolor newpath 0 0 moveto (bx) show grestore grestore grestore grestore showpage %%EOF ssr-0.4.2/doc/manual/images/coordinate_system.svg000066400000000000000000000205371236416011200220720ustar00rootroot00000000000000 image/svg+xml x y alpha r bx ssr-0.4.2/doc/manual/images/local_coordinate_system.eps000066400000000000000000000132251236416011200232300ustar00rootroot00000000000000%!PS-Adobe-3.0 EPSF-3.0 %%Creator: inkscape 0.43 %%Pages: 1 %%Orientation: Portrait %%BoundingBox: 255 447 484 623 %%HiResBoundingBox: 255.6 447.5536 483.31722 622.33213 %%EndComments %%Page: 1 1 0 842 translate 0.8 -0.8 scale gsave [1 0 0 1 0 0] concat 0 0 0 setrgbcolor [] 0 setdash 1 setlinewidth 0 setlinejoin 0 setlinecap newpath 340 492.42018 moveto 340 295.76107 340 295.76107 340 295.76107 curveto stroke gsave [-2.4492936e-17 0.4 -0.4 -2.4492936e-17 340 299.76107] concat gsave 0 0 0 setrgbcolor newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 1.25 setlinewidth 0 setlinejoin 0 setlinecap newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath stroke grestore 0 0 0 setrgbcolor [] 0 setdash 1 setlinewidth 0 setlinejoin 0 setlinecap newpath 320 472.36218 moveto 580 472.36218 580 472.36218 580 472.36218 curveto stroke gsave [-0.4 0 0 -0.4 576 472.36218] concat gsave 0 0 0 setrgbcolor newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 1.25 setlinewidth 0 setlinejoin 0 setlinecap newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath stroke grestore gsave [1 0 0 1 413.15239 217.18279] concat gsave [1 0 0 -1 179.8125 261.15373] concat gsave /newlatin1font {findfont dup length dict copy dup /Encoding ISOLatin1Encoding put definefont} def /BitstreamVeraSans-Roman-ISOLatin1 /BitstreamVeraSans-Roman newlatin1font 20 scalefont setfont 0 0 0 setrgbcolor newpath 0 0 moveto (x) show grestore grestore grestore gsave [1 0 0 1 180.81731 -85.862962] concat gsave [1 0 0 -1 155.5625 371.24748] concat gsave /BitstreamVeraSans-Roman-ISOLatin1 findfont 20 scalefont setfont 0 0 0 setrgbcolor newpath 0 0 moveto (y) show grestore grestore grestore 0 0 0 setrgbcolor [] 0 setdash 1 setlinewidth 0 setlinejoin 0 setlinecap newpath 450 414.36218 moveto 450 410.36218 450 410.36218 450 410.36218 curveto stroke 0 0 0 setrgbcolor [] 0 setdash 1 setlinewidth 0 setlinejoin 0 setlinecap newpath 448 412.36218 moveto 452 412.36218 452 412.36218 452 412.36218 curveto stroke 0 0 0 setrgbcolor [] 0 setdash 1 setlinewidth 0 setlinejoin 0 setlinecap newpath 440 402.36218 moveto 440 402.36218 440 402.36218 440 422.36218 curveto 460 422.36218 460 422.36218 460 422.36218 curveto stroke 0 0 0 setrgbcolor [] 0 setdash 1 setlinewidth 0 setlinejoin 0 setlinecap newpath 440 402.36218 moveto 440 402.36218 440 402.36218 475 387.36218 curveto 460 422.36218 460 422.36218 460 422.36218 curveto stroke 0 0 0 setrgbcolor [4 4] 0 setdash 1 setlinewidth 0 setlinejoin 0 setlinecap newpath 450 412.36218 moveto 520 342.36218 520 342.36218 520 342.36218 curveto stroke gsave [-0.28284271 0.28284271 -0.28284271 -0.28284271 517.17157 345.19061] concat gsave 0 0 0 setrgbcolor newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 1.25 setlinewidth 0 setlinejoin 0 setlinecap newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath stroke grestore 0 0 0 setrgbcolor [4 4] 0 setdash 1 setlinewidth 0 setlinejoin 0 setlinecap newpath 450 412.36218 moveto 385 347.36218 385 347.36218 385 347.36218 curveto stroke gsave [0.28284271 0.28284271 -0.28284271 0.28284271 387.82843 350.19061] concat gsave 0 0 0 setrgbcolor newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 1.25 setlinewidth 0 setlinejoin 0 setlinecap newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath stroke grestore 0 0 0 setrgbcolor [] 0 setdash 1 setlinewidth 0 setlinejoin 0 setlinecap newpath 450 412.36218 moveto 465 352.36218 465 352.36218 465 352.36218 curveto stroke gsave [-0.09701425 0.388057 -0.388057 -0.09701425 464.02986 356.24275] concat gsave 0 0 0 setrgbcolor newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 1.25 setlinewidth 0 setlinejoin 0 setlinecap newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath stroke grestore gsave [0.9559172 0 0 0.9420662 21.270752 22.078729] concat gsave 1 1 1 setrgbcolor newpath 461.00363 368.72825 moveto 469.31649 370.82459 475.90018 374.57754 481.93942 380.66244 curveto eofill grestore 0 0 0 setrgbcolor [2.1075561 2.1075561] 0 setdash 1.0537781 setlinewidth 0 setlinejoin 0 setlinecap newpath 461.00363 368.72825 moveto 469.31649 370.82459 475.90018 374.57754 481.93942 380.66244 curveto stroke gsave [0.4087154 0.10307004 -0.10307004 0.4087154 465.09078 369.75895] concat gsave 0 0 0 setrgbcolor newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 1.25 setlinewidth 0 setlinejoin 0 setlinecap newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath stroke grestore grestore gsave [0.7071068 -0.7071068 0.7071068 0.7071068 -124.32626 391.38539] concat gsave [1 0 0 -1 388.40625 320.74748] concat gsave /BitstreamVeraSans-Roman-ISOLatin1 findfont 20 scalefont setfont 0 0 0 setrgbcolor newpath 0 0 moveto (yprime) show grestore grestore grestore gsave [0.7071068 -0.7071068 0.7071068 0.7071068 -100.59276 532.59801] concat gsave [1 0 0 -1 580.34375 312.65373] concat gsave /BitstreamVeraSans-Roman-ISOLatin1 findfont 20 scalefont setfont 0 0 0 setrgbcolor newpath 0 0 moveto (xprime) show grestore grestore grestore gsave [0.707107 -0.707107 0.707107 0.707107 -224.163 491.88] concat gsave [1 0 0 -1 557.09375 416.21623] concat gsave /BitstreamVeraSans-Roman-ISOLatin1 findfont 20 scalefont setfont 0 0 0 setrgbcolor newpath 0 0 moveto (alphaprime) show grestore grestore grestore grestore showpage ssr-0.4.2/doc/manual/images/local_coordinate_system.svg000066400000000000000000000250051236416011200232370ustar00rootroot00000000000000 image/svg+xml x y yprime xprime alphaprime ssr-0.4.2/doc/manual/images/moving_source_without_doppler.svg000066400000000000000000000344311236416011200245240ustar00rootroot00000000000000 image/svg+xml t-1 t0 t+1 t+2 t+3 t+4 t+5 t+6 t+7 ssr-0.4.2/doc/manual/images/screenshot.eps000066400000000000000000006452061236416011200205120ustar00rootroot00000000000000%!PS-Adobe-3.0 EPSF-3.0 %%Creator: GIMP PostScript file plugin V 1.17 by Peter Kirchgessner %%Title: screenshot.eps %%CreationDate: Thu May 20 18:05:00 2010 %%DocumentData: Clean7Bit %%LanguageLevel: 2 %%Pages: 1 %%BoundingBox: 14 14 918 557 %%EndComments %%BeginProlog % Use own dictionary to avoid conflicts 10 dict begin %%EndProlog %%Page: 1 1 % Translate for offset 14.173228346456694 14.173228346456694 translate % Translate to begin of first scanline 0 542.08362832430942 translate 903.4727138738491 -542.08362832430942 scale % Image geometry 1280 768 8 % Transformation matrix [ 1280 0 0 768 0 0 ] % Strings to hold RGB-samples per scanline /rstr 1280 string def /gstr 1280 string def /bstr 1280 string def {currentfile /ASCII85Decode filter /RunLengthDecode filter rstr readstring pop} {currentfile /ASCII85Decode filter /RunLengthDecode filter gstr readstring pop} {currentfile /ASCII85Decode filter /RunLengthDecode filter bstr readstring pop} true 3 %%BeginData: 214647 ASCII Bytes colorimage Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb8:#rW) Jb4NcJb4NcJb4NcJb4NcJb8:#rW) Jb4NcJb4NcJb4NcJb4NcJb8:#rW) Jb4NcJb4NcJb4NcJb4NcJb84!!!) Jb4NcJb4NcJb4NcJb4NcJb84!!!) Jb4NcJb4NcJb4NcJb4NcJb84!!!) Jb4NcJb4NcJb4NcJb4NcJb84!!!) Jb4NcJb4NcJb4NcJb4NcJb84!!!) Jb4NcJb4NcJb4NcJb4NcJb84!!!) Jb4NcJb4NcJb4NcJb5Z.r;[H Jb4NcJb4NcJb4NcJb5Z.r;[H Jb4NcJb4NcJb4NcJb5Z.r;[H Jb4NcJb4NcJb4NcJb5T,'Tijurq$BpPO"MboDnm_!!&ti !!)ui'/?*GYTre! Jb4NcJb4NcJb4NcJb5T,'Tijurq$BpPO"MboDnm_!!&ti !!)ui'/?*GYTre! Jb4NcJb4NcJb4NcJb5T,'Tijurq$BpPO"MboDnm_!!&ti !!)ui'/?*GYTre! Jb5E'eFW5&eFS7`Jb4NcJb4NcnamQi&AIf/"7,UE"S;f^o]cSO!;$9`!6Xp7"R>sX$39DalNQn\ o^qVQW:U&irU^'mrW!3)o]$J3&*3?crW!$$o^i-hoRH~> Jb5E'eFW5&eFS7`Jb4NcJb4NcnamQi&AIf/"7,UE"S;f^o]cSO!;$9`!6Xp7"R>sX$39DalNQn\ o^qVQW:U&irU^'mrW!3)o]$J3&*3?crW!$$o^i-hoRH~> Jb5E'eFW5&eFS7`Jb4NcJb4NcnamQi&AIf/"7,UE"S;f^o]cSO!;$9`!6Xp7"R>sX$39DalNQn\ o^qVQW:U&irU^'mrW!3)o]$J3&*3?crW!$$o^i-hoRH~> Jb5N*"7u*Qn(-n/n*oo>p[eRZo'l,!mK;qIo(.G)Jb4Nc\+B^`"oSE%!cES]a7KG:'Ys_?&1rcSoE$hLm/?P];_V2CoJ Jb5N*"7u*Qn(-n/n*oo>p[eRZo'l,!mK;qIo(.G)Jb4Nc\+B^`"oSE%!cES]a7KG:'Ys_?&1rcSoE$hLm/?P];_V2CoJ Jb5N*"7u*Qn(-n/n*oo>p[eRZo'l,!mK;qIo(.G)Jb4Nc\+B^`"oSE%!cES]a7KG:'Ys_?&1rcSoE$hLm/?P];_V2CoJ j7Ioiq"+RWdd7&)p%/7Sdd7,#mIL#D!:eM)!pT+Cp[eIVdd7&)Jb4NcJb8L)!GD`;!!*GaJb7"T !"/Z)&bN^`""1eS]I*e/^%; j7Ioiq"+RWdd7&)p%/7Sdd7,#mIL#D!:eM)!pT+Cp[eIVdd7&)Jb4NcJb8L)!GD`;!!*GaJb7"T !"/Z)&bN^`""1eS]I*e/^%; j7Iobq"+RVdcLPsp%/7QdH1Jso(VbVn'U7umIL#D!:eM"!:>@0Jb4Ncnaliq#Q=]($K$F,a7K?= r;[H%]I*e/^%Arb""1eS!;$9`!6XpF_^toio^i-%#UR1VP7J7b!3#N.Op_9^oZo],!!)Z`Z2h3& oZo],!!)Z`Op_8soRH~> j7E?=KDtr=q"+RWdd7&)p%/:Ro$m[Ap>G'%p[eLTo$m[Ap>G'%p[eIVdd7&)Jb4NcJb8L)"lBUd !!d1goRZZ*oDelBoX=CB~> j7E?=KDtr=q"+RWdd7&)p%/:Ro$m[Ap>G'%p[eLTo$m[Ap>G'%p[eIVdd7&)Jb4NcJb8L)"lBUd !!d1goRZZ*oDelBoX=CB~> j7E?6KDtr6q"+RVdcLPsp%/:Qm*u%;oA/Ntp[eLSm*u%;oA/Ntp[eIUdcLPsJb4NcJb8L)"lBUd !!d1goRZZ*oDelBoX=CB~> j7E?=KDtr=q"+RWdd7&)p@JFXn+,!."SUR-n+6AI!qYmOeGf[Fg>MGkq"+RWdd7&)Jb4NcJb8I( "DA#?$/^=+Jb8O*!!%TB[e,(~> j7E?=KDtr=q"+RWdd7&)p@JFXn+,!."SUR-n+6AI!qYmOeGf[Fg>MGkq"+RWdd7&)Jb4NcJb8I( "DA#?$/^=+Jb8O*!!%TB[e,(~> j7E?6KDtr6q"+RVdcLPsp@JFXmHi@&"S:7'n+6AI!qYgGeGf[CfAQ,hq"+RVdcLPsJb4NcJb8I( "DA#?$/^=+Jb8O*!!%TB[e,(~> j7E?=KDtr=q"+RWdd7&)p@JFXmdem-"SUO*mdp8H!qYjNeGf[Fg"u/gq"+RWdd7&)Jb4NcJb8I( "5a=cAV'K)oRZY2oRH~> j7E?=KDtr=q"+RWdd7&)p@JFXmdem-"SUO*mdp8H!qYjNeGf[Fg"u/gq"+RWdd7&)Jb4NcJb8I( "5a=cAV'K)oRZY2oRH~> j7E?6KDtr6q"+RVdcLPsp@JFXm-N7%"S:1#mdp8H!qYdFeGf[Ce_]`cq"+RVdcLPsJb4NcJb8I( "5a=cAV'K)oRZY2oRH~> j7E?=KDtr=q"+RWdd7&)p@JFXmdem-"SUL)mdp8H!qYjNeGf[Ff\Z&fq"+RWdd7&)iq.fhJaJKb !YPL0JaMpnJb4Nc\+G1~> j7E?=KDtr=q"+RWdd7&)p@JFXmdem-"SUL)mdp8H!qYjNeGf[Ff\Z&fq"+RWdd7&)iq.fhJaJKb !YPL0JaMpnJb4Nc\+G1~> j7E?6KDtr6q"+RVdcLPsp@JFXm-N7%"S:1#mdp8H!qYdFeGf[Ce_]`cq"+RVdcLPsiq.faJ``!T !YGF+J`cF`Jb4Nc\+G1~> j7E?=KDtr=q"+RWdd7&)p@JFXmdem-"SUL)mdp8H!qYjNeGf[Ff\Z&fq"+RWdd7&)iq*9=_L[l$ P)th.+99<1s7$$gm=Fn\oXFIC~> j7E?=KDtr=q"+RWdd7&)p@JFXmdem-"SUL)mdp8H!qYjNeGf[Ff\Z&fq"+RWdd7&)iq*9=hgs+) c]HHS-31r7s7$$gm=Fn\oXFIC~> j7E?6KDtr6q"+RVdcLPsp@JFXm-N7%"S:1#mdp8H!qYdFeGf[Ce_]`cq"+RVdcLPsiq*96l%/8R mZ?i<-ih/9s7$$gk(3/UoXFIC~> j7E?=KDtr=q"+RWdd7&)p@JFXmdfiH!q!b[rVlo55k"^Up=S6ioD/"[oBu2:s8Th;huE_k^YeP\ "SUL)mdp5G!:oRF!RJ(7m/Z\9oE"?AJLc:%O=LAL!5jBfnG`LUkk*$bmI\QglL]hujRds~> j7E?=KDtr=q"+RWdd7&)p@JFXmdfiH!q!b[rVlo55k"^Up=S6ioD/"[oBu2:s8Th;huE_k^YeP\ "SUL)mdp5G!:oRF!RJ(7m/Z\9oE"@*JZaR,OKJX2!5jBfnG`LUkk*$bmI\QglL]hujRds~> j7E?6KDtr6q"+RVdcLPsp@JFXm-O3@!p78TrVlo55k"^Uo@;^coD/"[oBbi2s8Th;huE_k^YeP\ "S:1#mdp5G!:fL>!QhJ,joFf.oE"+BJbO`iOS8f,!5jBfnG`LNkk*$[mI\Q`lL]hnjRds~> j7E?=n,NCfQi@'Qq"+RWi9^T+$NU1n!fdWgr9XLSp@JFXmdfiH!l"_#rr3&7!'p#Q"SUL)mdp8H !qYjNmf*R-!!#:a^An7!nG`Xbf\Z&fq"+RWmd1+^!'@LNm/Z\9oE"?AJLc:%O=LAL!5jBfnG`LU kk"lBbl7\1mIUDGj#ZK*!:B(D!:@T`i;WfElMgkOjRds~> j7E?=n,NCfQi@'Qq"+RWi9^T+$NU1n!fdWgr9XLSp@JFXmdfiH!l"_#rr3&7!'p#Q"SUL)mdp8H !qYjNmf*R-!!#:a^An7!nG`Xbf\Z&fq"+RWmd1+^!'@LNm/Z\9oE"@*JZaR,OKJX2!5jBfnG`LU kk"lBbl7\1mIUDGj6u:5!:B(D!:@Vki;WfElMgkOjRds~> j7E?6n,NCfQi@'Jq"+RVi8t)u$NU1g!f@?cr8n"Hp@JFXm-O3@!l"_#rr3&7!'p#Q"S:1#mdp8H !qYdFmf*R-!!#:a^An7!nG`X_e_]`cq"+RVmcFVR!'77HjoFf.oE"+BJbO`iOS8f,!5jBfnG`LN kk"l;bl7\*mIUD@j#ZK*!9WS=!9V*Yi;Wf>lMgkHjRds~> j7E?=r;ZQmr;Zcsr;Z]qT`5#Zq"+RWlKnVX48J7E(Vp":!>iQKm/Z\MoE+XPo'cSa^ApP!s1\O6 5k"^Up=S6ioD/"[oBu2;rs6@>!'pS!!!#:Rrri&JhX:"1oDnIMm/XHk!!,0elg4]Biq*9=Ob#$6 /!)7_(]_I)s7$$gmHF0BmFV@=LQ;00!:B1G!:Af-lMgkOlLY)D_)gNS!:B(O!:Ak>J,~> j7E?=r;ZQmr;Zcsr;Z]qT`5#Zq"+RWlKnVX48J7E(Vp":!>iQKm/Z\MoE+XPo'cSa^ApP!s1\O6 5k"^Up=S6ioD/"[oBu2;rs6@>!'pS!!!#:Rrri&JhX:"1oDnIMm/XHk!!,0elg4]Biq*9=c[i6M Y)odf,lki6s7$$gmHF0BmFV@=LQ;00!:B1G!:Ah8lMgkOlLY)D_=-=^!:B(O!:Ak>J,~> j7E?6r;ZQmr;Zcsr;Z]qT`5#Sq"+RVlK/,K3Vhe<(VBY.!>iBFjoFfBoE+XNm-jr[^ApP!s1\O6 5k"^Uo@;^coD/"[oBbi3rs6@>!'pS!!!#:RrrhrDhX:"1oDnFLjoDO_!!,-_lfJ37iq*96mXb+` pQ5+N.fdJ j7E?=r;ZQmr;Zcsr;Z]qT`5#Zq"+RWlKnU6ZMEkf!W; j7E?=r;ZQmr;Zcsr;Z]qT`5#Zq"+RWlKnU6ZMEkf!W; j7E?6r;ZQmr;Zcsr;Z]qT`5#Sq"+RVlK/+-XSLuY!W;'[!W(pYmIp/RoBbi4rrg(:5l\Uu!!#:Q rrhrDhX:"2oE+XNm-alZ^An7!s1\O65k+dVo@;^coD%qXna>N>Xo&/"3j%f3!:Ae j7E?=r;ZcsmJm1dT`5#Zq"+RWq<\Nu-Nkg+m/VnR&^OX ZL@/\n`'0Am?)rMJLcg4!Z1pTJcG6>!:B"B!:B.Q!!*#u##ojAGZXnFqYpf]5YLJ/7RcM;rrDHR oDn:E3rK'Y3rK'FrrDHOoDn9p3oL# j7E?=r;ZcsmJm1dT`5#Zq"+RWq<\Nu-Nkg+m/VnR&^OX ZL@/\n`'0AmE^?dJZb*;![RiaJcG6>!:B"B!:B.Q!!*#u##ojAGZXnFqYpf]5YLJ/7RcM;rrDHR oDn:En,37dn,37QrrDHOoDn9pn)43GmHX]OmH!ni~> j7E?6r;ZcsmJm1dT`5#Sq"+RVq;r$i-3P[$joC#F%ugMM$*6/Lk-?<:-F2e4!<;3[!;tsZmIp/R oBbi4rr^"95em6t!'p#Q"S:1#mdp8H!qYdFmf*R-!!#:a^An7!nG`X_e_]`cq"+RVmcFMOq>^OW XmbBPmGda=k3dfpJbP9#!\48gJcG6>!9WM;!9WYJ!!*#u##ojAGZXnFqYpf]5YLJ/7RcM;rrD3K oDn%>3rK'Y3rK'FrrD3HoDn$i3oL# j7E?=r;Zcsq#C9mqZ$QqqYp`G8.5e`Z+p;>k3;^As6':Z!:BRR!:osQ$7:nsO!j;pMWLiOrp:(T ZL.$+Ol_WOmHjoOm/R._m/Z\MoE+XPo'cS\^ApMar;Zg_n,EOaf\Z&fq=Fa[mdffG#ep?>5l\S! !'p&R"SUL)mdp5G!:oRF!4)A"!B]0.m/Z\9oE"?AJLc:%O=LAL!5jBfnG`LUkk"lBm/I%crr3`I kl9i\rr1GrrDHOoDn9p3oL# j7E?=r;Zcsq#C9mqZ$QqqYp`G8.5e`Z+p;>k3;^As6':Z!:BRR!:osQ$7:nsO!j;pMWLiOrp:(T ZL.$+Ol_WOmHjoOm/R._m/Z\MoE+XPo'cS\^ApMar;Zg_n,EOaf\Z&fq=Fa[mdffG#ep?>5l\S! !'p&R"SUL)mdp5G!:oRF!4)A"!B]0.m/Z\9oE"@*JZaR,OKJX2!5jBfnG`LUkk"lBm/I%crr3`I kl9i\rr j7E?6r;Zcsq#C9mqZ$QqqYp`G8.5e`Z+p;>k3;^As6':Z!9X(K!:fmI$71\gM^IQdL>Ja>roOSK XQJXkNS]R?k2l[Ajo>DXjoFfBoE+XNm-jrV^ApMar;Zg_n,EO^e_]`cq=Fa[m-O0?#ep?>5l\S! !'p&R"S:1#mdp5G!:fL>!3Q"r!BSm'joFf.oE"+BJbO`iOS8f,!5jBfnG`LNkk"l;m/I%crr3`I kl9i\rr1GrrD3HoDn$i3oL# j7E?=r;Zcsq#C9mqZ$Qqqu6]K%/^)*Z,-GBj!:B"B!:B.Q !!*#u"9SNaqucru#3p=ks8TjPqu6]2_u0N7mHsNGmHZe?!!E?81HGL=$;'.BrrDHOoDn9p3oL#< mHX]OmH!ni~> j7E?=r;Zcsq#C9mqZ$Qqqu6]K%/^)*Z,-GBj!:B"B!:B.Q !!*#u"9SNaqucru#3p=ks8TjPqu6]2_u0N7mHsNGmHa3J!"TB(dI.&G$;'.BrrDHOoDn9pn)43G mHX]OmH!ni~> j7E?6r;Zcsq#C9mqZ$Qqqu6]K%/^)*Z,-GBj!9WM;!9WYJ !!*#u"9SNaqucru#3p=ks8TjPqu6]2_u0N7k3_d@k3G&8!!E?81HGL=$;'.BrrD3HoDn$i3oL#< k3DsHk2c/b~> j7E?=r;ZTnqu?ZrqZ$Qqqu6n(!1*6MVZ8WkrrhU=!%mL7rrhL5!&Em!:B"B !:B.Q!!*#u";C&"kS4!q!VZcerrDHRoDn:C3W_/X%K8Bil?,,SrrDHOoDn9p3oL# j7E?=r;ZTnqu?ZrqZ$Qqqu6n(!1*6MVZ8WkrrhU=!%mL7rrhL5!&Em!:B"B !:B.Q!!*#u";C&"kS4!q!VZcerrDHRoDn:CmfM6]2?*%Gl?,,SrrDHOoDn9pn)43GmHX]OmH!ni~> j7E?6r;ZTnqu?ZrqZ$Qqqu6n(!1*6MVZ8WkrrhU=!%mL7rrhL5!&Eme-&pc>-KaFjr8mtZqW7eFp@JFXm-O3@#ep@)s8P=a!'p#Q"S:1#mdp8H!qYdF mf*R-!!#:a^An7!nG`X_e_]`cq"+RVmcFMOqZ$XXXmY!9WM; !9WYJ!!*#u";C&"kS4!q!VZcerrD3KoDn%<3W_/X%K8Bil?,,SrrD3HoDn$i3oL# j7E?=r;ZTnqu?ZrqZ$Qqqu6m;!!<9'!!!;?rsS*D!%mL9s5td11@=u=!:BRR!:osQ#.>i\-F`@B rp9dfmBShPm0C'H""(MEm/[(O!!)l_!:fdT!qYjNn,EI(!'pP`!^H`Nn,EOaf\Z&fq=Fa[mdffG #ep?>5l\S!!'p&R"SUL)mdp5G!:oRF!4)P'!B[URm/Z\9oE"?AJLc:%O=LAL!5jBfnG`LUkk"lB m/I%crr3+"HMDeerr32nKJrl1]"\(A!P,uCrrDHRoDn:B3WV2@r]1,\pBBsa!:B(D!:@T`i;WfE lMgkOjRds~> j7E?=r;ZTnqu?ZrqZ$Qqqu6m;!!<9'!!!;?rsS*D!%mL9s5td11@=u=!:BRR!:osQ#.>i\-F`@B rp9dfmBShPm0C'H""(MEm/[(O!!)l_!:fdT!qYjNn,EI(!'pP`!^H`Nn,EOaf\Z&fq=Fa[mdffG #ep?>5l\S!!'p&R"SUL)mdp5G!:oRF!4)P'!B[URm/Z\9oE"@*JZaR,OKJX2!5jBfnG`LUkk"lB m/I%crr3+"HMDeerr32nKJrl1]"\(A!P,uCrrDHRoDn:BmfDVUrpKpgpBBsa!:B(D!:@Vki;WfE lMgkOjRds~> j7E?6r;ZTnqu?ZrqZ$Qqqu6m;!!<9'!!!;?rsS*D!%mL9s5td11@=u=!9X(K!:fmI#-fHV-*lY5 roO:_k,^TAjp/.;"!t88joG>A!!)lX!:BLP!qYdFn,EI(!'pP`!^H`Nn,EO^e_]`cq=Fa[m-O0? #ep?>5l\S!!'p&R"S:1#mdp5G!:fL>!3Q2"!BR@LjoFf.oE"+BJbO`iOS8f,!5jBfnG`LNkk"l; m/I%crr3+"HMDeerr32nKJrl1]"\(A!P,uCrrD3KoDn%;3WV2@r]1,\pBBsa!9WS=!9V*Yi;Wf> lMgkHjRds~> j7E?=r;ZcspAb-mqZ$Qqqu6X!q>e;1$0j)B/aDLI!&Es5l\S!!'p&R"SUL)mdp5G!:oRF"L8"-40n/=!:f(@ !UZ##.tK44.fpOf_h%jWrrDHMoDn:Hs82j5s5,a=38sl;!!bgDs8TnVK)P`Lr-]^,r;QcamIUDG krJl)*Xr$/!qM"&mJd1RlLY)D_)gNS!:B(O!:Ak>J,~> j7E?=r;ZcspAb-mqZ$Qqqu6X!q>e;1$0j)B/aDLI!&Es5l\S!!'p&R"SUL)mdp5G!:oRF"L8"-40n/=!:f(@ !U\3aY(J,~> j7E?6r;ZcspAb-mqZ$Qqqu6X!q>e;1$0j)B/aDLI!&Es5l\S!!'p&R"S:1#mdp5G!:fL>"K_Y(3j%]0!:Ae< !Trg$pOW(#pAuiF_h%jWrrD3FoDn%As82j5s5,a=38sl;!!bgDs8TnVK)P`Lr-]^,r;QcZmIUD@ krJl)*Xr$/!qM"&mJd1KlLY)=_)gNS!9WSH!9WA7J,~> j7E?=r;ZcspAb-mqYpTu!;ucp!Yk_$\c2p"/H@4G!&Es;rrDH]oDnI1m/Z\MoE+XPo'cS[cTo8C rr>=Qrri&JhX:"2oE+XPo'ZM`cTja7s3EqqE:El1p=S6ioD%qXo'YlHZ9,AUm/Z\9oE"?AJLc:% O=LAL!5jBfnG`LUkk"lBgA_EC7RcM$5YLJ@rrDHRoDn:E3r]0c!Y7%I!!!6ig?nn?mHX j7E?=r;ZcspAb-mqYpTu!;ucp!Yk_$\c2p"/H@4G!&Es;rrDH]oDnI1m/Z\MoE+XPo'cS[cTo8C rr>=Qrri&JhX:"2oE+XPo'ZM`cTja7s3EqqE:El1p=S6ioD%qXo'YlHZ9,AUm/Z\9oE"@*JZaR, OKJX2!5jBfnG`LUkk"lBgA_EC7RcM$5YLJ@rrDHRoDn:En,E@n# j7E?6r;ZcspAb-mqYpTu!;ucp!Yk_$\c2p"/H@4G!&Es;rrD3VoDnF0joFfBoE+XNm-jrUcTo8C rr>=QrrhrDhX:"2oE+XNm-alZcTja7s3EqqE:El1o@;^coD%qXna>N@XZETJjoFf.oE"+BJbO`i OS8f,!5jBfnG`LNkk"l;gA_EC7RcM$5YLJ@rrD3KoDn%>3r]0c!Y7%I!!!6ig?nn?k3DR=k.s+: rrD3HrrD3BoRH~> j7E?=r;ZcspAb-mqYpX5!3?+trs)R);V_38X?aK!"mRN:!&![6rrDH]oDnI1m/Z\MoE+XPo$m[B p=S6ioD/"[oBu2!rri&JhX:"1oDnI1m/Z\9oE"?AJLc:%O=LAL!5jBfnG`LUkk"lBg&D6SJeSlS rqcWpmHsNGmGg7mrrDHOoDn9p3oL# j7E?=r;ZcspAb-mqYpX5!3?+trs)R);V_38X?aK!"mRN:!&![6rrDH]oDnI1m/Z\MoE+XPo$m[B p=S6ioD/"[oBu2!rri&JhX:"1oDnI1m/Z\9oE"@*JZaR,OKJX2!5jBfnG`LUkk"lBg&D6SJeSlS rqcWpmHsNGmGm[#rrDHOoDn9pn)43GmHX]OmH!ni~> j7E?6r;ZcspAb-mqYpX5!3?+trs)R);V_38X?aK!"mRN:!&![6rrD3VoDnF0joFfBoE+XNm*u%< o@;^coD/"[oBbhnrrhrDhX:"1oDnF0joFf.oE"+BJbO`iOS8f,!5jBfnG`LNkk"l;g&D6SJeSlS rqcWpk3_d@k2SMfrrD3HoDn$i3oL# j7E?=r;Zcsq>^6jrVljrr;cis!S.bX!5/=6e-?<5p\t6\q"+RWdd7&)p@JFXmdem-"SUL)mdp8H !qYjNeGf[Ff\Z&fq"+RWdd7&)iq*9=Ob#$6/!)7_(]_I)s7$$gmHF0BmFV@=NgBJ?!:B1G!:Af- lMgkOlLY)D_)gNS!:B(O!:Ak>J,~> j7E?=r;Zcsq>^6jrVljrr;cis!S.bX!5/=6e-?<5p\t6\q"+RWdd7&)p@JFXmdem-"SUL)mdp8H !qYjNeGf[Ff\Z&fq"+RWdd7&)iq*9=c[i6MY)odf,lki6s7$$gmHF0BmFV@=NgBJ?!:B1G!:Ah8 lMgkOlLY)D_=-=^!:B(O!:Ak>J,~> j7E?6r;Zcsq>^6jrVljrr;cis!S.bX!5/=6e-?<5p\t6Uq"+RVdcLPsp@JFXm-N7%"S:1#mdp8H !qYdFeGf[Ce_]`cq"+RVdcLPsiq*96mXb+`pQ5+N.fdJ j7E?=r;Zcsq>^6jrVm#W3s5N`rVm)S=r7AV:qXF?s4?uE!:BRR!:nS*!:fdT!qYjNeGf[Ff\Z&f q=Fa[mdem-"SUL)mdp5G!:nS*!:f(@!UZ##.tK44.fpOf_h%jWrrDHMoDn:&rrDHRoDn:=3pQ_F mHX j7E?=r;Zcsq>^6jrVm#W3s5N`rVm)S=r7AV:qXF?s4?uE!:BRR!:nS*!:fdT!qYjNeGf[Ff\Z&f q=Fa[mdem-"SUL)mdp5G!:nS*!:f(@!U\3aY( j7E?6r;Zcsq>^6jrVm#W3s5N`rVm)S=r7AV:qXF?s4?uE!9X(K!:eM"!:BLP!qYdFeGf[Ce_]`c q=Fa[m-N7%"S:1#mdp5G!:eM"!:Ae j7E?=KDtr=q"+RWdd7&)p@JFXmdem-"SUL)mdp8H!qYjNeGf[Ff\Z&fq"+RWdd7&)iq*9=Ob#$6 /!)7_(]_I)s7$$gmHF2bmHsPgmHX j7E?=KDtr=q"+RWdd7&)p@JFXmdem-"SUL)mdp8H!qYjNeGf[Ff\Z&fq"+RWdd7&)iq*9=c[i6M Y)odf,lki6s7$$gmHF2bmHsPgmHX j7E?6KDtr6q"+RVdcLPsp@JFXm-N7%"S:1#mdp8H!qYdFeGf[Ce_]`cq"+RVdcLPsiq*96mXb+` pQ5+N.fdJ j7E?=KDtr=q"+RWdd7&)p@JFXmdem-"SUO*mdp8H!qYjNeGf[Fg"u/gq"+RWdd7&)iq*9=Ob#$6 /!)7_(]_I)s7$$gm=FoUoDn9p3oL# j7E?=KDtr=q"+RWdd7&)p@JFXmdem-"SUO*mdp8H!qYjNeGf[Fg"u/gq"+RWdd7&)iq*9=c[i6M Y)odf,lki6s7$$gm=FoUoDn9pn)43GmHX]OmH!ni~> j7E?6KDtr6q"+RVdcLPsp@JFXm-N7%"S:1#mdp8H!qYdFeGf[Ce_]`cq"+RVdcLPsiq*96mXb+` pQ5+N.fdJ j7E?=KDtr=q"+RWdd7&)p@JCWn'^S*g>MGkq=F^Zn'^S*g>MGkq"+RWdd7&)iq*9=]RbfgL6.,k LOAiIs7$$gm=FoUoDn9p3oL# j7E?=KDtr=q"+RWdd7&)p@JCWn'^S*g>MGkq=F^Zn'^S*g>MGkq"+RWdd7&)iq*9=h1 j7E?6KDtr6q"+RVdcLPsp@JCWmF(,!fAQ,hq=F^ZmF(,!fAQ,hq"+RVdcLPsiq*96l@JDTmuZu> Y'Thns7$$gk(30NoDn$i3oL# j7Ioi]CZ6qjjV8gfY[j1a4'/>p[eUXjjV8gfY[j1a4'/>Vt>Q-JaJ$UJaN:#Jb7:\!:@T`i;WfE lMgkOjRds~> j7Ioi]CZ6qjjV8gfY[j1a4'/>p[eUXjjV8gfY[j1a4'/>Vt>Q-JaJ$UJaN:#Jb7:\!:@Vki;WfE lMgkOjRds~> j7Iob]CZ6qjjV8gfY[j1a4'/>p[eUXjjV8gfY[j1a4'/>Vt>Q&J`_OGJ`cdjJb7:\!9V*Yi;Wf> lMgkHjRds~> Jb5N*"7kj@i7?`ei8s@sp[eRYmH*9Wh?2]tmIGi#Jb4NcJb4NcJb8@%L@+'oJ,~> Jb5N*"7kj@i7?`ei8s@sp[eRYmH*9Wh?2]tmIGi#Jb4NcJb4NcJb8@%L@+'oJ,~> Jb5N*"7kj@i7?`ei8s@sp[eRYmH*9Wh?2]tmIGi#Jb4NcJb4NcJb8@%L?@RhJ,~> Jb5N*"7u*Qn(-n/n*oo>p[eRZo'l,!mK;qIo(.G)Jb4Nc^@VQ*.0_N;qXar]+pBX.o_ec@oRZXc oZd"/gYCl^J,~> Jb5N*"7u*Qn(-n/n*oo>p[eRZo'l,!mK;qIo(.G)Jb4Nc^@VQ*.0_N;qXar]+pBX.o_ec@oRZXc oZd"/gYCl^J,~> Jb5N*"7u*Qn(-n/n*oo>p[eRZo'l,!mK;qIo(.G)Jb4Nc^@VQ*.0_N;qXar]+pBX.o_ec@oRZXc oZd"/gYCl^J,~> Jb5E'eFW5&eFS7`Jb4Nc]CZ5)c0_ADqXatgam>N2oDjI7Jb4Ncb4G]a0]_kV~> Jb5E'eFW5&eFS7`Jb4Nc]CZ5)c0_ADqXatgam>N2oDjI7Jb4Ncb4G]a0]_kV~> Jb5E'eFW5&eFS7`Jb4Nc]CZ5)c0_ADqXatgam>N2oDjI7Jb4Ncb4G]a0]_kV~> Jb4NcJb4NcJb4Nckk#&EUF,X*!;c Jb4NcJb4NcJb4Nckk#&EUF,X*!;c Jb4NcJb4NcJb4Nckk#&EUF,X*!;c Jb4NcJb4NcJb4Ncl1FuU!>s//i,CW+oRZXco[!.36imEbjn+'~> Jb4NcJb4NcJb4Ncl1FuU!>s//i,CW+oRZXco[!.36imEbjn+'~> Jb4NcJb4NcJb4Ncl1FuU!>s//i,CW+oRZXco[!.36imEbjn+'~> Jb4NcJb4NcJb4Nckk"rATITd0"2l@(o)81]liht4Jb4Ncd.@Li$31&4fB2^U~> Jb4NcJb4NcJb4Nckk"rATITd0"2l@(o)81]liht4Jb4Ncd.@Li$31&4fB2^U~> Jb4NcJb4NcJb4Nckk"rATITd0"2l@(o)81]liht4Jb4Ncd.@Li$31&4fB2^U~> Jb4NcJb4NcJb4NclLY3bfBo=FqXaiT1VN_Y!pG(iJb4NcJb7=]"_%]:!!d3"oRH~> Jb4NcJb4NcJb4NclLY3bfBo=FqXaiT1VN_Y!pG(iJb4NcJb7=]"_%]:!!d3"oRH~> Jb4NcJb4NcJb4NclLY3bfBo=FqXaiT1VN_Y!pG(iJb4NcJb7=]"_%]:!!d3"oRH~> Jb4NcJb4NcJb4NclLY=M+Ts9to^i.^o_\[mkl:\g51+rRJb4NcYOm>~> Jb4NcJb4NcJb4NclLY=M+Ts9to^i.^o_\[mkl:\g51+rRJb4NcYOm>~> Jb4NcJb4NcJb4NclLY=M+Ts9to^i.^o_\[mkl:\g51+rRJb4NcYOm>~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> g[rMV!;"2%`ps-$b4G]*_X[]u`Uq1@!;"2%`ps-$Jb4NcJb4NcJb6kPJ,~> g[rMV!;"2%`ps-$b4G]*_X[]u`Uq1@!;"2%`ps-$Jb4NcJb4NcJb6kPJ,~> g[rJT`Uq.>b4NL>`Uq.>`Uq.>Jb4NcJb4NcJb6kPJ,~> Jb4Nco(2qQYO)>ZJb4NcJb4NcJb4NcJb8$qJ,~> Jb4Nco(2qQYO)>ZJb4NcJb4NcJb4NcJb8$qJ,~> Jb4Nco(2tQkHO(Lm.5f#Jb4NcJb4NcJb4Ncjn+'~> Jb4Nco(2qMY3c5WJb4NcJb4NcJb4NcJb8'rJ,~> Jb4Nco(2qMY3c5WJb4NcJb4NcJb4NcJb8'rJ,~> Jb4Nco(2qGY3#cLo7?OboRZXcoRZXcoRZYsoRH~> Jb4Nco(9$N!:GF1Jb4NcJb4NcJb4Nck4F0~> Jb4Nco(9$N!:GF1Jb4NcJb4NcJb4Nck4F0~> Jb4Nco(9$G!U93,oRZXcoRZXcoRZXco]Pjs~> Jb4Nco(9!M!:GF1Jb4NcJb4NcJb4NckOa9~> Jb4Nco(9!M!:GF1Jb4NcJb4NcJb4NckOa9~> Jb4Nco(9!F!9o(,Jb4NcJb4NcJb4NckOa9~> b4GZ6an,V>\A\">!6Oj6ZFJ]^WpKfUORrRZ\?tl.!86uFZFGJXJb4NcJb4Nce+A.~> b4GZ6an,V>\A\">!6Oj6ZFJ]^WpKfUORrRZ\?tl.!86uFZFGJXJb4NcJb4Nce+A.~> b4GZ6an,V>\A\">!6Oj6ZFJ]^Woa f^o=7C(h)2oCN"_cL_1H*8f3:!m1Mc^@VK8*8fTEp[&(`KC+6jkNr6Lh f^o=7C(h)2oCN"_cL_1H*8f3:!m1Mc^@VK8*8fTEp[&(`KC+6jkNr6Lh f^o=7C(h)2oCN"_cL_1H*8f3:!m1Mc^@VK8*8fTEpZ;SYJ*)1[kN2aEh<"aEXnh)ZnCR=3!5A(, [N+2RoE;c^"sKQ$oDemEoE)H@hgtENoRZXcoRZYaoRH~> f^o;e:>Y&KoCN"_c1D'b6h8^u!eg]0^%;AR6h9++p[&(`-L<#YkNr6Lhj1e[!8-oFHpdIUoRZXcoRZXco[WSa~> f^o;e:>Y&KoCN"_c1D'b6h8^u!eg]0^%;AR6h9++p[&(`-L<#YkNr6Lhj1e[!8-oFHpdIUoRZXcoRZXco[WSa~> f^o;e:>Y&KoCN"_c1D'b6h8^u!eg]0^%;AR6h9++pZ;SY-06BPkN2aEh<"`aD#*2okO[9s!!'_) !dmB+eFWk[R-W:-kk"iTf^o5m6h5g#Jb4NcJb4Nceb"@~> f^o1_eG]">!<2Tn!;,m`!<2Wh!<;]k!!i]ZZh3kiEXOMToE+PKIadoj5[QeUoEPA:$SM)D!!*#j rr<*'/[Ngr!qAR*hXpR0$NN/53<8HU-Nkg+mJR"o$gdoo">$_Cm/V&3/a`K:hsCGmE;8f#n(.+: !;,m`!!Fm[o_eaj$hXAt!"70G!qAR*eFXq/HG/Y:oDih9-G]9!2[(G:oZfB!,J!Hc0*;4HoDjd] & f^o1_eG]">!<2Tn!;,m`!<2Wh!<;]k!!i]ZZh3kiEXOMToE+PKIadoj5[QeUoEPA:$SM)D!!*#j rr<*'/[Ngr!qAR*hXpR0$NN/53<8HU-Nkg+mJR"o$gdoo">$_Cm/V&3/a`K:hsCGmE;8f#n(.+: !;,m`!!Fm[o_eaj$hXAt!"70G!qAR*eFXq/HG/Y:oDih9-G]9!2[(G:oZfB!,J!Hc0*;4HoDjd] & f^o1_eG]">!<2Tn!;,m`!<2Wh!<;]k!!i]ZZh3kiEXOMToE+PKIadoj5[QeUoEPA:$SM)D!!*#j rr<*'/[Ngr!qAR*hXpR)$NN)/2ZW!G-3P[$k5>8h$g%Eh"=gD5joB0(/*?d1hrXrcD#!,mlIPS5 !;,m`!!Fm[o_eaj$hXAt!"70G!qAR*eFXq/HG/Y:oDih9-G]9!2[(G:oZfB!,J!Hc0*;4HoDjd] & f^o@Mo(2n^oDnm_!!*#j!!)ui!!)og#k>PEo^i-=l"&iH!5A;]!d4rbeas45$eu-So^i-=l"'ec !!)rh!qEo?fCT'WqLScl$Y\Gip[&:fRKArYm3[tWO!jqo!f[Yurp:*Y[MS>u!/' f^o@Mo(2n^oDnm_!!*#j!!)ui!!)og#k>PEo^i-=l"&iH!5A;]!d4rbeas45$eu-So^i-=l"'ec !!)rh!qEo?fCT'WqLScl$Y\Gip[&:fRKArYm3[tWO!jqo!f[Yurp:*Y[MS>u!/' f^o@Mo(2n^oDnm_!!*#j!!)ui!!)og#k>PEo^i-=l"&iH!5A;]!d4rbeas45$eu-So^i-=l"'ec !!)rh!qEo?fCT'WqLScl$Y\GipZ;e_Q3*BQjs?#DM^JGc!f7AlroOUKYnuQb!.Wd1k2l["joKdE pZ;JGfCT%CrU^0]]GUY?!g*r*rq$8S%DCBc!5A;]!d4rbeasd-A2"UeoDk*@Q7MS^T'`a,oJ)s) YTrckNU?`;"9=MKY7osg!5A;]!d4rbJb4NcJb4NcJb7OcJ,~> f^oLfg%=n:oDnOUn,WI[!!)ui!!)ui#`g#.!!2`a!:Sn=!4VfF!bMaOearjBrW!)%o^i.Pr:Bsh rq$>S*Wc;nc!!2KS!9jCK!!(jB!I\q] m/ZS,oF:sX"9@r`oDmqD!4VfF!bMaOJb4NcJb4NcJb7OcJ,~> f^oLfg%=n:oDnOUn,WI[!!)ui!!)ui#`g#.!!2`a!:Sn=!4VfF!bMaOearjBrW!)%o^i.Pr:Bsh rq$>S*Wc;nc!!2KS!9jCK!!(jB!I\q] m/ZS,oF:sX"9@r`oDmqD!4VfF!bMaOJb4NcJb4NcJb7OcJ,~> f^oLfg%=n:oDnOUn,WI[!!)ui!!)ui#`g#.!!2`a!:Sn=!4VfF!bMaOearjBrW!)%o^i.Pr:Bsh rq$>S*WcsX"9@r`oDmqD!4VfF!bMaOJb4NcJb4NcJb7OcJ,~> f^oMb>isoDoFnLrO9#:P!!)ui!=%*RoEPZ*l`:*YoDmS:!qAC*eFWuXK(OY,o^i.\oDemjoE5H' l`:*5oE+PFKAQ7pm/R.cm0Wj\-B[DN-gdoJ]Dh1&#HHm`!:B.Rr9XLhcee$iI;noI!:nn:%WCL4 +nYC4YNr7ioWJtErq$8q'Yr#d!qAC*eFX5Qe*Wb=oDk0BQ7;G^X6fiX!]-nDrq$7BNUHf f^oMb>isoDoFnLrO9#:P!!)ui!=%*RoEPZ*l`:*YoDmS:!qAC*eFWuXK(OY,o^i.\oDemjoE5H' l`:*5oE+PFKAQ7pm/R.cm0Wj\-B[DN-gdoJ]Dh1&#HHm`!:B.Rr9XLhcee$iI;noI!:nn:%WCL4 +nYC4YNr7ioWJtErq$8q'Yr#d!qAC*eFX5Qe*Wb=oDk0BQ7;G^X6fiX!]-nDrq$7BNUHf f^oMb>isoDoFnLrO9#:P!!)ui!=%*RoEPZ*l`:*YoDmS:!qAC*eFWuXK(OY,o^i.\oDemjoE5H' l`:*5oE+PFKAQ7pjo>D\jpD+N-&pc>-K_-<[Jo:n#Gp:O!9WDDr8n"`akl.\H>N<;!:\b8%WCL4 +nYC4YNr7ioWJtErq$8q'Yr#d!qAC*eFX5Qe*Wb=oDk0BQ7;G^X6fiX!]-nDrq$7BNUHf f^oR:>RV6noTKdtN<&VK!<;["C^:%5oQq)ZK)kQ9!86uGG"D5SoEQP]oZ89KoDnj^qu?omHOgX> !7:?>G"D5]o_7_Q!<2?k!:@3A""(ME$2ji0m f^oR:>RV6noTKdtN<&VK!<;["C^:%5oQq)ZK)kQ9!86uGG"D5SoEQP]oZ89KoDnj^qu?omHOgX> !7:?>G"D5]o_7_Q!<2?k!:@3A""(ME$2ji0m f^oR:>RV6noTKdtN<&VK!<;["C^:%5oQq)ZK)kQ9!86uGG"D5SoEQP]oZ89KoDnj^qu?omHOgX> !7:?>G"D5]o_7JJ!<2*d!9UO4"!t88$2ji0k&_(CHiW On8^Z+6&7O!jEd+h=S2D$*tUsl[ On8^Z+6&7O!jEd+h=S2D$*tUsl[ On8^Z+6&7O!jEd+h=S2=$*G4ijEbY&E?2@\joFq:oE)6=im%)q!5\:/YTMkUoRZXcoRZXco[NM`~> On8[`]SlC_\%LMg[d==Q5sTF#4%A!"m/ZR>oDuG6bk(l8_=Rc;]Rf^+oRZXcoRZY`oRH~> On8[`]SlC_\%LMg[d==Q5sTF#4%A!"m/ZR>oDuG6bk(l8_=Rc;]Rf^+oRZXcoRZY`oRH~> On8[`]SlC_\%LMg[cRhD5 Jb4Nco(9?W"m^F@#_).6m/ZX,oRZXcoRZXcoRZXco^;@%~> Jb4Nco(9?W"m^F@#_).6m/ZX,oRZXcoRZXcoRZXco^;@%~> Jb4Nco(9?P"lse5#^b\,joF^uoRZXcoRZXcoRZXco^;@%~> Jb4Nco(99Us((`g!:bX4Jb4NcJb4NcJb4Ncn+;,~> Jb4Nco(99Us((`g!:bX4Jb4NcJb4NcJb4Ncn+;,~> Jb4Nco(99N!FT@-joFk$oRZXcoRZXcoRZXco^DF&~> Jb4Nco(8[D!:td6Jb4NcJb4NcJb4NcnFV5~> Jb4Nco(8[D!:td6Jb4NcJb4NcJb4NcnFV5~> Jb4Nco(8^>!U'!(oRZXcoRZXcoRZXco^ML'~> Jb4Nco(8XC!:td6Jb4NcJb4NcJb4Ncnaq>~> Jb4Nco(8XC!:td6Jb4NcJb4NcJb4Ncnaq>~> Jb4Nco(8[=!U'$)oRZXcoRZXcoRZXco^VR(~> Jb4Nco(8XC!:GF1Jb4NcJb4NcJb4Ncnaq>~> Jb4Nco(8XC!:GF1Jb4NcJb4NcJb4Ncnaq>~> Jb4Nco(8X Jb4Nco(8UB!UfQ1oRZXcoRZXcoRZXco^h^*~> Jb4Nco(8UB!UfQ1oRZXcoRZXcoRZXco^h^*~> Jb4Nco(8U;!U90+oRZXcoRZXcoRZXco^h^*~> Jb4Nco(8O@!:td6Jb4NcJb4NcJb4Nco^mY~> Jb4Nco(8O@!:td6Jb4NcJb4NcJb4Nco^mY~> Jb4Nco(8R:!U'!(oRZXcoRZXcoRZXco^qd+~> j7E?ASb)pOn+6VMSb)pOn+6VMSa?FHn+6VMSb)pOn+6VMSb)pOJb4NcJb4NcJb8'rJ,~> j7E?ASb)pOn+6VMSb)pOn+6VMSa?FHn+6VMSb)pOn+6VMSb)pOJb4NcJb4NcJb8'rJ,~> j7E?=Sb)pKn+6VISb)pKn+6VIS`Tq=n+6VISb)pKn+6VISb)pKJb4NcJb4NcJb8'rJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$Uq<\=@\ZW?jJaJ$UJaJ$Ub3e6]J,~> j7IoiJaJ$UJaJ$Uq<\=%>WYc_JaJ$UJaJ$Ub3e6]J,~> j7IobJ`_OGJ`_OGq;qgi9IE#sJ`_OGJ`_OGb3%aVJ,~> j7IoiJaJ$UJaJ$Uq<\8JaJ$UJaJ$Ub3e6]J,~> j7IoiJaJ$UJaJ$Uq<\;TW9;`YJaJ$UJaJ$Ub3e6]J,~> j7IobJ`_OGJ`_OGq;qf j7IoiJaJ$UJaJ$Uq<]T=S\)-MRb,r,0mHq!uY.3l#XKB"fmB"^?Yg`!r\#m-rJaJ$U JaJ$Umd9',J,~> j7IoiJaJ$UJaJ$Uq<]S+P/QPNmDDm`<5\)]ME=bVmHlfj5$@822E=mPm5CP'6Da(e<&mpqJaJ$U JaJ$Umd9',J,~> j7IobJ`_OGJ`_OGq;s(hKY-.(k.3o,6b;D)I47Ipk2mn;.nA_H,:5pdjs-$C0Uh.86RG13J`_OG J`_OGmcNR%J,~> j7IoiJaJ$UJaJ`i!4(bS(?ENGZ.A j7IoiJaJ$UJaJ`i!4(bS(=Rh;6`fZRVWXaSm5Cd'VcN3P2OifZrp:3]_FX&PT]`jSm8KnFUf;p4 JaJ$UJaMmmj7Ij~> j7IobJ`_OGJ``6[!3PDG( j7IoiJaJ$UJaJros0;Veq>^RZGI$K#&`L"^Xm>B?XmN4UmH^nAmB#XQm/bVAri?LpXR,?AXR3.U mH^m*m=FYUm=FZmmGmhh~> j7IoiJaJ$UJaJros0;Veq>^RZGI$K#&^pa3&W__2X9\s2XNKe mH-E[m=FYUm=FZmmGmhh~> j7IobJ`_OGJ``Has/c8^q>^RXFKOfi&]6Bd-K!tB-KZQ1k2.QKjs/mbjq*e>,pO\t,N@kJ,N^?1 k2%G9k(2ZGk(2[_k2Z)a~> j7IoiJaJ$UJaK&r$*nb;!!#5JIt+Kt j7IoiJaJ$UJaK&r$*nb;!!#5JIt*FV)ZU?H!!.8Jn*Ld5e*!i"m8KnFUf?_>VWXjWmHlh1m05Y_ kI3A:2ZE(J2XOG?kG2(.m=FYUm=FZmmGmhh~> j7IobJ`_OGJ``Qd$*A;1!!#5JIt*7Q&HE+9!!.,An)b9tbMf0Lk!GQpR7$*]S)9]'k2moejp!9< hlJ'b,Q?g0,N_=^hjHi_k(2ZGk(2[_k2Z)a~> j7IoiJaJ$UJaK)s"?ZYa5JR-r!:'Ka#(]eT!-EAMo'I+@[B6mjmG>:R[bgDQb,r,/mHq"Rm0:h> Yg9E j7IoiJaJ$UJaK)s"?ZYa5JR-r!6tF""t1Dj!-EAMo'I*j:cVI]mD;d];o@u\M*"VRmHlh1m08b@ 6C9[u2ZE(J2XS@[3)d_Gm=FYUm=FZmmGmhh~> j7IobJ`_OGJ``Te"?HM_5JR-r!6G'_"r\'R!-*&@o&^UW4siRpk.*f*6Fu;(Hmq@nk2moejp$Mm 08q.<,Q?g0,NcO'-:5:pk(2ZGk(2[_k2Z)a~> j7IoiJaJ$UJaK/u"EshGJ%tj[!:'B^"]bU'Ep2lRm/X5=m=FYUm=FZQmGmhh~> j7IoiJaJ$UJaK/u"EshGJ%tj[!6t j7IobJ`_OGJ``Zg"EXVDJ%tj[!6Fs\"VC_7DrKgAjo?WWk(2ZGk(2[Ck2Z)a~> j7IoiJaJ$UJaK3!"$?QJ^\Ig/lM.IcepdZ"Wp&g7m/X5=m=FYUm=FZQmGmhh~> j7IoiJaJ$UJaK3!"$?QJ^\Ig/c1j1$RXY;;Wp&g7m/T%qm=FYUm=FZQmGmhh~> j7IobJ`_OGJ``]h"$-EH^\Ig/aS6taOFI61V;^P$jo?WWk(2ZGk(2[Ck2Z)a~> j7IoiJaJ$UJaK9#"0t#hJ+imBlM.L]rrp/&!-%&cJaJ$UJaJ$U]C"YNJ,~> j7IoiJaJ$UJaK9#"0t#hJ+imBc1j3srrp/&!-%&cJaJ$UJaJ$U]C"YNJ,~> j7IobJ`_OGJ``cj"0FTaJ+imBaS7"[rrp/&!,^TRJ`_OGJ`_OG]B8/GJ,~> j7IoiJaJ$UJaK<$"0qn,^\.U,lM7O^f)>UO5QG]^l@J>Rm=FYUmC_iHoRH~> j7IoiJaJ$UJaK<$"0qn,^\.U,c1s6tRf36h5QG]^l@J>Rm=FYUmC_iHoRH~> j7IobJ`_OGJ``fk"0DP'^\.U,aS@%\OT#1^5QGTUj+6?Dk(2ZGk.Kj:oRH~> j7IoiJaJ$UJaK<$rrDZj!:'B^qu6eK!2AH9JaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaK<$rrDZj!6t j7IobJ`_OGJ``fkrrDZj!6Fs\qu6eK!1hm'J`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaK?%!^$ItoD\gUqQ0bKqYp[`!2/97JaJ$UJaJ$U^?stQJ,~> j7IoiJaJ$UJaK?%!^$ItoD\g8qDJ>BqYp[`!2/97JaJ$UJaJ$U^?stQJ,~> j7IobJ`_OGJ``il!]g=roD\g3qBH!%qYp[`!1_d'J`_OGJ`_OG^?4JJJ,~> j7IoiJaJ$UJaKB&!d=Woo)A^TqlKk7q#:I^1=kCKJaJ$UJaJ$U^[:(RJ,~> j7IoiJaJ$UJaKB&!d=Woo)A^7q_eFfq#:I^1=kCKJaJ$UJaJ$U^[:(RJ,~> j7IobJ`_OGJ``lm!d"Elo)A^2q]c)Dq#:I^1!nY7J`_OGJ`_OG^ZOSKJ,~> j7IoiJaJ$UJaKB&!<@W>rrD?]XT6rIrrp.;B%Q&.JaJ$UJaJ$U_!U1SJ,~> j7IoiJaJ$UJaKB&!<@W>rrC=@2?93@rrp.;B%Q&.JaJ$UJaJ$U_!U1SJ,~> j7IobJ`_OGJ``lm!<@W>rrC.;,63i#rrp.;A's)mJ`_OGJ`_OG^uj\LJ,~> j7IoiJaJ$UJaKE'!I"P5rrD?^XT6rGrrbRec._60m=FYUm=FZ?mGmhh~> j7IoiJaJ$UJaKE'!I"P5rrC=A2?93>rrbRec._60m=FYUm=FZ?mGmhh~> j7IobJ`_OGJ``on!H\>2rrC.<,63i!rrbReaOK'tk(2ZGk(2[1k2Z)a~> j7IoiJaJ$UJaKH(!d=WonG`LRr2oYl"ht$kd,!r;m=FYUm=FZ@mGmhh~> j7IoiJaJ$UJaKH(!d=WonG`L5r&46J"ht$kd,!r;m=FYUm=FZ@mGmhh~> j7IobJ`_OGJ``ro!d"ElnG`L0r$1n7"ht$jbLba)k(2ZGk(2[2k2Z)a~> j7IoiJaJ$UJaKH(!I"P4rrD?_XT6rErrkXfaO]A;JaJ$UJaJ$U_X6CUJ,~> j7IoiJaJ$UJaKH(!I"P4rrC=B2?93 j7IobJ`_OGJ``ro!H\>1rrC.=,63htrrkXf_pI0"J`_OGJ`_OG_WKnNJ,~> j7IoiJaJ$UJaKH(!<@Wh!'CWJaJ$UJaL>Aj7Ij~> j7IoiJaJ$UJaKH(!<@Wh!'CWJaJ$UJaL>Aj7Ij~> j7IobJ`_OGJ``ro!<@W j7IoiJaJ$UJaKK)!I"P3rrD?`XT6rCrrkY?`ms)8JaJ$UJaJ$U_sQLVJ,~> j7IoiJaJ$UJaKK)!I"P3rrC=C2?93:rrkY?`ms)8JaJ$UJaJ$U_sQLVJ,~> j7IobJ`_OGJ``up!H\>0rrC.>,63hrrrkY>_U$uuJ`_OGJ`_OG_rg"OJ,~> j7IoiJaJ$UJaKK)!I$fsrr_PgXk*%B"[E(&cJ7Z8m=FYUm=FZBmGmhh~> j7IoiJaJ$UJaKK)!I$fsrr^M(2Oa19"[E(&cJ7Z8m=FYUm=FZBmGmhh~> j7IobJ`_OGJ``up!H^Tprr^=e,EVHq"[E("ak#I&k(2ZGk(2[4k2Z)a~> j7IoiJaJ$UJaKK)!<@W;rr_PgXm5HV"ht%paOfR*m=FYUm=FZBmGmhh~> j7IoiJaJ$UJaKK)!<@W;rr^M(2U(^k"ht%paOfR*m=FYUm=FZBmGmhh~> j7IobJ`_OGJ``up!<@W;rr^=e,KK?S"ht%n`7!Ook(2ZGk(2[4k2Z)a~> j7IoiJaJ$UJaKK)!<@W;rrVJff'iVA!/JJ@iIUBIm=FYUmDJ>OoRH~> j7IoiJaJ$UJaKK)!<@W;rrUG'Rd^7Z!/JJ@iIUBIm=FYUmDJ>OoRH~> j7IobJ`_OGJ``up!<@W;rrU7dORN2P!/824gO\L j7IoiJaJ$UJaKN*!jVgAmf*=QXmZ2m!*?n[h<]^[JaJ$UJaLDCj7Ij~> j7IoiJaJ$UJaKN*!jVgAmf*=42X^SK!*?n[h<]^[JaJ$UJaLDCj7Ij~> j7IobJ`_OGJ`a#q!j)I j7IoiJaJ$UJaKN*!jVgAmf*=Qf'`PA!*6_TgZj@WJaJ$UJaLDCj7Ij~> j7IoiJaJ$UJaKN*!jVgAmf*=4RdU1Z!*6_TgZj@WJaJ$UJaLDCj7Ij~> j7IobJ`_OGJ`a#q!j)I j7IoiJaJ$UJaKN*!I"P2rrD?OrrkY<\B*=cJaJ$UJaJ$U`9lUWJ,~> j7IoiJaJ$UJaKN*!I"P2rrC=2rrkY<\B*=cJaJ$UJaJ$U`9lUWJ,~> j7IobJ`_OGJ`a#q!H\>/rrC.-rrkY;[DUJPJ`_OGJ`_OG`9-+PJ,~> j7IoiJaJ$UJaKN*!O;^XrrkYe[`6q]JaJ$UJaJ$U`9lUWJ,~> j7IoiJaJ$UJaKN*!O;^XrrkYe[`6q]JaJ$UJaJ$U`9lUWJ,~> j7IobJ`_OGJ`a#q!Nc@SrrkYdZbb&HJ`_OGJ`_OG`9-+PJ,~> j7IoiJaJ$UJaKN*!jVgAg&D3U;mjQpk(2oNm=FYUmDSDPoRH~> j7IoiJaJ$UJaKN*!jVgAg&D3U;mjQpk(2oNm=FYUmDSDPoRH~> j7IobJ`_OGJ`a#q!j)I j7IoiJaJ$UJaKN*!jVgAg&D3U;mjQok(2oNm=FYUmDSDPoRH~> j7IoiJaJ$UJaKN*!jVgAg&D3U;mjQok(2oNm=FYUmDSDPoRH~> j7IobJ`_OGJ`a#q!j)I j7IoiJaJ$UJaKN*!jVgAg&D3U;mjTqk(2oNm=FYUmDSDPoRH~> j7IoiJaJ$UJaKN*!jVgAg&D3U;mjTqk(2oNm=FYUmDSDPoRH~> j7IobJ`_OGJ`a#q!j)I j7IoiJaJ$UJaKK)!BNL"rs!uRWOKn!k^i,Pm=FYUmDSDPoRH~> j7IoiJaJ$UJaKK)!BNL"rs!uRWOKn!k^i,Pm=FYUmDSDPoRH~> j7IobJ`_OGJ``up!B j7IoiJaJ$UJaKK)!BWR#rs!uRX1?=)l%/5Qm=FYUmDSDPoRH~> j7IoiJaJ$UJaKK)!BWR#rs!uRX1?=)l%/5Qm=FYUmDSDPoRH~> j7IobJ`_OGJ``up!BNL"rs!uRW3sOlidp6Ck(2ZGk/?EBoRH~> j7IoiJaJ$UJaKK)!jMa@g]%HX:p.FRgZj@WJaJ$UJaLDCj7Ij~> j7IoiJaJ$UJaKK)!jMa@g]%HX:p.FRgZj@WJaJ$UJaLDCj7Ij~> j7IobJ`_OGJ``up!il=:g]%HX:TM"Ff&M>DJ`_OGJ`ao5j7Ij~> j7IoiJaJ$UJaKK)!pfnPh#@S/!)g):cea6?JaJ$UJaLABj7Ij~> j7IoiJaJ$UJaKK)!pfnPh#@S/!)g):cea6?JaJ$UJaLABj7Ij~> j7IobJ`_OGJ``up!p'DIh#@S/!)]o2b1M:-J`_OGJ`al4j7Ij~> j7IoiJaJ$UJaKH(!BC/8rs!uRW3jIkiIUBIm=FYUmDJ>OoRH~> j7IoiJaJ$UJaKH(!BC/8rs!uRW3jIkiIUBIm=FYUmDJ>OoRH~> j7IobJ`_OGJ``ro!B:)7rs!uRVQmn\gO\L j7IoiJaJ$UJaKH(!j2NRhZ!hr!)Tc-ak5j/m=FYUm=FZBmGmhh~> j7IoiJaJ$UJaKH(!j2NRhZ!hr!)Tc-ak5j/m=FYUm=FZBmGmhh~> j7IobJ`_OGJ``ro!iZ0MhZ!hr!)KW'`R j7IoiJaJ$UJaKH("7%oH^u,.sJ,lLQ^sCj%JaJ$UJaJ$U_sQLVJ,~> j7IoiJaJ$UJaKH("7%oH^u,.sJ,lLQ^sCj%JaJ$UJaJ$U_sQLVJ,~> j7IobJ`_OGJ``ro"6;B@^u,.sJ,lCK]ZSjdJ`_OGJ`_OG_rg"OJ,~> j7IoiJaJ$UJaKH("75+L5iDYIJ,iEI\Am+]JaJ$UJaJ$U_X6CUJ,~> j7IoiJaJ$UJaKH("75+L5iDYIJ,iEI\Am+]JaJ$UJaJ$U_X6CUJ,~> j7IobJ`_OGJ``ro"6JD?5iDYIJ,iBF[)12IJ`_OGJ`_OG_WKnNJ,~> j7IoiJaJ$UJaKE'"6sh*JE$S7^]7-.ZbXoCl@J>Rm=FYUmDA8NoRH~> j7IoiJaJ$UJaKE'"6sh*JE$S7^]7-.ZbXoCl@J>Rm=FYUmDA8NoRH~> j7IobJ`_OGJ``on"644uJE$S7^]7**Ye8-1j+6?Dk(2ZGk/-9@oRH~> j7IoiJaJ$UJaKB&"6OG#^uPIp!!T1$]?/[bJaJ$UJaJ$U_ j7IoiJaJ$UJaKB&"6OG#^uPIp!!T1$]?/[bJaJ$UJaJ$U_ j7IobJ`_OGJ``lm"5mno^uPIp!!T*u\&HbMJ`_OGJ`_OG_<0eMJ,~> j7IoiJaJ$UJaKB&"RG&*!5R^r#XJH:XLH0thWf[ZJaJ$UJaL;@j7Ij~> j7IoiJaJ$UJaKB&"RG&*!5R^r#XJH:XLH0thWf[ZJaJ$UJaL;@j7Ij~> j7IobJ`_OGJ``lm"QeDq!5R^r#XJH8WO'Cbf]7VGJ`_OGJ`af2j7Ij~> j7IoiJaJ$UJaK?%"R"W!!.a;5$,Ah,HE4EhcJ.T7m=FYUm=FZ?mGmhh~> j7IoiJaJ$UJaK?%"R"W!!.a;5$,Ah,HE4EhcJ.T7m=FYUm=FZ?mGmhh~> j7IobJ`_OGJ``il"Q@uh!.a;5$,Ah,Gc7m[ajoC%k(2ZGk(2[1k2Z)a~> j7IoiJaJ$UJaK?%"mk@*B)mJIrs?I@!.(]Q^ j7IoiJaJ$UJaK?%"mk@*B)mJIrs?I@!.(]Q^ j7IobJ`_OGJ``il"m+XmAH78Grs?I@!-kHI]#W:UJ`_OGJ`_OG^ZOSKJ,~> j7IoiJaJ$UJaK<$#41F*B)k2Imf*[15l`:0XgZ$gf])7Km=FYUm=FZ>mGmhh~> j7IoiJaJ$UJaK<$#41F*B)k2Imf*[15l`:0XgZ$gf])7Km=FYUm=FZ>mGmhh~> j7IobJ`_OGJ``fk#3F^lA,nlFmf*[15l`:-Wj9:WdbEl7k(2ZGk(2[0k2Z)a~> j7IoiJaJ$UJaK9##OCI*BE/$_JF`^K^d.r#;5pe1_Tg]kl[eGSm=FYUmCquJoRH~> j7IoiJaJ$UJaK9##OCI*BE/$_JF`^K^d.r#;5pe1_Tg]kl[eGSm=FYUmCquJoRH~> j7IobJ`_OGJ``cj#NX^kAH2^\JF`^K^d.r#:o:A&^<"aWjFQHEk(2ZGk.^! j7IoiJaJ$UJaK6""RG1*dq&FX!J%u\s8TkC5l^m5W3*G>`mEH!l[eGSm=FYUmChoIoRH~> j7IoiJaJ$UJaK6""RG1*dq&FX!J%u\s8TkC5l^m5W3*G>`mEH!l[eGSm=FYUmChoIoRH~> j7IobJ`_OGJ```i"Q\Fkc=-\P!J%u\s8TkC5l^m5VQ-o3_TUHajFQHEk(2ZGk.Tp;oRH~> j7IoiJaJ$UJaK3!#41L/f$%Q.rW)sardb#7rW! j7IoiJaJ$UJaK3!#41L/f$%Q.rW)sardb#7rW! j7IobJ`_OGJ``]h#3Fdqd)TKtrW)sardb#7rW! j7IoiJaJ$UJaK/u$LHs7g=+6`?<\s2rW)pI%"No4ZaRK_aO&StkNmceJaJ$UJaL):j7Ij~> j7IoiJaJ$UJaK/u$LHs7g=+6`?<\s2rW)pI%"No4ZaRK_aO&StkNmceJaJ$UJaL):j7Ij~> j7IobJ`_OGJ``Zg$K^7$eBQ"K>ZiR-rW)pH%"3W,YHtaQ_opK]i8oOPJ`_OGJ`aT,j7Ij~> j7IoiJaJ$UJaK)s)=-GCg=4Eh_nWjq\@8oU\\#Sma3;uahrX3Um=FYUm=FZ8mGmhh~> j7IoiJaJ$UJaK)s)=-GCg=4Eh_nWjq\@8oU\\#Sma3;uahrX3Um=FYUm=FZ8mGmhh~> j7IobJ`_OGJ``Te) j7IoiJaJ$UJaK#q($k#Ah:U6(cHFAOaN;WMd*pV%i8s6Tm=FYUm=FZ6mGmhh~> j7IoiJaJ$UJaK#q($k#Ah:U6(cHFAOaN;WMd*pV%i8s6Tm=FYUm=FZ6mGmhh~> j7IobJ`_OGJ``Nc($+<.f@/'haiDB=_o0R9bKePeg>:k@k(2ZGk(2[(k2Z)a~> j7IoiJaJ$UJaJro#OCU8io&YIgA]b0gYC]Fjll&^m=FYUm=FZ4mGmhh~> j7IoiJaJ$UJaJro#OCU8io&YIgA]b0gYC]Fjll&^m=FYUm=FZ4mGmhh~> j7IobJ`_OGJ``Ha&`r!/gtC<1eC2mre^iF.hW!XJk(2ZGk(2[&k2Z)a~> j7IoiJaJ$UJaJlms6L*YlKIEnk3)!nlKj)hJaJ$UJaKc1j7Ij~> j7IoiJaJ$UJaJlms6L*YlKIEnk3)!nlKj)hJaJ$UJaKc1j7Ij~> j7IobJ`_OGJ``B_s5a=Cj5Ksp"6/A$jalQFk(2ZGk-F.0oRH~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaK6"j?*FJJaJ$UJaJ$U\aAGLJ,~> j7IoiJaJ$UJaK6"j?*FJJaJ$UJaJ$U\aAGLJ,~> j7IobJ`_OGJ```ij>m:BJ`_OGJ`_OG\`VrEJ,~> j7IoiJaJ$UJaK6"!!)d(mlpgQZ@VCom=FYUmCDWEoRH~> j7IoiJaJ$UJaK6"!!)dUmlpgQZ@VCom=FYUmCDWEoRH~> j7IobJ`_OGJ```i!!)d(mlpgQXF]Mbk(2ZGk.0X7oRH~> j7IoiJaJ$UJaK6"!<=/:3WNsbrrTq8Z@VCom=FYUmCDWEoRH~> j7IoiJaJ$UJaK6"!<@??mf;tKrrTq8Z@VCom=FYUmCDWEoRH~> j7IobJ`_OGJ```i!<=/:3WNsbrrTq8XF]Mbk(2ZGk.0X7oRH~> j7IoiJaJ$UJaK6"!<=/:3WNsbrrTq8Z@VCom=FYUmCDWEoRH~> j7IoiJaJ$UJaK6"!<@??mf;tKrrTq8Z@VCom=FYUmCDWEoRH~> j7IobJ`_OGJ```i!<=/:3WNsbrrTq8XF]Mbk(2ZGk.0X7oRH~> j7IoiJaJ$UJaK6"!<=/:3WNsbrrTq8Z@VCom=FYUmCDWEoRH~> j7IoiJaJ$UJaK6"!<@??mf;tKrrTq8Z@VCom=FYUmCDWEoRH~> j7IobJ`_OGJ```i!<=/:3WNsbrrTq8XF]Mbk(2ZGk.0X7oRH~> j7IoiJaJ$UJaK6"!<=/:3WNsbrrTq8Z@VCom=FYUmCDWEoRH~> j7IoiJaJ$UJaK6"!<@??mf;tKrrTq8Z@VCom=FYUmCDWEoRH~> j7IobJ`_OGJ```i!<=/:3WNsbrrTq8XF]Mbk(2ZGk.0X7oRH~> j7IoiJaJ$UJaK6"rrDj*mm-sSZ@VDRm/TfTmJ]b0m=FYamGmhh~> j7IoiJaJ$UJaK6"rrDjWmm-sSZ@VDRm/Vn:mJ_ikm=FYamGmhh~> j7IobJ`_OGJ```irrDj*mm-sSXF]NEjo?Y%k5HTVk(2ZSk2Z)a~> j7IoiJaJ$UJaK6"j?!@IJaL;@"CfgHd:2_7!)%n1JaJHaj7Ij~> j7IoiJaJ$UJaK6"j?!@IJaL;@"I.^/gQ_ j7IobJ`_OGJ```ij>d4AJ`af2"?sfe`'3eT!$d'WJ`_sSj7Ij~> j7IoiJaJ$UJaJ$UJaLGD"HTAGUQ4s5!)%n1JaJHaj7Ij~> j7IoiJaJ$UJaJ$UJaLGD"L5].^9R:s!/?'lJaJHaj7Ij~> j7IobJ`_OGJ`_OGJ`ar6"E][dNHOjT!$d'WJ`_sSj7Ij~> j7IoiJaJ$UJaJ$UJaLGD#eYBKG.d5092#9O#KULPB#sB,9E"8X9*.k&_L_**m?[/"oRH~> j7IoiJaJ$UJaJ$UJaLGD#gT13U!;ulL5,]p#M#/sQdtKjLAgN>L'!1!dXge:m?[/"oRH~> j7IobJ`_OGJ`_OGJ`ar6#ch%h=01QH,:!3M#J*8L6b;FB,Q6d),6B2^Z@V.hk*G/ioRH~> j7IoiJaJ$UJaJ$UJaMIas0CeS"Q`S?;s+)H!)*=Y#A((%X_-tf9De)Xlc34\m=FYjmGmhh~> j7IoiJaJ$UJaJ$UJaMIas0CeS"R(d)Ms:6.!/CL?#Fm+%2Km=FYjmGmhh~> j7IobJ`_OGJ`_OGJ`btSs/kGG"PkZ]/`$Eg!$hL*#==p;R7$-!,Q$U)j1.+"k(2Z\k2Z)a~> j7IoiJaJ$UJaJ$UJaM[gs0DYeq>gP>!4(qX!csEErTsSXrp9qbkj@P.mHmRFm0'Cq9M8'1m=FYj mGmhh~> j7IoiJaJ$UJaJ$UJaM[gs0DYeq>gP>!4(qX!hZ`jrTsT>rp9rGlL!hlmHoZ,m0(:iLPCP7m=FYj mGmhh~> j7IobJ`_OGJ`_OGJ`c1Ys/l;^q>gP;!3PSL!`X8ArT4))roOG4hr`cBk2moejogr2,pO[dk(2Z\ k2Z)a~> j7IoiJaJ$UJaJ$UJaMai$*lYn!'`\tJ%oRo,leSW!!.90md1(L:87^S!)*=Y#\C1&X_-tf>L*68 "Alks]h j7IoiJaJ$UJaJ$UJaMai$*lYn!'`\tJ%pX86NA;/!!.90md1(hLqWcS!/CL?#aWHp`0]lYOP0X# "GtLjcA$o;JaJcjj7Ij~> j7IobJ`_OGJ`_OGJ`c7[$*?5g!'`\tJ%nk[&HE+9!!.-%mcFS/-B.oc!$hL*#XY$ j7IoiJaJ$UJaJ$UJaMgk"gU5j5JOu2rr@ j7IoiJaJ$UJaJ$UJaMgk"gU5j5JOu2rrAnnL]9aA!!.3,nEgA3L>)%^r.YgNeWtuVea;bBMMCNc \rD]_L4S`Cm?[/"oRH~> j7IobJ`_OGJ`_OGJ`c=]"g'fc5JOu2rr?7#,QAAU!!.*"nE'kj,JrU*r$)g9\O%W0]&r.B.jO3= KeG*<,D#!.k*G/ioRH~> j7IoiJaJ$UJaJ$UJaMmm"L:,i5JR$o!-n<4"=4$JF6S8CJaJ$Ulg j7IoiJaJ$UJaJ$UJaMmm"L:,i5JR$o!2]LH"@E.hF6S8CJaJ$Ulg j7IobJ`_OGJ`_OGJ`cC_"Ka]b5JR$o!*]1C";(V6E8lH2J`_OGlfR7"J,~> j7IoiJaJ$UVsOI7q j7IoiJaJ$UVsOJdq j7IobJ`_OGVre!Qq<%\MqrRnPJ`_OGgu\]\!!'e.rr?6t,6a0Y!,^TPJ`_OGJ`c@^j7Ij~> j7IoiJaJ$UYO)H^bjESCoBcN.lg4hP7P@WOJaJ$Una-C%!'g/V!-n<4#c.M#IfMCbkNmceJaJ$U n*T0-J,~> j7IoiJaJ$UYO)J$ipGa6oBcO[lg4i*[B@*sJaJ$Una-C%!'g/V!2]LH#fH]BIfMCbkNmceJaJ$U n*T0-J,~> j7IobJ`_OGYN>u[kiV+$oB$&HlfJ?:oChe@J`_OGn`Bmp!'g/V!*]1C#a#)dIfM@[iT5XQJ`_OG n)i[&J,~> j7IoiJaJ$UYO)I?Q0jiaoBcN.lg4gjbNG`aJaJ$Uo'HL&!.XY@!-n<4rVm%N!&`Wpm"+PTm=FZo mGmhh~> j7IoiJaJ$UYO)J6d-]3#oBcO[lg4hlip5.#JaJ$Uo'HL&!.XY@!2]LHrVm%N!&`Wpm"+PTm=FZo mGmhh~> j7IobJ`_OGYN>uWmH3g*oB$&HlfJ??ki_=*J`_OGo&^!q!.XY@!*]1CrVm%N!&W?cjalQFk(2[a k2Z)a~> j7IoiJaJ$UYO)V$?gV4+mHlDSrp9s,9.ilRmHlG%mJe^9m0"r-3B3`,m/konl%/5QmI]cIG6!-a rr@ j7IoiJaJ$UYO)VL^?rYfmHq$Xrp9sI\$!3smHq%RmJjZE_pRm/lFQl[eGSmI]cIG6!-a rrAnlLB,6irrp.;1u%*XJaJ$UJaN!pj7Ij~> j7IobJ`_OGYN?,Vn`KB-k2uC2roOINoChe@k2uC8k5Y/SjokDFp%7VFjoO]>J`_OGoB$*r!.XV? !*]4D!0I-Y"ht$nf%oG=k(2ZGk4.TooRH~> j7IoiJaJ$UYO)JR2;L.Orp9\9rp9q_U?8+AmHlG$m/S[9m0"r0k-dAHm/l*BJq< j7IoiJaJ$UYO)JXYir=Vrp9]frp9s"eEbSmmHq%Qm/X9fm0'Q>leA`?m/lL_b(8r2mI]cIG6!-` rrAnlL\h#S^Aqs$jQqHbJaJ$Uo'PK0J,~> j7IobJ`_OGYN>uNp#l#2roO4SroOIWmH j7IoiJaJ$UcgCb j7IoiJaJ$UcgCb!3Q7f#HmC[l`TXBXo%JcXo@\jXmGHBXnh>diO6ts JaJ$Uo^)YZ5OnaRV>H#J_tj<9IfO0:jQqHbJaJ$UoBkT1J,~> j7IobJ`_OGcfY80!-J3$rcA.BlK/0=nFHP?!;?ES#PR?CkP"'#pAUVJ!+j^;jalQFk(2[dk2Z)a~> j7IoiJaJ$Udd7(@4T,9[5Q;)h!!,0*mHjt50*Tg!%\'9!%\'9!%[p5 !hrW0JaJ$UoBcR&!:^!gGl127V"all5QK0djalfMm=FZrmGmhh~> j7IoiJaJ$Udd7(@4T,9[5Q<84!!,0*mHju$Y0[(6!3Q7f#e/=Le$n`3Zf1iL!3Q7f!3Q7f!3Q+b !n=gQ)K_tX065QK0djalfMm=FZrmGmhh~> j7IobJ`_OGdcLS43rK'Y5Q5TTFOSAbX5QK!Yhgsp@k(2[dk2Z)a~> j7IoiJaJ$UeEmE%!!#5J^]"32/,p=e$31'KlgX9Ea=GC"mJJICmD)=G9#9o^T//"bmJJI8m482' !%[p5r$qd.JaJ$Up[&$+!.XMm=FYUmI]f+oRH~> j7IoiJaJ$UeEmE%!!#5J^]"32Y5_N*.f]QllgX9Ei2tu*mJO'pmG>=T\)-N>e$#n@mJO'emB-BT !3Q+br2ftaJaJ$Up[&$+!.XMm=FYUmI]f+oRH~> j7IobJ`_OGeE-oq!!#5J^]"32p]#RC4TGJ&jRD:7l1XB(k5=o]k3DR3oBPK#m.L)7k5=oRk4\f: !;?9Or:U-RJ`_OGpZ;O!!.XM j7IoiJaJ$Uf'NTa!!#6uqZ$CB"s=W\!-@DkJaJ$U]'TNB!:TpfGl:88V"O`kIfPebip;6`JaJ$U p$Lf3J,~> j7IoiJaJ$Uf'NTa!!#6uqZ$Dp#(fkU!-@DkJaJ$U]'TNB!:TpfV>Z/L_tF$5IfPebip;6`JaJ$U p$Lf3J,~> j7IobJ`_OGf&d*W!!#6uqZ$Ed#.]'\!-$rZJ`_OG]&j$8!:Tpf>5]ZGOS/VWIfP\ZguX+LJ`_OG p#b<,J,~> j7IoiJaJ$Uf^/gI49.U\q#C(="A&SQXQb<'JaL):!<@W j7IoiJaJ$Uf^/gI49.U\q#C)k"DRorXQb<'JaL):!<@W j7IobJ`_OGf]E==3WMCZq#C*_"FU8.VrE9iJ`aT,!<@W j7IoiJaJ$Ug$JmI!!'e-s7e)LPlH45!3#&HJaJ$U^?krF!:KjeGlC>9V"=Ti5QJaPi9Z$^JaJ$U p?go4J,~> j7IoiJaJ$Ug$JmI!!'e-s7i`%f)L4"!3#&HJaJ$U^?krF!:KjeV>c5M_t3m35QJaPi9Z$^JaJ$U p?go4J,~> j7IobJ`_OGg#`C=!!'e-s7l5f`HORrJU5QJUGg?!nJJ`_OG p?(E-J,~> j7IoiJaJ$Ug?erc!'g,Upac=C"@)s*jQ_<`JaL5>!<@W;rr@ j7IoiJaJ$Ug?erc!'g,UpoaSq"@)s*jQ_<`JaL5>!<@W;rrAnpL[tHK^As8;gZX4UJaJ$Up?go4 J,~> j7IobJ`_OGg?&HY!'g,Uq"Oae"@)s'h;a(KJ`a`0!<@W;rr?7%,P(j<^As24e_u)AJ`_OGp?(E- J,~> j7IoiJaJ$Ug[,&d!'g)Tq'uH0r;QnL!2JQ;JaJ$U_!M.&J+!==GY:]*nc&am>/f%Nm"+PTm=F[! mGmhh~> j7IoiJaJ$Ug[,&d!'g)Tq5s_Kr;QnL!2JQ;JaJ$U_!M.&J+!==V2'iDnc&am>/f%Nm"+PTm=F[! mGmhh~> j7IobJ`_OGgZAQZ!'g)Tq=amdr;QnL!2&'+J`_OG^ubXtJ+!===sG8Rnc&^l=i&G=J`_OGJ`c[g j7Ij~> j7IoiJaJ$Uh!G0K!'g&Sq()=A"@)s\i9Gm\JaL>A!I"P2rrRF j7IoiJaJ$Uh!G0K!'g&Sq6'So"@)s\i9Gm\JaL>A!I"P2rrT$P_sm[05V0[rh<]^[JaJ$Up[.#5 J,~> j7IobJ`_OGgu\[?!'g&Sq=jac"@)sXg>dbHJ`ai3!H\>/rrQ@KORW8R5V'IhfB%SGJ`_OGpZCN. J,~> j7IoiJaJ$Uh!P+Anc/G9!0m j7IoiJaJ$Uh!P+Anc/Hg!7puG"@+`niTc!]JaLAB!I"P2rrJsOn,EQA!4p;*l%/5Qm=F[!mGmhh~> j7IobJ`_OGgueV:nc/I[!;lTl"@+]ggZ*kIJ`al4!H\>/rrH:Jn,EQA!4Tqsidp6Ck(2[hk2Z)a~> j7IoiJaJ$Uh j7IoiJaJ$Uh j7IobJ`_OGh<"c\!5J((qtC*dq#:N4!+XI4jFQHEk/?B_3^E*4!EtTRrrmoQZbb#GJ`_OGJ`c^h j7Ij~> j7IoiJaJ$UhX(BM!.XM j7IoiJaJ$UhX(BM!.XM j7IobJ`_OGhW=mA!.XM42Co^Aro"c.dg3J`_OGpZCN. J,~> j7IoiJaJ$UhX(>f!:Tsc.fbh,rr`p]JaJ$Uq!I,6 J,~> j7IoiJaJ$UhX(>f!:TscXoR&Grr`p]JaJ$Uq!I,6 J,~> j7IobJ`_OGhW=i\!:TscpAk*`rr` j7IoiJaJ$UhX(=DJ+*F8/+iiF^AsPKiTl'^JaLJE!BWR"rs$4#Rem"+PTm=F["mGmhh~> j7IoiJaJ$UhX(=DJ+*F8Y4V_t^AsPKiTl'^JaLJE!BWR"rs$4#Rem"+PTm=F["mGmhh~> j7IobJ`_OGhW=h=J+*F8p[nIh^AsDAgZ3qJJ`au7!BNL!rs$4 j7IoiJaJ$UhsCGg!:Kmc.fbh*rr` j7IoiJaJ$UhsCGg!:KmcXoR&Err` j7IobJ`_OGhrXr]!:KmcpAk*^rr` j7IoiJaJ$UhsCFEJ+!@8/+W]D5QJ^Nhs>p]JaLMF!BWR"rs!uRY.Mp6l[eGSm=F["mGmhh~> j7IoiJaJ$UhsCFEJ+!@8Y4DSr5QJ^Nhs>p]JaLMF!BWR"rs!uRY.Mp6l[eGSm=F["mGmhh~> j7IobJ`_OGhrXq>J+!@8p[\=f5QJREg#[eIJ`b#8!BNL!rs!uRX1-.$jFQHEk(2[ik2Z)a~> j7IoiJaJ$Ui9^TO!.XG:r[Ru5o)Am.!0,4VkCN#OmDnSpG6$=r#(Q]V]?T0tJaJ$UJaN7"j7Ij~> j7IoiJaJ$Ui9^TO!.XG:riQ7Po)Am.!0,4VkCN#OmDnSpG6$=r#(Q]V]?T0tJaJ$UJaN7"j7Ij~> j7IobJ`_OGi8t*C!.XG:rq?Eio)Am.!/ehHiIU-Bk/ZTbF9("o#(Q]S\B*:_J`_OGJ`caij7Ij~> j7IoiJaJ$Ui9^Ph!:Bgd.fbh'rr`=X`RNjtm=FZFm/jQn^tSem5Z+_ucJIo=m=FYUmJ-)/oRH~> j7IoiJaJ$Ui9^Ph!:BgdXoR&Brr`=X`RNjtm=FZFm/jQn^tSem5Z+_ucJIo=m=FYUmJ-)/oRH~> j7IobJ`_OGi8t&^!:BgdpAk*[rr`=U_9^kek(2[8joVXb^tSem5Z"Pmak5^+k(2ZGk4n*!oRH~> j7IoiJaJ$Ui9^Ph5jeUO.fd]\rrkY=^X(d'JaJ$UaR'&q!5R=g"TVE)^X1nim=FYUmJ$#.oRH~> j7IoiJaJ$Ui9^Ph5jeUOXoRbVrrkY=^X(d'JaJ$UaR'&q!5R=g"TVE)^X1nim=FYUmJ$#.oRH~> j7IobJ`_OGi8t&^5jeUOpAk0]rrkY<]?8dfJ`_OGaQ j7IoiJaJ$Ui9^P,J*m7:/!TN&"[FTMbM)33m=FZGm/lW85hl;CJ,k#)_pdUrm=FYUmJ$#.oRH~> j7IoiJaJ$Ui9^P,J*m7:Y1E(A"[FTMbM)33m=FZGm/lW85hl;CJ,k#)_pdUrm=FYUmJ$#.oRH~> j7IobJ`_OGi8t&$J*m7:p\XFZ"[FQH`mj"!k(2[9joXX(5hl;CJ,ju$^ j7IoiJaJ$Ui9^OFJ*m79/+3E@J,m'uf&qVOJaLMF!jD[?h#@S/!33NJf'.bQJaJ$Up[.#5J,~> j7IoiJaJ$Ui9^OFJ*m79Y3u;nJ,m'uf&qVOJaLMF!jD[?h#@S/!33NJf'.bQJaJ$Up[.#5J,~> j7IobJ`_OGi8t%?J*m79p[8%bJ,lpmdGTTJ`_OGpZCN.J,~> j7IoiJaJ$Ui9^OFJ*m79Pj\PTJ,lsoeDl,IJaLMF!p]hOh#@QY;6@IRg?O7VJaJ$Up[.#5J,~> j7IoiJaJ$Ui9^OFJ*m79f'`PAJ,lsoeDl,IJaLMF!p]hOh#@QY;6@IRg?O7VJaJ$Up[.#5J,~> j7IobJ`_OGi8t%?J*m79qsOIfJ,ljhcJ4!5J`b#8!os>Hh#@QY:o_"EeDl,BJ`_OGpZCN.J,~> j7IoiJaJ$Ui9^OFJ(ai*J,lmkdG]ZDJaLMF!phoIh>[_1!2m-=d,+,?m=FYUmJ$#.oRH~> j7IoiJaJ$Ui9^OFJ(ai*J,lmkdG]ZDJaLMF!phoIh>[_1!2m-=d,+,?m=FYUmJ$#.oRH~> j7IobJ`_OGi8t%?J(ai*J,ldcbhI^2J`b#8!p)?@h>[_1!2Zm3bLkp-k(2ZGk4e#uoRH~> j7IoiJaJ$Ui9^OFJ(ai*J,ljhd,9KBJaLJE!j)Gfhu j7IoiJaJ$Ui9^OFJ(ai*J,ljhd,9KBJaLJE!j)Gfhu j7IobJ`_OGi8t%?J(ai*J,laabLqI/J`au7!iZ/bhu j7IoiJaJ$Ui9^OFJ(ai+J,lgfd,42@m=FZFm/uZn!5ROm#66*KZG+W>l@J>Rm=FZumGmhh~> j7IoiJaJ$Ui9^OFJ(ai+J,lgfd,42@m=FZFm/uZn!5ROm#66*KZG+W>l@J>Rm=FZumGmhh~> j7IobJ`_OGi8t%?J(ai+J,l^_bLu!.k(2[8joa[]!5ROm#66*HYI_j,j+6?Dk(2[gk2Z)a~> j7IoiJaJ$Ui9^P,5hZ/@5UiqOd,0EAJaLGD!p2EAiVs.5!)Tf.`n'.#m=FYUmIfl,oRH~> j7IoiJaJ$Ui9^P,5hZ/@5UiqOd,0EAJaLGD!p2EAiVs.5!)Tf.`n'.#m=FYUmIfl,oRH~> j7IobJ`_OGi8t&$5hZ/@5U`bGbLqI/J`ar6!oGm9iVs.5!)KW&_U.%gk(2ZGk4RlsoRH~> j7IoiJaJ$Ui9^P,5hZ/@!)g):d,9KBJaLGD"7)re5i_kMJ,iBE[)12Gl@J>Rm=FZtmGmhh~> j7IoiJaJ$Ui9^P,5hZ/@!)g):d,9KBJaLGD"7)re5i_kMJ,iBE[)12Gl@J>Rm=FZtmGmhh~> j7IobJ`_OGi8t&$5hZ/@!)]o2bM%O0J`ar6"6?9Y5i_kMJ,i?BZ+eE5j+6?Dk(2[fk2Z)a~> j7IoiJaJ$Ui9^TN!5R=g"TVH,_:%:nm=FZCm/uRL!'oZG#_2nNVm3qZf][nRJaJ$Uo^1]2J,~> j7IoiJaJ$Ui9^TN!5R=g"TVH,_:%:nm=FZCm/uRL!'oZG#_2nNVm3qZf][nRJaJ$Uo^1]2J,~> j7IobJ`_OGi8t*B!5R=g"TVE'^!5;_k(2[5joaS9!'oZG#_2nMV67ALdc#c>J`_OGo]G3+J,~> j7IoiJaJ$Ui9^TO!5R@h#(Q]P[D^VXJaJ$U`9d]ojD4f]kPkj=!)KMr\]<7Ym"+PTm=FZsmGmhh~> j7IoiJaJ$Ui9^TO!5R@h#(Q]P[D^VXJaJ$U`9d]ojD4f]kPkj=!)KMr\]<7Ym"+PTm=FZsmGmhh~> j7IobJ`_OGi8t*C!5R@h#(Q]NZ,"]CJ`_OG`9%3ahIusTkPkj=!)BAl[_pGFjalQFk(2[ek2Z)a~> j7IoiJaJ$Ui9^U43C)@!#(S,&\B!7dJaJ$U_sIWniLp5OJEm.@5la]YYIVX#hWf[ZJaJ$UoBkT1 J,~> j7IoiJaJ$Ui9^U43C)@!#(S,&\B!7dJaJ$U_sIWniLp5OJEm.@5la]YYIVX#hWf[ZJaJ$UoBkT1 J,~> j7IobJ`_OGi8t+&3'c6u#(S,#[DUGPJ`_OG_r_-`gR\BFJEm.@5laZVXL5mhf].PFJ`_OGoB,** J,~> j7IoiJaJ$UhsCKJ!5RFj#JU79Y.Vs4l@J>RmDA5ol/A j7IoiJaJ$UhsCKJ!5RFj#JU79Y.Vs4l@J>RmDA5ol/A j7IobJ`_OGhrY!>!5RFj#JU78X164#j+6?Dk/-6aj4g+[!.aJ:$GZWA:T(A)_9CNjJ`_OGJ`cOc j7Ij~> j7IoiJaJ$UhsCL1!'oB?#(Q]OZGFoFJaJ$U_!M?ii7DQ@!.aV>$c#%.-[`&']u\[XkCN#Om=FZp mGmhh~> j7IoiJaJ$UhsCL1!'oB?#(Q]OZGFoFJaJ$U_!M?ii7DQ@!.aV>$c#%.-[`&']u\[XkCN#Om=FZp mGmhh~> j7IobJ`_OGhrY"#!'oB?#(Q]MYJ&-4J`_OG^ubj[g$c#%.-[Met\\l\Ci.:$Ak(2[b k2Z)a~> j7IoiJaJ$UhsCO4E<*f3rs-=>:oq1If'%\PJaL5>$0gI&cXHkSJ:PH1rscb0!!"EqX0K4PbLPJ4 JaJ$UJaMsoj7Ij~> j7IoiJaJ$UhsCO4E<*f3rs-=>:oq1If'%\PJaL5>$0gI&cXHkSJ:PH1rscb0!!"EqX0K4PbLPJ4 JaJ$UJaMsoj7Ij~> j7IobJ`_OGhrY%&D?.K0rs-=>:T:_=d,BQ j7IoiJaJ$UhX(C.2aH:##ClfPZ+eN=l@J>RmCqrklf6aJa^G&Is$?[8r^$M`$7UD)Z*q JaJ$UJaMpnj7Ij~> j7IoiJaJ$UhX(C.2aH:##ClfPZ+eN=l@J>RmCqrklf6aJa^G&Is$?[8r^$M`$7UD)Z*q JaJ$UJaMpnj7Ij~> j7IobJ`_OGhW=mu2F-1"#ClfMY.Da+j+6?Dk.]s]jPAA2`EiEBs$?[8r^$M`$7L;$Y-Y[Tb0o#( J`_OGJ`cF`j7Ij~> j7IoiJaJ$UhX(F2WW7S.rs+&S:oq1He_u)HJaL,;%.!$4f[.aW?!8d0.ffPus*tHZZ*h-W`m*&h j6D3_JaJ$Umd9',J,~> j7IoiJaJ$UhX(F2WW7S.rs+&S:oq1He_u)HJaL,;%.!$4f[.aW?!8d0.ffPus*tHZZ*h-W`m*&h j6D3_JaJ$Umd9',J,~> j7IobJ`_OGhW=q%V#Z&)rs+&S:TCe?NI,.KKGs%"3W,Y-PLJ_8ssR h;j.LJ`_OGmcNR%J,~> j7IoiJaJ$Uh6NhUp9#`koI%\[],Y\[oJi`QHQYgu@Pa JaJ$UJaMjlj7Ij~> j7IoiJaJ$Uh6NhUp9#`koI%\[],Y\[oJi`QHQYgu@Pa JaJ$UJaMjlj7Ij~> j7IobJ`_OGh<"guCB201rs4,T9r>/-ak,R(k(2[+jr*7:f[A$b_S3Uk[^EKL[C<]Z_8XUEf%]3G J`_OGJ`c@^j7Ij~> j7IoiJaJ$Uh j7IoiJaJ$Uh j7IobJ`_OGh<"k%h),2Jjo5V%!%+JC\AcnPJ`_OG\E4RginrJ@da$%W`l5m8`lH6Hd+$b)ioPaR J`_OGlfR7"J,~> j7IoiJaJ$Uh!G70h_bDLkPkk(!%+PE\AcqSl@J>RmC)Bem-EirinrSHrn7V3gtq#Mk3D;am=FYU mHO#uoRH~> j7IoiJaJ$Uh!G70h_bDLkPkk(!%+PE\AcqSl@J>RmC)Bem-EirinrSHrn7V3gtq#Mk3D;am=FYU mHO#uoRH~> j7IobJ`_OGgu\b"feWWDkPkk(!%+J@[)(&@jFQHEk-jCWjlGCWgY(3/rmV2'f%Aa5i8`pMk(2ZG k3;$goRH~> j7IoiJaJ$Uh!G:3k1hcnJEd(?J,gsnXgZ*nh j7IoiJaJ$Uh!G:3k1hcnJEd(?J,gsnXgZ*nh j7IobJ`_OGgu\e%i79[aJEd(?J,gpkWjBF_fA_ADJ`a9#s5aUKj5JtTi8WeWjlM'UJ`_OGk2t^r J,~> j7IoiJaJ$Ug[,12jP+;IJF*:C^d.rrVlmPHcJ.N5m=FYUm=FYUm>:5joRH~> j7IoiJaJ$Ug[,12jP+;IJF*:C^d.rrVlmPHcJ.N5m=FYUm=FYUm>:5joRH~> j7IobJ`_OGgZA\$h:6$9JF*:C^d.rqV6%& j7IoiJaJ$Ug?f+1jP"5H!.aP<$@i*V;5ph2`6[/uJaJ$UJaJ$UJaJ9\j7Ij~> j7IoiJaJ$Ug?f+1jP"5H!.aP<$@i*V;5ph2`6[/uJaJ$UJaJ$UJaJ9\j7Ij~> j7IobJ`_OGg?&V#h:-!9!.aP<$@i*V:oCJ(^rk0_J`_OGJ`_OGJ`_dNj7Ij~> j7IoiJaJ$Ug$K%1jP80n!!%QArscb0!!"FFXgGd]dG*aKJaJ$UJaJ$UJaJ9\j7Ij~> j7IoiJaJ$Ug$K%1jP80n!!%QArscb0!!"FFXgGd]dG*aKJaJ$UJaJ$UJaJ9\j7Ij~> j7IobJ`_OGg#`P#hU]qZ!!%QArscb0!!"CCWj0+ObgkP2J`_OGJ`_OGJ`_dNj7Ij~> j7IoiJaJ$Uf^/k.jk\FQ@/^0+JGR%]62gij."/5'\\ZJ=h<':UJaL#8!-dKdJaJ$UJaMgkj7Ij~> j7IoiJaJ$Uf^/k.jk\FQ@/^0+JGR%]62gij."/5'\\ZJ=h<':UJaL#8!-dKdJaJ$UJaMgkj7Ij~> j7IobJ`_OGf]EA!hq-/;?N's)JGR%]62gij."&%u[CsT*fAM5BJ`aN*!-I9_J`_OGJ`c=]j7Ij~> j7IoiJaJ$UfBik1k2=n=a'W(&q#LFD$@]([[(3rld+I:?JaJ$U\Es<@!;m>n5Q:_7m=FYUmHj6# oRH~> j7IoiJaJ$UfBik1k2=n=a'W(&q#LFD$@]([[(3rld+I:?JaJ$U\Es<@!;nqF5Q:_7m=FYUmHj6# oRH~> j7IobJ`_OGfB*A#i7ZQ%_d-Fsq#CgO.=A+rYdM*[bL5)&J`_OG\E3g6!;m>n5Q:_7k(2ZGk3V6j oRH~> j7IoiJaJ$Uea4:@j5/D7b/:rOLk^Y;KX%kj[^s2jajAYrjQqHbJaKr6!I"PA3WP];rrIWMJaJ$U JaMgkj7Ij~> j7IoiJaJ$Uea4:@j5/D7b/:rOLk^Y;KX%kj[^s2jajAYrjQqHbJaKr6!I"PAmf<+LrrIWMJaJ$U JaMgkj7Ij~> j7IobJ`_OGe`Ie2h:U,u`P9!?L4k22K!);^ZaRH[`QQZ]hW9=NJ`aH(!H\>>3WP];rrIWMJ`_OG J`c=]j7Ij~> j7IoiJaJ$UeEmV0ki:OQeBc=Y`P_\k$,a^%bKn\khW*jOm=FZ4m/_D2r&OhJmJd3;!.jNgm=FZk mGmhh~> j7IoiJaJ$UeEmV0ki:OQeBc=Y`P_\k$,a^%bKn\khW*jOm=FZ4m/_D2r9jX[mJd3;!.jNgm=FZk mGmhh~> j7IobJ`_OGeE.,"iSE/9cH=/E^qTf]$,43m`lcTVf\PP j7IoiJaJ$Udd7>,lfR3`gXt*,rmM/&f%Aa7io]RnJaJ$U[-[m j7IoiJaJ$Udd7>,lfR3`gXt*,rmM/&f%Aa7io]RnJaJ$U[-[m j7IobJ`_OGdcM8*jPSbFf$i!lcHXY\d*gLugu%2SJ`_OG[,qC2!;nkXSF-=WJ,k/_J`_OGm,m@# J,~> j7IoiJaJ$Ud-^l""R>45jQ$7!"ln\+l0@Vdm=FZ0m/_D2r&OhJmJd3;!.jNgm=FZkmGmhh~> j7IoiJaJ$Ud-^l""R>45jQ$7!"ln\+l0@Vdm=FZ0m/_D2r9jX[mJd3;!.jNgm=FZkmGmhh~> j7IobJ`_OGd,tAi"QSIuhVJ1d"l8%mioB-Ok(2["joKQ(r&OhJmJd3;!.j9`k(2[]k2Z)a~> j7IoiJaJ$UbjGGsrp0RMJaJ$UXmH.5!;d;n5l^n9m=FYUmHj6#oRH~> j7IoiJaJ$UbjGGsrp0RMJaJ$UXmH.5!;enF5l^n9m=FYUmHj6#oRH~> j7IobJ`_OGbi\ods5a1@J`_OGXl]Y+!;d;n5l^n9k(2ZGk3V6joRH~> j7IoiJaJ$UJaJ$UJaMso!4([/JaJ$UJaMgkj7Ij~> j7IoiJaJ$UJaJ$UJaMso!4([/JaJ$UJaMgkj7Ij~> j7IobJ`_OGJ`_OGJ`cIa!3P=&J`_OGJ`c=]j7Ij~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$Ug$Rh*JaJ$UJaJ$UJaJ*Wj7Ij~> j7IoiJaJ$Ug$Rh*JaJ$UJaJ$UJaJ*Wj7Ij~> j7IobJ`_OGg#h=sJ`_OGJ`_OGJ`_UIj7Ij~> j7IoiJaJ$Ug$Jc$jT#9BJaJ$UJaJ$UJaJ*Wj7Ij~> j7IoiJaJ$Ug$Jc$jT#9BJaJ$UJaJ$UJaJ*Wj7Ij~> j7IobJ`_OGg#`8qjT#9@J`_OGJ`_OGJ`_UIj7Ij~> j7IoiJaJ$Ug$Jf%5j,l_5WaPaJaJ$UJaJ$UKC.^kJ,~> j7IoiJaJ$Ug$Jf%5j,l_5WaPaJaJ$UJaJ$UKC.^kJ,~> j7IobJ`_OGg#`;r5j,l_5WODXJ`_OGJ`_OGKBD4dJ,~> j7IoiJaJ$Ug$Jf%5j/.J5WaPaJaJ$UJaJ$UKC.^kJ,~> j7IoiJaJ$Ug$Jf%5j/.J5WaPaJaJ$UJaJ$UKC.^kJ,~> j7IobJ`_OGg#`;r5j/.J5WODXJ`_OGJ`_OGKBD4dJ,~> j7IoiJaJ$Ug$Jf%5j/.J5WaPaJaJ$UJaJ$UKC.^kJ,~> j7IoiJaJ$Ug$Jf%5j/.J5WaPaJaJ$UJaJ$UKC.^kJ,~> j7IobJ`_OGg#`;r5j/.J5WODXJ`_OGJ`_OGKBD4dJ,~> j7IoiJaJ$Ug$Jf%5j/.J5WaPaJaJ$UJaJ$UKC.^kJ,~> j7IoiJaJ$Ug$Jf%5j/.J5WaPaJaJ$UJaJ$UKC.^kJ,~> j7IobJ`_OGg#`;r5j/.J5WODXJ`_OGJ`_OGKBD4dJ,~> j7IoiJaJ$Ug$Jf%5j*Xu!'>c!JaJ$UJaJ$UKC.^kJ,~> j7IoiJaJ$Ug$Jf%5j*Xu!'>c!JaJ$UJaJ$UKC.^kJ,~> j7IobJ`_OGg#`;r5j*Xu!',VmJ`_OGJ`_OGKBD4dJ,~> j7IoiJaJ$Ug$Jc$jT#9BJaJ$UJaJ$UJaJ*Wj7Ij~> j7IoiJaJ$Ug$Jc$jT#9BJaJ$UJaJ$UJaJ*Wj7Ij~> j7IobJ`_OGg#`8qjT#9@J`_OGJ`_OGJ`_UIj7Ij~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$Ug$Je0q j7IoiJaJ$UJaJ$UJaJ$Ug$Jduq j7IobJ`_OGJ`_OGJ`_OGg#`8Mq<%Ylk2uXYroX1qJ``6[j7Ij~> j7IoiJaJ$UJaJ$UJaJ$UiU$d j7IoiJaJ$UJaJ$UJaJ$UiU$d.lg=#uoBcP:jQus+r9XLCJaJ`ij7Ij~> j7IobJ`_OGJ`_OGJ`_OGiT:8!_refloB$#gjQ6FXr8mtpJ``6[j7Ij~> j7IoiJaJ$UJaJ$UJaJ$UiU$d;n*TT4oBcPJjQus;r9XLSJaJ`ij7Ij~> j7IoiJaJ$UJaJ$UJaJ$UiU$d1kO%HooBcP:jQus+r9XLCJaJ`ij7Ij~> j7IobJ`_OGJ`_OGJ`_OGiT:8[LusJ1oB$#gjQ6FXr8mtpJ``6[j7Ij~> j7IoiJaJ$UJaJ$UJaJ$UiU$p>nEo`5mHsH6rp9sYnF?#9mHsHBm0rFPnaPu6naH# j7IoiJaJ$UJaJ$UJaJ$UiU$p9jR)$mmHrlkrp9sUj5K%`mHrm2m2>3LiT'4jiTT:\kj@ZriSj1k lf@$alL!utr9XLCrTsaQj5K%`JaK&rj7Ij~> j7IobJ`_OGJ`_OGJ`_OGiT:ED:?G9Lk2mEproOHp3#Xcck2mHXjr(Wf)E>4g(5sL`Q0,eE)&DAs `C1_&Zf^Bcr8mtprT46l3#XccJ``Qdj7Ij~> j7IoiJaJ$UJaJ$UJaJ$Ui9^U9mIC/B!:g'U#Oq3LmdTW2nbqhanF,c4nEoc6mI'N3nF->C#Oq0K mdTW2nbhbSnbqhXnF,c4n:BtumGmhh~> j7IoiJaJ$UJaJ$UJaJ$Ui9^U)m,@p1!9*qE#NP%8ki;'tiVi-Rj65moj6bggmHNWtj65k/m0D\8 m- j7IobJ`_OGJ`_OGJ`_OGiT:9u+k@gmroO1rroOGEQJ[odk2mHXjpj>9hip+_(6@@R-K[CMiVqsE 5b6l82!3p9r8mtprT45AQJ[odJ``Qdj7Ij~> j7IoiJaJ$UJaJ$UJaJ'V"1!,54T,9[499Z3lg4cBnF->C!:g'U!:g'U"7bdGnbqhTnc%nWna5lG m/lhFnbhePnc.tVnbhbSnbqhTnc%nUnU^)!mGmhh~> j7IoiJaJ$UJaJ$UJaJ'V"1!,54T,9[499Z3lg4c6k2ZF-!9*qE!9*qE"6&Y7iVi-DiVr3MiU,Uj mHrm"iV`*@iW&9FiV`'CiVi-DiVr3EiIUBfmGmhh~> j7IobJ`_OGJ`_OGJ`_RH"0HZ+3rK'Y3WX?)lfJ7@D+jii!#GRr#8uF]hAYP=(B*A));jT5)Whgp k2mHA)W(GT!#khP!uL+^(B!:p(B*@u);jT5)M.%Bk2Z)a~> j7IoiJaJ$UJaJ$UJaJ0Y!O=ie!<5:`J,XZm4T5<]G'EBA!q,UIrTsUTrp:!\n*TQ4mHsE2rp9pZ n*TQ4mIC2C"7bdJn,;VTnF#`Cm/c_ErTsXTmf)SVnF,c4n:BtumGmhh~> j7IoiJaJ$UJaJ$UJaJ0Y!O=ie!<5:`J,XBe3;rmYG'EBA!pAP*rTsUDrp:!Okj@TomHrp!rp9pM kj@TomG\'3"QAb.kj8<9!TWGBm/c54rTsXFli-8Sj65moj+6ThmGmhh~> j7IobJ`_OGJ`_OGJ`_[K!NeE^!<5:`J,TfU"o\K&Ec^[2!ehPIrT4(qroOJFQJ[odk2n2-roODD QJ[odjqdsY";^,]NoC!"!^WdCrT4,2^&@'o/%trE"?2Y(PW46VT'#)*J,~> j7IoiJaJ$UJaK&rrUKp\rV?Kms7uWdrpbR,JaNL)"gU5j5 j7IoiJaJ$UJaK&rrUKp\rV?Kms7uWdrpbR,JaNL)"gU5j5 j7IobJ`_OGJ``QdrTsRTrV6Els7lQ`rp53uJ`d!p"g'fc5jp@4n(?Xbf2]4Q`J``Qd j7Ij~> j7IoiJaJ$UJaK?%s7-*ak5PAU!VPu6m>:35Z9/+O^\Rp*o)Eq:!-78fJaJ$Ud-]lcJ,~> j7IoiJaJ$UJaK?%s7-*ak5PAU!VPu6m>:35Z9/+O^\Rp*ir j7IobJ`_OGJ``ils6Ta[k5PAT!V5T.k)&4'XZ?GH^\Rp*(B4aE!,pfVJ`_OGd,sB\J,~> j7IoiJaJ$UJaKK)s7$*bgAV0Lp% j7IoiJaJ$UJaKK)s7$*bgAV0Lp% j7IobJ`_OGJ``ups6Ka\gAV0Ko(%@oNogFc!!'e.s7[94IfKIdiT5XQJ`bDCj7Ij~> j7IoiJaJ$UJaKW-!qGsZdf'=Dp% j7IoiJaJ$UJaKW-!qGsZdf'=Dp% j7IobJ`_OGJ`a,t!poLQnG`"Wo)8^co(%@oPNDpg!'g/Vp_F*ls8RQKCu+*%k(2[Dk2Z)a~> j7IoiJaJ$UJaKc1!qGsZm/HSSmJd.YJaJil!d=Woo`+^WrVm"M!,^]YJaJ$UeEu;gJ,~> j7IoiJaJ$UJaKc1!qGsZm/HSSmJd.YJaJil!d=Woo`+^GrVm"M!,^]YJaJ$UeEu;gJ,~> j7IobJ`_OGJ`a9#!poLQo`!kKp&>!^J``?^!d"Elo`+\%rVm"M!,C9IJ`_OGeE5f`J,~> j7IoiJaJ$UJaKl4!V-!Uro!bGrW)VBm@ j7IoiJaJ$UJaKl4!V-!Uro!bGrW)VBm@ j7IobJ`_OGJ`aB&!UTUTrqQHUr:p6brW)M?k+(Q8F9$g^s7mE1MZ!JXIfO39i8oOPJ`bPGj7Ij~> j7IoiJaJ$UJaKu7!qGsZo`"Xakkt5Sp&>!aJaK,t!d=Woo)JRW!;QEj"FgDdh<0@VJaM%Uj7Ij~> j7IoiJaJ$UJaKu7!qGsZo`"Xakkt5Sp&>!aJaK,t!d=Woo)JRG!:]jb"FgDdh<0@VJaM%Uj7Ij~> j7IobJ`_OGJ`aK)!poLQp\t0jf)5OGq#: j7IoiJaJ$UJaL&9!V-!Xrq66Rr:U$^rW2ifJaK3!!<@W>s8)9dq"apm5QID4kCN#OmFUacoRH~> j7IoiJaJ$UJaL&9!V-!Xrq66Rr:U$^rW2ifJaK3!!<@W>s8(^TnG3(e5QID4kCN#OmFUacoRH~> j7IobJ`_OGJ`aQ+!UTUVrqcTEr;-BgrW2fbJ``]h!<@W>s8!K2MY[8T5QI8*iIU-Bk1AbUoRH~> j7IoiJaJ$UJaL,;!V-![rqZNDr;$+FJaJ$Uf^7_kJ,~> j7IoiJaJ$UJaL,;!V-![rqZNDr;$+FJaJ$Uf^7_kJ,~> j7IobJ`_OGJ`aW-!UTUWrr2ldr94%Jr;QZkrW)M?k,%2@F9(h1q\B0hp\t?r0[AA2J`_OGf]M5d J,~> j7IoiJaJ$UJaL5>!V-!ZrqHBBr:g0brr;YBmA]ISG6!-^s82B\rrp.;Qe(-^JaJ$Ug$RhlJ,~> j7IoiJaJ$UJaL5>!V-!ZrqHBBr:g0brr;YBmA]ISG6!-^s81gLrrp.;Qe(-^JaJ$Ug$RhlJ,~> j7IobJ`_OGJ`a`0!UTUWrqlZer8@JFr;6Hirr;P?k,IJEF9$g[s8*T*rrp.;PL8.HJ`_OGg#h>e J,~> j7IoiJaJ$UJaL;@!V-![rqZN:r;$.[0?r23JaJ$Ug$RhlJ,~> j7IoiJaJ$UJaL;@!V-![rqZN:r;$.[0?r23JaJ$Ug$RhlJ,~> j7IobJ`_OGJ`af2!UTUXrqu`ir;-.[0$)Q"J`_OG g#h>eJ,~> j7IoiJaJ$UJaLAB!;6*cr;H$\kkjcFr;HHj!;1p1Y3c:7!.XJ;r:9L["ht&$dGO2>m=FZYmGmhh~> j7IoiJaJ$UJaLAB!;6*cr;H$\kkjcFr;HHj!;1p1Y3c:7!.XJ;r8RAK"ht&$dGO2>m=FZYmGmhh~> j7IobJ`_OGJ`al4!:osbrr)Thrqk.?rqlHfrr)`n!:k^'Y3#e-!.XJ;r"er)"ht&!bh;!,k(2[K k2Z)a~> j7IoiJaJ$UJaLGD!V-!\rqcTbr7q2@r;-BfrW)VBmBGsYG6%(2rUKs`oD\qY0?Ml,JaJ$Ug?mqm J,~> j7IoiJaJ$UJaLGD!V-!\rqcTbr7q2@r;-BfrW)VBmBGsYG6%(2rSdhHoD\qY0?Ml,JaJ$Ug?mqm J,~> j7IobJ`_OGJ`ar6!UTUYrqlZjr;6B>qYU0dr;6HjrW)M?k-3tKF9(b/r>#BjoD\qY0#Z2oJ`_OG g?.GfJ,~> j7IoiJaJ$UJaLJE!;60equ-*`qtoCLqtos^qu-Ek!W)J?mBZ*[!.XG:rpg'ao)AkY!5[% j7IoiJaJ$UJaLJE!;60equ-*`qtoCLqtos^qu-Ek!W)J?mBZ*[!.XG:ro*qIo)AkY!5[% j7IobJ`_OGJ`au7!:p!crr)Zjrql3]kkX`ErqlNhrr)co!Vl5:k-F+M!.XG:rY>Kko)AkY!5-M, jalQFk1f%YoRH~> j7IoiJaJ$UJaLPG!:fj`qYg0dpA j7IoiJaJ$UJaLPG!:fj`qYg0dpA j7IobJ`_OGJ`b&9!:9R]r;HNjr;63ap\F=Rp\FOZr;6Bhr;HQm!:k^'[,qF3!5S%&rY>LPo)Am. !/SVBi.:$Ak1f%YoRH~> j7IoiJaJ$UJaLVI!V-!]rqu`gr;6B>qYU0`r;?NirW)VBmC)B_G6%%1s7-0bnG`Xl=i8_Hm"+PT mG.*hoRH~> j7IoiJaJ$UJaLVI!V-!]rqu`gr;6B>qYU0`r;?NirW)VBmC)B_G6%%1s5F%JnG`Xl=i8_Hm"+PT mG.*hoRH~> j7IobJ`_OGJ`b,;!UTUYrr2lmr;QTcqYp j7IoiJaJ$UJaLYJ!;60err)NfrVO\4rVQ9crr)`n!VPu6mC;Na4$`35s75XY"[FWQcJ@i j7IoiJaJ$UJaLYJ!;60err)NfrVO\4rVQ9crr)`n!VPu6mC;Na4$`35s5NMI"[FWQcJ@i j7IobJ`_OGJ`b/ j7IoiJaJ$UJaL_L!V-!^rqu`hr;6B4qYU0ar;?NjrW)VBmCDTb4+HYt!V6$SrrkY<]#iXhJaJ$U h!O.oJ,~> j7IoiJaJ$UJaL_L!V-!^rqu`hr;6B4qYU0ar;?NjrW)VBmCDTb4+HYt!TNV;rrkY<]#iXhJaJ$U h!O.oJ,~> j7IobJ`_OGJ`b5>!UTUZrr)fnr;?HfqY^09q#'s`qY^6gr;HTmrW)M?k.0UT3e-Ps!>^R]rrkY; [`$YSJ`_OGgudYhJ,~> j7IoiJaJ$UJaLeN!V-!^rr)fir;?HVqXF=FqY^6br;HTkrW)VBmCV`d!.XD9!:oRX"b6T]`Ra1& m=FZ[mGmhh~> j7IoiJaJ$UJaLeN!V-!^rr)fir;?HVqXF=FqY^6br;HTkrW)VBmCV`d!.XD9!93GH"b6T]`Ra1& m=FZ[mGmhh~> j7IobJ`_OGJ`b;@!UTUZrr2lmr;QThqYp<\pu_>@q#:*bqYpBgr;QZnrW)M?k.BaV!.XD9!#G#& "b6TZ_9h(jk(2[Mk2Z)a~> j7IoiJaJ$UJaLhO!;63frVcKgs826\h"gC7s82QgrVcZn!;1p1]^5_"J*m79q!S.cJ,lsne)GrG JaM7[j7Ij~> j7IoiJaJ$UJaLhO!;63frVcKgs826\h"gC7s82QgrVcZn!;1p1]^5_"J*m79nF$;[J,lsne)GrG JaM7[j7Ij~> j7IobJ`_OGJ`b>A!:p$ds8Dfl!;lNes7uB`oCqhLoCqtRs7uEc!;lWjs8Doq!:k^']]K4pJ*m79 MXLKJJ,lgfcJ4!5J`bbMj7Ij~> j7IoiJaJ$UJaLkP!;66gqu-Bhr;6![qYB:KqYBXWr;6?gqu-Hl!:bX-^?kq$J(ai*J,ljidGTTC JaM7[j7Ij~> j7IoiJaJ$UJaLkP!;66gqu-Bhr;6![qYB:KqYBXWr;6?gqu-Hl!:bX-^?kq$J(ai*J,ljidGTTC JaM7[j7Ij~> j7IobJ`_OGJ`bAB!:p'erVcZlrqlQgrqZ3]qtK+DqtKaXrqZEerqlWkrVc]o!:5:!^?,FrJ(ai* J,labbh@X1J`bbMj7Ij~> j7IoiJaJ$UJaLnQ!;ZKjrr)Wirql6^qt]%Bqt]dYrqlKgrr)`n!;1p1^[2%%J(ai*J,lgfd,0EA JaM7[j7Ij~> j7IoiJaJ$UJaLnQ!;ZKjrr)Wirql6^qt]%Bqt]dYrqlKgrr)`n!;1p1^[2%%J(ai*J,lgfd,0EA JaM7[j7Ij~> j7IobJ`_OGJ`bDC!;QKks8Dims82Tfs7uEaqtKaVo(DYKqtKj[s7uHds82]ks8Doq!:k^'^ZGOs J(ai*J,l^_bLqI/J`bbMj7Ij~> j7IoiJaJ$UJaLtS!V-!_rqu`kr;?H`qYg6>q#1$[qY^6er;?NkrW)VBmD/)i4$_L!#(S/,^ j7IoiJaJ$UJaLtS!V-!_rqu`kr;?H`qYg6>q#1$[qY^6er;?NkrW)VBmD/)i4$_L!#(S/,^ j7IobJ`_OGJ`bJE!UTUZrr2lpr;HNiqYg6`q#:$Xp@@nHpAXg[q#1$cqYg j7IoiJaJ$UJaM"T!:fpbr;HNjrql9_r;"_7r;#p[rqlTjr;HQm!;1p1_ j7IoiJaJ$UJaM"T!:fpbr;HNjrql9_r;"_7r;#p[rqlTjr;HQm!;1p1_ j7IobJ`_OGJ`bMF!:9U^s8Dlns82ZhrqZ?ar:fsZi:Zj j7IoiJaJ$UJaM%U!;66grr)WirqlEcqt]gXk4eHAqt]s^rqlKgrr)fp!;1p1_X.@c!87AU!)g); dGTTCJaM7[j7Ij~> j7IoiJaJ$UJaM%U!;66grr)WirqlEcqt]gXk4eHAqt]s^rqlKgrr)fp!;1p1_X.@c!87AU!)g); dGTTCJaM7[j7Ij~> j7IobJ`_OGJ`bPG!:p'e!<)fms82Zhs7uNdqY0m\de3M2qY0j]s7uNfs82]k!<)or!:k^'_WCk[ !87AU!)]o3bh@X1J`bbMj7Ij~> j7IoiJaJ$UJaM(V!;66grVcTjr;63arqZ$Xk4e?>rqZ9ar;6?grVc]o!;_96_sIN1!5R@h#/:.d [)1;OJaJ$Uh!O.oJ,~> j7IoiJaJ$UJaM(V!;66grVcTjr;63arqZ$Xk4e?>rqZ9ar;6?grVc]o!;_96_sIN1!5R@h#/:.d [)1;OJaJ$Uh!O.oJ,~> j7IobJ`_OGJ`bSH!:p'es8DlnrqlThs7uKcrV-*\de3D/rV--_s7uQgrqlWks8Drr!;M--_r_$% !5R@h#/:.cYeJE j7IoiJaJ$UJaM+W!;ZNkrVcTjs82KcrqZ3]f(\h3rqZ6`s82ZjrVcZn!:bX-`U*`n!.`i(#(Q]Q [`-h[JaJ$Uh!O.oJ,~> j7IoiJaJ$UJaM+W!;ZNkrVcTjs82KcrqZ3]f(\h3rqZ6`s82ZjrVcZn!:bX-`U*`n!.`i(#(Q]Q [`-h[JaJ$Uh!O.oJ,~> j7IobJ`_OGJ`bVI!;QNlrr)fns82Zh!;ZHcr:g*^o(D/;o(DnRr:g$^!;ZKfs82cmrr)iq!:5:! `T@6`!.`i(#(Q]NZGFoGJ`_OGgudYhJ,~> j7IoiJaJ$UJaM.X!;ZQlrVcTjs82Ndr;$-_bkLo-r;$'_s82ZjrVc]o!;1p1`pEio3^DI""oqK$ \]EFfJaJ$Uh!O.oJ,~> j7IoiJaJ$UJaM.X!;ZQlrVcTjs82Ndr;$-_bkLo-r;$'_s82ZjrVc]o!;1p1`pEio3^DI""oqK$ \]EFfJaJ$Uh!O.oJ,~> j7IobJ`_OGJ`bYJ!;QNls8Dln!;lWhs7uQeqtL'_p%A(Om.9uDp%A:WqtL!_s7uNf!;l]ls8Drr !:k^'`o[?a3'c6u"oqGu[`$YTJ`_OGgudYhJ,~> j7IoiJaJ$UJaM4Z!:fscr;HQkrqlBbs7u?_bkLf*s7u?arqlWkr;HTn!;1p1`pEi3!5RFj#CleO YJ&39m"+PTmG.*hoRH~> j7IoiJaJ$UJaM4Z!:fscr;HQkrqlBbs7u?_bkLf*s7u?arqlWkr;HTn!;1p1`pEi3!5RFj#CleO YJ&39m"+PTmG.*hoRH~> j7IobJ`_OGJ`b_L!:9X_rr)fns82]i!;ZHcrV-3_p@\@Ui:Hm=p@\=VrV--_!;ZNgs82cmrr)iq !:k^'`o[?'!5RFj#CleNXLZF'jalQFk1o+ZoRH~> j7IoiJaJ$UJaM7[!:fscrr)Zjs82NdrqZ<`me?)Ame?JNrqZ9as82Wirr)fp!;1p1a6`ro3'c=" #(Q]Q[):>MJaJ$Ug[4%nJ,~> j7IoiJaJ$UJaM7[!:fscrr)Zjs82NdrqZ<`me?)Ame?JNrqZ9as82Wirr)fp!;1p1a6`ro3'c=" #(Q]Q[):>MJaJ$Ug[4%nJ,~> j7IobJ`_OGJ`bbM!:9X_s8Dlns82]i!;ZKdrV-9ap%A:UnalPHnal_Op%A:WrV-0`!;ZNgs82`l s8Drr!:k^'a6!Ha2aH4!#(Q]NZ+eK:J`_OGgZIPgJ,~> j7IoiJaJ$UJaM:\!;66grVcWkrVQ?cs7u?_o_7;;o_8%Rs7uEcrVQKirVc]o!;1p1aR')rX8p&n rs+&S;6IIOf][nRJaM4Zj7Ij~> j7IoiJaJ$UJaM:\!;66grVcWkrVQ?cs7u?_o_7;;o_8%Rs7uEcrVQKirVc]o!;1p1aR')rX8p&n rs+&S;6IIOf][nRJaM4Zj7Ij~> j7IobJ`_OGJ`beN!:p'err)fnrqlZjs7uKcs7cEaq"=XYmIU,DmIU>Lq"=OXs7c?as7uWirqlZl rr)iq!:k^'aQJ`b_Lj7Ij~> j7IoiJaJ$UJaM=]!:fscrr)]krqlHds7uHboCqnNo(DYKoCr%Ts7uEcrqlQirr)fp!;1p1aR'&n 2uqk9#67MtZbXrDm"+PTmG%$goRH~> j7IoiJaJ$UJaM=]!:fscrr)]krqlHds7uHboCqnNo(DYKoCr%Ts7uEcrqlQirr)fp!;1p1aR'&n 2uqk9#67MtZbXrDm"+PTmG%$goRH~> j7IobJ`_OGJ`bhO!:9X_rr)fns82`js7uQerV-9ap\"OXp@I;7p@J:Up\"LYrV-3as7uThs82cm rr)iq!:k^'aQ j7IoiJaJ$UJaM=]qu6TnqYU?jq"jsbq=sRUq=`h@q=aFSq=spaq"b!fqY^?krVcfiJaLSH"73&g JDpM5J,lCJ]#`OdJaJ$Ug?mqmJ,~> j7IoiJaJ$UJaM=]qu6TnqYU?jq"jsbq=sRUq=`h@q=aFSq=spaq"b!fqY^?krVcfiJaLSH"73&g JDpM5J,lCJ]#`OdJaJ$Ug?mqmJ,~> j7IobJ`_OGJ`bhOr;Q`prVQZmqtg?gqtU-arq?*Zrq-$Xf(/Y.rq,sXrq? j7IoiJaJ]h!2B;VrhB;W!2>&3hsCF>r;QZnr;?Qkq"k!cq"XRWq=`V:q=aOVq"Xjaq"k!er;?Nl rVcfeJaLSH"6se)JE-Y8J,iBF[DUDLl@J>RmFpsfoRH~> j7IoiJaJ]h!+5Qhra5Qi!+1r;QZnr;?Qkq"k!cq"XRWq=`V:q=aOVq"Xjaq"k!er;?Nl rVcfeJaLSH"6se)JE-Y8J,iBF[DUDLl@J>RmFpsfoRH~> j7IobJ``3Z!$h=%rZh=&!$d'WhrXq6rVlfprqufnr;-HhqY:*br:]sZrq,sVf(/S,rq-$Zr:^0b qY:*dr;-Hjrqucorr)oaJ`b):"641tJE-Y8J,i?CZG4W:j+6?Dk1\tXoRH~> j7IoiJaJup"KgVLi2uV!JE?e:J,iBDZG+T;l%/5QmFgmeoRH~> j7IoiJaJup"EW,\e8t6K!+5'Zra16Do'HGMr;Q]oqu$Kkp\Opcq=s[XqY&G3qY'XWq=t!cp\Ope qu$HlrVcfeJaLSH"6F>!JE?e:J,iBDZG+T;l%/5QmFgmeoRH~> j7IobJ``Kb"?sfe`'3eT!$gglrZd!Vo&]rCrVlfprqlcnqt^ j7IoiJaJup"N&@LbJEf;!2A`F!2>&3oBcPNr;Q]oqu$Kkq"k!cqY9dYqY'UTkk+Q@qY'XWqY:'c q"k$fqu$Hlrr)ooJaLSH"RG#]!.a84#_2nNVm*eSf&;2IJaM+Wj7Ij~> j7IoiJaJup"IcI[XIAbJ!+5!X!+1 j7IobJ``Kb"E][dNHOjT!$gaj!$d'WoB$&DrVliqrVQZmqt^ j7IoiJaJup#hHHO[asj5U8%YS#McJWY2&F3UAXEYUAjN_U j7IoiJaJup#f;;_Ku36E?XNg"#L.F#G0TFA?i9 j7IobJ``Kb#ch%h=01QH,:!3M#J*8L6b;FB,Q-^(,Q?g.,Ea`7ReZ"F,D#"LjoFuNrr2lqr;ZZl qZ$Bgq#C*epA+C]o_J+Po(;JGnFZ8Eo(hnWo_J1YpAamaq#C0fqZ$Hlr;QZprW)_Ek/l`giRh`B JEd(?J,i?@XgQ$kg#IYGJ`bVIj7Ij~> j7IoiJaJup"R;lIVX"0J!2BJ[#HlG>c`Z[&UAO j7IoiJaJup"Qj=TB'90\!+5`m#BmQ>[ j7IobJ``Kb"PkZ]/`$Eg!$hL*#==p;R7$-!,Q$U',Q?g.,F./i1AKu3,D#"MjoFuNrr;rqquHWk q>g?gq#:$epA4I\o_\7To(MV;nFlDIo)&%Wo_S7[pAXg`p]1-eq>gEkr;Z`qrW)M?k/l`hi7DQ@ !.aJ:$GZWA:T(A)_Tg]mJ`_OGfB2,cJ,~> j7IoiJaL2=s0DYeqZ$[[GB`B?!johPrTsTZrp9rclg'g]p\X[Vp"]33p&"IYp\FUaq>'mdqu$BjrVlcromuMQm0E"?h:#j6J:R1i $c#%.-[`&']u\[Yl%/5QmFL[boRH~> j7IoiJaL2=s0DYeqZ$[[GB`B?!eQknrTsSlrp9r!kj@SBmHn9Xm/UMmm0$e]mHn9Um/ULEmIooI nbiCbrVZTmqt^*gq>'g]p\X[Vp"]33p&"IYp\FUaq>'mdqu$BjrVlcromuMQm0E"?h:#j6J:R1i $c#%.-[`&']u\[Yl%/5QmFL[boRH~> j7IobJ`a]/s/l;^qZ$[YFEHj2!`X8ArT4))roOG4hr`cBk2mocjo?Y*jocpgk2mo`jo?WWk4[p; m/?q_rVl`pqu$9sep\jg_p&=UZoD%nQn_ip5nbD\NoD\CZp&4Uap\Xacq>C*hqu6NmrVuis nq#rGjp1#*f?Re)J:R1i$c#%.-[Met\\l\Dj+6?Dk18\ToRH~> j7IoiJaL8?#dQPm!'`\tJ#E6BrVutAG4+`!b+m,Xm/Wm[m0KlAld)a$mA]"@m/Wm[m/Wm[m/WmU m/Wl3mIorErr;ror;ZZiqYp j7IoiJaL8?#dQPm!'`\tJ!U$drVutAG4+`!WaVt,m/UMmm0ImDkd>U$m;:;Lm/UMmm/UMmm/UMg m/ULEmIorErr;ror;ZZiqYp j7IobJ`ac1#d$,f!'`\tIu!t7rVut>F7//lM^FD]jo?Y*jp4;ChjHjoju:*Rjo?Y*jo?Y*jo?Y$ jo?WWk4[s8rr;rrr;ZZlq>g?gq#C*dpA=O_o_\7Uo(hhKnF5o>nG2VMo)&%Yo_\=ZpAamap]1-f qZ$Hlr;Z`prW)M?k/ufhjl"h=@f??.JACpZ%D[K[!%3)kY-ks_d+I<.k(2[Fk2Z)a~> j7IoiJaL>A"EshG5JR-r!9j?U!I@WB!!.6/na-J9U>PA*r1XVegTI mE4f#m-!RmF:O`oRH~> j7IoiJaL>A"EshG5JR-r!8%-U!CngB!!.6/na-J-?de?/r*Km"b][E mE4f#m-!RmF:O`oRH~> j7IobJ`ai3"EXVD5JR-r!6G'_!?)RL!!.-%n`Btk,JrU*r$)X4\O%W0]&r.B.jO3=r$)7),Q?g* ,PpR#,D#"QjoG2TrW)oqquHWkq>g?gq#C*fpA=O^o_e=Vo(hhOnD`p4nG2VMo)/+Yo_\=\pAama p]1-eq>gEkquH]prW);9k/ufjjl+qAa^ABQ!!#=_JH%q7!"(-$W3*A9_910Zj+6?Dk1&PRoRH~> j7IoiJaLDC"L:,i5JR'p!9j6R"]6]KEp)b>mCqrfp&+ggrVZTmqt^'gq>'g^p\aa[p$22CoBPuC p&+O[p\FRaq>'mdqu$BkrVc]qnU^)Lm0Mt:gXOHd?<\uY!<4JV.Y+Y*[(*igbgbG.l@J>RmF1I_ oRH~> j7IoiJaLDC"L:,i5JR'p!8%$R"YU\mEp)b>mCqrfp&+ggrVZTmqt^'gq>'g^p\aa[p$22CoBPuC p&+O[p\FRaq>'mdqu$BkrVc]qnU^)Lm0Mt:gXOHd?<\uY!<4JV.Y+Y*[(*igbgbG.l@J>RmF1I_ oRH~> j7IobJ`ao5"Ka]b5JR'p!6Fs\"VD%@DrB`.k.]sXo)AXfrVl`pqu$9jq>0jdp\jg_p&F[]oD.tT nbM\0n+lJNnbMbQoDeI[p&4Rap\OXbq>C*hqu6NmrVuism"+<@jp:#&f$;=O>ZrZU!<+DTIB'ER [(=&ncIUe3J`_OGe)o]_J,~> j7IoiJaLGD"*X`0^\Ig/kkChUIfKIhkNmce_1-eqtTs\r:^![nati4nau\N r:]p[qtU3eq>1-grVZWmrr)ooJaLPG)XQYGgXXWk_nWgo\$i]P\%0,c_oU-QgZ%G`JaJ$Udd?)e J,~> j7IoiJaLGD"*X`0^\Ig/fCrVUIfKIhkNmce_1-eqtTs\r:^![nati4nau\N r:]p[qtU3eq>1-grVZWmrr)ooJaLPG)XQYGgXXWk_nWgo\$i]P\%0,c_oU-QgZ%G`JaJ$Udd?)e J,~> j7IobJ`ar6"*=N-^\Ig/aS-n_IfKIdiT5XQ_<(agrVliqrqucmrV?Nir:p9crq?6^s7H6\q==FQ qXF.GnF#r;qXFCPq==ITs7H3]rq??cr:g9frVHNjrqufps8E#nJ`b&9$g$=$e^2IW^Upt_Zi76< ZaI9S^Ve1>e_B*FJ`_OGdcTT^J,~> j7IoiJaLJE!^$I4pAY-VpnJ)As8P=aDrg27mDJ;knbiCcrVZTnqt^*gq>9s^p\jg_p%.h.oCMVO p&4UZp\Xacq>'mequ$BkrVlcromuMNm2#$Nj5&D j7IoiJaLJE!^$I4pAY-Epg49/s8P=aDrg27mDJ;knbiCcrVZTnqt^*gq>9s^p\jg_p%.h.oCMVO p&4UZp\Xacq>'mequ$BkrVlcromuMNm2#$Nj5&D j7IobJ`au7!]g=2pAY-6p`g$*s8P=aCu+-&k/6<]m/?n`rVZQnqu$9jq>9sep]'s`p&F[_oD.tS nb_hNn)Ed1n,)VOnbMbSoDeIZp&Facp\X^cq>C'hqu$?orVlfeJ`b#8$0C+#f@/'haMl'r_?n-! a32fXfA,?HJ`_OGd,sB\J,~> j7IoiJaLMF!^$Itp&>$Uq4dr;rVm"M!,gf[JaLDC!;69hrr)cms82Wgs7uQeq=jm^q=X@Of^n_, q=Xa\q=jd]s7uKes82`lrr)fp!:bX-`pFE*kiLg]gtUN7f@\d1gu%,Ql0NugJaLbMj7Ij~> j7IoiJaLMF!^$Itp&>$Dq-O-)rVm"M!,gf[JaLDC!;69hrr)cms82Wgs7uQeq=jm^q=X@Of^n_, q=Xa\q=jd]s7uKes82`lrr)fp!:bX-`pFE*kiLg]gtUN7f@\d1gu%,Ql0NugJaLbMj7Ij~> j7IobJ`b#8!]g=rp&>$5q',m$rVm"M!,L?JJ`ao5!:p*f!<)los82`j!;ZNe!;HKdqt:!]r:BaT r:0^Sp[@&.p[A%Lr:0URr:Bs\qt9s^!;HBc!;ZQhs82cm!<)or!:5:!`o[NfinrMDf%&7KdKe7N f%Jj9j5kjSJ`b8?j7Ij~> j7IoiJaLPG!^$Ito`"pTq4mie"FgDfhrfRX`pEd!r;Q]or;6Qlq>1*dr:p*^rV$*\p%6W&p%8+R rV$']r:p9eq>(*gr;?Qmrr)ooJaLDCs6L-Zl0. j7IoiJaLPG!^$Ito`"pCq-X%!"FgDfhrfRX`pEd!r;Q]or;6Qlq>1*dr:p*^rV$*\p%6W&p%8+R rV$']r:p9eq>(*gr;?Qmrr)ooJaLDCs6L-Zl0. j7IobJ`b&9!]g=ro`"p4q'5e:"FgDcg#7ME`o[9lrVliqrqufnr;$Ehr:p j7IoiJaLSH!d=WooD\gSqP+&1-eqtU$^rV$'[p%6W&p%8(Q rV$*^qtU3eq>1-grVZWmrr)ojJaL2=!:5:(JaLGDj7Ij~> j7IoiJaLSH!d=WooD\gBqHj6*qYp\J!2&-3JaLMFr;QZnrVZ]mq>1-eqtU$^rV$'[p%6W&p%8(Q rV$*^qtU3eq>1-grVZWmrr)ojJaL2=!:5:(JaLGDj7Ij~> j7IobJ`b):!d"EloD\g3qBH!%qYp\J!1VX"J`b#8rVlfprqufnrqcZjqtL0crV$0^s7H3[qss^U r:'LMf'`>%r:'[TqssXUs7H6^rUp6cqtU3erqcZlrqucos8E#hJ`a]/!9JdoJ`ar6j7Ij~> j7IoiJaLVI!d=Woo)A^RqP3ic"@)t:hs#^ZamB*$rVlfpr;?Qkqtg j7IoiJaLVI!d=Woo)A^AqHs$t"@)t:hs#^ZamB*$rVlfpr;?Qkqtg j7IobJ`b,;!d"Elo)A^2qBPe8"@)t4g#@SFalWTorr2rrrVQZmr;$Ehr:p j7IoiJaLVI!<@W>rrD9[UB&^:rrp.;A_#`'JaLYJ!;69hrr)cm!;lWhrqZKeq=jm^qXsXUo(1f1 o(2YKqXsj]q=jg^rqZEe!;l]lrr)iq!;_96JaJ$UK'hUjJ,~> j7IoiJaLVI!<@W>rrC[J?N@8(rrp.;A_#`'JaLYJ!;69hrr)cm!;lWhrqZKeq=jm^qXsXUo(1f1 o(2YKqXsj]q=jg^rqZEe!;l]lrr)iq!;_96JaJ$UK'hUjJ,~> j7IobJ`b,;!<@W>rrC.;,63i#rrp.;@aNigJ`b/ j7IoiJaLYJ!I"P5rrD9\UB%n!rrmlPR+UHcJaL\K!;ZTmrVc]ms82Zhs7uQeqY1!_q=XOTo(1f1 o(2YKq=Xa\qY0m^s7uNfs82cmrVc`p!:bX-dHpq>jEH1BJaLkPj7Ij~> j7IoiJaLYJ!I"P5rrC[K?N>iSrrmlPR+UHcJaL\K!;ZTmrVc]ms82Zhs7uQeqY1!_q=XOTo(1f1 o(2YKq=Xa\qY0m^s7uNfs82cmrVc`p!:bX-dHpq>jEH1BJaLkPj7Ij~> j7IobJ`b/2rrC.<,61m?rrdfOPg\?C!fp\jg`p&F[_oDA+V nbqtRn+Z8*mJ$&Fn,;bRnb_nUoDeI\p&4Rap\agdq>L0iqu6NnrVuism"+O oRH~> j7IoiJaLYJ!<@W=rrD9\UB&^7rrbS@cJ.Q6mEP"up&4mhrVZTnqtg0hq>C$ap\jg`p%S+No@EL) oCqnTp&4U\p\agdq>0sfqu$BkrVuisqLS%]m/T60!!#*3m=FZPmGmhh~> j7IoiJaLYJ!<@W=rrC[K?N@8%rrbS@cJ.Q6mEP"up&4mhrVZTnqtg0hq>C$ap\jg`p%S+No@EL) oCqnTp&4U\p\agdq>0sfqu$BkrVuisqLS%]m/T60!!#*3m=FZPmGmhh~> j7IobJ`b/C$fp\sm`o`4X_oD8%V nbqtSn+lDAmIKW6mJ62In,;bSnbVhTo)SF[p&=[bp\addq>U6iqu?Tnr;lomJ`bAB!'93A!',Vm J`bABj7Ij~> j7IoiJaL\K!I"P4rrD9]UB&^6rrmlPPgeRWJaLbMr;Q]or;?TlqYC3fqtU$^rq?6^p[n+Lf(/J) p[n@Urq?3_qtL0eqYL6hr;?Qmrr)ojJaLnQ!BUhb3qN@P5WaPaJaLkPj7Ij~> j7IoiJaL\K!I"P4rrC[L?N@8$rrmlPPgeRWJaLbMr;Q]or;?TlqYC3fqtU$^rq?6^p[n+Lf(/J) p[n@Urq?3_qtL0eqYL6hr;?Qmrr)ojJaLnQ!BWF:n+6P[5WaPaJaLkPj7Ij~> j7IobJ`b2=!H\>1rrC.=,63htrrmlPOj;\BJ`b8?rVlfps8;lnrqZWjr:p j7IoiJaL\K!<@WgEjr;QZorW)J>mF1G'3tM!FoD\hV4+ZdMmEt=]oRH~> j7IoiJaL\K!<@WgEjr;QZorW)J>mF1G'4$<3)oD\hV4+ZdMmEt=]oRH~> j7IobJ`b2=!<@Wg?hq#C*epAO[ao_nCYo)8+W nGMbNme6&-m.TiBmelPPnGVnSo)81[o_nI]pAambp]1-gq>gEkr;Z`qrW);9k0rGn3Y1mEoD\hV 3J$=Dk0`>OoRH~> j7IoiJaL_L!jVfUn,ECOrhBJ@o)AkY/]6&qm"+QOm/ZhWrr2lpr;ZZjqZ$Bgq"sgbpAFUXo_A%J o'#W6o(_hNo_eC]pA=U]q#C0dqZ$Hkr;QZorW)eGmF1G'3tM!FoD\hV4+ZdMmEt=]oRH~> j7IoiJaL_L!jVfUn,EC>ra,Z.o)AkY/]6&qm"+QOm/ZhWrr2lpr;ZZjqZ$Bgq"sgbpAFUXo_A%J o'#W6o(_hNo_eC]pA=U]q#C0dqZ$Hkr;QZorW)eGmF1G'4$<3)oD\hV4+ZdMmEt=]oRH~> j7IobJ`b5>!j)HPn,EC/rZ_E)o)AkY/AKH_jalRAjoFuNrqu]lqZ$Bhq#C*gpAFU`o`"IZo)/%V nGVhPmeH2%m.fuEmeuVQnGMhRo)A7\o_eC^pAambq#C0fqY^9krW)_Ek0rGn3Y1mEoD\hV3J$=D k0`>OoRH~> j7IoiJaL_L!I"P3rrD9^U[e?f^As8;g?=+TdI$l2rVc`nrqlQg!;ZKdr:g3aqt9^Uq"+(Ik4/$5 q"+7Pqt9s^r:g'_!;ZKfrql]mrVc`p!:bX-eEm8u&+r!TrrGAHJaJ$UdI#udJ,~> j7IoiJaL_L!I"P3rrC[M?h+."^As8;g?=+TdI$l2rVc`nrqlQg!;ZKdr:g3aqt9^Uq"+(Ik4/$5 q"+7Pqt9s^r:g'_!;ZKfrql]mrVc`p!:bX-eEm8u4SA17rrGAHJaJ$UdI#udJ,~> j7IobJ`b5>!H\>0rrC.>,Otd;^As24eDYu@dH:E,!<)lo!;l]j!;ZNe!;HKdr:L'^rU]pWrUKmV qsXCLq!H`#q!IqGqsXORrUKdUrUU$]r:U'_!;HBc!;ZQh!;l`m"9&5um"+ j7IoiJaL_L!BWR7rr_J[UXAW3"ht%n`ms("mF(A%p&4mhrVZTnqtp6iq>9sap\smap%\1SoCVV/ nauDIoD%tUp&=[^p\Xacq>:$gqu$BkrVuisqLS%`m/]9KoagX[!<>34m=FZPmGmhh~> j7IoiJaL_L!BWR7rr^k[?`rr!"ht%n`ms("mF(A%p&4mhrVZTnqtp6iq>9sap\smap%\1SoCVV/ nauDIoD%tUp&=[^p\Xacq>:$gqu$BkrVuisqLS%`m/]9KofVh3!<>34m=FZPmGmhh~> j7IobJ`b5>!BNL6rr^=e,EVHq"ht%l_U%"gk0iAlo)AXgrVcWoqu-?kq>C$fp\smao`4X^oDJ1Y nbqtTn+uJJmI]c3lh'Q>mJ?8Kn,;bTnbhtUo)SF\p&=[bp\addq>L0iqu6Nnr;lomJ`bJE!BL5R &+`'f!',VmJ`bABj7Ij~> j7IoiJaL_L!<@W;rrVDZdI7)=!*I(ahs>p]e*[)4!<)fmrqlThs7uWgqY0s^rUp!Yp[e+Lf(&J) p[e4QrUp-_qY0s`s7uQgrqlTj!<)or!;1p1ea3?[jZ j7IoiJaL_L!<@W;rrUeZY4)Ao!*I(ahs>p]e*[)4!<)fmrqlThs7uWgqY0s^rUp!Yp[e+Lf(&J) p[e4QrUp-_qY0s`s7uQgrqlTj!<)or!;1p1ea3?[jZ j7IobJ`b5>!<@W;rrU7dORN2Q!*?hVg#[eIe)pW.!<)lo!;l]j!;ZWhrqHHdr:U']rq$'Yr:0dU rU9XOq j7IoiJaL_L!<@W;rrVDZkjSNT5V'Lkg['LYeEm8/rVliqqu$Hjqt^ j7IoiJaL_L!<@W;rrUeZf'iVB5V'Lkg['LYeEm8/rVliqqu$Hjqt^ j7IobJ`b5>!<@W;rrU7da7'$35V'Ccf&_JFeE-c%rr2rrrVQZmrV?NirV6Bds7ZB`s7H9]r:9jW rUBaRqsO:Ip[$i(p[%_CqsOCNrUBgVr:9gXs7H6^s7ZHdrV-BgrV?NkrVZ]o!WMkFk(2ZGk(i*Z oRH~> j7IoiJaLbM!jVgAmf*=OdI.#<5V'Cdf]RhQeF!25s8Dlns82]is7uTfqtL*`r:TmXp[e.MnF?)= nF?8Dp[e4Qr:U'_qtL$`s7uQgs82`ls8Drr!:bX-JaJ$UM!a6pJ,~> j7IoiJaLbM!jVgAmf*=>Y3u;n5V'Cdf]RhQeF!25s8Dlns82]is7uTfqtL*`r:TmXp[e.MnF?)= nF?8Dp[e4Qr:U'_qtL$`s7uQgs82`ls8Drr!:bX-JaJ$UM!a6pJ,~> j7IobJ`b8?!j)Il?eE6`/s8Drps82fls7uWgs7cNdrUp3_rU]sXrpfsV rU9XOqX+(Eo'?#7o'?);qX+1JrU9^SrpfpWrU^']rUp0`s7cKes7uZjs82fns8Dus!:5:!J`_OG M!!aiJ,~> j7IoiJaLbM!jVgAmf*:NmJd>T.^m=Ik(2pMm/ZhXrr2lor;ZZkqZ$Bhq"sgbpAFUZo_7tPo'c,> nF,oBo(VbOo_eC]pA=U^q#C0eqZ$Hjr;QZprW)eGm=FYUm>1/ioRH~> j7IoiJaLbM!jVgAmf*:=mJd>T.^m=Ik(2pMm/ZhXrr2lor;ZZkqZ$Bhq"sgbpAFUZo_7tPo'c,> nF,oBo(VbOo_eC]pA=U^q#C0eqZ$Hjr;QZprW)eGm=FYUm>1/ioRH~> j7IobJ`b8?!j)IT.C6h:hgsq?joFuOrr2lrr;QTmqZ$Bip]1'epAO[ao_nCZo)A1Y nGMbPmeZ>Gm.0K9lLO99m/$,HmelPQnG_tUo)81[o_nI]p&Ojcq#C0hqYpBlr;QWqrqM',J`_OG M!!aiJ,~> j7IoiJaLbM!jVgAg&D4@.^[+Ck(2pMm/ZhXrr;rpquHWiq>g?gq#'mcpAFUZo_J+Ro()>5nFH,E o(hnQo_eC]pAF[^p]1-cq>gEjr;Z`prW)J>m=FYUm>:5joRH~> j7IoiJaLbM!jVgAg&D4@.^[+Ck(2pMm/ZhXrr;rpquHWiq>g?gq#'mcpAFUZo_J+Ro()>5nFH,E o(hnQo_eC]pAF[^p]1-cq>gEjr;Z`prW)J>m=FYUm>:5joRH~> j7IobJ`b8?!j)Ig?hq#C*fpAO[ao`"I[o)8+XnGVhP mecDIm.]i.lM'W?m/-2HmeuVRnGVnTo)A7\o_nI^pAambp]1-gq>gEkqucosrp53uJ`_OGM< j7IoiJaLbM!jVgAg&D4@.^R"@iIUCHmJ[%`rVZQnqtg0hq>L*bp\smap&"CWoCqhLn_EX-nb;VM oDA1Xp&=[]p\jmeq>0pfqu$BkrVuisqLS$bm=FY]mGmhh~> j7IoiJaLbM!jVgAg&D4@.^R"@iIUCHmJ[%`rVZQnqtg0hq>L*bp\smap&"CWoCqhLn_EX-nb;VM oDA1Xp&=[]p\jmeq>0pfqu$BkrVuisqLS$bm=FY]mGmhh~> j7IobJ`b8?!j)IU0gp]'sbp&F[`oDA+Xnc&%Vn,)PL mJH8Eleq(&lhg&FmJH>Mn,DhUnb_nVoDeI\p&Fabp\spfq>L0iqu6Nnr;lomJ`_OGJ`_gOj7Ij~> j7IoiJaL_L!<@W&rrkYcZbb,LJaM%U!;6 j7IoiJaL_L!<@W&rrkYcZbb,LJaM%U!;6 j7IobJ`b5>!<@W&rrkYbYJ&38J`bPG!:p-gs8Drps82fls7uZhs7cNdr:L'^rU]sXrpg!Wr9sON qX+1HpZpc&pZq_CqX+1Jr9sXSrpfpWrUU$]r:U'_s7cNfs7uZjs82fns8DusJ`_OGJ`_gOj7Ij~> j7IoiJaL_L!<>C j7IoiJaL_L!<>C j7IobJ`b5>!<>CJ`bPGrVcfqrqlcnr;$EhrV6Eerq?<`s7H9]rUTpWs7$!UqX4=L q<[tDf'<+tq<\(IqX4=Ns7$!WrUTpYs7H9_rq?BdrV-Bgr;$Ejrqllsrr288k(2ZGk)/<]oRH~> j7IoiJaL_L!BWR#rs$4<;maHlk(2pOm/ZhXrr;rpr;ZZkqZ$Bhq#'mbpAXa\o_S1To(V\GnEfW8 nFuJKo(qtRo`"O^pAF[_q#C0eqZ$Hjr;Z`qrW)eGm=FYUm>C;koRH~> j7IoiJaL_L!BWR#rs$4<;maHlk(2pOm/ZhXrr;rpr;ZZkqZ$Bhq#'mbpAXa\o_S1To(V\GnEfW8 nFuJKo(qtRo`"O^pAF[_q#C0eqZ$Hjr;Z`qrW)eGm=FYUm>C;koRH~> j7IobJ`b5>!BNL"rs$4<;R+!_hgsqAjoFuOrr;rrquHWmq>g?iq#:!fpAFU`o`"I[o)8+XnGVhR melJLm.ou;lLO30lM9cCm/68KmeuVRnGVnTo)A7\o_e@^pAXgbp]1-gq>gElr;Z]rrqM',J`_OG MWWskJ,~> j7IoiJaL_L!HnIsrs!uRWOBgtk(2pOm/["]rr;rpr;ZZkqZ$Bhq#'mcpAO[[o_S1To(V\GnF#`G mdKc:p[S(MqXaOTrUp0`r:g-as7uQgs82`ls8Drr!:bX-JaJ$UMs]QsJ,~> j7IoiJaL_L!HnIsrs!uRWOBgtk(2pOm/["]rr;rpr;ZZkqZ$Bhq#'mcpAO[[o_S1To(V\GnF#`G mdKc:p[S(MqXaOTrUp0`r:g-as7uQgs82`ls8Drr!:bX-JaJ$UMs]QsJ,~> j7IobJ`b5>!HS7prs!uRVmF4ei.:%BjoG2Urr;rrquHWmq>g?iq#C*gpAFU`o`"I[o)A1YnG_nQ melJLm.ou@lKma8kl0i?lM9cCm/68Imf)\SnG_tUo)A7\o_eC^pAamcp]1-gq>gElr;Z`qrW);9 k(2ZGk)8B^oRH~> j7IoiJaL_L!jMa@g]%J..'0u#g$+(Tf^/\3rVlfprVQZmqYL6frV69arq?9_qXj[Vq==:Mp@.>8 rpKaRp@.hHq==FSqXj^Yrq?6`rV6EgqYC3hrVZZns8E#pJaJ$UJaJB_j7Ij~> j7IoiJaL_L!jMa@g]%J..'0u#g$+(Tf^/\3rVlfprVQZmqYL6frV69arq?9_qXj[Vq==:Mp@.>8 rpKaRp@.hHq==FSqXj^Yrq?6`rV6EgqYC3hrVZZns8E#pJaJ$UJaJB_j7Ij~> j7IobJ`b5>!iuC;g]%J..&jVme)Gr@f]E2)rr2oqs82lorV?Nir:g9drq?9_!;-6]r:9mXrp]mT r9jLMqX"+Fp$1i,s60FGp$2G?qX".Ir9jOPrp]sXr:9gX!;-3^rq6?dr:g9frV?Tmr;?Qm!WMkF k(2ZGk)8B^oRH~> j7IoiJaL_L!php4g]%HX;6R[Wh!BUZf^/\3rVlfprVQZmqYC3fr:p0`rq?9_qXj[Vq==:Mp@.J< !q,OGrU'[SrpKLLp[S(Mqt'[Vr:U'_qtL$`!;ZKf!;l`mrr)iqJaJ$UJaJB_j7Ij~> j7IoiJaL_L!php4g]%HX;6R[Wh!BUZf^/\3rVlfprVQZmqYC3fr:p0`rq?9_qXj[Vq==:Mp@.J< !q,OGrU'[SrpKLLp[S(Mqt'[Vr:U'_qtL$`!;ZKf!;l`mrr)iqJaJ$UJaJB_j7Ij~> j7IobJ`b5>!p)@+g]%HX:oq4Jf&_JFf]E2)rr2oqs82lorV?Nir:g9drq?<`!;-3\rUTsXrp]mT r9jLMqX"+FpZh,0!pJn5rTF7Groa@HpZhYAqX".Ir9jOPrp]pWrUTmX!;-6_rq6?dr:g9frV?Tm r;?Qms8IT3J`_OGMrs'lJ,~> j7IoiJaL\K!j)I[\0!2m-=d,0EAfBrP9rr)fns82Zh!;ZQfr:g3ar:TsZq=FIRq=41Jm-jl? s6]gRrTsXRn+Q8HnbVhPoDA1Xp&=[_p\addq>0sfqu-HlrVuisnU^(Ym=FY`mGmhh~> j7IoiJaL\K!j)I[\0!2m-=d,0EAfBrP9rr)fns82Zh!;ZQfr:g3ar:TsZq=FIRq=41Jm-jl? s6]gRrTsXRn+Q8HnbVhPoDA1Xp&=[_p\addq>0sfqu-HlrVuisnU^(Ym=FY`mGmhh~> j7IobJ`b2=!iH%6h>[\0!2Zm4bLqI/fB3)3s8Drps82ck!;ZWhrqHHdr:L'^rq$'Yrpg!WrU9[P r9aCJqs4"Am-4H3!9a:DroX=Gl1aK?lhg&HmJQDNn,DhUnbhtWo)SF\p&Fabp\spfq>L0iqu6Nn r;loaJ`_OGJ`_pRj7Ij~> j7IoiJaL\K!pK\Mh>[ZZ:TLtFe`MGMf^/\3rVlfprVZ]mr;-EgrqQ?as7ZEaqXjUTqssRQp[IM; r9jUPr9X@KrpBdTp[J"KqssRSqXjaZs7Z<`rqQKgr;-HjrVZZns8E#pJaJ$UJaJE`j7Ij~> j7IoiJaL\K!pK\Mh>[ZZ:TLtFe`MGMf^/\3rVlfprVZ]mr;-EgrqQ?as7ZEaqXjUTqssRQp[IM; r9jUPr9X@KrpBdTp[J"KqssRSqXjaZs7Z<`rqQKgr;-HjrVZZns8E#pJaJ$UJaJE`j7Ij~> j7IobJ`b2=!oj8Gh>[ZZ:8kP:d,0E:f]E2)rr2rrrqu]j!;ZTgs7cNdrUp3_rq$*ZrUKmVrpTgR qX+7JqWmqAm-4H3roa7Br9+(Cq!._ArTsCJrU0[Rrp]mVrUTsZs7H9_rq?BdrV-Bgr;-Ejs8;ut pjqRYk(2ZRk2Z)a~> j7IoiJaL\K"71"-^u,.sJ,lFM^!5 j7IoiJaL\K"71"-^u,.sJ,lFM^!5 j7IobJ`b2="6FD#^u,.sJ,l@H\]E=]J`bVIrVliqrqu]j!;ZTgs7cNdrUg0_rU^!Yrpg$XrU9[P qX+7JqWn"ClKS<3s6'FEs5j:ArT+(BkPXQ?lMBiFm/$,ImeuVSnG_tVo)8.[o_nI^pAamcp]1-f qYpEmrVuism"+;Mk(2ZSk2Z)a~> j7IoiJaLYJ!p;L-i;X%4!)KZ(`73^rmF^e+nbrIerVZQnqtg0hq>L*cp]'sbp%n=XoD.tRnbDV@ mgnjTmdBK0m-O''jj_Mui9:.("7#4>mJcPKnG;\No)&%To_eC^pA=U_q#C0dq>gEjr;Z`qrW)eG m=FYUm>^MnoRH~> j7IoiJaLYJ!p;L-i;X%4!)KZ(`73^rmF^e+nbrIerVZQnqtg0hq>L*cp]'sbp%n=XoD.tRnbDV@ mgnjTmdBK0m-O''jj_Mui9:.("7#4>mJcPKnG;\No)&%To_eC^pA=U_q#C0dq>gEjr;Z`qrW)eG m=FYUm>^MnoRH~> j7IobJ`b/C!hp\+=Yo`4X_oDJ1Znc&%V n,2VOmJQ>JlhTi9l2BfUkNM-mk2tdejQ,4EaN*'6j8\0Dk3(pkkiqp.qs41Hr9aFMrU9aTs7-$X rUU$]r:U*`!;HEd!;ZTi!;l`m!r`,nJ`_OGJ`_sSj7Ij~> j7IoiJaLYJ"7)reJDpP.!!T7)^ j7IoiJaLYJ"7)reJDpP.!!T7)^ j7IobJ`b/<"6?9YJDpP.!!T1$]#W@\J`bVI!:p-g!<)lo!;lcls7uZhrqHHdr:L'^rU^$ZrUKmV rpTgRr9aFKqWn"Co]c59rT=7EjlQL&#ijR2BO5#%al!*V#ian*k3(sll08'0qWn(Gr9aINrpTjU rUKmXrUU$]r:U*`rqHEes7uZj!;l`m!r`,nJ`_OGJ`_sSj7Ij~> j7IoiJaLVI"6aV&JE-Y75lc55\]E=[JaM(V!;cZns8Dln!;lWh!;ZQfr:g3arUp'[qt'XSqXOCN mI()DmJcJPl5AjMkN:ER j7IoiJaLVI"6aV&JE-Y75lc55\]E=[JaM(V!;cZns8Dln!;lWh!;ZQfr:g3arUp'[qt'XSqXOCN mI()DmJcJPl5AjMkN:ER;cA'c??X?Jj5f=bkiqC!mI'H3q=+7Nq==FSqt0j[rq?9ar:g9fqYC3h r;?Tnrr)ofJaJ$UJaJKbj7Ij~> j7IobJ`b,;"6""qJE-Y75lc/0[DU>FJ`bSH!;QQm!<)ops82fl!;ZTgrqHHdrUp3_rU^$Zrpg!W rU9^Qr9aFKqWn%DoBH/9s6'CD)s-/@io/kGKhtH[BNK5kh;7&IiSrnZk3(sml20cBlhp,ImJZJO n,DhUnc&+XoDeI]p&Fabp\jjeq>U6jqu6KqrVlf_J`_OGJ``!Tj7Ij~> j7IoiJaLVI"RP/a!.a22#_2nOX1#pnh j7IoiJaLVI"RP/a!.a22#_2nOX1#pnh j7IobJ`b,;"QeHQ!.a22#_2nNW3X.]fAhGEfB3)3s8Drp!;l`k!;ZTgs7cNdrq6<`rq$'Yrpg$X r9sXQr9aCJr9O4Ep$)88+m8"MjlGI^inrG)Hq.>-Z*&:>L=4c1h;7,Mj5f=akNM1,lMTuFm/68M melPRnG_tUo)A7\o`"O_pAamcp]1-gq>gElr;Z]rrp53uJ`_OGNooBoJ,~> j7IoiJaLSH"R4iY!.a84#XJGeWjBF`g#[ePfBiS.rVlfprVZ]mr;-Hhr:p3arq?9_qt0dWqXXOR q=+"Es6fgPs6LoplKRNqin(Z%@ml14oD81->AqsEh;7)Mjlbppm-O-.n+lJMnbVhRoDA1Xp&=[_ p\agdq>C*hqu-HlrVuisqLS$bm=FYbmGmhh~> j7IoiJaLSH"R4iY!.a84#XJGeWjBF`g#[ePfBiS.rVlfprVZ]mr;-Hhr:p3arq?9_qt0dWqXXOR q=+"Es6fgPs6LoplKRNqin(Z%@RG[lhr`t8=)ZOAh;7)Mjlbppm-O-.n+lJMnbVhRoDA1Xp&=[_ p\agdq>C*hqu-HlrVuisqLS$bm=FYbmGmhh~> j7IobJ`b):"QS3J!.a84#XJGdVm!\PeDGi>fB*)"rr2rrrqlcnrV?NirV6Eerq??as7H<^r:9jW s7#sTrpK^Oqs=7Hqs*hL-iqu6Nnr;lomJ`_OGJ``!Tj7Ij~> j7IoiJaLPG"R"TS!.aA7$G\q-H)e3ab1>S=JaM(V!;6 j7IoiJaLPG"R"TS!.aA7$G\q-H)e3ab1>S=JaM(V!;6 j7IobJ`b&9"Q7mD!.aA7$G\q-GGhXT`R3H%J`bSH!:p-g!<)lo!;lcl!;ZQf!;HKdrUg0_rU^$Z rUKmVrpTdQr9aFKrTj=Fs69RIrosCFqWIn@roGBcj5].T`1q0<8ksE1_SjC1Zs$bEdaQb!gYC]G jQ5Oekiq@.lM^&Hm/68Kmf)\SnGVnVo)8.[o_nI^p&Ojbp]1-hq>gEkquZirpjqRYk(2ZTk2Z)a~> j7IoiJaLPG#41I,BE/$_mJdND!%3)o[(aZ5i9Gm\f'NJ1rr2rrr;?Tlqt^ j7IoiJaLPG#41I,BE/$_mJdND!%3)o[(aZ5i9Gm\f'NJ1rr2rrr;?Tlqt^ j7IobJ`b&9#3FanAH2^\mJdND!%)ujZ+@p$g>mhIf&d,+rr2lprqufnrqZWjrV6Eerq?<`!;-9^ rUTpWs7$!UrpK^Or9X@Iqs"1GrTO:E!9jFFpuW1MjQ,@\hUp/aQ!upXBSr[q_u7LeZu\g2bKeMc f@f! j7IoiJaLMF#OCL,SmqbNJFWXIJ,fRGW3<\Hbgt_;JaM"TrVliqr;?Tlqt^Vkj%L$mf)\P nG;\Po(qtTo_eC^pA=U_p]1-eqZ$Hjr;Z`qrW)J>m=FYUm>pYpoRH~> j7IoiJaLMF#OCL,SmqbNJFWXIJ,fRGW3<\Hbgt_;JaM"TrVliqr;?Tlqt^rKGbg=nngu7>Vkj%L$mf)\P nG;\Po(qtTo_eC^pA=U_p]1-eqZ$Hjr;Z`qrW)J>m=FYUm>pYpoRH~> j7IobJ`b#8#NagnRUH2HJFWXIJ,fRFVQ@,:a3`N"J`bMFrr2rrrqufnrqZWjrV6Eerq??as7H<^ rUTsXrp]mTrpK^Or9X@IrTaFI!9j@Ds5s=B!p&J)r8dn<%cZI"_O,@V;*.WsTXMSNqns)YI5[5, aNMrZfA,6@j5oCckl0iDlMTuGm/68MmeuVRnG_tVo)A7\o`"O_pAamcp]1-hqZ$Hlr;Z]rrp53u J`_OGO65KpJ,~> j7IoiJaLJE"RP:-eRe^[!J%u\s8TkC5l^m5IB9ce`Qm,pl@J?Qm/Z\Trr;rpr;ZZlqZ$Biq#'mc pAFU]o_\7Vo)%qXnF-AG"n1mImHj0lKRR4kksWDjq$=fJknQq4@!81\'`mbl1k5R')D1) 6sHb5bKnYih;RGXl0@[(rpT[PqsjORqt'aXr:U'_r:g0bs7uThs82`ls8Dus!;_96JaJ$UO6u!" J,~> j7IoiJaLJE"RP:-eRe^[!J%u\s8TkC5l^m5IB9ce`Qm,pl@J?Qm/Z\Trr;rpr;ZZlqZ$Biq#'mc pAFU]o_\7Vo)%qXnF-AG"n1mImHj0lKRR4kksWDjq$=fJknKk3BL;jVn^6pf&Zel''8)L 6 j7IobJ`au7"QeOncXQkR!J%u\s8TkC5l^m5H`FLli$)Pl0.?ok2lR("Qe_*io0gpro"FHfYDu34#o2W9l#fbWiWYG rPT>d^lL]`Wl)m#cI1>"hr<_YkNMp0qs++Fr9X@Kr9jRQs7$$XrUTpY!;-9`rUp6cr:p9fs82fn s8;utpjqRYk(2ZUk2Z)a~> j7IoiJaLGD#41O1f?Ri2rW)sardau6rW!:'I]]ob^W=jWj6M9`eEm8/rr2oqrVQZmqYC3fr:p3a rq??aqXj[VqXXORr9jdVmHj0)rTXCIroX=Ejo4?=it9h3?oL\nI[[40Oe9+mVm+&.rV@-%o8Z@b \];n>eCi[:jlksqmeuVNnGDbPo)&%To`"O_pAF[_p]1-dq>gEkr;QWqrq_35JaJ$UO6u!"J,~> j7IoiJaLGD#41O1f?Ri2rW)sardau6rW!:'I]]ob^W=jWj6M9`eEm8/rr2oqrVQZmqYC3fr:p3a rq??aqXj[VqXXORr9jdVmHj0)rTXCIroX=Ejo4?=it9h3?T(>]Ef?EMJrce,QCtM=rT4^Ri.X[E \];n>eCi[:jlksqmeuVNnGDbPo)&%To`"O_pAF[_p]1-dq>gEkr;QWqrq_35JaJ$UO6u!"J,~> j7IobJ`ar6#3FdrdE,a#rW)sardau6rW!:&I&aBW]>VtDguX+LeE-c%rr2rrs82lorV?Nir:g?f p@eFW!;-6]rpp'Yrp]pUrU0XOr9X@Ir9=7F#j171jQ,F`j5U$qs53_1&^ZqO1I+rLG_:E_H]F/; LmPdQ`"Bo%F@:5Z`5g$He(<@4ioK:drTX1Dr9O:IrU'ROrpTjUrpg!YrUU$]r:U*`!;HEd!;ZTi #5e>rrVc`jJ`_OGJ``$Uj7Ij~> j7IoiJaLAB%-Z^*eBGml>V%EV!!*!Js*tH[ZF7B]a3WAokNmcee*R/.rr2oqrVQZmqYC3fr:p3a rq??aqXj[VqssURs6p!U5jdq(lKRNsjlbgfi8`hUhr3PPi8 j7IoiJaLAB%-Z^*eBGml>V%EV!!*!Js*tH[ZF7B]a3WAokNmcee*R/.rr2oqrVQZmqYC3fr:p3a rq??aqXj[VqssURs6p!U5jdq(lKRNsjlbgfi8`hUhr3PPi8/ jPSnXj2+tbRDHnddb!41j6#UmmI'uBqXF@OqssXUqXjd[rq?9ar:g9fqYC3hrVZZns8IT:JaJ$U O6u!"J,~> j7IobJ`al4%,p!lcH!bZ=tD0R!!*!I%"3W,YHt^O_ogE[i8oOPe)gZ$rr2rrs82lorV?Nir:g?f p@eFW!;-6]rpp'Yrp]pUrU0XOr9X@Is69RI"6\h/jSn1"iSrhQgYLZ>g>(H;g=k63[WaN$Bo&n# [Cj>g['[6PZE(gZ_nNn(_5:'oQG(/Uc-b(phVdGVkNMp0qWe"Er9XCLrU0[Rrp]pWrpp$Z!;-3^ s7QHer:g9frV?`qr;?Nlrr.K2J`_OGO65KpJ,~> j7IoiJaL>A)=6PFgXXWl`5'$s\@8oU\[oMk`llc]h;mmQmEt>"rr;rpquHWjq>g?iq#'mcpAO[\ o_e=Xo(ql'nF,f4mHj*!i6B0PYcFb'X.l>gYHPRPbLO%\:/?2Tjn\]IqYL$eq?$Eeq>U4*pZ005 iUa9KB!T4lc-k8#iT01elg=]>s6ojSqXOIRr:BdWrUp0`r:g0bs7uQg!;l]ls8Dus!:bX-JaJ$U OR;*#J,~> j7IoiJaL>A)=6PFgXXWl`5'$s\@8oU\[oMk`llc]h;mmQmEt>"rr;rpquHWjq>g?iq#'mcpAO[\ o_e=Xo(ql'nF,f4mHj*!i6B0PYcFb'X.l>gYHPRPbLO%\:/#]:e)fZPk2te)joXW*jo4BWj3b'@ cekKlB!T4lc-k8#iT01elg=]>s6ojSqXOIRr:BdWrUp0`r:g0bs7uQg!;l]ls8Dus!:bX-JaJ$U OR;*#J,~> j7IobJ`ai3)jrW)oqquHWmq>g?jq#C*fpAOXa o`"I[o)J7ZnG_nSmeuPNm/?8Ml5o0PkN:g`gW7+=XJ_qnVk9TZX/i_@a3h/K9LiZaZi7lB_uR^S _u[`mrPTVjZ^./d^5YWnY._]ocI:D"hr<_ZkNV6pl08*1rTjCJrU'ONrpTjUs7-$Xrpp-^rUp0` s7cNf!;ZTi!;l`m!r`,bJ`_OGJ``'Vj7Ij~> j7IoiJaL8?(@1/DhV$H,cHFAOaN;WLcdUM#i8j.hJaLhOrVliqr;6QlqYC3frV62jnW6NM"5E`[raN`2fhW!\] lKmp+mdKWAnG;\Po)/+Uo_nI^pAF[`q#C0eq>gEjr;Z`qrW)YCm=FYUm?$_qoRH~> j7IoiJaL8?(@1/DhV$H,cHFAOaN;WLcdUM#i8j.hJaLhOrVliqr;6QlqYC3frV6gEjr;Z`qrW)YCm=FYUm?$_qoRH~> j7IobJ`ac1(?OK1f[S6kaiDB=_o0R8b0A>bg>:lOJ`b>Arr)orrVQZmrV?NirqQNfrq?<`!;-9^ r:9mXrp]pUr9jONr9XCJ+R84Sk1\(eQ&UXhkiq@/lM^&Hm/?>Lmf)\SnGi%Vo)A4\o_nI^pAamdp]1-gq>gEkquZir nq#qSk(2ZVk2Z)a~> j7IoiJaL2=#OL[9j5AbJgA]b0gYCZEjQPr]mEY)!p&4mirVcZoqtp3iq>C$cp]'sbp&+IYoDA+T nK75dhnP>*I=@-7P,YR_Ye7QHYGJ1]K8O/)-p0psXRPZS'_fn^[Z j7IoiJaL2=#OL[9j5AbJgA]b0gYCZEjQPr]mEY)!p&4mirVcZoqtp3iq>C$cp]'sbp&+IYoDA+T nK75dhnP>*I=@-7P,YR_Ye7QHYGJ1]K8O/(-Sm_LRb[>n']cp(V1_6MYJ8'!d+6t0jQGdnmI'uB qsaIPqssUTr:Kp[s7ZBbr:g9fqtg?irVZ]os8E#pJaJ$UJaJQdj7Ij~> j7IobJ`a]/&`r$0gtLB2eC2mre^iC-hW!XJk0E)ho)AUgrVcWoqu6Elq>L*gp\smcp&F[`oDJ1Z nc/+Wn,2VQmJQ>KlQ>BRft3;qI"%!5P,YR_Ye7QHYGA+\K8F)&,pjrjICnpc'Z-N'NH7?tXLuEi bgG"qhVmMVkNMm/r9F4Gr9XFMr9jOPs7$'Yr:9jYs7H<`rq?BdrV-BgrqZWlrVQ`qrqM',J`_OG OQPTqJ,~> j7IoiJaL):%.*E'rVliqrVZ]mr;-Hhr:p3as7ZEaqt0aVrUTgT,4P'b aHD.,PdJ9^l/qF-qXF1UqsWqBqU_N42*4?#;1RX$n+mdomFn7T7mi$<_8ssPf\YWLkj%O%meuVO nG;\Ro(qtTo_nI_pAF[_q#C0fqZ$Hkr;Z`prdjHfm=FYdmGmhh~> j7IoiJaL):%.*E'rVliqrVZ]mr;-Hhr:p3as7ZEaqt0aVrUTgT,4P'b aHD.,PdJ9^l/qF-qXF1UqsWqBqU_K30etaM6#Y'1n)bAGg;f@h6UHO7_8ssPf\YWLkj%O%meuVO nG;\Ro(qtTo_nI_pAF[_q#C0fqZ$Hkr;Z`prdjHfm=FYdmGmhh~> j7IobJ`aT,s5a=BiSjdo"QAD$jlM'UbiSorrr)orrqufnrqZWjrV6Eerq??as7H<^rpp$Xs7$!U rpK^Or9XCJ)X?>"OF`"J[)(P^k3hs>nFc_Vn*TZAf:tbcrZqXs j7IoiJaJ$UJaN:#r;Q`prVZ]mr;-Hhr:p3as7ZEaqt0aVrUTgT%clr]L4cDFi:['FrVuosqYq!' qTE@t5"8.:9l/\^mJ7LhhP=a>99-X%bL"hpi8a"clg=*C*h qu-HmrVlcrnU^(Ym=FYemGmhh~> j7IoiJaJ$UJaN:#r;Q`prVZ]mr;-Hhr:p3as7ZEaqt0aVrUTgT%clr]L4cDFi:['FrVuosqYq!' qTE=s1H7EV4^6+kmH,)@bEZ9d99-X%bL"hpi8a"clg=*C*h qu-HmrVlcrnU^(Ym=FYemGmhh~> j7IobJ`_OGJ`cdjrVcfqrqufnrqZWjrV6Eerq??as7H<^rpp$Xs7$!UrpK^Or9XFK%c?EOKS-2D i:['FrVuosqYp`uqTE:o-N5:n-q(,RmDK[RXaa,"8rL3oa3;r_g>1`Kjlbk+l2BoFlhp,ImJcPP n,MnUnc&+YoDeI^p&4Rap\jjeq>U6jqu6KqrVlf_J`_OGJ``*Wj7Ij~> j7IoiJaJ$UJaN:#rVlfprVZ]mr;-Hhr:p6brq?<`qt0gXqssUR#M-&+O1t,Wrq$-orn20P3(Z^@ 8ciSBo]u>dpW[YAC*hqu-HlrVuis nU^(Ym=FYemGmhh~> j7IoiJaJ$UJaN:#rVlfprVZ]mr;-Hhr:p6brq?<`qt0gXqssUR#M-&+O1t,Wrq$-orn20N0KD/m 3W`C*hqu-HlrVuis nU^(Ym=FYemGmhh~> j7IobJ`_OGJ`cdjrr2rrrqu]jr:pXhros=Fr9O@Kr9aFMs6osVrpg!YrUU$]r:U*` !;HEdr;-Ejs8;utm"+;Mk(2ZWk2Z)a~> j7IoiJaJ$UJaN:#rVlfprVQZmqtg?gr:p6bs7ZB`r:KmXqssUR"K[95g[an7"oS%K1H7f#8c`&" q>(Elp%n+?m.p8@nbi4qqs6omTqXOIRqt'aXr:U*`rV-3a!;ZNg !;l`mrr)lr!;;!2JaJ$UOmV3$J,~> j7IoiJaJ$UJaN:#rVlfprVQZmqtg?gr:p6bs7ZB`r:KmXqssUR"K[95g[an7"oS%K1,(Wa3WVaC q;r"DiTAkDf]D#Eh>HCIjk-h73-)LFaNrDihrEk_lg=]>s6omTqXOIRqt'aXr:U*`rV-3a!;ZNg !;l`mrr)lr!;;!2JaJ$UOmV3$J,~> j7IobJ`_OGJ`cdjrr2rrs82lor;-?d!r)E^r:L-`oCMnNrpg$XrpTdQrpBXMr9FKaJr/7Qnc&ai onP4Kq]u7tK)*=k_8+"#['mld\@haX&];/G7lG=m^Vn=Be_/d:j5oJ'kl9oFlMTuGm/HDMmf)\T nG_tUo)J:]o_eC^p&Ojbq#'smqu$BjrVlA=k(2ZGk)nfdoRH~> j7IoiJaLJEj?*FJJaLqR!:g$err)fn!;lZis7uTfrV-?cr:U![qt'^Uqsaj+PD$UHrpg!llaK)7 6V75K"%uG%q>C(!nEK#TYc3qLL8_Gsg?A5.q@rP/9MM4q`QZc^gYq5VlKmp+mdKWBnG;\Po)&%V o_eC^pAOa_p]1-eq>gEkr;QZprW)YCm=FYUm?-eroRH~> j7IoiJaLJEj?*FJJaLqR!:g$err)fn!;lZis7uTfrV-?cr:U![qt'^Uqsaj+PD$UHrpg!llaK)1 2`^gt"$8`Gjo"6Nh:9o\SsP+YFdA]+a40)HjqQ^M7nfVk`QZc^gYq5VlKmp+mdKWBnG;\Po)&%V o_eC^pAOa_p]1-eq>gEkr;QZprW)YCm=FYUm?-eroRH~> j7IobJ`au7j>m:BJ`bGD!:9^as8Duq!;l]jr:g?fp@eFW!qc*Ur:9jWs7$$Vr9jROr9XCJ"h]qL ]_qd!rrVMq5PcZ.-7Xqkr59)X['>sKHYR%pHA%K[[(lRX&&+&i9:ic3aNi5bgYUoMk5OTEl29iE lhp,KmJQDOn,MnVnbhtXo)SF\p&F^cp\agaq?Hipr;HTnnq#qSk(2ZWk2Z)a~> j7IoiJaLJE!!)s-l9>:LG(K&0m/Z\Trr2lqr;ZZkq>g?iq"sgcpAFU]o_\7Wo)/"\nApBNjR<*O c=8@k8c)*]=L7GpqC1je_O#.R>>/$s91i<&J$@o*q>0s^h0(i([_^)3eCrg>jlu'smI'E2qXFFQ qssXUr:Kp[s7Z?arV-Bgqtg?irqucos8E#pJaJ$UJaJTej7Ij~> j7IoiJaLJE!!)sZl9>:LG(K&0m/Z\Trr2lqr;ZZkq>g?iq"sgcpAFU]o_\7Wo)/"\nApBNjR<*O c=/1[3Vti=8>4c:jsf$*YD$7`8j#O14$#l9DP#,4jlY^cb\T:a[_^)3eCrg>jlu'smI'E2qXFFQ qssXUr:Kp[s7Z?arV-Bgqtg?irqucos8E#pJaJ$UJaJTej7Ij~> j7IobJ`au7!!)s-l9>:LEe3B%joFcIrr;rsquHWmq>g?jq#:$fpAOXao`"I[o)J7ZnG_nSmf)VO m/H;RlGJ=AjR<*Mc=&!o,m#iT\c0Mn\YtX+7R/gY0/"Xu0geunXh_im`5/T_2KlC?`ll`Zg"bNG jlZR+!9s@Frp0LKrpBXOrpTjUs7-$Xrpp-^rUp3arqHEe!;ZTi#5e>rrVc`jJ`_OGJ``*Wj7Ij~> j7IoiJaLJE"99H'3c"$\!<@:omF1G&nbrIdrVl`pqtp3iq>L*cp]'sbp&+IZoDA+VnHA?lK;JD5 rrhVL6p"!:8e#18nFuG"Vg;?D9hJ#Nr(7,d93d:qoD\S"prH$4VnL$ldF[45jQGdnmHs?1qXFFQ qssXUr:Kp[s7Z?arV-Bgqtg?irqucos8E#pJaJ$UJaJTej7Ij~> j7IoiJaLJE"9L*cp]'sbp&+IZoDA+VnHA?lK;JD5 rrhVL5qtCr3XnlZguI5)Q"`ZV4ZYVcr&P!D4%sk-hu;aOjLI]aVnL$ldF[45jQGdnmHs?1qXFFQ qssXUr:Kp[s7Z?arV-Bgqtg?irqucos8E#pJaJ$UJaJTej7Ij~> j7IobJ`au7"99H'3c"$\!<@.kk0rGmm/I"arVucqqu-?kq>U0gp]'sco`4X`oDJ1Znc&%Wn,2VQ mJQ>MlNHL\Ju/;4rrVJJ5PQN63JG)]]UsIH0/+e#,prO9"XbWS?(&D*`"U"Z5[("q^rO^JfA#3B j5oIekiqs/rp0LKrpBXOrpTjUs7-$Xrpp-^rUp3arqHEe!;ZTi#5e>rrVc`jJ`_OGJ``*Wj7Ij~> j7IoiJaLJE"99H'3c"$\!<@:omF1G&p&=sjrVZTnqtp3iq>L*dp\smcp%n=XoDJ1VnH@jqJ[PLI rri>5;E7,?8dAP%p><&q<_i?\**?c\;c8V*nG;t9CdXDu_T0pNg"tcOkj%O%mdL#AqsjUTqt'[V rq69ar:g0b!;ZNgs82`ls8;utqLS$bm=FYemGmhh~> j7IoiJaLJE"9L*dp\smcp%n=XoDJ1VnH@jqJ[PLI rri>5;DgE/3X86Filmp&7R$N,*(X(,6:-(;gud_AB0qfo_T0pNg"tcOkj%O%mdL#AqsjUTqt'[V rq69ar:g0b!;ZNgs82`ls8;utqLS$bm=FYemGmhh~> j7IobJ`au7"99H'3c"$\!<@.kk0rGmo)AUgrVl]pqu-?kq>U0gp]'sco`4X`oDJ1Znc/+Wn,DbQ mJQ>MlNH%aJ@5CHrr`84:bDGg#;^hEXFEkr.J>&.1-R`N/RkKf`5A`^6'/Uj_o^6Sf\PHEjQ>Xh l08*1rp0LKr9aLOrU9dUs7-$Xrpp-^rUp3arqHEe!;ZTi!;lcn!r`,nJ`_OGJ``*Wj7Ij~> j7IoiJaLJE"99H'3c"$\!<@:omF1G&p&=sjrVZTnqu$L*dp]'scp%n=ZoD8%TnHA7#H`lo0 rrrApF#uKSqFUoa:8Q0g?h6l/:0MRkKR/^6pA+?T4b:#S`66NXgYh,Skj%O%rpK^RqsjORrU]mX rUp3ar:g0bs7uThs82`ls8;utqLS$bm=FYemGmhh~> j7IoiJaLJE"9L*dp]'scp%n=ZoD8%TnHA7#H`lo0 rrrApF#Ys9qDngB4d3Eu:HS20)a6l#F`M2FaPu@@Q9Bf,]u8+@e_B$Bk32*smJcPOnGDbPo)81V o_nI_pAF[`q#C0fqZ$Hjr;Z]rrq_35JaJ$UOmV3$J,~> j7IobJ`au7"99H'3c"$\!<@.kk0rGmo)AUgrVl]pqu-?kq>U0hp\smbo`4X`oDS7Znc/+Wn,DbQ mJcJMlNHFhHEQf/rr`5nF#m;^^OnRrZF[ilbgFtph;I;Sk32'n qs++Frp9RMrpKaRs7$$XrUTsZ!;-6_rq?BdrqHKhrV?NkrqlirrqM',J`_OGOlk]rJ,~> j7IoiJaLJE"99H'3c"$\!<@:omF1G&p&=sjrVZTnqu$L*dp]'scp%n=ZoD8%TnHA=*K:V2r rri/;2E4/#8d%k7DGNpq8f1?2FGZ9bhtHl_;.Zr?^W4UJf\PKGk3;42m/ZSPnGDbPo)81Vo_nI_ pAF[`q#C0fqZ$Hjr;Z`qrdjHfm=FYemGmhh~> j7IoiJaLJE"9L*dp]'scp%n=ZoD8%TnHA=*K:V2r rri/;2DR5d3WqT[>sCF@3Z((V@X!Hobi7`j:1^W<^W4UJf\PKGk3;42m/ZSPnGDbPo)81Vo_nI_ pAF[`q#C0fqZ$Hjr;Z`qrdjHfm=FYemGmhh~> j7IobJ`au7"99H'3c"$\!<@.kk0rGmo)AUgrVl]pqu-?kq>U0hp\smbo`4X`oDS7Znc/+Wn,DbQ mJcJMlNHIoJXtuprr`):2C\AH"=I):.44R2(b.L4nj^r)U4A? j7IoiJaLJErrE$/l9PFNG(K&0m/ZhYrr;rpr;ZZmqYp#hQ0]#)P6e_8m>jlbmom-X60n,DhRnbVhToD8+Xp&Fa` p\jmdq>L0iqu$BlrVukJm=FYUm?-eroRH~> j7IoiJaLJErrE$\l9PFNG(K&0m/ZhYrr;rpr;ZZmqYpjlbmom-X60n,DhRnbVhToD8+Xp&Fa` p\jmdq>L0iqu$BlrVukJm=FYUm?-eroRH~> j7IobJ`au7rrE$/l9PFNEe3B%joFuOrW)orquHWmq>g?jq#C*fpAOXao`"I\o)A1ZnG_nTmelJO m/6/Ql.M;VgA1C@"T@b?-RSR6""#KF-LE)u/jWfuY.CN);l6h/^;S1@e_&[7iT0+_kNM0prp'FI rp9RMrU0[Rs7$$XrUTsZ!;-6_rq?BdrqHKhrV?Nkrqlirrr.K2J`_OGOlk]rJ,~> j7IoiJaLJEj?!?bJaLqR!;c]os8Dlns82ckrqZNfr:g6brUp$ZrU]mVqsamYm>:mf)nZ"o@"E 1.Fjk8f1opbhGllN3'P'_o^9Ug>CrPkj%L#mI0NCnGDbPo)81Vo_nI_pAF[`q#:*fqZ$Hjr;Z`q rdjHfm=FYemGmhh~> j7IoiJaLJEj?!?bJaLqR!;c]os8Dlns82ckrqZNfr:g6brUp$ZrU]mVqsamYm>:mf)nZ"o@"E /N>cH3Z(V=]#dQDN3'P'_o^9Ug>CrPkj%L#mI0NCnGDbPo)81Vo_nI_pAF[`q#:*fqZ$Hjr;Z`q rdjHfm=FYemGmhh~> j7IobJ`au7j>d3\J`bGD!;QQm!<)op!;l`k!;ZWhs7cNdrUg0_rq$*Zrpg$XrpTgRr9aLMr9FRO k,*X/mf)nZ!rC\Bg*IRq@%48d;MqH]]"c8/d+-k+hr<_Yk3)!nrp'FIrp9RMrU0[Rs7$$XrUTsZ !;-6_rq?BdrqHKhrV?Nkrqlirrr.K2J`_OGOlk]rJ,~> j7IoiJaJ$UJaN=$!;c]os8DoorqlZjs7uTfr:g6brUp$ZrU]mVr:($\md/G\NS++#rri,*,WnWR 8f: j7IoiJaJ$UJaN=$!;c]os8DoorqlZjs7uTfr:g6brUp$ZrU]mVr:($\md/G\NS++#rri,*,VqI: 3Z1%F5uMVc5AGk<\%fu,d+-n-ioTCglg4'.rUBaTqXXUVqXjaZs7ZBbr:p j7IobJ`_OGJ`cgk"ShuqrVc`n!;lcl!;ZTgs7cQerUp3_rq$*Zrpg$XrpTgRrU'RMrTa^Rki^ j7IoiJaJ$UJaN=$!;c]os8DoorqlZjs7uTfr:g6brUp*\qt'[Tr:(']md9,9H^`mhp\tErpO"GX 8`!&Y92BAJF(7EqH[U!jX2)g#dam./ioT@glg=-/rp]jUqXXOTr:Ks\s7ZBbr:p j7IoiJaJ$UJaN=$!;c]os8DoorqlZjs7uTfr:g6brUp*\qt'[Tr:(']md9,9H^`mhp\tErpO"AI 3Sle94$Qq]@oYQNH[U!jX2)g#dam./ioT@glg=-/rp]jUqXXOTr:Ks\s7ZBbr:p j7IobJ`_OGJ`cgk"ShuqrVc`n!;lcl!;ZTgs7cQerUp3_rq$*Zs7-*XrpTgRrU'RMrTaaRki^m% HCEdgp\t j7IoiJaJ$UJaN=$rVliqrVZZlrVHQir:p3as7ZEar:KmXqXXRS$1REOm+dhZ[e9Xorrh^b1dt!k 8fUWDSt\k>Tq$IMGbDjmcI:D"hVmPYl0Rg*n*oiEnbVhRoDJ7Zp&Fa`p\agdq>L0hqu-HmrVuis nU^(Ym=FYfmGmhh~> j7IoiJaJ$UJaN=$rVliqrVZZlrVHQir:p3as7ZEar:KmXqXXRS$1REOm+dhZ[e9Xorr_Xa00%]I *'[_gP"%O,S9o:$TYSUne(<@2iT01dm-X60nF6AGqXXOTr:Ks\s7ZBbr:p j7IobJ`_OGJ`cgkrr2rrrqlcnrqZWjrV6Ees7ZEas7H<^rUU!Yrp]pUrU0XOrTsLK$0pd=k1>`K [e9XorrVR`.,#se.Re?$4Zf@-HZF>$]u.t;dam+,hVmMWkNM0qlM^&Im/?>Mmf)\SnGi%Wo)A7\ o_nI_pAamcp]1-hq>gElr;Z]rrp53uJ`_OGP31fsJ,~> j7IoiJaJ$UJaN=$rVliqrVZZlrVHQir:p3as7ZEar:KmXqssXS$LmNPmHDH?KtlsOq#:H?6omg) 8c`9rmRe?s2)p]Y`0,@kQ+4r\db!71iT'(blg=-/nGDbQo)&%Vo_nI_pAF[_q#C0gqYpBjr;Z`q rW)J>m=FYUm?6ksoRH~> j7IoiJaJ$UJaN=$rVliqrVZZlrVHQir:p3as7ZEar:KmXqssXS$LmNPmHDH?KtlsOq#:H?6o$^g 3WWGRmQ)4T0fP3T`0,@kQ+4r\db!71iT'(blg=-/nGDbQo)&%Vo_nI_pAF[_q#C0gqYpBjr;Z`q rW)J>m=FYUm?6ksoRH~> j7IobJ`_OGJ`cgkrr2rrrqlcnrqZWjrV6Ees7ZEas7H?_r:9mXrp]pUrU0XOrTsLKs69mRkMs:. KtlsOq#:E>6RqFK!EM)G,oJHf2j!+=P'D8:[`$84dam+,hVdGUkNM0qrTjFKrU'ROrpTjUs7-$X s7?9_rUp3as7cKe!;ZWj!;lcns8;utm"+;Mk(2ZXk2Z)a~> j7IoiJaJ$UJaN=$rVliqrVZZlrVHQir:p3as7ZEar:KmXqssXSs6p?`md'#HKo3e>rqZQr[mrGK m7ICd;=7$o7lMl6kP=)1M0OQT`QZ`[g"t]JkNV=!mdL&CqssXUr:Ks\s7ZBbr:p j7IoiJaJ$UJaN=$rVliqrVZZlrVHQir:p3as7ZEar:KmXqssXSs6p?`md'#HKo3e>rqZQr[mi&2 m5b8P7IE2S3A2dtkP=)1M0OQT`QZ`[g"t]JkNV=!mdL&CqssXUr:Ks\s7ZBbr:p j7IobJ`_OGJ`cgkrr2rrrqlcnrqZWjrV6Ees7ZEas7H?_r:9mXrp]pUrU0XOrTsLKs69pTkiUj4 K8RSMmf)\SnGi%V o)J=]o_nI_pAamcp]1-hq>gElr;Z]rrpkX&J`_OGP31fsJ,~> j7IoiJaJ$UJaN@%!:g$es8DoorqlZjs7uTfr:g6brUp*\qt'^Uqsj[S#k%*Fh5km[_=7=$"S8`e 5=t<9s&]85r`JV])+6,ig]$m._M<;aWQ360e_/d:jlbmom-aB@nb_nSoDJ7Zp&Fa`p\agdq>L0h qu-HmrVuisp4;U^m=FYfmGmhh~> j7IoiJaJ$UJaN@%!:g$es8DoorqlZjs7uTfr:g6brUp*\qt'^Uqsj[S#k%*Fh5km[_=7=$"S8`b 1HFnbs%i]-r_W&E))j'Wg]$m._M<;aWQ360e_/d:jlbmom-aB@nb_nSoDJ7Zp&Fa`p\agdq>L0h qu-HmrVuisp4;U^m=FYfmGmhh~> j7IobJ`_OGJ`cjl!:9^as8Drp!;lcl!;ZTgs7cQerUp3_s7?0Zs7-*XrpTgRrU'RMrTjII#jCI4 f;X"Q_=7=$!qWN]m3Vd$!+u4!m3NQ[g]$m._M<8^V8LBud+$_&hr<_Yk32+0li$2KmJZJPn,DhV nbhtXoDeI]p&Facp\jjeq>U3jqu6Nnr;logJ`_OGJ``-Xj7Ij~> j7IoiJaJ$UJaN@%!:g$es8DoorqlZjs7uTfr:g6brUp*\qt'aVqXG$an*f]3lf[-,Jqq84r;?Qs l&\%nmn*d]=BJp;=AeNZ"%>7Tdf07\pXYpaHE"s/dF?h(iT01dlg=01nG_tTo)&%Vo_nI_pAF[_ q#C0gqYpBjr;Z`qrW)YCm=FYUm?6ksoRH~> j7IoiJaJ$UJaN@%!:g$es8DoorqlZjs7uTfr:g6brUp*\qt'aVqXG$an*f]3lf[-,Jqq84r;?Qs l&[t`mlCYA:f(A#:dsh6"#i)@df07\pXYpaHE"s/dF?h(iT01dlg=01nG_tTo)&%Vo_nI_pAF[_ q#C0gqYpBjr;Z`qrW)YCm=FYUm?6ksoRH~> j7IobJ`_OGJ`cjl!:9^as8Drp!;lcl!;ZTgs7cQerUp3_s7?0Zs7-*XrpTgRrU'RMrTapXl07Ep jl+mlJVV/3r;?Qrl&Rdk,mH4r7TNPZ2XVjm/m6X%rtY:ZSpYd[`5p0LeCWL5iT0+`l0@R"rTsLM rU0[Rrp]sXr:9mZs7H9_s7ZKerV-BgrqZWlrqufp!WMY@k(2ZGk*"leoRH~> j7IoiJaJ$UJaN=$rVliqrVZZlrVHQir:p3as7ZEar:KmXr:9^Ss6pEbmd9?'jOp;VN5>#]rr_bQ -pm8m!FJ^l8d%^S4D4!Yrt>4gTn%6ea3E#]g"t]KkNV=#n,DeVnb_nSoDJ7Zp&Fa`p\agdq>L0h qu-HmrVuisp4;U^m=FYfmGmhh~> j7IoiJaJ$UJaN=$rVliqrVZZlrVHQir:p3as7ZEar:KmXr:9^Ss6pEbmd9?'jOp;VN5>#]rr_bQ -T16O!F&"[3Wh?#BC#Nr')U[^J97p6c-b%ohVmPYl0Id+rpTpXqssXUr:Ks\s7ZBbr:p j7IobJ`_OGJ`cgkrr2rrrqlcnrqZWjrV6Ees7ZEas7H?_r:9mXrp]pUrU0XOrTsLKs6:!Vki_*e hpe#]rrV\P,jQ`W<]04i!\\3PrVmW0ih:jDWPcj%d+$b(hr<\YkiqBtli-8LmJZJPn,DhV nbhtXoDeI]p&Facp\jjeq>U3jqu6Nnr;logJ`_OGJ``-Xj7Ij~> j7IoiJaJ$UJaN=$rVliqrVZZlrVHQir:p3as7ZEar:KmXqssXSs7$'W'CP2Ljl4adIs@X?o)A[g r-Tt\8aT+N?W$5`"A(s7IJWmA(&-LZH@VX,b0\_ohW!Y[lL"$.n*olEo)&%Vo_nI_pAF[_q#C0g qYpBjr;Z`qrW)YCm=FYUm?6ksoRH~> j7IoiJaJ$UJaN=$rVliqrVZZlrVHQir:p3as7ZEar:KmXqssXSs7$'W'CP2Ljl4adIs@X?o)A[g r-TkM3UJj.>=Is@"?AClIJWmA(&-LZH@VX,b0\_ohW!Y[lL"$.n*olEo)&%Vo_nI_pAF[_q#C0g qYpBjr;Z`qrW)YCm=FYUm?6ksoRH~> j7IobJ`_OGJ`cgkrr2rrrqlcnrqZWjrV6Ees7ZEas7H?_r:9mXrp]pUrU0XOrTsLKrot*[kiUm_ h9VjFGG*[\rr;uqI-N!Y<]04i!\&Qor;RK*gn]7HS\iCjdam+-i8Nb\kiq?trp9UNrU0[Rrp]sX r:9mZs7H9_s7ZKerV-BgrqZWlrqufp!WMY@k(2ZGk*"leoRH~> j7IoiJaJ$UJaN=$rVliqrVZZlrVHQir:p3as7ZEar:KmXqssXSrpUWjmd03#jl5!rIY!d/p&4p\ C,eOOfLc9<5pi5tqu7B0m^.alT#8^tf%]-Cjlu."mdKZ8qssXUr:Ks\s7ZBbr:p j7IoiJaJ$UJaN=$rVliqrVZZlrVHQir:p3as7ZEar:KmXqssXSrpUWjmd03#jl5!rIY!d/p&4p\ C,@h/f/a!k-D1#/rtPJ#["idq_og?WgYh,Rl0Rj+n*olEo)&%Vo_nI_pAF[_q#C0gqYpBjr;Z`q rW)J>m=FYUm?6ksoRH~> j7IobJ`_OGJ`cgkrr2rrrqlcnrqZWjrV6Ees7ZEas7H?_r:9mXrp]pUrU0XOrTsLKrot-\kiUpa hqch^I=[[.p&4p\C+n'_![OE3qu7?/m^.^jS&!%fdam10i8a"akiqF4m/?>Mmf)\SnGi%Vo)J=] o_nI_pAamcp]1-hq>gElr;Z]rrp53uJ`_OGP31fsJ,~> j7IoiJaJ$UJaN=$!;c]os8DoorqlZjs7uTfr:g6brUp*\qt'^Uqsj[T'CYAVlKIEminLnuG`BN- s5\Gb5h8R-3WZAQqu7B0qWFc#I&4j+d+@(2j6,aqmdBQ6qssXUr:Ks\s7ZBbr:p j7IoiJaJ$UJaN=$!;c]os8DoorqlZjs7uTfr:g6brUp*\qt'^Uqsj[T'CYAVlKIEminLnuG`BN- s5\G_1tF\i0fcB+qu7B0qWFc#I&4j+d+@(2j6,aqmdBQ6qssXUr:Ks\s7ZBbr:p j7IobJ`_OGJ`cgk"ShuqrVc`n!;lcl!;ZTgs7cQerUp3_s7?0Zs7-*XrpTgRrU'RMrTjFI&aAK> j5JnNf!:'UNPP#_jEWN2,m,DPXmu;g'E7jkVg!K__T0mKfA#0AjlbjllMg/KmJZJPn,DhVnbhtX oDeI]p&Facp\jjeq>U3jqu6Nnr;loaJ`_OGJ``-Xj7Ij~> j7IoiJaJ$UJaN=$!;c]os8DoorqlZjs7uTfr:g6brUp*\qt'[Tr:0aT'CYATlKIBkiSDVEH%Dg8 fRkV=8_?W<8Ma8Zp%\P!mAtb^\&ckDf\GEHl0Rd)md^2Fqt'aXrUp3ar:g-as7uWirqlZls8Dus !:bX-JaJ$UP3q<%J,~> j7IoiJaJ$UJaN=$!;c]os8DoorqlZjs7uTfr:g6brUp*\qt'[Tr:0aT'(>8SlKIBkiSDVEH%Dg8 fRkS1f/a$p/Q':Uq#:rnXF>H(aNi8egu.;Ym-O0.nb_nSoDJ7Zp&Fa`p\agdq>L0hqu-HmrVuis nU^(Ym=FYfmGmhh~> j7IobJ`_OGJ`cgk"ShuqrVc`n!;lcl!;ZTgs7cQerUp3_rq$*Zs7-*XrpTgRrU'RMrTjFI&F&E@ jQ#4Vgt9T3G_)^7fRhU!"!g[Wp%\P#mAt_\Zc(#4e(<@3j6#Ogl0I^&rU'ROrpTjUs7-'Yrq$0^ rUp3as7cKe!;ZWj!;lcns8;utm"+;Mk(2ZXk2Z)a~> j7IoiJaJ$UJaN=$!;c]os8DoorqlZjs7uTfr:g6brUp$ZrU]mVr:0aTs6gQem-Vgs;Y ]mqk(8_?W;6S2Woq#;!+qr*rbP/>Ahe_8m>k3;3umd^2FrU]mXrUp3ar:g-as7uWirqlZls8Dus JaJ$UJaJTej7Ij~> j7IoiJaJ$UJaN=$!;c]os8DoorqlZjs7uTfr:g6brUp$ZrU]mVr:0aTs6gQem-Vgs;Y ]mqdo3S6@p2C`%_q#;!+qr*rbP/>Ahe_8m>k3;3umd^2FrU]mXrUp3ar:g-as7uWirqlZls8Dus JaJ$UJaJTej7Ij~> j7IobJ`_OGJ`cgk"ShuqrVc`n!;lcl!;ZTgs7cQerUp3_rq$*Zrpg$XrpTgRrU'RMr9O@Is61*X k2kU\hV?`)U4.TQ]mq[]eKk6$eG0%XrqkZBFHF8WbgFtohVmMVkNV=!m/?>Mmf)\TnG_tVo)A7\ o_nI_pAamcp]1-hq>gElr;Z]qrdj3_k(2ZWk2Z)a~> j7IoiJaJ$UJaN=$!;c]os8DoorqlZjs7uTfr:g6brUp$ZrU]mVr:0aTs6p!T&+&WGjl>:SfY*Dj Ipk2t8_?Z83rsunpAYa'g6$KK`QZc^hVmPZlKms/nbhtVoD8+Xp&Fa`p\agdq>L0hqu-HmrVukJ m=FYUm?-eroRH~> j7IoiJaJ$UJaN=$!;c]os8DoorqlZjs7uTfr:g6brUp$ZrU]mVr:0aTs6p!T&+&WGjl>:SfY*Dj Ipk/i3S6@p1-'sGpAYa'g6$KK`QZc^hVmPZlKms/nbhtVoD8+Xp&Fa`p\agdq>L0hqu-HmrVukJ m=FYUm?-eroRH~> j7IobJ`_OGJ`cgk"ShuqrVc`n!;lcl!;ZTgs7cQerUp3_rq$*Zrpg$XrpTgRrU'RMr9O@Is61'X kN:mci832=e%(Q_Ipk(^,m#uViqEBbrS!BTSAE:ke(NO6ioK7dlg4!:mJZJPn,MnVnbr%XoDeI] p&Facp\jjeq>U3jqu6Nnr;chJk(2ZGk)nfdoRH~> j7IoiJaJ$UJaN=$!;6?js8Dlns82ckrqZNfr:g6brUp$ZrU]mVqsj[Ts6p!Us6U9\kiUp_gt9uZ MHqno8_H]=82X&Jo_/:srpmXHJ@N\FeD&m?k3;4!nF?GIrU]mXrUp3ar:g0brqZNhs82`ls8Dus JaJ$UJaJTej7Ij~> j7IoiJaJ$UJaN=$!;6?js8Dlns82ckrqZNfr:g6brUp$ZrU]mVqsj[Ts6p!Us6U6[kiUp_gt9uZ MHqkgfK'-q/kWPDpAYa(o"'-t\B)qEgY_#Ql0Ia+nbhtVoD8+Xp&Fa`p\jmdq>L0iqu$BlrVukJ m=FYUm?-eroRH~> j7IobJ`_OGJ`cgk!:p-g!<)op!;l`k!;ZWhs7cNdrUg0_rq$*Zrpg$XrpTgRr9aLMr9O@Is69OH% Hlj5iSN8=e'"nN>q[JS,m$WFo_/:srpmXGJ$m5:d+6q,iT'%`lKeB8rU0[Rs7$$XrUTsZ!;-6_r q?BdrqHKhrV?Nkrqlirrr.K2J`_OGOlk]rJ,~> j7IoiJaJ$UJaN=$!;6?js8Dlns82ckrqZNfr:g6brUp$ZrU]mVqsjUR&b#/UmHa'%kN(UWf[R]h 3]KX_8cqC7=OQg@&*B:%V8^X,f\GEGkNV@%nGMkUoD8+Xp&Fa`p\jmdq>L0iqu$Blr;looJaJ$U JaJTej7Ij~> j7IoiJaJ$UJaN=$!;6?js8Dlns82ckrqZNfr:g6brUp$ZrU]mVqsjUR&b#/UmHa'%kN(UWf[R]h 3\`YJ3Wh8l=46^?&*B:%V8^X,f\GEGkNV@%nGMkUoD8+Xp&Fa`p\jmdq>L0iqu$Blr;looJaJ$U JaJTej7Ij~> j7IobJ`_OGJ`cgk!:p-g!<)op!;l`k!;ZWhs7cNdrUg0_rq$*Zrpg$XrpTgRr9aLMr9O:Gs61$W kN:jciSN>@e'G[W3@NK*![h'toD]?`R<,+>a3N/cgY_#OkNV=1mJZJPn,MnVnbr%Xo)SF]p&=[b p\spfq>L-iqu6KprVlSCk(2ZGk)nfdoRH~> j7IoiJaJ$UJaN=$!:g'fs8Dlns82`js7uWgr:g6brUp$ZrU]mVqsjURrpL?bm-Es#kN1XXg!mK4 1.9ef"$/W%jRiHbrq=$NL;M*]f%T!@jlu."n+#rIo)81Vo_nI_pAF[`q#C0fqZ$Hjr;Z]rrq_35 JaJ$UOmV3$J,~> j7IoiJaJ$UJaN=$!:g'fs8Dlns82`js7uWgr:g6brUp$ZrU]mVqsjURrpL?bm-Es#kN1XXg!mK3 /iVHE"#2oojRiHbrq=$NL;M*]f%T!@jlu."n+#rIo)81Vo_nI_pAF[`q#C0fqZ$Hjr;Z]rrq_35 JaJ$UOmV3$J,~> j7IobJ`_OGJ`cgk!:9^a!<)op!;l`k!;ZWhs7cNdrUg0_rq$*Zrpg$XrU9aRr9aLMr9O7Fs61!V kN1dbi835@eBtX&.+]aH3f)khrt58(]m5:F`m2u^g"tZIkNM3trU'UPrU9dUrpfsXrpp-^rUp0` s7cNf!;ZTi!;lcn!r`,nJ`_OGJ``*Wj7Ij~> j7IoiJaJ$UJaN=$!:g'fs8Dlns82]i!;ZTgr:g3arq6-[qt'aVqsjOP&F]&Sm-Es#k2bLYg:$gqu$Blr;looJaJ$U JaJTej7Ij~> j7IoiJaJ$UJaN=$!:g'fs8Dlns82]i!;ZTgr:g3arq6-[qt'aVqsjOP&F]&Sm-Es#k2bLYg:$gqu$Blr;looJaJ$U JaJTej7Ij~> j7IobJ`_OGJ`cgk!:9^a!<)op!;l`k!;ZWhrqHHdrUg0_rq$'Ys7--YrU9aRr9aFKrp0IHs61$W kN:mfio&YIg==;h.43[n!`$Q+oD]?ugl6fm_p$HUg"bNHkNV:2m/QJPmeuVSnGi%Vo)A4\o_nI_ pAXgcp]1-gq>gElquZirpjqRYk(2ZWk2Z)a~> j7IoiJaJ$UJaN=$!:g'fs8Dlns82]i!;ZTgr:g3arq6-[qt'aVqsjOPs6gHcmHs9*kiUpah:e>] 4@n4!"$\PZg@G7Vrp5o#ZcC;:fA5EIl0Rj-nac8JoD8+Yp&=[_p\jjeq>:$gqu$Blr;lokJaJ$U JaJTej7Ij~> j7IoiJaJ$UJaN=$!:g'fs8Dlns82]i!;ZTgr:g3arq6-[qt'aVqsjOPs6gHcmHs9*kiUpah:e>[ 1-+)M"#DTKg@G7Vrp5o#ZcC;:fA5EIl0Rj-nac8JoD8+Yp&=[_p\jjeq>:$gqu$Blr;lokJaJ$U JaJTej7Ij~> j7IobJ`_OGJ`cgk!:9^a!<)op!;l`k!;ZWhrqHHdrUg0_rq$'Ys7--YrU9aRr9aFKrp0IHrojjT kND!gio&\Kf[Z?LfHgSNDqad^&H;$]J$?i3bgP/!iT97dlKnK:rpKaRs7$'Yr:9jY!;-6_s7ZHd rqHKhrV?NkrqlirrpkX&J`_OGOlk]rJ,~> j7IoiJaJ$UJaN=$!:g$err)ios82]i!;ZTgqtL-ar:U![qt'^Ur:0XQs6osT%.3EHkiUp`h7667 84hZ1"\D6?9="!Urt4LdHEYB4cdpk/jQZ%!n+#u>qt'aXr:U*`qtL'a!;ZNgs82fnrr)lr!;;!2 JaJ$UOmV3$J,~> j7IoiJaJ$UJaN=$!:g$err)ios82]i!;ZTgqtL-ar:U![qt'^Ur:0XQs6osT$gmj4YK+i4f%]-Dl0Rj,nac8JoDJ7Yp&Fa_p\jjeq>:$gqu6NmrVuisp4;U^ m=FYemGmhh~> j7IobJ`_OGJ`cgk!:9^as8Duq!;l`k!;ZWhrqHHdrUg0_rq$'Ys7-*XrpTdQrpBXMrp0FGs69OH s5sXKio&\JfX=H+,m,DaZL78_&a>j3XM`*%dFR(1jQ>[jm-X3 j7IoiJaJ$UJaN:#rVlfprVQZmqtg?gr:p6bs7ZB`r:KmXqssURr:'[Rs6^9^lg!`sj5JBo2F:!l 8d.^K.#I-bnc'3`QZK(EbL+qsi8s1gmI0T8o)&%Vo_eC^pAOa_p]1-eq>gEkr;QZprW)YCm=O^* @=dg.mGmhh~> j7IoiJaJ$UJaN:#rVlfprVQZmqtg?gr:p6bs7ZB`r:KmXqssURr:'[Rs6^6]lg!`sj5JBo1H!uL "Z\CeM j7IobJ`_OGJ`cdjrr2rrs82lor;-?d!r)E^r:L-`oCMnNrpg$XrpTdQrpBXMr9O:GrTX@G$0UI2 io&\Jc!N*X,m5P@MrrVc`dJ`_RH!2"i)Olk]rJ,~> j7IoiJaJ$UJaN:#rVlfprVQZmqtg?gr:p6bs7ZB`r:KmXqssURr:'UP$h!BJlK@ j7IoiJaJ$UJaN:#rVlfprVQZmqtg?gr:p6bs7ZB`r:KmXqssURr:'UP$h!BJlK@ j7IobJ`_OGJ`cdjrr2rrs82lor;-?d!r)E^r:L-`oCMnNrpg$XrpTdQrpBXMr9O:GqrnFNkN1da i8<1R4PM.a/kt-tnc'6uglZWi_8aaKfA,?Gk32*smHsi>rpTmVrpfpWs766_r:U*`!;HEdr;$Wp r;?NlrpkX&VWJ$C^87UDoB$%Fn`BhDJ``*Wj7Ij~> j7IoiJaJ$UJaN:#!;c]orr)fns82`js7uTfrV-7@F2Tnc'6d[!nCZ`llf_gu7D[m-aB5o()_Mqt0j[rq? j7IoiJaJ$UJaN:#!;c]orr)fns82`js7uTfrV-mAB7R Ip^meHh6V)+7\^u++`gAmGmhh~> j7IobJ`_OGJ`cdj"ShuqrVc`nr;-?ds7ZNer:L'^rU^$Zrpg!Ws6omRr9aLMr9O7FqrnFNkN1a` i8)7S/_hWS6(.cPnc'6d[!n=T_T0sNfA,?Gk3D:!mHsi>s6osVrpg!YrUU$]r:U*`!;HEdr;-Ej s8;utm"+;rjoiK-hr;EBjoD%DjoD$)k)nfdoRH~> j7IoiJaJ$UJaN:#!;c]orr)fns82`js7uTfrV- j7IoiJaJ$UJaN:#!;c]orr)fns82`js7uTfrV-,"8s-++GV%_X/Ee,"8s-PrU$1+75=R+s.nXmAK@3 oRH~> j7IobJ`_OGJ`cdj"ShuqrVc`nr;-?ds7ZNer:L'^rU^$Zrpg!Ws6omRr9aLMr9O7FqWS=Mk2k[_ hq+)S-f63P,WL2'o)BI(r:e$^EhL4cajAPjh;[PZlKdg(mJQDPn,DhUnc&+Xo)SF\p&F^cp\aga qYpEmr;chJk,%2@VVV45$eDAOX4HF*aJ>TRk5CqOjrpA4U9*LLT:i:7k+BG_Wn6C*T:i:7k/aKo T:b8eW1]cTJ``lmj7Ij~> j7IoiJaJ$UJaN:#!:g$es8Doos82`js7uTfr:g6brUp'[qXa[VqXOIPq=.H2d1?lca86B<0YKmHnPe fBIkRVX j7IoiJaJ$UJaN:#!:g$es8Doos82`js7uTfr:g6brUp'[qXa[VqXOIPq j7IobJ`_OGJ`cdj!:9^a!<)ops82fl!;ZTgs7cNdrq6<`rq$-[rUKpWrU9aRr9aFKr9O:GqWS:K k2ta`hn`YLi$ALQ.;dN-o`+sj(&6h+KQE-d_oBpKf%]-Cjll!qm-X3>meuVSnGVnVo)A7\o`"O^ p&Ojcp]1-hqZ$HlquZirpjqSDjoDOGjoM.:rT4QhbN-gHk+B`Ud\Q$fTD[gRT>op(roOLJeu%T% b2gaHroO[Oeu%T(`7sD9k+]fKjalQkk2Z)a~> j7IoiJaJ$UJaN:#!:g$es8Doos82]i!;ZQfr:g6br:U![qXaUTqXOIPqX4aYmHa$"j/u'77H6uE 4#TaSoDegdru1n:rqcKdjMdUgh;RM]m-a?3o(qtUo_eC^pAF[_p]1-eqZ$Hkr;Z`q rW)YCmDnVoZN!&a!!59,GO4\tE81?Y%:eVhk[X!glL!tImHn?[m/^Y\rp:]2EaiHdm:#5Gk[a(d EaiHdmD=d(BVt16Mfi^1JaKB&j7Ij~> j7IoiJaJ$UJaN:#!:g$es8Doos82]i!;ZQfr:g6br:U![qXaUTqXOIPqX4aYmHa$"j/ks/2rcq' 1,DSGoDegdru1n:rqcKdjMdUgh;RM]m-a?3o(qtUo_eC^pAF[_p]1-eqZ$Hkr;Z`q rW)YCmDnVoZN!&a!!59,GO4\t2:DHj%3js%jWa(6l0[j[mHl"nm/\@/l>s'QiJaKB&j7Ij~> j7IobJ`_OGJ`cdj!:9^a!<)lo!;lcl!;ZTgs7cNdrq6<`rq$'Ys7--YrU9[PrpBXMr9O:Gq<84L k2k[`h5X$r-K?BV,VVm6oDegdru1n:rqcKdjMdUU3jqu-EorVlA=k/ZWaXoCHZ!!53'FR8,jVVV45%A;eEj.PP,jQ>Tsk2r<6 joM+8roP3AW1]cTk+C_sjIt_WW1]cTk0^T9UVc0@[Afq j7IoiJaJ$UJaN7"!;c]os8Doos82]i!;ZQfr:g6br:U![qXaUTqXOIPqrTsSn rTt/-`TY3Jm:!Kf[sIFQ`TY3Jrp9dRb%CY?!pn'AJaKB&j7Ij~> j7IoiJaJ$UJaN7"!;c]os8Doos82]i!;ZQfr:g6br:U![qXaUTqXOIPq j7IobJ`_OGJ`cai"8MlprVcWoqu6Elq>L*gp\smcp&F[`oDJ1Znc&%Wn,2VQmJQ>KlhfuCkm6J7 jPo+HMD$=9,pu j7IoiJaJ$UJaN7"!;c]os8Dln!;lWh!;ZTgr:g3arUp$Zr:BgVqXOIPqQ3(<(( /4i;i0/[U'R\QjhYIV'V^:CAGRuW>WF`N)&[_Ti(cI:D"hW!Y\lg=01nacVLqXjaZrq?9arV6Eg qt^C91k2@A.7Ucg@ZGC91k2@: j7IoiJaJ$UJaN7"!;c]os8Dln!;lWh!;ZTgr:g3arUp$Zr:BgVqXOIPqP1,nGY /3,XB/2V4#R\QjhYIV'V^:CAGRuW>WF`N)&[_Ti(cI:D"hW!Y\lg=01nacVLqXjaZrq?9arV6Eg qt^p"X!.!H6j[4 m/S7,m2Um4/<(b=++>M"_X3+0/<(b=+!2XeRI)(<+Zc$BmAK@3oRH~> j7IobJ`_OGJ`cai"8MlprVcWoqu-?kq>U0hp\smbo`4X`oDJ1Znc&%Wn,2VPmJQ>LlhfuCkm?S: jl>@Yd=s_3jWu?)1i8)L-iqu-EnrVhB1b2rgf!!#6ur;Qc[r1F7<:B1ATEollS%-5*tUW)BH Wh?BCk+DY8"eXr>^7h1ujoD%PjrD:6UW/qgT>7/#fB0uGUW/qgT:_dTal]4fTW#t`k,7A%oRH~> j7IoiJaJ$UJaN7"!:g'frr)fn!;lWh!;ZQfr:g6brUp$Zqt'^UqXOFOqX4g[mHj-$j2Q:&1I4Ru 8h0pfqu-Hlr;looJaL\K"L8"-J%tj[!8.*U"Y]oVEp)e?m=FZ^mGmhh~> j7IoiJaJ$UJaN7"!:g'frr)fn!;lWh!;ZQfr:g6brUp$Zqt'^UqXOFOqX4g[mHj-$j2Q:$/iG`U 3\*$?.Rm`bG'8+QG'\[bFaJOaGB7hIK9)k=^r+.6cI:D"hr j7IobJ`_OGJ`cai!:9^as8Duq!;l`k!;ZQf!r)E^r:L'^rU^$Zrpg!WrpTgRrU'OLr9O7Fq<87M kN1a`hnjIk-RS(("=GWf??#me$$:<9J9l?bI!^):Eu'=dTsVMK_T'aDdam.-i8`n\kj%L$mJZJP n,DhUnc&+Xo)SF\p&F^cp\addq>L-oqu$BjrVlSCk02rjXT/?N^\[s1kP:bS:B1B5iT5XQJ`bkP j7Ij~> j7IoiJaJ$UJaN7"!:g'frr)fn!;lWh!;ZQfr:g3arq6-[qt'^UqXOFOqX4p^mHs6(k2XL95qY2c 7nN8D01ec(4"i[)PEhN,R$gEkr;QWqrq:p1c0YTq!'e6nrrC^H@0Wob!!'"^m"+PTmG[HmoRH~> j7IoiJaJ$UJaN7"!:g'frr)fn!;lWh!;ZQfr:g3arq6-[qt'^UqXOFOqX4p^mHs6(k2XL95q=ZJ 3'$Ih/N5F93b";0pfqu-Hlr;lokJaL_L"$?QJ^\Ig/a7p_\Ndh$/WT`]"m=FZ`mGmhh~> j7IobJ`_OGJ`cai!:9^as8Duq!;l`k!;ZQf!r)E^r:L'^rU^$Zrpg!WrpTgRrU'OLr9O4EqW\%D #j17/i8);&5pttE/LDi)@$6_=S j7IoiJaJ$UJaN4!rVliqr;?Tlr;-HhrV6 j7IoiJaJ$UJaN4!rVliqr;?Tlr;-HhrV6 j7IobJ`_OGJ`c^hrr2rrrqufnrqcQf!;HHcrq-9`rU^!Yrpg$XrpTdQr9aFKr9O7FqWSCOkN:jb i7uJA?Tft.,n)+R7Xh#3Y-G=>[^imH\K/2l]Y2%o^qmn*`5]pBcI:=qg>1]HjlYdllg4$+r9jRQ s7$$XrUTpY!;-9`rUp6cr:p9fs82fns8;utnq#rKjoRRDJ+imBkP1\TkPtQI!,^TRJ`_OGio]:n J,~> j7IoiJaJ$UJaN4!!;c]os8Dlns82`js7uWgqtL-ar:U![qt'[TqsjLOqC*hqu$BlrVukJmEY,!!!'e,rrC^I@0!M0rrdfODWBu4m=FZbmGmhh~> j7IoiJaJ$UJaN4!!;c]os8Dlns82`js7uWgqtL-ar:U![qt'[TqsjLOq o)&%Vo_eC^pA=U_q#C0fqZ$Hjr;Z`qrdjI^mJd.d^\.U,a8$eWNrAt\IfOBHl%/5QmGmTooRH~> j7IobJ`_OGJ`c^h"ShuqrVc`ns82flr:p*281bL3L,o%aQ-SmegPc_6t[^iu\]Y;+q_8F1/rkfJi`lH3Ebg"J\e(<:-hVdDTkNM0r m/HDNmeuVSnG_tVo)A7\o`"O_pAambq#:-iqu6Nnr;chJk0E,h!!'e,rrD6XTE*=:rrdfOCY[p# k(2[Tk2Z)a~> j7IoiJaJ$UJaN4!!:g'fs8Dlns82]i!;ZTgqtL*`rUp'[qXaUTqXOIPq!TQtmHa$"jl,+M[>%jK 3&`cX5X7_*5[jm-a?3nac8I oDA1Yp&=[^p\jjeq>:$gqu$Blr;looJaLeN!^$Ito`"pDq-j1#"FgEGi95aZJaMOcj7Ij~> j7IoiJaJ$UJaN4!!:g'fs8Dlns82]i!;ZTgqtL*`rUp'[qXaUTqXOIPq!TQtmHa$"jl,+M[>%jJ 2)6mA2)I6N1GU^:0K21:S#a!-]"Geq`Q.u""3Sj4cMl/hckOpWe^rL.gYLfIjQ>[jm-a?3nac8I oDA1Yp&=[^p\jjeq>:$gqu$Blr;looJaLeN!^$Ito`"p3q&fM6"FgEGi95aZJaMOcj7Ij~> j7IobJ`_OGJ`c^h"RQ-erVc`ns82fl!;ZTgs7cNdrUg0_rq$*Zrpg!WrU9aRr9aFKr9O4Eq<8FR kN(X\hV?i7Z%Q7A1+apR,oS*V,pjuc.l9D/RAdL#\%0/e_8F71`Q#s?b5TT`bS&1Hd*gFof%Aa5 hr3SVkNM0qm/?>NmeuVRnG_tVo)A4\o_nI^pAamcp]1-hqZ$Hlr;Z]qpjqSRjoRRD^\%O+kP:eJ rrdfOS_E%Rk(2[Uk2Z)a~> j7IoiJaJ$UJaN4!!:g$err)ios82]is7uTfrV-2`!?`@XOQK]=bnp_SjI8bKeM`rR:er(=h!ff\"m2g=tH?i8`q]kNV9tmI0Q7rpopW qt0j[rq? j7IoiJaJ$UJaN4!!:g$err)ios82]is7uTfrV- j7IobJ`_OGJ`c^h!:9^a!<)lo!;lcl!;ZQf!;HKdrUg0_rU^$ZrUKpWrU9[Pr9aFKrTj=FqW\%D -g0XPiSWGDf@.U0Gu6ac1b'k(1,(OS@!S'A\@K5c^VRe*`lZHLcMc)hckXsVe'lgsf%8R-gYUiH ioK4akiqF"rpBXOrU9dUrUKmXrUU$]rUp0`!;HEd!;ZWj!;l`m!r`,hJ`b>A!d"FWoD\gRqOdi7 qYp[`!1_a%J`_OGjlYUqJ,~> j7IoiJaJ$UJaN0urVlfprqufnqtg?gr:p3arq??aqt0aVqssRQqXF=L..6Khlg!]qj58YFf@.I. O*PnjDKq2*U:J"P^qmn-aihoRd*^@nf@^)V!8IM,'AM:$gqu6NmrVuisp4;VYm/hJ3J+EU>f_T%OrrbSBe_o\Cm=FZemGmhh~> j7IoiJaJ$UJaN0urVlfprqufnqtg?gr:p3arq??aqt0aVqssRQqXF=L..6Khlg!]qj58YFf@.I. O*PnjDKq2*U:J"P^qmn-aihoRd*^@nf@^)V!8IM,'AM:$gqu6NmrVuisp4;VYm/hJ3J+EU>a8-nQrrbSBe_o\Cm=FZemGmhh~> j7IobJ`_OGJ`c[grr)orrVQZmrqZWjr:g9drq?<`s7H<^rpp$Xrp]pUr9jLMr9XCJq@XgtLB0da,LsN-BA`D0Cl"T=);C]Y2(s`Q-'BbKS8YdFA0G(Y7-fe^i@*g=tB;h;-rHioB.` kiqBtm/?>Lmf)\SnGVnVo)A7\o_nI^p&Ojbp]1-hq>gEkquZirnq#rNjoTW)J+EU>kPCkHrrbSA d+[N2k(2[Wk2Z)a~> j7IoiJaJ$UJaN0u!;c]orr)fns82`js7uTfr:g3ar:TsZqt'[TqsjLOq j7IoiJaJ$UJaN0u!;c]orr)fns82`js7uTfr:g3ar:TsZqt'[TqsjLOqfuq#:N4!+ja?l[eGSmH j7IobJ`_OGJ`c[g"ShuqrVc`n!;l`k!;ZTgs7cNdrq6<`rq$'Yrpg$Xr9sXQr9aCJr9O4EqWS^X kND!hio&\Jf[eNucHOJL_nj+(rk^,%`5KX7ai_fPd*gCpf%8R-g"Y9:gtUT=h;7#Gi8N\Uj5f@d kj%L"m/68MmelPRnG_tUo)A7\o`"O_pAamcp]1-gq>gElr;Z]qrdj4YjoGI3nc&UPqk*r8q#:N4 !+XF2jFQHEk3(meoRH~> j7IoiJaJ$UJaN0u!;6?jrr)fns82`jrqZNfqtL-ar:TsZqt'[TqsjLOq!\7N'(,#Mki_'fiSN;? f%&6sdEg(^rlbhrcd:.ke^rL.gYUlHiVqm:jT"C*hqu-HlrVukJmEt;%G6%.4!8.3X!3c+r"@)sTg#IYNJaMXfj7Ij~> j7IoiJaJ$UJaN0u!;6?jrr)fns82`jrqZNfqtL-ar:TsZqt'[TqsjLOq!\7N'(,#Mki_'fiSN;? f%&6sdEg(^rlbhrcd:.ke^rL.gYUlHiVqm:jT"C*hqu-HlrVukJmEt;%G6%.4!6>!Z!06dQ"@)sTg#IYNJaMXfj7Ij~> j7IobJ`_OGJ`c[g"S2QkrVc`n!;l`k!;ZTgs7cNdrq6<`rq$'Yrpg!WrU9aRr9aCJr9O4Eq<@qC ''JE;io&_MgXt'*dEp1`bfe,Mrl>\nb08/Wd*^=lf%Aa3gtgfChYuF3i;V[EioB(\jlYahl0@R" m-OZ;rpKaRrp]pWr:9jYs7H<`rq?BdrV-BgrV?Nkrqufp!<.Q3dH1IO!:^!gkPUtQch@AA5QI)! h1=^>k3(meoRH~> j7IoiJaJ$UJaN0u!:g$es8Dln!;lWh!;ZQfr:g3arUp'[qt'URqsjLOqX=FO&+/`Kl0%3jio&\K g=b*/ebdi*e^iF.gtgiEi8WeXjlZR+roa=F"6o+:lMg,LlileEmdK]9qXXOTqt0j[rq?9ar:g9f qYC3hr;?Tns8E#lJaLnQ!jVfUnG`L@r*f6t"9:.&h!'CWJaM[gj7Ij~> j7IoiJaJ$UJaN0u!:g$es8Dln!;lWh!;ZQfr:g3arUp'[qt'URqsjLOqX=FO&+/`Kl0%3jio&\K g=b*/ebdi*e^iF.gtgiEi8WeXjlZR+roa=F"6o+:lMg,LlileEmdK]9qXXOTqt0j[rq?9ar:g9f qYC3hr;?Tns8E#lJaLnQ!jVfUnG`L/r#bS2"9:.&h!'CWJaM[gj7Ij~> j7IobJ`_OGJ`c[g!:9^a!<)ops82fl!;ZTgrqHHdrUp3_rU^$Zrpg!WrU9^Qr9aFKqWn"Cqs"+D &Ei3:j5JqRgtLE3eC2jnd*V[9%+3PSf%8U/gYL`Di8ORms5O+?rT4%@s5sUMl0@U#m-OZ;rU0XQ rp]pWrpp$Zs7H9_s7ZHdrV-BgrqcZlrqlirrpkX&dcLV5!.XM j7IoiJaJ$UJaN-t!;c]os8Dln!;lWh!;ZQfr:g3ar:TpYqt'[Tq=47LqsP'`mHs9+lKIEmjQ#7X hVHucg'ufjg=tE>hr*JQj5f@croa@Hrp'OLs69dRm-O-,n,;\Rn+uVPoD8+Wp&=[_p\addq>0pf qu$BlrVuisp4;VZm/_D2n,EC?rF#]0o`#([!6 j7IoiJaJ$UJaN-t!;c]os8Dln!;lWh!;ZQfr:g3ar:TpYqt'[Tq=47LqsP'`mHs9+lKIEmjQ#7X hVHucg'ufjg=tE>hr*JQj5f@croa@Hrp'OLs69dRm-O-,n,;\Rn+uVPoD8+Wp&=[_p\addq>0pf qu$BlrVuisp4;VZm/_D2n,EC.r>u$"o`#([!6 j7IobJ`_OGJ`cXf"8MlprVcWoqu-?kq>C!hp\+=Yo`4X_oDJ1Znc&%Vn,2VOmJQ>Jlh]oCkn*(A kN:meio/hQh;$`:f@JLOeG[o'f%Jd2gtgiEi;Va9ireB(jQ-:$!p8\/rosIH"mP=;lg4!9mJQDN n,DhVnbhtVo)SF\p&F^cp\addq>L-iqu-EorVlA=k0iAmF9(e0!9a j7IoiJaJ$UJaN-t!:g'fs8Dln!;lWhs7uWgqtL*`rUp$Zqt'XSqXO@MqX5$amHs9+lKRNqk2k[b j5T"ShYuC?i8NYSj5]4^k32'prTjILs6U$WmI'E0mdBu@!V#XUo)&%To_nI^pA=U_q#C0dq>gEj r;Z`qrdjIam/[3:n,EC?rF, j7IoiJaJ$UJaN-t!:g'fs8Dln!;lWhs7uWgqtL*`rUp$Zqt'XSqXO@MqX5$amHs9+lKRNqk2k[b j5T"ShYuC?i8NYSj5]4^k32'prTjILs6U$WmI'E0mdBu@!V#XUo)&%To_nI^pA=U_q#C0dq>gEj r;Z`qrdjIam/[3:n,EC.r?(Y2"ht%tc.hH5m=FZhmGmhh~> j7IobJ`_OGJ`cXf"76$drVcWoqu-?kq>C!hp\+=Yp&F[`oDJ1Ync/+Wn,2VOmJQ>JlhKcBklBrH k6pG5jQ#7Xhqm2FgtM\Z$JO7hh;-rFi8N\Ur8mt@!9jCErTO.C"R>==m-OZ;r9jOPs7$$Xr:9jY s7H6^s7QHer:g9frV?NkrVQ]prdj4ZjoGI3n,ECNrLic^"ht%qaOT:$k(2[Zk2Z)a~> j7IoiJaJ$UJaN*srVlfprVZ]mr;-HhrV66`s7ZEaqXjUTqssRQp[J+LrpKgR#OLdBlKRNqk5OKB jSn3>j:CA7kND'ml0@U$m-X3.mdL,Br9j[Un,2YTnb_nQoD8+Xp&Fa^p\jmeq>C*hqu-HlrVuis p4;VZm/_D2mf*:>ra>f1o)AkY/]H5um"+PTmHX*!oRH~> j7IoiJaJ$UJaN*srVlfprVZ]mr;-HhrV66`s7ZEaqXjUTqssRQp[J+LrpKgR#OLdBlKRNqk5OKB jSn3>j:CA7kND'ml0@U$m-X3.mdL,Br9j[Un,2YTnb_nQoD8+Xp&Fa^p\jmeq>C*hqu-HlrVuis p4;VZm/_D2mf*:-rZ;-#o)AkY/]H5um"+PTmHX*!oRH~> j7IobJ`_OGJ`cUerr2rrrqu]j!;ZTgs7cNdrUg0_rU^!Yrpg!WrpTdQqX+7JqWmtBr9=1D#Nt73 jlGI]iVh[7hu2F2hZ2U5iWJ6%jlZR*!9jFHroj@GrosRLlK\B7s6TULr9jRQrp]pWrUTpY!;-6_ rq?BdrV-Bgr;-Ejs8;utnq#rOjoKQ(mf*:Mrh'8;o)AkY/]#`ejalQFk3D*hoRH~> j7IoiJaJ$UJaN*s!;c]orr)fns82]is7uTfr:g6bqt9jYq=FIRq=41Jr9sXQ$17*Glg!a!kih4/ kl0cFkl9oIlMp/Rm-X3.mdL,D!q5XIr:'URqXXIRqt0dYs7ZBbr:p j7IoiJaJ$UJaN*s!;c]orr)fns82]is7uTfr:g6bqt9jYq=FIRq=41Jr9sXQ$17*Glg!a!kih4/ kl0cFkl9oIlMp/Rm-X3.mdL,D!q5XIr:'URqXXIRqt0dYs7ZBbr:p j7IobJ`_OGJ`cUe"ShuqrVc`ns82fls7uZhs7cNdrUp3_rq$'Yrpg!WrU9[Pr9aCJqs4%BqW\%D #3Y.2jlPR`qr7V6!94"U6jqu6Nnr;chJk0`;lF9(b/"6Z>M\+0J%J,m1&f&D8CJ`c7[j7Ij~> j7IoiJaJ$UJaN*s!:g'frr)fns82Zh!;ZQfr:g3ar:TsZq=FIRq=41JqX=IPrpBaP!:0OI!pT"8 rTjIL!:BaQs6o^Oq=4=Pq=FLUr:U'_r:g-a!;ZKfs82cmrqulsp4;VYm/[3:mf*CA@:?A0rrp.; MTFB2JaJ$UlL!X(J,~> j7IoiJaJ$UJaN*s!:g'frr)fns82Zh!;ZQfr:g3ar:TsZq=FIRq=41JqX=IPrpBaP!:0OI!pT"8 rTjIL!:BaQs6o^Oq=4=Pq=FLUr:U'_r:g-a!;ZKfs82cmrqulsp4;VYm/[3:mf*C0+!6O5rrp.; MTFB2JaJ$UlL!X(J,~> j7IobJ`_OGJ`cUe"RQ-erVc`ns82ck!;ZWhrqHHdr:L'^rq$'Yrpg!WrU9[Pr9aCJqs4"AqW\%D roa=Dr8mk;rT4%@!9a=E!9s7Cqs4.Gr9aFMrU9aTrpfpWrpp-^r:U*`rqHEe!;ZTis82fns8;rg J`bAB!<@W;rr_GWT[30."ht%m_pR@mk(2[[k2Z)a~> j7IoiJaJ$UJaN'r!;c]orr)fn!;lWhs7uWgqtL*`r:TpYqt'XSp[RtHqX=IPs6TgQrpBRKr9aIN s6oaPp[S(Mqt'[Vr:U'_qtL'as7uNf!;l`mrr)lr!:bX-dHpr7J+!= j7IoiJaJ$UJaN'r!;c]orr)fn!;lWhs7uWgqtL*`r:TpYqt'XSp[RtHqX=IPs6TgQrpBRKr9aIN s6oaPp[S(Mqt'[Vr:U'_qtL'as7uNf!;l`mrr)lr!:bX-dHpr7J+!=<`ug?1rriC*_U@B/JaJ$U lg j7IobJ`_OGJ`cRd!r2corr)lp!;l`k!;ZQf!;HKdr:L'^rU]sXs7-*XrU9[Pr9a@Iqs3t@qW\%D qWIbL-kqu$Bkr;loaJ`bAB !<@W;rrVAVcgUl:!*?nZg4AC;k3D*hoRH~> j7IoiJaJ$UJaN'r!:g'frr)fn!;lWhs7uWgqtL*`r:TpYqt'XSp[RtHp[8+MrpBaRqsF@Mr9s@K p[S(Mqt'[Vr:U'_qtL'as7uNf!;l`mrqulsp4;VYm/jQn_!_4'fO=4J"TVW j7IoiJaJ$UJaN'r!:g'frr)fn!;lWhs7uWgqtL*`r:TpYqt'XSp[RtHp[8+MrpBaRqsF@Mr9s@K p[S(Mqt'[Vr:U'_qtL'as7uNf!;l`mrqulsp4;VYm/jQn_!_4'`ujaL"TVW j7IobJ`_OGJ`cRd!popcrr)lp!;l]jr:p j7IoiJaJ$UJaN$qrVliqr;?Tlqtg?gr:p3arq?<`q=OOTq==:MoC2VF"7GXGn,;\Rn,MeUn+6&C nbMbPoD/%Wp&=[_p\agdq>:$gqu$BlrVuisp4;VYm/jQn_!_4'fWX`C"[FTLakH!1m=FZjmGmhh~> j7IoiJaJ$UJaN$qrVliqr;?Tlqtg?gr:p3arq?<`q=OOTq==:MoC2VF"7GXGn,;\Rn,MeUn+6&C nbMbPoD/%Wp&=[_p\agdq>:$gqu$BlrVuisp4;VYm/jQn_!_4'a,]Tf"[FTLakH!1m=FZjmGmhh~> j7IobJ`_OGJ`cOcrr2rrrqlcnrV?NirV6Ees7ZB`s7H<^r:9jWrp]mTqsOCLqs=.Eo]l;;"R,+6 l0832rosIH!9s.@q j7IoiJaJ$UJaN$q!;c]os8Dlns82]is7uTfr:g0`r:TsZq"+=Pp@7G;!:T@Fp@7tLq"+CTr:U$^ r:g-as7uQgs82`ls8DusJaLhO!jVgAmf*:>mJd>T._!FMkCN#OmHa0"oRH~> j7IoiJaJ$UJaN$q!;c]os8Dlns82]is7uTfr:g0`r:TsZq"+=Pp@7G;!:T@Fp@7tLq"+CTr:U$^ r:g-as7uQgs82`ls8DusJaLhO!jVgAmf*:-mJd>T._!FMkCN#OmHa0"oRH~> j7IobJ`_OGJ`cOc"ShuqrVc`n!;l`k!;ZQfs7cNdrUg0_r:BmXrpg!WrU9UNrU'FIqWmY9qrn1G l07^&qWmtDrU'ILrU9aTrpfsXr:9p\rUp0`s7cHd!;ZTi!;lcns8;rsJ`b>A!j)IT .^[(@i.:$Ak3M0ioRH~> j7IoiJaJ$UJaN$q!:g$e!<)fm!;lZirqZNfqY1$`r:TsZq"+:Op[Qi(p[S%Lq"+CTr:U*`qY0s` rqZHf!;lZk"9&5up4;VXm/jQn^tJ_k5Us4]f&hPNJaMdjj7Ij~> j7IoiJaJ$UJaN$q!:g$e!<)fm!;lZirqZNfqY1$`r:TsZq"+:Op[Qi(p[S%Lq"+CTr:U*`qY0s` rqZHf!;lZk"9&5up4;VXm/jQn^tJ_k5Us4]f&hPNJaMdjj7Ij~> j7IobJ`_OGJ`cOc!:9^a!<)op!;l]j!;ZTgs7cNdr:L'^rU^!YrUKmVrU9XOrU'FIqWl_tqWmtD rU'LMrU9aTrUKjWrUU$]r:U'_s7cKe!;ZQh!;lcn!r`,hJ`b>A!j)I j7IoiJaJ$UJaN!p!;c]orr)fns82Zh!;ZQfqtL*`qt9jYq=F@Op@6r-p@7nJq=FLUqt9s^qtL$` !;ZKfs82cmrr)lrJaLeN!jVgAg&D4@;maHmk(2oNmHa0"oRH~> j7IoiJaJ$UJaN!p!;c]orr)fns82Zh!;ZQfqtL*`qt9jYq=F@Op@6r-p@7nJq=FLUqt9s^qtL$` !;ZKfs82cmrr)lrJaLeN!jVgAg&D4@;maHmk(2oNmHa0"oRH~> j7IobJ`_OGJ`cLb"ShuqrVc`ns82ck!;ZWhrqHHdr:U*^rq$$Xrpg!WrU9XOqX+1HpZpc&pZq_C qX+1JrU9aTrpfmVrq$0^r:U*`rqHEe!;ZTis82fns8;rsJ`b;@!j)I j7IoiJaJ$UJaN!p!:g$es8Dln!;lWhs7uQer:g3ar:TmXq=FCPo'tl3o'uMGq=FFSr:U'_r:g*` s7uNf!;l]ls8Dus!;;!2cg:e>!5R:f"TVK._pmh"m=FZjmGmhh~> j7IoiJaJ$UJaN!p!:g$es8Dln!;lWhs7uQer:g3ar:TmXq=FCPo'tl3o'uMGq=FFSr:U'_r:g*` s7uNf!;l]ls8Dus!;;!2cg:e>!5R:f"TVK._pmh"m=FZjmGmhh~> j7IobJ`_OGJ`cLb!:9^a!<)ops82ck!;ZQf!;HHcrUg0_rU]sXrpg!Wr9sLMqsF4GpZpu,pZqYA qsF7Jr9sXSrpfpWrUU$]rUp-_!;HEd!;ZTis82fn!r`,hJ`b;@!j)I j7IoiJaJ$UJaMso!;c]os8Dln!;lTg!;ZNeqtL-ar:TmXq=FCPo'tl3o'uMGq=FFSr:U*`qtL!_ !;ZHe!;l]ls8Dus!;;!2cKtW4^tJ_k!)p8Ce`MGMJaMdjj7Ij~> j7IoiJaJ$UJaMso!;c]os8Dln!;lTg!;ZNeqtL-ar:TmXq=FCPo'tl3o'uMGq=FFSr:U*`qtL!_ !;ZHe!;l]ls8Dus!;;!2cKtW4^tJ_k!)p8Ce`MGMJaMdjj7Ij~> j7IobJ`_OGJ`cIa"8MlprVcWoqu-?kq>C$fp\smbp&F[`oDJ1XnbqtUn,)PMmJ6,ClfRL,lhToE mJH>Mn,;bSnbhtWoDeI]p&=[bp\addq>L-iqu-EorVlA=k0E)i!5R:f"TVH*^sLtik(2[\k2Z)a~> j7IoiJaJ$UJaMso!;?Bjs8Dlns82]is7uTfqtL*`r:TmXp[e1Nn+#uIk(2oNmHa0"oRH~> j7IoiJaJ$UJaMso!;?Bjs8Dlns82]is7uTfqtL*`r:TmXp[e1Nn+#uIk(2oNmHa0"oRH~> j7IobJ`_OGJ`cIa!:p-gs8Drps82fls7uWgs7cNdrUp3_rU]sXrpg!Wr9sONqX+(Eo'?#7o'?); qX+1Jr9sXSrpfpWrU^']rUp0`s7cKes7uZjs82fns8DusJ`b5>!BNL"rs!uRI'^Z9hgsp@k3M0i oRH~> j7IoiJaJ$UJaMpnrVliqr;?Tlqtg j7IoiJaJ$UJaMpnrVliqr;?Tlqtg j7IobJ`_OGJ`cF`rr2rrrqufnrV?NirV6Eerq?9_s7H<^r:9gVrp]mTqsO=Jq!@P:q!.D8q!@kE qsOFOrp]mVr:9jYs7H6^rq?BdrV-BgrVHQkrqufp!WMY@k0<#h3WRn6#(Q]P[):>OJ`_OGlfR7" J,~> j7IoiJaJ$UJaMpn!;?Eks8DimrqlTh!;ZQfqY1!_r:TmXp[e+Lf(&J)p[e4Qr:U'_qY0p_!;ZNg rqlTjs8DusJaL\K!jMa@g]%HX:p%@PgZj@WJaMdjj7Ij~> j7IoiJaJ$UJaMpn!;?Eks8DimrqlTh!;ZQfqY1!_r:TmXp[e+Lf(&J)p[e4Qr:U'_qY0p_!;ZNg rqlTjs8DusJaL\K!jMa@g]%HX:p%@PgZj@WJaMdjj7Ij~> j7IobJ`_OGJ`cF`"S2QkrVc]m!;l`k!;ZTgrqHHdr:U*^rU]sXrUKmVr9sONq j7IoiJaJ$UJaMmm!;c]orr)fnrqlThs7uQer:g0`r:TjWq"+.Kh!t%-q"+:Qr:U$^r:g*`s7uQg rqlZlrr)lr!;;!2bj>Ju!'o?>#ClePYeJE=m"+PTmHa0"oRH~> j7IoiJaJ$UJaMmm!;c]orr)fnrqlThs7uQer:g0`r:TjWq"+.Kh!t%-q"+:Qr:U$^r:g*`s7uQg rqlZlrr)lr!;;!2bj>Ju!'o?>#ClePYeJE=m"+PTmHa0"oRH~> j7IobJ`_OGJ`cC_"ShuqrVc`ns82ck!;ZQf!;HHcrUp3_r:BjWrpg!Wr9sILq j7IoiJaJ$UJaMmm!:g$es8Dlns82]irqZKeqtL'_rUosXq"+.Kh!t%-q"+:QrUp-_qtL$`rqZHf s82`ls8Dus!;;!2b3]3i!8RSYJ,lOT_p[Iom=FZimGmhh~> j7IoiJaJ$UJaMmm!:g$es8Dlns82]irqZKeqtL'_rUosXq"+.Kh!t%-q"+:QrUp-_qtL$`rqZHf s82`ls8Dus!;;!2b3]3i!8RSYJ,lOT_p[Iom=FZimGmhh~> j7IobJ`_OGJ`cC_!:9^a!<)lo!;l]j!;ZTgs7cNdr:U*^rU]sXrUKmVr9sLMq j7IoiJaJ$UJaMjl!;c]orVc]ms82Zhs7uQer:g0`r:TgVq"+%Hkje36q"+7Pr:U$^r:g*`s7uNf s82cmrVccqJaLSH!jDZTh>[ZZ:oq4KfB@eQJaMaij7Ij~> j7IoiJaJ$UJaMjl!;c]orVc]ms82Zhs7uQer:g0`r:TgVq"+%Hkje36q"+7Pr:U$^r:g*`s7uNf s82cmrVccqJaLSH!jDZTh>[ZZ:oq4KfB@eQJaMaij7Ij~> j7IobJ`_OGJ`c@^"8MlprVcWoqu$9jq>9pep\smap&F[`oDA+WnbqtTn+uJImF^e#mJ?8Kn,;bS nb_nVoDeI\p&=Xbp\X^cq>C'hqu-EnrVhB1alWZ)!.`o*"oqGu[D^JOJ`_OGlK7.!J,~> j7IoiJaJ$UJaMgkrVlfpr;6Qlq>(*eqtU'_rV$-]q"47NnFH/?nFH8Dq"4IVrV$-_qtL0eq>(*g r;?Qms8E#lJaLSH"7'n+^u,.sJ,lIO^WtX"JaJ$UlL!X(J,~> j7IoiJaJ$UJaMgkrVlfpr;6Qlq>(*eqtU'_rV$-]q"47NnFH/?nFH8Dq"4IVrV$-_qtL0eq>(*g r;?Qms8E#lJaLSH"7'n+^u,.sJ,lIO^WtX"JaJ$UlL!X(J,~> j7IobJ`_OGJ`c=]rr2rrrqufnrV?Nir:p j7IoiJaJ$UJaMgk!;?Ekrr)cm!;lTgs7uTfqY0s^r:TjWp%-Q$p%.tNr:U$^qY0p_s7uKe!;l]l rqulsqLS%Sm/lDM5iDYIJ,iEI\&QtXJaJ$Ul0[O'J,~> j7IoiJaJ$UJaMgk!;?Ekrr)cm!;lTgs7uTfqY0s^r:TjWp%-Q$p%.tNr:U$^qY0p_s7uKe!;l]l rqulsqLS%Sm/lDM5iDYIJ,iEI\&QtXJaJ$Ul0[O'J,~> j7IobJ`_OGJ`c=]"S2QkrVc]m!;l`k!;ZQfs7cKcr:L'^rU]pWrUKgTrU9RMp?gl+p?h\DrU9[R rUKdUrUU$]r:U$^s7cHd!;ZTi!;l`ms8;rmJ`b#8!oEuCi;X%4!)KT$_9gqfk(2[Zk2Z)a~> j7IoiJaJ$UJaMdj!;c]orr)cms82Zhs7uTfqY1!_qt9^Uo^gZ)o^hhLqt9s^qY0p_s7uNfs82`l rr)lr!:k^.a6`upWW7S/rs6C?:8bD4cJ7Z8m=FZhmGmhh~> j7IoiJaJ$UJaMdj!;c]orr)cms82Zhs7uTfqY1!_qt9^Uo^gZ)o^hhLqt9s^qY0p_s7uNfs82`l rr)lr!:k^.a6`upWW7S/rs6C?:8bD4cJ7Z8m=FZhmGmhh~> j7IobJ`_OGJ`c:\!r2corr)lprql]k!;ZQfs7cNdr:U*^rU]mVrpfsVqX=7Jna5o6na6/?qX=CP rpfjUrU^']r:U'_s7cHd!;ZWjrql`nrqulsm"+<>joa\?!.a)/#f$F7V6@MQf&M>DJ`c4Zj7Ij~> j7IoiJaJ$UJaMdj!:g$es8Dlns82Wg!;ZQfq=jm^r:TgVo^gZ)o^hhLr:U'_q=jg^!;ZHes82`l s8Dus!;;!2`U*cjD#f+Hrs(giUoq;Nf&D8JJaM[gj7Ij~> j7IoiJaJ$UJaMdj!:g$es8Dlns82Wg!;ZQfq=jm^r:TgVo^gZ)o^hhLr:U'_q=jg^!;ZHes82`l s8Dus!;;!2`U*cjD#f+Hrs(giUoq;Nf&D8JJaM[gj7Ij~> j7IobJ`_OGJ`c:\!:9^as8Drps82ck!;ZTgrqHHdqt:!]rq$!Wr:0aTqsXCLna5o6na62@qsXLQ r:0XSrq$0^qt:!_rqHBd!;ZTis82fns8;utnq#rBjoaOV!.a,0#=/?aX1#sngk"U=k31sfoRH~> j7IoiJaJ$UJaMai!;?EkrVc]ms82Zhs7uQeqY0s^qXsXUo(1f1o(2YKqXsg\qY0m^s7uNfs82cm rVccqJaLDC"RP/,!5R^r#XJH:XLH-rhWf[ZJaM[gj7Ij~> j7IoiJaJ$UJaMai!;?EkrVc]ms82Zhs7uQeqY0s^qXsXUo(1f1o(2YKqXsg\qY0m^s7uNfs82cm rVccqJaLDC"RP/,!5R^r#XJH:XLH-rhWf[ZJaM[gj7Ij~> j7IobJ`_OGJ`c7["S2QkrVc]m!;l]j!;ZQfs7cNdr:U*^rU]pWr:0^SqsX:Ih!Ok(qsXIPr:0[T rU^']r:U'_s7cHd!;ZQh!;l`ms8;rsJ`ao5"QeDq!5R^r#XJH8WO'Cbf].PFJ`c1Yj7Ij~> j7IoiJaJ$UJaM^hrVlfpr;?Qkqtg?gqtU$^rV$*\p@RhFm.0W:p@S4SrV$*^qtU3eqtg j7IoiJaJ$UJaM^hrVlfpr;?Qkqtg?gqtU$^rV$*\p@RhFm.0W:p@S4SrV$*^qtU3eqtg j7IobJ`_OGJ`c4Zrr2rrrqufnrV?NiqtL0crV$-]!;-0[qss^UrUBUNp$V&2p$VVDrUBdUqssXU !;-0]rUp6cqtL0erVHQkrqufp!WMY@k/6^d.rrW3EqVe`)/BJ`c.Xj7Ij~> j7IoiJaJ$UJaM^h!:g$es8Dims82]irqZKeq=jj]qt9[Tn+5o:n+68Fqt9p]q=jg^rqZHfs82]k s8Dus!;;!2_X.Nnk1jSLJEm.@^]4@\X0fUaf].PMJaMUej7Ij~> j7IoiJaJ$UJaM^h!:g$es8Dims82]irqZKeq=jj]qt9[Tn+5o:n+68Fqt9p]q=jg^rqZHfs82]k s8Dus!;;!2_X.Nnk1jSLJEm.@^]4@\X0fUaf].PMJaMUej7Ij~> j7IobJ`_OGJ`c4Z!:9^a!<)lo!;l]js7uWgrqHHdqXjj\r:BdUrUKgTqX=+FkjA'2qX=@OrUKaT r:9p\qXsm^rqHBds7uTh!;l`m!r`,hJ`ai3"m+XmAH78Grs?I@!-kHH]#W7SJ`_OGk2t^rJ,~> j7IoiJaJ$UJaM[g!;c]orVc]ms82Wgs7uQeq=jj]qXsORd.?r&qXsg\q=jd]s7uKes82cmrVccq JaL8?#41I+B)hp^mJdMZ!%3,q[D0o:iTu-_JaMUej7Ij~> j7IoiJaJ$UJaM[g!;c]orVc]ms82Wgs7uQeq=jj]qXsORd.?r&qXsg\q=jd]s7uKes82cmrVccq JaL8?#41I+B)hp^mJdMZ!%3,q[D0o:iTu-_JaMUej7Ij~> j7IobJ`_OGJ`c1Y!r2corr)ios82ck!;ZNes7cNdqt0s]qt'^Ur:0^Sq j7IoiJaJ$UJaMXf!;?Bjrr)cms82Wgs7uQeq=jj]qXsIPf^n_,qXsg\q=jd]s7uKes82`lrr)lr !;_96^$Psgjk\EsrVutL_"ds0^^NL/!%<8t[(OE*g#D:Jm=FZcmGmhh~> j7IobJ`_OGJ`c.X!:p-g!<)los82`j!;ZNe!;HKdqt:!]r:BaTqsjURq![//q!\.MqsjLQr:Bs\ qt9s^!;HBc!;ZQhs82cm!r`,nJ`aZ."QeOmc=6bQ!J%u\s8TkC5l^m5I&aH]_9:?_jFQHEk2b[b oRH~> j7IoiJaJ$UJaMUe!;?BjrVc]ms82Wgs7uQeq=jg\q=X:Miq)^4q=X[Zq=jd]s7uKes82cmrVc`p JaL):#41L0f$.W/rW)sardau6rr j7IoiJaJ$UJaMUe!;?BjrVc]ms82Wgs7uQeq=jg\q=X:Miq)^4q=X[Zq=jd]s7uKes82cmrVc`p JaL):#41L0f$.W/rW)sardau6rr j7IobJ`_OGJ`c+W!:p-gs8DrprqlZj!;ZQfs7cKcqt:!]r:BaTr:0[Ro'c)9o'cJFr:0URr:Bs\ qt9p]s7cHd!;ZTirql]ms8DusJ`aT,#3Fdrd)]QurW)sardau6rr j7IoiJaJ$UJaMRd!;cZnrr)cm!;lTgrqZKeq"O^[qXsCNiq)^4qXsd[q"O^]rqZBd!;l]lrr)iq !:k^.]'Tjkki1@GbJ]EF/1gc#r[Rqu$EpeE]>;S8f\blYJaJ$UipGduJ,~> j7IoiJaJ$UJaMRd!;cZnrr)cm!;lTgrqZKeq"O^[qXsCNiq)^4qXsd[q"O^]rqZBd!;l]lrr)iq !:k^.]'Tjkki1@GbJ]EF/1gc#r[Rqu$EpeE]>;S8f\blYJaJ$UipGduJ,~> j7IobJ`_OGJ`c(V"ShuqrVc]ms82ck!;ZQfrqHEcqt9s\rU]jUq=4@Oo'c;?o'cJFq=4:OrU^$\ qt9s^rqH?c!;ZTis82cms8;utm"+<1jpL5/gXXTi^l)AI/,fPJ.1CWSXKSt>]u/%Ag#1tCk(2[S k2Z)a~> j7IoiJaJ$UJaMOc!;c]orr)`ls82Wgs7uNdq=jg\q=X%Fp@ISAq=X[Zq=ja\s7uKes82]krr)lr !;;!2\*Y(!k2G(FccF#=]XkV^[^WfZ^;@n6db*F;l[eGSmGRBloRH~> j7IoiJaJ$UJaMOc!;c]orr)`ls82Wgs7uNdq=jg\q=X%Fp@ISAq=X[Zq=ja\s7uKes82]krr)lr !;;!2\*Y(!k2G(FccF#=]XkV^[^WfZ^;@n6db*F;l[eGSmGRBloRH~> j7IobJ`_OGJ`c%U"ShuqrVc]m!;l]j!;ZKd!;HHcqt9s\r:BaTqXOCNi:$L2qXOCPr:Bp[qt9p] !;H?b!;ZQh!;l`ms8;utnq#r4jr*:=gXXWm`4rpo[^EHJ['dBS^;@q8db*F;J`_OGi9'(lJ,~> j7IoiJaJ$UJaMOc!:g$err)`ls82Wgs7uKcq=jg\p@Zl+p@\@Wq=j^[s7uKes82]krr)lr!;;!2 [-\Uok2P4Le^;[cb/hZGbKS>^fA, j7IoiJaJ$UJaMOc!:g$err)`ls82Wgs7uKcq=jg\p@Zl+p@\@Wq=j^[s7uKes82]krr)lr!;;!2 [-\Uok2P4Le^;[cb/hZGbKS>^fA, j7IobJ`_OGJ`c%U!:9^arr)ios82cks7uQes7cHbr:U*^qXaORqXO@MkjS<9qXOCPqXaaZr:U!] s7cEcs7uWis82fnrqulsnq#r1jqZt8gt1$$bfRoE`5BO5a32cVf%T!@J`_OGhWEkjJ,~> j7IoiJaJ$UJaMLb!:g$es8Dfl!;lTgrV??cq"O[Zq"<)-q"=OXq"O[\rV?9c!;lWjs8Dus!;;!2 Z0`.hkiLj_h:pZ:g"P39hr3Y[m"+PTmG.*hoRH~> j7IoiJaJ$UJaMLb!:g$es8Dfl!;lTgrV??cq"O[Zq"<)-q"=OXq"O[\rV?9c!;lWjs8Dus!;;!2 Z0`.hkiLj_h:pZ:g"P39hr3Y[m"+PTmG.*hoRH~> j7IobJ`_OGJ`c"T!:9^as8Doo!;l]js7uTfrqHBbqt9s\r:BaTp[RnFp@%YCp[S(Mr:Bp[qt9p] rqH?cs7uTh!;l`ms8;utnq#r.jq6_6hqZo:f$r0teCE.'gYUrOJ`_OGgudYhJ,~> j7IoiJaJ$UJaMIa!;?Bjrr)`ls82WgrqZEcp\4UZp%@,4p%A7Vp\4OZrqZBds82]krr)lr!;_96 XmHP`lg!]sk2tjklK[a&JaJ$Ug?mqmJ,~> j7IoiJaJ$UJaMIa!;?Bjrr)`ls82WgrqZEcp\4UZp%@,4p%A7Vp\4OZrqZBds82]krr)lr!;_96 XmHP`lg!]sk2tjklK[a&JaJ$Ug?mqmJ,~> j7IobJ`_OGJ`btS!:p-gs8Doo!;lZi!;ZNes7cKcqXsj[qXaLQp[RD8p[S%LqXa^YqXsg\s7cEc !;ZNg!;l`ms8;utpjqS0joa`+j5Ksp"QJJ%jlM'UJ`b\Kj7Ij~> j7IoiJaJ$UJaMF`!;?BjrVcZlrqlNfs7uHbq=jd[nb), j7IoiJaJ$UJaMF`!;?BjrVcZlrqlNfs7uHbq=jd[nb), j7IobJ`_OGJ`bqR!:p-grr)ios82`j!;ZKds7cKcqXsm\q"+4Mp[RbBp[RtJq"+OXqXsg\s7cBb !;ZQhs82fnrqulspjqRYk(2ZGk4n*!oRH~> j7IoiJaJ$UJaMC_!;?EkrVcZlrqlKes7uKcp\4RYoC_>>oC`"Sp\4LYs7uHdrqlWkrVccq!:k^. JaJ$UJaN7"j7Ij~> j7IoiJaJ$UJaMC_!;?EkrVcZlrqlKes7uKcp\4RYoC_>>oC`"Sp\4LYs7uHdrqlWkrVccq!:k^. JaJ$UJaN7"j7Ij~> j7IobJ`_OGJ`bnQ!qQ?irr)ios82`js7uQes7cKcq=XaZqXaFOh!t+/qXa^Yq=X^[s7cEcs7uTh s82fnrqulsm"+;Mk(2ZGk4n*!oRH~> j7IoiJaJ$UJaM@^!;c]orVcWks82Tfs7uHbp\4OXde3J1p\4IXs7uHds82]krVccq!:k^.JaJ$U JaN4!j7Ij~> j7IoiJaJ$UJaM@^!;c]orVcWks82Tfs7uHbp\4OXde3J1p\4IXs7uHds82]krVccq!:k^.JaJ$U JaN4!j7Ij~> j7IobJ`_OGJ`bkP!r2corVccos82]i!;ZNerqH?aqXsj[q=F.Im.'W:q=FUXqXsd[rqH j7IoiJaJ$UJaM=]!;c]orr)]k!;lNerqZEcp%S=Vf(Jn5p%S=XrqZ j7IoiJaJ$UJaM=]!;c]orr)]k!;lNerqZEcp%S=Vf(Jn5p%S=XrqZ j7IobJ`_OGJ`bhO!r2corr)io!;lWh!;ZQfrV-9aq=XaZqXa7Jm.'W:qXa^Yq=X^[rV-6b!;ZKf !;lcnrqulsnq#r2k2\Q'XF]Mbk2>C^oRH~> j7IoiJaJ$UJaM=]!:g!drr)`ls82TfrV?9ao_81Ti:Zp>o_81VrV?6bs82]krr)iq!;;!2[-[hn p^ZaW!<@:om=FZ_mGmhh~> j7IoiJaJ$UJaM=]!:g!drr)`ls82TfrV?9ao_81Ti:Zp>o_81VrV?6bs82]krr)iq!;;!2[-[hn pcIq/!<@:om=FZ_mGmhh~> j7IobJ`_OGJ`bhO!:9^arr)fn!;lZi!;ZKdrqHBbq"=XYq"*)-q"+LWq"=UZrqH9a!;ZNg!;l`m rqulsnq#r1jo>DV%duU_!-3YQJ`bnQj7Ij~> j7IoiJaJ$UJaM:\!:g!drr)`lrVQBds7uEap%S4Skk4]Dp%S4Us7uHdrVQKirr)iq!;;!2Zg@bn *VhbbrrE+CJaJ$Ui9fRsJ,~> j7IoiJaJ$UJaM:\!:g!drr)`lrVQBds7uEap%S4Skk4]Dp%S4Us7uHdrVQKirr)iq!;;!2Zg@bn GPU^rrrE+CJaJ$Ui9fRsJ,~> j7IobJ`_OGJ`beN!:9^arVc`nrqlZjrqZHds7cEaq=XaZo^h)5o^i(Sq=XXYs7cEcrqZNhrql]m rVZcrnq#r0joGH&q)\!E!<@.kk(2[Qk2Z)a~> j7IoiJaJ$UJaM7[!:g!ds8DflrqlHdrqZBbnb:i4nb;kSrqZ j7IoiJaJ$UJaM7[!:g!ds8DflrqlHdrqZBbnb:i4nb;kSrqZ j7IobJ`_OGJ`bbM!:9^arr)fns82]i!;ZNerV-9ap\"LWp[dD8p[e@Up\"LYrV-3a!;ZNgs82cm rqulsnq#r/joGH&q)\!E!<@.kk(2[Qk2Z)a~> j7IoiJaJ$UJaM4Z!V-$brr2lmr;QTfqYpp@J%Pq#:*`qYpBgr;QZorW)YCmBQ$Z!$1kr n,ECgG(K%3mGRBloRH~> j7IoiJaJ$UJaM4Z!V-$brr2lmr;QTfqYpp@J%Pq#:*`qYpBgr;QZorW)YCmBQ$Z!-e8- n,ECgG(K%3mGRBloRH~> j7IobJ`_OGJ`b_L!UTU\rr;rqr;QTkqZ$Bfq#:$cp@n7Zo^;>Go'Z,Ko_8%VpAXg_q#C0fqYpBj r;Z`qrW)M?k-=%L!$1krn,ECgEe3A(k2>C^oRH~> j7IoiJaJ$UJaM.X!;c]orr)Zjs82NdrV?6`lhBuDlhC2LrV?0`s82Wirr)lr!:k^.YjDGk*Vhbb rrE+CJaJ$Ui9fRsJ,~> j7IoiJaJ$UJaM.X!;c]orr)Zjs82NdrV?6`lhBuDlhC2LrV?0`s82Wirr)lr!:k^.YjDGkGPU^r rrE+CJaJ$Ui9fRsJ,~> j7IobJ`_OGJ`bYJ"ShuqrVcZls82]i!;ZKdr:g3ap%A4SoCMbJoCMkOp%A=Xr:g'_!;ZNgs82`l s8;utm"+<'joGH&q)\!E!<@.kk(2[Qk2Z)a~> j7IoiJaJ$UJaM+W!;?BjrVcWks82KcrqZ9_bkLi+rqZ6`s82]krVccq!W)ApW!;R/o5lgt/ JaJ$Ui9fRsJ,~> j7IoiJaJ$UJaM+W!;?BjrVcWks82KcrqZ9_bkLi+rqZ6`s82]krVccq!W)ApW!;SbG5lgt/ JaJ$Ui9fRsJ,~> j7IobJ`_OGJ`bVI!qQ?irr)fns82]is7uNdrV-3_p%A7Tiq**?p%A4UrV-0`s7uQgs82cmrqulm m"+<&k5PGW&+;db!-3YQJ`bnQj7Ij~> j7IoiJaJ$UJaM(V!;?Bjr;HNjrqlBbrqZ3]f(\h3rqZ6`rqlTjr;HZp!;_96XmP12!-Wq\JaMC_ j7Ij~> j7IoiJaJ$UJaM(V!;?Bjr;HNjrqlBbrqZ3]f(\h3rqZ6`rqlTjr;HZp!;_96XmP12!-Wq\JaMC_ j7Ij~> j7IobJ`_OGJ`bSH!:p-grr)fns82]is7uKcrV-0^o_&%Po(2\Lo_&(SrV--_s7uQgs82cmrquls pjqS0k2\N&Ee3A(k2>C^oRH~> j7IoiJaJ$UJaM%U!;?BjrVcTjs82Kcr;$']f(\n5r;$$^s82ZjrVccq!;;!2JaJ$UJaMjlj7Ij~> j7IoiJaJ$UJaM%U!;?BjrVcTjs82Kcr;$']f(\n5r;$$^s82ZjrVccq!;;!2JaJ$UJaMjlj7Ij~> j7IobJ`_OGJ`bPG!:p-gs8Dln!;lTg!;ZKdr:g*^nb), j7IoiJaJ$UJaM"T!:g!drr)ZjrVQ j7IoiJaJ$UJaM"T!:g!drr)ZjrVQ j7IobJ`_OGJ`bMF!:9^as8Dims82Zh!;ZHcr:g$\de3G0r:g$^!;ZKfs82]ks8;utnq#qSk(2ZG k3V6joRH~> j7IoiJaJ$UJaLqR!;cZnqu-Eirql?arV=V2rV?*^rqlTjqu-Nn!;;!2JaJ$UJaMdjj7Ij~> j7IoiJaJ$UJaLqR!;cZnqu-Eirql?arV=V2rV?*^rqlTjqu-Nn!;;!2JaJ$UJaMdjj7Ij~> j7IobJ`_OGJ`bGD!;QQmrr)fnrqlThrV?6`rqH*Zi:Zd:rqH3_rV??erqlZlrr)lr!:k^'J`_OG J`c:\j7Ij~> j7IoiJaJ$UJaLnQ!;?Bjr;HKis82B`qt\\8qt]gZs82Zjr;HWo!:k^.JaJ$UJaMaij7Ij~> j7IoiJaJ$UJaLnQ!;?Bjr;HKis82B`qt\\8qt]gZs82Zjr;HWo!:k^.JaJ$UJaMaij7Ij~> j7IobJ`_OGJ`bDC!:p-gs8Dim!;lTgs7uHbqY0gZi:Zp>qY0d[s7uKe!;lZks8Dus!:5:!J`_OG J`c7[j7Ij~> j7IoiJaJ$UJaLkP!;??irVcQirVQ-]rqY.?rqZ*\rVQEgrVc`p!W)A j7IoiJaJ$UJaLkP!;??irVcQirVQ-]rqY.?rqZ*\rVQEgrVc`p!W)A j7IobJ`_OGJ`bAB!:p-grr)cmrqlQgrqZ9_rV,mVo(DSIrV-$\rqZEerqlWkrquotq! j7IoiJaJ$UJaLhO!;??irr)Wirql6^q"`qEq"aIVrqlKgrr)iq!;_96JaJ$UJaMXfj7Ij~> j7IoiJaJ$UJaLhO!;??irr)Wirql6^q"`qEq"aIVrqlKgrr)iq!;_96JaJ$UJaMXfj7Ij~> j7IobJ`_OGJ`b>A!:p-gs8Dfl!;lQfs7uEaq=jUVo(D_Mq=jXYs7uHd!;lWjs8;utpjqRYk(2ZG k3(meoRH~> j7IoiJaJ$UJaLbM!;?Bjr;HEgrql-[q>'CPq>'ITrqlKgr;HZp!W)A j7IoiJaJ$UJaLbM!;?Bjr;HEgrql-[q>'CPq>'ITrqlKgr;HZp!W)A j7IobJ`_OGJ`b8?!:p-grr)cmrqlNfs7u<^q=j%Fq=jOVs7uKerqlWkrquotq! j7IoiJaJ$UJaL_L!VZBgrqu`ir;6BWqX"%DqYU0br;?NmrW)YCm=FYUm=FZcmGmhh~> j7IoiJaJ$UJaL_L!VZBgrqu`ir;6BWqX"%DqYU0br;?NmrW)YCm=FYUm=FZcmGmhh~> j7IobJ`_OGJ`b5>"7l6drr)fmr;HNhqZ$B[pu_>>q#C0dqYg j7IoiJaJ$UJaLYJ!;?BjrVcHfrVPjUnG2>FrVQ j7IoiJaJ$UJaLYJ!;?BjrVcHfrVPjUnG2>FrVQ j7IobJ`_OGJ`b/ j7IoiJaJ$UJaLSH!;??irVcHfr;4S3r;63crVc`p!W)A j7IoiJaJ$UJaLSH!;??irVcHfr;4S3r;63crVc`p!W)A j7IobJ`_OGJ`b):!:p-grr)]krqlKerV=h8rV?6brqlQirquotq! j7IoiJaJ$UJaLPG!;??iqYg3eq>8V:q>9sbqYgEm!;;!2JaJ$UJaM@^j7Ij~> j7IoiJaJ$UJaLPG!;??iqYg3eq>8V:q>9sbqYgEm!;;!2JaJ$UJaM@^j7Ij~> j7IobJ`_OGJ`b&9!:p*frVcWkrVQ?crV>+@rV?3arVQKirVccq!:k^'J`_OGJ`bkPj7Ij~> j7IoiJaJ$UJaLJE!;? j7IoiJaJ$UJaLJE!;? j7IobJ`_OGJ`au7!:p-grr)Zjs82EarV>CHrV?']s82Wirquotq! j7IoiJaJ$UJaLDC!;??iqYg*bq"s(Iq"sa^qYgBl!:k^.JaJ$UJaM7[j7Ij~> j7IoiJaJ$UJaLDC!;??iqYg*bq"s(Iq"sa^qYgBl!:k^.JaJ$UJaM7[j7Ij~> j7IobJ`_OGJ`ao5!:p-gr;HNjr;6-_q>'OTq>'[Zr;6Bhr;HZp!:5:!J`_OGJ`bbMj7Ij~> j7IoiJaJ$UJaLAB!;?9grVc9ar;5LMr;6$^rVcZn!;;!2JaJ$UJaM1Yj7Ij~> j7IoiJaJ$UJaLAB!;?9grVc9ar;5LMr;6$^rVcZn!;;!2JaJ$UJaM1Yj7Ij~> j7IobJ`_OGJ`al4!:p*frr)Wis820ZnG2GIs82Thrr)lr!:k^'J`_OGJ`b\Kj7Ij~> j7IoiJaJ$UJaL;@s7Z?hqu-'_i;<'Bqu-Km!W)A j7IoiJaJ$UJaL;@s7Z?hqu-'_i;<'Bqu-Km!W)A j7IobJ`_OGJ`af2s76-frVcQir;4q=r;6 j7IoiJaJ$UJaL2=!;? j7IoiJaJ$UJaL2=!;? j7IobJ`_OGJ`a]/!:p*fqu-?gq>9+Hq>:!cqu-Qo!Vl#4k(2ZGk(2[Gk2Z)a~> j7IoiJaJ$UJaL,;s7ZBiq>JP8q>L9k!VZ)8m=FYUm=FZSmGmhh~> j7IoiJaJ$UJaL,;s7ZBiq>JP8q>L9k!VZ)8m=FYUm=FZSmGmhh~> j7IobJ`_OGJ`aW-s760grVcEerVPOLrVQ9crVccq!V5T.k(2ZGk(2[Ek2Z)a~> j7IoiJaJ$UJaL&9!V5s\rqQHCr:p6grW2ldJaJ$UJaJ$UdI#udJ,~> j7IoiJaJ$UJaL&9!V5s\rqQHCr:p6grW2ldJaJ$UJaJ$UdI#udJ,~> j7IobJ`_OGJ`aQ+!UTCTrr)ffr8RVGr;HTnrW2f\J`_OGJ`_OGdH9K]J,~> j7IoiJaJ$UJaKr6!;?9gnbq\LnbrCb!W)A j7IoiJaJ$UJaKr6!;?9gnbq\LnbrCb!W)A j7IobJ`_OGJ`aH(!:p'eq#0g^o)&%Vq#13k!Vl#4k(2ZGk(2[@k2Z)a~> j7IoiJaJ$UJaKl4s7Z6epAO4QpAOje!W)A j7IoiJaJ$UJaKl4s7Z6epAO4QpAOje!W)A j7IobJ`_OGJ`aB&s76'dr;G4Er;HTn!Vl#4k(2ZGk(2[=k2Z)a~> j7IoiJaJ$UJaKc1!qQ'Yq>T%Fqu6Wl!:k^.JaJ$UJaLSHj7Ij~> j7IoiJaJ$UJaKc1!qQ'Yq>T%Fqu6Wl!:k^.JaJ$UJaLSHj7Ij~> j7IobJ`_OGJ`a9#!poLKr;QEgkkt/Qrr2rm!:5:!J`_OGJ`b):j7Ij~> j7IoiJaJ$UJaKW-!qQ'Yp&=1Rp\k6jp@ j7IoiJaJ$UJaKW-!qQ'Yp&=1Rp\k6jp@ j7IobJ`_OGJ`a,t!poLKqu6Kkkkt>VrVclno'CqiJ`_OGJ`ar6j7Ij~> j7IoiJaJ$UJaKH(s7Y1G!rDW]JaJ$UJaJ$U_ j7IoiJaJ$UJaKH(s7Y1G!rDW]JaJ$UJaJ$U_ j7IobJ`_OGJ``ros76!bkl(DX!r2?QJ`_OGJ`_OG_<0eMJ,~> j7IoiJaJ$UJaK<$s7ZHgkl1PWs71a.JaJ$UJaL/ j7IoiJaJ$UJaK<$s7ZHgkl1PWs71a.JaJ$UJaL/ j7IobJ`_OGJ``fks760akl1PUs6P=!J`_OGJ`aZ.j7Ij~> j7IoiJaJ$UJaK#qrpp$]qtg j7IoiJaJ$UJaK#qrpp$]qtg j7IobJ`_OGJ``Ncrp9USqtU0]rp53uJ`_OGJ`a?%j7Ij~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$U\*X4__Y j7IoiJaJ$U\*X4]FSG/'FR"GEJaJ$UJaJ$UQ0mW(J,~> j7IobJ`_OG\)m_NAbY j7IoiJaJ$Ukj8N=ag/+qipH`Hp[&"4ci3;;lc??4XR5E$eq*4>m=FYUm=FYimGmhh~> j7IoiJaJ$Ukj8N:LIFFoipH_'p[&!_RJuRZkCuD`2XTn"XF]bim=FYUm=FYimGmhh~> j7IobJ`_OGkiN$-Gr%%1io^4bpZ;LMN;hrFiH[7),Ne-ITn2?Wk(2ZGk(2Z[k2Z)a~> j7IoiJaJ$Ukj8Mr^ust(i9^NGp[&!si;W*Fb.-L1m/c+[JaJ$UJaJ$UJaJ`ij7Ij~> j7IoiJaJ$Ukj8M3Dqj6,i9^M&p[%uua8YH.M-83lrp9a+D1V)*m=FYUm=FYimGmhh~> j7IobJ`_OGkiN"s@+*bTi8t"apZ;K_^&I-rHq26=roO6o?@h6ik(2ZGk(2Z[k2Z)a~> j7IoiJaJ$Ukj8DWjSeH]hRBVrhX0^3bHAABXO4;We*Z#$Xgd0^m/X6^m/aKVrp9`lh#-O?l*5g, JaJ$UJaJ$UQ0mW(J,~> j7IoiJaJ$Ukj8CBec"kN_c/Wq_sQhLN':D32P*9&V j7IobJ`_OGkiMn)bl-Z>\jIf3]&r-"IOdqN,Ea`7Rc_1K-:>B>jo?Y#joI[f#7gg,4HD J`_OGJ`_OGQ0.-!J,~> j7IoiJaJ$Ukj8DQm/?;e[aaWl\*`Rad-A[=XO6@fZ0f!LldN? j7IoiJaJ$Ukj8C1li$2d;l8`R<:0'2S)o4g2PB6D7I=$NkG2)Pm/T'=m/]65rp9tNNBKq>mHs1! JaJ$UJaJ$UJaJ`ij7Ij~> j7IobJ`_OGkiMmmj8J*U6CZl#6g!MUO54m>,F./i1?LomhjHk,jo?Y#joHghroOJ5IOdFUk2tYM J`_OGJ`_OGJ``6[j7Ij~> j7IoiJaJ?^s0DVdrW)s\!I(j8m/aQSrTslmlg=#?mHq"@rp:&om-X2AmB,^Al`KsL!3Gq^!O:ng m0KrMleJc4mHLm,m=FYUm=FYUm?R)!oRH~> j7IoiJaJ?^s0DVdrW)s\!I(j8m/]f/rTskMkO%ClmHlgrrp:%NlL!ssm5X:rjuN5(!&jT=!CbuH m0H%gke'=NmGUNcm=FYUm=FYUm?R)!oRH~> j7IobJ`_jPs/l8]rW)sZ!HbI0joIEbrT4A4hr`cBk2moJroOP4io]AJjsK!JhBr$[!$h7#!ArO0 jp3ZCij(<$k1V\Dk(2ZGk(2ZGk*>)hoRH~> j7IoiJaJHa!O?qK!<,1cIqi"7/,fMLG'EBA%EcKLl,rD%eEbSmmHq"Rm/X6em09ZZ[aaWl\+o=j XS;)_^u,.Y#f#'Ygp-86hnXg1JaJ$UJaJ$UQ0mW(J,~> j7IoiJaJHa!O?qK!<,1cIn^jk%K6>.G'EBA%>o)Zied&$VWXaSmHlh1m/T'Dm05K9;l8`R<;>h\ 2YZS>D8uSB#]7'7]LD5T`bg:oJaJ$UJaJ$UQ0mW(J,~> j7IobJ`_sS!NgJC!<,1cIn1.W#lXf)Ec^[2%=MX5g3i$LS)9W$k2moejo?Y*jp!'m6CZl#6hodD ,PU=$?G-:"#[XUlZ81S)]O$0OJ`_OGJ`_OGQ0.-!J,~> j7IoiJaJNc#-p>k!'e65rVll6rN-*O j7IoiJaJNc#-p>k!'e65rVlk2rAF[))u]g9X='QR&kl+JaJ$UJaJ$UQ0mW(J,~> j7IobJ``$U#-Bod!'e65rVlk$r?D=]&cMb2EoccQ%H2Ld/W8c%67,%1k2moejo?Y*jp!'m\O%W0 ])(Re,PgI&ToB!5#g5ph4/_6'N1kgbJ`_OGJ`_OGQ0.-!J,~> j7IoiJaJQd"*X_FJ,0*E_>:;6 j7IoiJaJQd"*X_FJ,0*EB_jXe)Zp("FR$a1!U=e$m/_:nJaJ$UJaJ$UJaJ`ij7Ij~> j7IobJ``'V"*=MCJ,0*E>5BKD&H`"lET=pu!TI\bjoK#PJ`_OGJ`_OGJ``6[j7Ij~> j7IoiJaJWf"0t#hJ+rsC_>(,9C&e6Rk3RZdJaJ$UJaJ$UJaLVIj7Ij~> j7IoiJaJWf"0t#hJ+rsCB_XIh9E5'4k3RZdJaJ$UJaJ$UJaLVIj7Ij~> j7IobJ``-X"0FTaJ+rsC>50 j7IoiJaJZg"0qnk^\7[-_>12<_>jOb!,gf[JaJ$UJaKB&raG9_!+CHGJaL):j7Ij~> j7IoiJaJZg"0qnk^\7[-B_aOkB`J*^!,gf[JaJ$UJaKB&rZCUr!$?dZJaL):j7Ij~> j7IobJ``0Y"0DPf^\7[->59BJ=o\MO!,UHLJ`_OGJ``lmrh&ZA!2"i)J`aT,j7Ij~> j7IoiJaJ]h!d=W/o`"p-q5jYJrr3.O!&icum"+PTm=FZ-m0DmJD.p!;m:"l=!+CHGJaL\Kj7Ij~> j7IoiJaJ]h!d=W/o`"o)q)/5Arr3.O!&icum"+PTm=FZ-m0Dj$0I_tJm2t3P!$?dZJaL\Kj7Ij~> j7IobJ``3Z!d"E,o`"npq',m$rr3.O!&WBejalQFk(2Ztjp1"^V5)#lk+CMm!2"i)J`b2=j7Ij~> j7IoiJaJ`i!jVfUoD\g,qQ0bKr;QqM!&`Tom"+PTm=FZ.m0BDFhrLHtm:"l=!+CHGJaL\Kj7Ij~> j7IoiJaJ`i!jVfUoD\f(qDJ>Br;QqM!&`Tom"+PTm=FZ.m0A,Jg#.5Em2t3P!$?dZJaL\Kj7Ij~> j7IobJ``6[!j)HPoD\eoqBH!%r;QqM!&W j7IoiJaJcj!jVejo)A^+qQ9Vo"b-Mdgu[mRm=FYUmBGsYE81 j7IoiJaJcj!jVejo)A]'qDS3M"b-Mdgu[mRm=FYUmBGsY2:DEi!$D1,ruVXn7CMUN++G\.mHku; rp:(8PV+PXmEJBN4M1"Z++`g1mEY+ZoRH~> j7IobJ``9\!j)Geo)A\nqBPk:"b-Mbf&,S?k(2ZGk-3tKVVV14!2'5Ps.9^mXOH=oT>72&k2r;. roOT\aJ>TRk1d)5WR^/0T7Q-Uk0E,LoRH~> j7IoiJaJcj!B^APrrBn4XT6rJrrdfOSD<4Wm=FYUmBGsYA*a'[!+Gcl#j]]PmHn>bkF2D;!+Glo% :QR+`GFpi]BR=XmHn>5m=FZKmGmhh~> j7IoiJaJcj!B^APrr?a02?93ArrdfOSD<4Wm=FYUmBGsY,3o*n!$D+*#jT)ZmHl!XjE(=f!$D4-% 3Lr!ZS:c;V!#*ImHl!Hm=FZKmGmhh~> j7IobJ``9\!BL5Nrr?7",63i$rrdfOR+C/Gk(2ZGk-3tKT]Q+6!2'/N#j'X7k2r;Uj1PBR!2'8Q% A1`&d\Q$-c/d j7IoiJaJfk!d=Wonc&U*qlTVm"@+]hh<':UJaJ$UdI$q=rceCJl0SLjg\gF=@K#R!Z>sD_@dWPk lMU#L@K,Wu@I!7I@I7S(@0:!+m9tS5JaL\Kj7Ij~> j7IoiJaJfk!d=Wonc&T&q_n3K"@+]hh<':UJaJ$UdI$q=rceCJl0SL/derJ4+8tR4QT--1+RSP< l29oK+9(X3+6i1[+719O*s3YQm2poHJaL\Kj7Ij~> j7IobJ``<]!d"Elnc&Smq]kk8"@+ZbfAM5BJ`_OGdH:G1rcJ1Bl/i#Dh>HC8TDdmXaJbc(TB>:. jS\-?TDmsWTB,.sTB;gFTE"$RT7Q-Uk02uJoRH~> j7IoiJaJfk!<@W=rrBn5XT6rHrrp.;ACKH#JaJ$UJaM%Us0;VepAb4VGO4]&To@pkU$_5bdf/V= A] j7IoiJaJfk!<@W=rr?a12?93?rrp.;ACKH#JaJ$UJaM%Us0;VepAb4VGO4]&IUCgfII6DX`W#60 -*ua<+78G;qs=A*rp9\-rp9h1m7OSFrTsV2dt-n;mEP%YoRH~> j7IobJ``<]!<@W=rr?7#,63i"rrp.;@a:.qrRmNroO3QroO?Uk-!(lrT4-Rh1=^>k0<&KoRH~> j7IoiJaJil!I"P4rrBn6XT6rFrrmlPQe(3`JaJ$UJaM.X'XBh$!'`]_^]4=B@7W(+(]XPYG3n_c 'Bu'(BWLOf[:`q8mAOc7XC:M`@K#Tk@0,b(rp:)%mF[)'@:AQjT4MG^m=FZMmGmhh~> j7IoiJaJil!I"P4rr?a22?93=rrmlPQe(3`JaJ$UJaM.X'XBh$!'`]_^]4Fu#64aHG3n_c 'BaTo.?*Z]ROB9Fm>!5DN?@QT+8tU)*s(aNrp:(8mESWV+!9n;H6jYam=FZMmGmhh~> j7IobJ``?^!H\>1rr?7$,63hurrmlPPgS:JJ`_OGJ`bYJ'WjCr!'`]_^]4>,TQa1--NF-dEolfQ 'BHj&UW)BXb+bNjk/3sh`h2DUTDdpMT)n)HroOT\k1d/7T:b9.^7h0Mk(2[?k2Z)a~> j7IoiJaJil!<@W j7IoiJaJil!<@W j7IobJ``?^!<@W j7IoiJaJlm!I"P3rrBn7XT6rDrrkY?`ms&7JaJ$UJaM:\"*X_F^\Ij(@0J=b!-7;iJaJ$UPO7E& J,~> j7IoiJaJlm!I"P3rr?a32?93;rrkY?`ms&7JaJ$UJaM:\"*X_F^\Ij(*sE/T!-7;iJaJ$UPO7E& J,~> j7IobJ``B_!H\>0rr?7%,63hsrrkY>_9^ltJ`_OGJ`beN"*=MC^\Ij(TES*k!,piXJ`_OGPNLot J,~> j7IoiJaJlm!BWR7rrBn7Xn)Jq^As;=gZX4UJaJ$UhX(A,!.X_BpL+:+IfMFekj3lfJaJ`ij7Ij~> j7IoiJaJlm!BWR7rr?a32Y-kO^As;=gZX4UJaJ$UhX(A,!.X_BpE'V>IfMFekj3lfJaJ`ij7Ij~> j7IobJ``B_!BNL6rr?7%,P(j<^As25e`)/BJ`_OGhW=l#!.X_BpRh`jIfM@]iT5XQJ``6[j7Ij~> j7IoiJaJlm!<@W;rr^*>Xk*%B"9;WFe`)/IJaJ$Ui9^WP493.hs7g".YQ+X;!!&nXm"+PTm?[/" oRH~> j7IoiJaJlm!<@W;rrZpm2Oa19"9;WFe`)/IJaJ$Ui9^WP493.hs7dZANrT-o!!&nXm"+PTm?[/" oRH~> j7IobJ``B_!<@W;rrZFL,EVHq"9;T@ceF$5J`_OGi8t-D3WQqfs7i5mci=$[!!&bNjalQFk*G/i oRH~> j7IoiJaJon!jVgAmf*@)Xf_*m"[FWRce[r=m=FYUmG[I4!!'e+s7p('YPnJ)^H_drjQqHbJaJfk j7Ij~> j7IoiJaJon!jVgAmf*?%2E(()"[FWRce[r=m=FYUmG[I4!!'e+s7m`:NrAt]^H_drjQqHbJaJfk j7Ij~> j7IobJ``E`!j)Il,9u^X"[FTMb1Ga+k(2ZGk2GJ&!!'e+s7r;fci*kI^H_dmhW9=NJ``<] j7Ij~> j7IoiJaJon!I"P2rrU$=f'r\CJ,m4(g$""SJaJ$Uip?e0!5J.*q-a9Tqu6d!1tgmRJaJ$UR-ir+ J,~> j7IoiJaJon!I"P2rrQjlRdg=\J,m4(g$""SJaJ$Uip?e0!5J.*q&]UVqu6d!1tgmRJaJ$UR-ir+ J,~> j7IobJ``E`!H\>/rrQ@KORW8RJ,m'teDc&AJ`_OGioU;'!5J.*q4I`Nqu6d!1Xt4?J`_OGR-*H$ J,~> j7IoiJaJon!I$frrrKsen,ER+!4Knuk(2oNm=FZbm/hJ3^[hF$@0!M-rrp.;1YCaSJaJ$URI0&, J,~> j7IoiJaJon!I$frrrHf%n,ER+!4Knuk(2oNm=FZbm/hJ3^[hF$*rq>trrp.;1YCaSJaJ$URI0&, J,~> j7IobJ``E`!H^TorrH;bn,ER+!40Mghgsp@k(2[TjoTW)^[hF$TE*=7rrp.;1=Ft>J`_OGRHEQ% J,~> j7IoiJaJon!BWR6rrBn'rrp.;L;D j7IoiJaJon!BWR6rr?a#rrp.;L;D j7IobJ``E`!BNL5rr?6jrrp.;K>#OaJ`_OGJ`c%U!d"Elnc/HYq#:N4!+j[9jalQFk*kGmoRH~> j7IoiJaJon!BWR6rrD?Prs$4 j7IobJ``E`!BNL5rrC+-rs$4"@)t/e_Yl>J``E`j7Ij~> j7IoiJaJon!BWR"rs$4 j7IoiJaJon!BWR"rs$4 j7IobJ``E`!BNL!rs$4 j7IoiJaJon!BWR"rs$4 j7IoiJaJon!BWR"rs$4 j7IobJ``E`!BNL!rs$4 j7IoiJaJon!BWR"rs!uRXh2d3l%/5Qm=FZfm/hJ3_!h=%@0!M'rrp.;@*RHhJaJ$USF,A/J,~> j7IoiJaJon!BWR"rs!uRXh2d3l%/5Qm=FZfm/hJ3_!h=%*rq>nrrp.;@*RHhJaJ$USF,A/J,~> j7IobJ``E`!BNL!rs!uRWjg"!idp6Ck(2[XjoTW)_!h=%TE*=1rrp.;?HC^VJ`_OGSEAl(J,~> j7IoiJaJon!I"Osrs!uRXh2d3l[eGSm=FZfm/]:umf35oo)AkY/]?/sm"+PTm@EY)oRH~> j7IoiJaJon!I"Osrs!uRXh2d3l[eGSm=FZfm/]:umf35-o)AkY/]?/sm"+PTm@EY)oRH~> j7IobJ``E`!H\=prs!uRWjg%"jFQHEk(2[XjoIMmmf36Yo)AkY/\oZcjalQFk+1YpoRH~> j7IoiJaJon!jVgAgA_@B.'C5-hBJaJ$USaGJ0J,~> j7IoiJaJon!jVgAgA_@B.'C5-hlrrp.;Nm6>BJaJ$USaGJ0J,~> j7IobJ``E`!j)I j7IoiJaJon!p]j%gA_?W;6[d\hs>p]JaJ$Ukj8Fq!5S%&raG j7IoiJaJon!p]j%gA_?W;6[d\hs>p]JaJ$Ukj8Fq!5S%&rZCY0"9=7te`)/IJaK#qj7Ij~> j7IobJ``E`!p'EtgA_?W:p%=Og#[eIJ`_OGkiMqg!5S%&rh/c\"9=1nceF$5J``Ncj7Ij~> j7IoiJaJon!phs5g]%Jm!)g,=dc#cEJaJ$UkNr:o!:Bgd@0!M#rrkY=^sCm(JaJ$UT'bS1J,~> j7IoiJaJon!phs5g]%Jm!)g,=dc#cEJaJ$UkNr:o!:Bgd*rq>jrrkY=^sCm(JaJ$UT'bS1J,~> j7IobJ``E`!p)C,g]%Jm!)]r5c.dg3J`_OGkN2ee!:BgdTE*=-rrkY<]ZSmgJ`_OGT'#)*J,~> j7IoiJaJlm!d+LYh#@S/!3*BFe`MGMJaJ$UkNr:35jeRP@>OCP"[FTMbM294m=FYrmGmhh~> j7IoiJaJlm!d+LYh#@S/!3*BFe`MGMJaJ$UkNr:35jeRP+'.k1"[FTMbM294m=FYrmGmhh~> j7IobJ``B_!c\4Uh#@S/!2d';cej<9J`_OGkN2e+5jeRPTXXCi"[FTI`ms("k(2Zdk2Z)a~> j7IoiJaJlm!p]i:h#@QY:oq4Lf]dtSJaJ$UkNr:3J*m7:@B]/""b6T^`n0C)m=FYrmGmhh~> j7IoiJaJlm!p]i:h#@QY:oq4Lf]dtSJaJ$UkNr:3J*m7:+-,gi"b6T^`n0C)m=FYrmGmhh~> j7IobJ``B_!os?3h#@QY:T:e@e)Gr@J`_OGkN2e+J*m7:T[3*,"b6TZ_U7:mk(2Zdk2Z)a~> j7IoiJaJlm!phoIh>[_1!2Zp7cJ@i j7IoiJaJlm!phoIh>[_1!2Zp7cJ@i j7IobJ``B_!p)?@h>[_1!2H[.ak,X*k(2ZGk3(k-!.XD9!71*5"b6TX^ j7IoiJaJil!j)HQhZ!c[:TCkBeDYuGJaJ$Uk3W0LJ(ai*J,lmjdG]ZDJaK&rj7Ij~> j7IoiJaJil!j)HQhZ!c[:TCkBeDYuGJaJ$Uk3W0LJ(ai*J,lmjdG]ZDJaK&rj7Ij~> j7IobJ``?^!iZ0MhZ!c[:8kJ6cJ*p4J`_OGk2l[EJ(ai*J,ldcbhI^2J``Qdj7Ij~> j7IoiJaJil"7'n+^u54tJ,lFM]Zf*oJaJ$UJaMUe!<@W&rrmoQYeJKBJaJ$UT'bS1J,~> j7IoiJaJil"7'n+^u54tJ,lFM]Zf*oJaJ$UJaMUe!<@W&rrmoQYeJKBJaJ$UT'bS1J,~> j7IobJ``?^"6=;!^u54tJ,l@H\B!+YJ`_OGJ`c+W!<@W&rrmoQXh)^/J`_OGT'#)*J,~> j7IoiJaJfk!p)@+iVs.5!)T`*`7*Xqm=FYUmH*^94$_L!#(Q]W^!GU&JaJ$UTC(\2J,~> j7IoiJaJfk!p)@+iVs.5!)T`*`7*Xqm=FYUmH*^94$_L!#(Q]W^!GU&JaJ$UTC(\2J,~> j7IobJ``<]!oGk#iVs.5!)KQ"^s:Ybk(2ZGk2k_+3^DBu#(Q]T\]WUeJ`_OGTB>2+J,~> j7IoiJaJfk"7)re5i_kMJ,i?BZG4Z=l%/5Qm=FZdm/]:6g&D8,.BgG1hs>p]JaK)sj7Ij~> j7IoiJaJfk"7)re5i_kMJ,i?BZG4Z=l%/5Qm=FZdm/]:6g&D8,.BgG1hs>p]JaK)sj7Ij~> j7IobJ``<]"6?6X5i_kMJ,i j7IoiJaJcj"6XP%JE?e:^]5aZYIhp-idpKJm=FZcm/]:6g&D4@;6[d]hgt0Gm@N_*oRH~> j7IoiJaJcj"6XP%JE?e:^]5aZYIhp-idpKJm=FZcm/]:6g&D4@;6[d]hgt0Gm@N_*oRH~> j7IobJ``9\"6""qJE?e:^]5^VXLH-qgk"U=k(2[UjoIM.g&D4@:p%=Pfn&::k+:_qoRH~> j7IoiJaJcj"RP/a!.a84$,?NfV6.5Gdba8?m=FYUmH!X9YlMSnrriC#Z,"cFJaJ$UT'bS1J,~> j7IoiJaJcj"RP/a!.a84$,?NfV6.5Gdba8?m=FYUmH!X9YlMSnrriC#Z,"cFJaJ$UT'bS1J,~> j7IobJ``9\"QeHQ!.a84$,?NfUT1Z9c.M'-k(2ZGk2bY+X8p&irriC"Y.W!4J`_OGT'#)*J,~> j7IoiJaJ`i"RG#]!'ofK$%N"'V6.2Cd+ml:m=FYUmGmR8Z2h\prs$4 j7IoiJaJ`i"RG#]!'ofK$%N"'V6.2Cd+ml:m=FYUmGmR8Z2h\prs$4 j7IobJ``6["Q\ j7IoiJaJ]h"mFlZ!!%Q:rsHP-!)KMq\AHPGk(2oNm=FZam/lW75hl;CJ,lUX`n'C*m=FYrmGmhh~> j7IoiJaJ]h"mFlZ!!%Q:rsHP-!)KMq\AHPGk(2oNm=FZam/lW75hl;CJ,lUX`n'C*m=FYrmGmhh~> j7IobJ``3Z"le3J!!%Q:rsHP-!)BAl[(aZ4i.:$Ak(2[SjoXX(5hl;CJ,lLR_9h1mk(2Zdk2Z)a~> j7IoiJaJ]h#OL[3eR\_GJFWXI^d.rJHE+9^`6d5uJaJ$UJaMC_!j2O=h#@QY:p%=Og?O7VJaK&r j7Ij~> j7IoiJaJ]h#OL[3eR\_GJFWXI^d.rJHE+9^`6d5uJaJ$UJaMC_!j2O=h#@QY:p%=Og?O7VJaK&r j7Ij~> j7IobJ``3Z#Naptcscu?JFWXI^d.rIGc7gS^rt6`J`_OGJ`bnQ!iZ18h#@QY:TCnCeDl,BJ``Qd j7Ij~> j7IoiJaJWf#jC4!AhX2bJACpZs1f%/!!#lqY-l!bdbO!OJaJ$UJaMC_!pTbNh>[_1!2Zs8cJ@i< m=FYrmGmhh~> j7IoiJaJWf#jC4!AhX2bJACpZs1f%/!!#lqY-l!bdbO!OJaJ$UJaMC_!pTbNh>[_1!2Zs8cJ@i< m=FYrmGmhh~> j7IobJ``-X#iXLc@kRf^JACpZs1f%/!!#imX0T=Sc.:e6J`_OGJ`bnQ!oj8Gh>[_1!2H^/ak,X* k(2Zdk2Z)a~> j7IoiJaJTe"mP%"cspD]!!#=aJ.]SIJ3cI$!%<5pY-baVbgbJ3JaJ$UJaM=]"7/#J^u#(q!)KW' `REdsm=FYqmGmhh~> j7IoiJaJTe"mP%"cspD]!!#=aJ.]SIJ3cI$!%<5pY-baVbgbJ3JaJ$UJaM=]"7/#J^u#(q!)KW' `REdsm=FYqmGmhh~> j7IobJ``*W"le:db@"TS!!#=aJ.]SIJ3cI$!% j7IoiJaJQd#j^R-f$;9B>V,aPs!\>*J?H2d]>D\;g#2.Hm=FYUmG.(1k5[d)rs+&SUp%DRg?F1U JaK#qj7Ij~> j7IoiJaJQd#j^R-f$;9B>V,aPs!\>*J?H2d]>D\;g#2.Hm=FYUmG.(1k5[d)rs+&SUp%DRg?F1U JaK#qj7Ij~> j7IobJ``'V#isgnd)j.1=tKONs!\>(I]T`Z\A#o*eCrr6k(2ZGk1o)#i;c.#rs+&SU9(lFeDc&A J``Ncj7Ij~> j7IoiJaJNc)XQ\Jgt'io_nWgo[^EKL[^Wf\_8a^Ig#(uZJaJ$UJaM4Z"7'n+^u>:uJ,iBF[DUJN JaJ$USF,A/J,~> j7IoiJaJNc)XQ\Jgt'io_nWgo[^EKL[^Wf\_8a^Ig#(uZJaJ$UJaM4Z"7'n+^u>:uJ,iBF[DUJN JaJ$USF,A/J,~> j7IobJ``$U)Wfr5f$MUZ^Upt_Za-j@ZF%'N]u%h7e(N[@J`_OGJ`b_L"6=;!^u>:uJ,i?BZG+T: J`_OGSEAl(J,~> j7IoiJaJHa(@:5EhV$H+c-")H`Q#s>bKeSggYh2ZJaJ$UJaM+W"6MNB^uGA!!%+SJ^<>6pJaJ$U SF,A/J,~> j7IoiJaJHa(@:5EhV$H+c-")H`Q#s>bKeSggYh2ZJaJ$UJaM+W"6MNB^uGA!!%+SJ^<>6pJaJ$U SF,A/J,~> j7IobJ`_sS(?ON2f[J0iaMl'6_84",`lZKQf%T!AJ`_OGJ`bVI"5l';^uGA!!%+ME]#W=[J`_OG SEAl(J,~> j7IoiJaJB_'("lEj5AbIf[n^+f\,$9iT04hJaJ$UJaM%U"7+u*5i_kL5ld^_\]<4YJaJ$US*f8. J,~> j7IoiJaJB_'("lEj5AbIf[n^+f\,$9iT04hJaJ$UJaM%U"7+u*5i_kL5ld^_\]<4YJaJ$US*f8. J,~> j7IobJ`_mQ''8-1gtCFJ`_OGS*&c' J,~> j7IoiJaJ9\"RPI=kiVm+"m5"4lKmkgm=FYUmF1G)kbeDGjo5TP!-tWQ_U.*'JaJ$US*f8.J,~> j7IoiJaJ9\"RPI=kiVm+"m5"4lKmkgm=FYUmF1G)kbeDGjo5TP!-tWQ_U.*'JaJ$US*f8.J,~> j7IobJ`_dN"Qe_(iSa[l"Q8;"jlM'UJ`_OGe)gbkU&[I?rs1mjGGqg]bLY^)k(2Zak2Z)a~> j7IoiJaJ*Wrp54'JaJ$Uc0Y]$jeMkV_!1k*^d.sGWjBIbg#[ePJaJonj7Ij~> j7IoiJaJ*Wrp54'JaJ$Uc0Y]$jeMkV_!1k*^d.sGWjBIbg#[ePJaJonj7Ij~> j7IobJ`_UIroJ^nJ`_OGc/o2khk'lK_!1k*^d.sEW3EnTeDGi>J``E`j7Ij~> j7IoiJaJ$UJaJ$UJaLYJ"mb1#1B;skrsHOA!.(`R^<>0gm"+PTm@*G&oRH~> j7IoiJaJ$UJaJ$UJaLYJ"mb1#1B;skrsHOA!.(`R^<>0gm"+PTm@*G&oRH~> j7IobJ`_OGJ`_OGJ`b/<"m"If1&ujjrsHOA!-tQK]#N1QjalQFk*kGmoRH~> j7IoiJaJ$UJaJ$UJaLVI#3t4#AcP)Hn,Ed25l`:1Y-u-hfAc.Jm=FYmmGmhh~> j7IoiJaJ$UJaJ$UJaLVI#3t4#AcP)Hn,Ed25l`:1Y-u-hfAc.Jm=FYmmGmhh~> j7IobJ`_OGJ`_OGJ`b,;#34Le@fScEn,Ed25l`:.X0TCXdG*c6k(2Z_k2Z)a~> j7IoiJaJZg!4([0JaJ$UJaM"T#O:=%B)hp^JFidL^d.rJHE+6\_p6onl[eGSm?m;$oRH~> j7IoiJaJZg!4([0JaJ$UJaM"T#O:=%B)hp^JFidL^d.rJHE+6\_p6onl[eGSm?m;$oRH~> j7IobJ``0Y!3P=(J`_OGJ`bMF#NOUhA,lU[JFidL^d.rJH)RmS^<+gXjFQHEk*Y;koRH~> j7IoiJaJZg!-dK(r9XLRp[%tMJaJ$UJaMRd$L?a,dUOp1!.]U8qu?]3s$7"m.!qtt[_9`.g>_FL m=FYkmGmhh~> j7IoiJaJZg!-dK(rTsXPjnJ-Cjm7QcJaJ$Uk3WNAj4i!l0)u!&JAD'^s1nY#$NMT%WirkEa3`Q" l[eGSm?d5#oRH~> j7IobJ``0Y!-I9%rT4.??2*I^?/gFtJ`_OGk2m$3h:BhZ/cYm%JAD'^s1nY#$NMQ#W3!;8_opQb jFQHEk*P5joRH~> j7IoiJaK/u"7PdMme?)LG6%(2rpfse!It1Km/cYDrp9^Rrp^-[mHsB1JaJ$UJaMRd#OCL-e]_H- /b]5R-n0VHYdM*[bLG>.m"+PTm?[/"oRH~> j7IoiJaK3!"RP:1iTKt&!I"P3s8M!UrrIWMrTsXLkPjiSl/Ua_l0[m$k^i,Pm=FZdm0Dt=h:9_X >qPdN$n*QRXK]+E_TUHajQqHbJaJcjj7Ij~> j7IobJ``]h"Q=AK+bf[Q!H\>0s8Ef4rrIWMrT4-OLAp<@UGEcDUupfoSUopSk(2[Vjp1#*f?_KE >V,ULs!S5%Wi`S9^;nRNhW9=NJ``9\j7Ij~> j7IoiJaK3!s6g$WmI0f:!I"P1rrDTfrrIWMrTsXTmf)SZna>f4na5`3n:BtXm=FZcm2PETiS2i+ `k[L9=]ea+t/ZaRH[`QQ`aiTGdZJaJ]hj7Ij~> j7IoiJaK3!"R"k4m,n'0!I"P1rrD$VrrIWMrTsXIli-8Wip,mpipH.!jalfMm=FZcm2PETiS2i+ `k[L9=]ea+t/ZaRH[`QQ`aiTGdZJaJ]hj7Ij~> j7IobJ``]h"F!qpflQoM!H\>.rr j7IobJ``]h!A)FmjqbEc)E>4gjq`.Z')r`9-7,JfaugS)b4tu:(B+76J,oQ1!@ZY#jp4)PiN5<+ k1M27k(2ZGk2PM1jP\eDd*'GH^V%/W\I,p\^;@n5d+-k.jalQFk*+rfoRH~> j7IoiJaL8?rNbVR!:g!S(@^e[mdTW2naCjds7,paqssdYqYp<^q#:?arVlnK!<)9bnbqhTmf)_V mf)SRnU^(Ym=FZ_m1epNjP\eFe^DgjcHjkceC`O5j65i\m=FYemGmhh~> j7IoiJaL8?rNbVR!9*kC(?=WGki;'tiTPZCs5EPJp>c28oD\@Dq#:?QrVlnK!<)9biVi-Llf-m_ lg=*+iIUBIm=FZ_m1epNjP\eFe^DgjcHjkceC`O5j65i\m=FYemGmhh~> j7IobJ`ac1rN58F!?'qrjq]nAhip+_jq`em%K@38pUr!T4/M\H4S\pV(B+76J,oQ1!?1"tjp8OC (*gHRk2I;1k(2ZGk2>A9jl,%Ke^DdgbK@rLbK\A]f%Jm=jalQFk)nfdoRH~> j7IoiJaLGDs0DYeq#LG=m-OfBr9XLSrp:?gmHsH2G5sr!6U=$.6179/8+-. j7IoiJaLGDs0DYeq#LG=m-Oi5li$2NiVr3WiU-$klurFqiBfgriBc?j5tBf,5lfmA5l^o`m/c24 rp:!Nl0RZpmHs9"JaJ$UJaM=]#41X=jl>=Xrn[k:i8`q_lKj)hJaJNcj7Ij~> j7IobJ`ar6s/l;^q#LG:m,e="bPgQ=);jT5)Wp/tiH,&c(-b)N(-ra$5sH1\5l_\u5l^o`joHm` roOJEWo9m8k2t>GJ`_OGJ`bhO#3Fn(hqd&?rn%G.g>(QEj5kjSJ``$Uj7Ij~> j7IoiJaLMF$F2bo!'`\t^V@:drcnDB!d/\QnEp8E&FSrSmI9T3mdTW2na3VD47W;3#4Xe(JjG": pcAKGq`FcD!V#OVm0N7MmI'N3mI'Ipm=FYUmG%%.m/QDNlMp/Mm"+PTm>LAloRH~> j7IoiJaLMF$F2bo!'`\t^V@"Prc%i:!d/\QnEgkOjQu6rmGmEtki;'tiU*p445p0##2qY`HU3%u pcAK7q`FcD!TiYHm0M_9m-LAloRH~> j7IobJ`b#8$EZ>h!'`\t^VVTS"r93 j7IoiJaLSH"gU5j5JOu2rrDllnc3q6!!.6.na-bRnaZ&7mdTc9md9E3rTsUTrp9p[mI'K6nF$/? r:0UM!UfFUm/ZSRnHA:ImI0Iom=FYUm?6l:9*.dq]%66Ok`T%@97cJ]mJlS6oRH~> j7IoiJaLSH"gU5j5JOu2rrDTdiW*rs!!.6.o'HnQk2P@_mHW`jj6H%!iVi-DiVr3KiU,pjiT'5" mJPr=m/cG6rp:!Uj5K%_mHs*#JaJ$UJaJWfs+UT1Pg.eH"R<=RL5(9HJaNL)j7Ij~> j7IobJ`b):"g'fc5JOu2rr@uS('"aA!!.-%o&^D?D^HW]k-d2d2n.r3(B*@q(B3G#(?W%a))o&$ k56"jjoM$-roOKY0GlO;k2qDhJ`_OGJ``-Xs!%Rk5,d&p"Q=V_,9qd%J`d!pj7Ij~> j7IoiJaLYJ"L:,i5JR$o!;ZH]"aE01Ep)c*m/ZYMm/ZX,m=FYUm?-c=9C;)c j7IoiJaLYJ"L:,i5JR$o!:fmE"`QI%Ep)c+m/cS8p[&"Cm"+PTm=FYfm0&12m*q$Gm/sbbh!BUZ JaNI(j7Ij~> j7IobJ`b/<"Ka]b5JR$o!/pXg"UbJ6DrB`pjoOIIpZ;K]idp6Ck(2ZXjocpkjL6hojo^!$a5qm7 J`csoj7Ij~> j7IoiJaL\K"0qn,J+rsCq=sLbpjW8DWTWW!m=FYUm=FZGm3kWplG$cp91r#o_X4:09hS.HW*i=m 9C9f4:1hT7aC\1ZaR)Wu=&uI"m/^G0JaJ$Ur9`P:J,~> j7IoiJaL\K"0qn,J+rsCnbD)Jn:(E j7IobJ`b2="0DP'J+rsCMte%lMLPU+UuCEdk(2ZGk(2[9jsVJ:iibN-,9nO*ZK@?5-6jbtP<:0: ,NcR)-:>B,\jIf3]&k6#0JJsljoIBKJ`_OGr9!&3J,~> j7IoiJaL_L"*X`0^\7[-q=sO[rrbReDrg58m=FYUm@N\F466GB43Hsi9*%l'rp:ioa'7q/U!r8n m8BY,l0[kLY3 j7IoiJaL_L"*X`0^\7[-nbD,CrrbReDrg58m=FYUm@N\F466GB43HsiL&m.Wrp:ipeWB0V^"ho? m>J[5lL!u)`TkC.m?Y`V`0]kG_WoL.qX"E@[u-?lJaJ$Us6\k=J,~> j7IobJ`b5>"*=N-^\7[-Mte(errbReCu46(k(2ZGk+:]83op>A3QgL`,699sroP?`\MrY%MT=`= jt)D1io]AiS)9W$k!GQpR7$*]Q/Ac#qW7o-IOdFUJ`_OGs5rA6J,~> j7IoiJaLbM!d=W/o`"peq=4O\rVm"M!,gi^JaJ$UJaK)s!BU;X*<8h&IfVerm-XhYr]L/["$CdO ZKpm+9C;#ZAF6@'9M8(H]R40eS*dk9@T/c`:$D!-:@1d>mH7739Btp39D\#Y j7IoiJaLbM!d=W/o`"p]q;MDDrVm"M!,gi^JaJ$UJaK)s!BU;XG6!!YIfVerm-XhYr]L/["$CdO ZKpm+L@+L[+9kM!]u_mHKGoL?n6oLAL9?NR-n j7IobJ`b8?!d"E,o`"oLq%`sfrVm"M!,LBMJ`_OGJ``Te!BL5W*<8h&IfVepm,n>Mr]:#Y"$1OG Xm>*t,Ne!h60=Fo,pO\tXBn+CK]]@B5:qQN-K!tB-KZZ4k2%HI,N@kJ,PpO*0Wj??0nJJ j7IoiJaLeN!jVfUoD\gdqXOX]qu6da!2AE8JaJ$UJaK,t!BUhg3WP]=rrGAHn*L.]4T5?[56QH^ ,n^ji!!.7emd1?SmHhhkm81R]]hA@l!pmj&rp:9``)5o5Y3 j7IoiJaLeN!jVfUoD\g\qVhMEqu6da!2AE8JaJ$UJaK,t!BWF?mf<+NrrGAHn*L.]4T5?[56QI' 6QmWP!!.7emd1@9mHi>\m>93TcA)>c!pnBnrp:9`du*OY`TkC.m?Y`V`0]kGrp9]?qX"E^cK`i< JaJ$Us6\k=J,~> j7IobJ`b;@!j)HPoD\fKqA''gqu6da!1hj&J`_OGJ``Wf!BLbf3WP]=rrGAFn)aYQ3rT-Y56QHJ &I8[A!!.+]mcFj$k2j'(jsdLkX<[`,!p$^=roOdQZn(9*S)9W$k!GQpR7$*]roO2*qW7oeXQ$N@ J`_OGs5rA6J,~> j7IoiJaLhO!jVejo)A^cqXXI^"@)s[i9>g[JaJ$UU$VkB&,J j7IoiJaLhO!jVejo)A^[qVq>N"@)s[i9>g[JaJ$UU$VkB4SnLHp[A+_5WeN'"gU5j!'e6rs87WV AO?Ft3d'fZmJ_h`PK<.&NK(W+m=p@NPf)mCL52#2mF@MXQIYBQQA:dVm=tW-!/C@;"Q#Z)PK\UF JaNL)j7Ij~> j7IobJ`b>A!j)Geo)A]JqA/o,"@)sWg>[\GJ`_OGU#lA:&,J j7IoiJaLhO!I"P6rrDllnc8I[rr`nFm=FYUm@rtK3tM0K!1`NX!C/a;m/oHN5JR$oq+Cl; '**(9l[eGSmC_iHoRH~> j7IoiJaLhO!I"P6rrDTdiW/KCrr`nFm=FYUm@rtK4$ j7IobJ`b>A!H\>3rr@uS(''6err` j7IoiJaLkP!d=Wonc&UbqssL]"ht&(fAc.Jm=FYUmA'%L3tM0=!+#'/!C/a=m/sWo!.XbCpIb[> !!%''m"+PTmCquJoRH~> j7IoiJaLkP!d=Wonc&UZqr7AM"ht&(fAc.Jm=FYUmA'%L4$ j7IobJ`bAB!d"Elnc&TIq\Jr+"ht&%dbNr8k(2ZGk+h&>3Y2' j7IoiJaLkP!<@W=rrDlmnc8IYrrbRec._91m=FYUmA'%K466GB48%tCG5skBpAan\#GhD"IfOEJ l%/5QmD&&KoRH~> j7IoiJaLkP!<@W=rrDTeiW/KArrbRec._91m=FYUmA'%K466GB48%tCG5skBpAaoC#K-TAIfOEJ l%/5QmD&&KoRH~> j7IobJ`bAB!<@W=rr@uT(''6crrbReaOK'tk(2ZGk+h&=3op>A3VDM:F9"P?pAan4#E\ucIfO<@ idp6Ck.g'=oRH~> j7IoiJaLnQ!I"P4rrDlmo(E"d^AsGEhW]UYJaJ$UV![3Ip?_p*!.XY@pe1Sc"b-MhiTTWZm=FZ@ mGmhh~> j7IoiJaLnQ!I"P4rrDTeiq< j7IobJ`bDC!H\>1rr@uT(A7\2^As>=f]%JEJ`_OGUup^=p>uEu!.XY@p`ob;"b-MegYq7Fk(2[2 k2Z)a~> j7IoiJaLnQ!<@W j7IoiJaLnQ!<@W j7IobJ`bDC!<@W`6m@kk(2ZGk(2[mjoTW)J+N^9,63i'rrmlPC"_G+J`_OG _WKnNJ,~> j7IoiJaLqR!d=Won,EC`rpg'aoD]!/!0>I^l%/5Qm=FYUmJcJSG6!-`s7o=arrdfOC>J'(m=FZA mGmhh~> j7IoiJaLqR!d=Won,ECXro*qIoD]!/!0>I^l%/5Qm=FYUmJcJSG6!-`s7qHHrrdfOC>J'(m=FZA mGmhh~> j7IobJ`bGD!d"Eln,EBGrY>KkoD]!/!0#(Pidp6Ck(2ZGk5OKEF9$g]s7mo9rrdfOB\20nk(2[3 k2Z)a~> j7IoiJaLqR!I"P3rrDloo'u__!%u=Cj+6TKm=FYUmJcJR!'g#RqF^gaq>UR_!1r'1JaJ$U_sQLV J,~> j7IoiJaLqR!I"P3rrDTgipm$O!%u=Cj+6TKm=FYUmJcJR!'g#RqM,'gq>UR_!1r'1JaJ$U_sQLV J,~> j7IobJ`bGD!H\>0rr@uV(@hD-!%l(7gk"U=k(2ZGk5OKD!'g#RqBH!%q>UR_!1MR!J`_OG_rg"O J,~> j7IoiJaLqR!<@W;rr`)fo(_bT"[FWSd,"&>mEY)"lauoY!P@ZKm=FZ@m/_D2nGiAY!2TAh"9:1+ hs#^ZJaLDCj7Ij~> j7IoiJaLqR!<@W;rr_fNipcL<"[FWSd,"&>mEY)"lAt],!KZMsm=FZ@m/_D2nGiB@!5nR2"9:1+ hs#^ZJaLDCj7Ij~> j7IobJ`bGD!<@W;rr\/p(5hY^"[FWObLbj,k0E)iiaqlH!G18Ak(2[2joKQ(nGiA1!0HsT"9:.$ g#@SFJ`ao5j7Ij~> j7IoiJaLqR!<@W;rrW#eo'lY_^Atjff]RhQs6TsT_l9NUip?lrXJE,Tq<\42b5Uc6lGKQpU@%?j dXgeTmJ]bZm/hJ3J+3L89D8Ah^AsVOiTu-_JaLGDj7Ij~> j7IoiJaLqR!<@W;rrV`MipcsO^Atjff]RhQs6TsRSnhr j7IobJ`bGD!<@W;rrS)o(@_>-^At^]e)>l?s5jIDGr%%1ioUB:4srn.q;q^ON;hrFiH[7),Ne-I Tn2?qk5HU+joTW)J+3L8,PM-@^AsJEgZ="KJ`ar6j7Ij~> j7IoiJaLtS!jVgAmf*@ao(__S"ht%n`RX""mJlPV`OY-p`8^.e]AW#&W;5f]\_d>Q!lV>%rTsXA \Uj.@mJKWWm/TfQm/Tegm/]9Kn,N;Y!2T;f"@+Wbg#[ePJaLGDj7Ij~> j7IoiJaLtS!jVgAmf*@YipcI;"ht%n`RX""mJlPVU5RpiT&T,?Nn!=(C\d>uMU;@i"/'tkm/HAQ c\d;@T^DZ3s6Tf@p?_j7\*X2X!:TscLB,6brrbS?c.V30m=FZDmGmhh~> j7IobJ`bJE!j)Iu?"\)m]P!:Tsc,63hurrbS>aOB!sk(2[6k2Z)a~> j7IoiJaLtS!I"P2rrMrjmf*FjKu)-om"+O*mAf4Fm2"K[UTWD>m@qRrYjJ10Vl.o/godETh"U1; jeV5=m/a0Mrp9`cgAL==kbs("T^;_Pqs=AVp?_iQ\Es@'!.XJ;r(H\^"ht&$dGO2>m=FZEmGmhh~> j7IoiJaLtS!I"P2rrMZZmf*FjKu)-om"+O*m;CYVm2!ri@6T^;`6qs=Bm=FZEmGmhh~> j7IobJ`bJE!H\>/rrJ%8mf*FjK=oC\jalOqju:N_jqbNq-:>B,js,=*8*2%Z0JJsX\jIf3](b=g d3mrMjoI[f#7gg,4HDT]Q5!qrRl'p>u?"\E3jp!.XJ;r$1k6"ht&!bh;!,k(2[7k2Z)a~> j7IoiJaLtS!I"P2rrMrjmf*Fj=1ul7m%3VCZKLTUU[7sH(9Z$Mc`Z[&U j7IoiJaLtS!I"P2rrMZZmf*Fj=1ul7m%3VCZKLTU@I*mZ(3[.M[LApQ@L@+qos6TsFSVWN[_ j7IobJ`bJE!H\>/rrJ%8mf*Fj`7!Imk(2[7k2Z)a~> j7IoiJaLtS!I"OrrriC&]#rdlU[A)es#g)Ws**hs!NP2Ym0KKQmHUJ6m@rqGm0]TRmHpY8U[.=4 U\X9ZhlOiaq<\3]kl0rTVq:7fW9s!9W.F?LmJaESmJKWWm/TfXm/TfYm/p#Km7[EF"Db@-^gb.I !<@W;s8GXkGOkb;IfR=3h<]^[JaLMFj7Ij~> j7IoiJaLtS!I"OrrriC&]#rdlU[A)es#g)Ws**hs!H-Wim0I.amHA!Gm9f/Xm0[4cmHn9J@Hd+B @JT9mdW2)Kq<\2ujSnNPBuK8VC@1ELCOtldmJaESmJM_=m/Vn>m/Vn?m/r+1m=tT,"IdEsd$2B6 !<@W;s8IcRV=OWiIfR=3h<]^[JaLMFj7Ij~> j7IobJ`bJE!H\=orriC%\&HnWUZVTYs#TrUs)dVi!B87$jp3 j7IoiJaLtS!I"OrrriC&\B*@fVpOsFa!-@m1%%Fk39hOY0cL\Y3kVN rTsTZrp9oamB4h%c`[-3!mn4-p[&!lh>ZdI\C^'"W9s!.\Uj.cmJjKl4Sf$Z4El#9m/TfVm/TfX m0?;OmHR@3m7[EF!)EMd!)18s!jVgAmf38Z!2T2c"ht%qak5bDm/ZL(m=FZZmGmhh~> j7IoiJaLtS!I"OrrriC&\B*@fV m0AC5mH]Mom=tT,!/UT._X.E1!5S%&reCKko)Am.!/ntOk3hU:m"+PTmG%$goRH~> j7IobJ`bJE!H\=orriC$[DUJQVb&cMe1EWG4jjpc`He(T336CZl#6g!MU rT4))roOD0k!GQpR7$i5!h2lppZ;K_]`.%!?EE.l1?R`3?@h7Vk5VR`3r/gX3coK1jo?Y'jo?Y) jp*-nk2IWJjs0ie!%.[i!$oGD!j)I j7IoiJaLtS!I"OrrriC%\&[.`VsOYn49.Tq^\n*3dJK[=;>pOrFmAPb%I1g/VTS;\Y+r/TmHpYH m/Wm[m09>,pa3'!!.9/ n*L*Jqs=DgcMm2:=Ma4$9C;+Bf`(7>CqSuIm/_D2mJm2Z!2T,a"TU-ldbsK\m/ZL(m=FZZmGmhh~> j7IoiJaLtS!I"OrrriC%\&[.`VsOYn49.Tq^\n*3Ykqc-0E(qPFmAPb%I'BuAub7BG%#R=mHn9Z m/UMmm06qbb][ED46UN$r!!.9/ n*L+0qs=EGg&C@ENm[./L@+Abi;W*FS'L<7m/_D2mJm3A!5n=+"TU-ldbsK\m/ZL(m=FZZmGmhh~> j7IobJ`bJE!H\=orriC$[)18KVre/b3WMBo^\n*3P5?I(&cMb2ETH]Q%H2Ld/W8c%67,%1k2moe jo?Y*jp!'m\O%W0])(Re,66QrjoM07roOL09dVs"k2qVrJ`ar6%BVYk!'`\t^V j7IoiJaLtS!jVgAgA_Al!.VJtf]]:$"*X`0^\Rm0dJ9R;;$-f=k3BkO!UIAlm/aibJaLMF"gU5j 5JOu2rr@Fp>mF65S92!.h!I$frs8Pa\rrkY= ^ j7IoiJaLtS!jVgAgA_Al!.VJtf]]:$"*X`0^\Rm0Yk_Z+0*;2pk3BkO!U>a?m/`74JaLMF"gU5j 5JOu2rrAnnLB_ j7IobJ`bJE!j)IQGpt!#k/^OO,9t(f!H^Tos8O>4rrkY< ]#`IaJ`_OGaQDOTJ,~> j7IoiJaLtS!jVgAgA_A-!.VN!g$#I'"0t#hJ+rsCdJ0I@d=2,rETc\>m=F[)m0'^W!'e6orr@<> 9*HUA!-@AjR-ao95jeRP9;M%V"b6T`aOo^-m=FZGmGmhh~> j7IoiJaLtS!jVgAgA_A-!.VN!g$#I'"0t#hJ+rsCYkVQ0Y($EOETc\>m=F[)m0'^W!'e6orrAnk LBU$F!-@AjR-ao95jeRPLVrT\"b6T`aOo^-m=FZGmGmhh~> j7IobJ`bJE!j)I j7L.Sn*YboVX4BaJ(jo,J,l[]akH![m/sWo5JQpl!7CS:rr3*d!,po_JaJ$U"*X_F^\@a.Gkao9 UjiA'j65ium/[3:mJd/Zmf*H@!4Khrj+6TKmE"\ToRH~> j7L.Sn*YboVX4BaJ(jo,J,l[]akH![m/sWo5JQpl!3l6*rr3*d!,po_JaJ$U"*X_F^\@a.V>,fM _g_YFj65ium/[3:mJd0Amf*H@!4Khrj+6TKmE"\ToRH~> j7IobJ`bGD!<@W'rs!uRWO9[oiiD2DXT1T#pAY,Up`oe<"@)s'h;a(KJ`[&V!!'e.rr?6t,6j6Z !&iZljdP<4!.XD9!$gq3"b6TY^sLngk(2[9k2Z)a~> j7L7V!q,OHn*pGJmXac(m/]75gA_@B.'C2*h!2!.!d=W/o`"p=q4dr;rVm"M!,gf[JaJ*W!d=W/ p&>#:q+D!hs8RQK!2nuGS*^4VJ*m79V!e6dJ,lpldc,iFJaLPGj7Ij~> j7L7V!q,OHn*pGJmXac(m/]75gA_@B.'C2*h!2!.!d=W/o`"oqq-O-)rVm"M!,gf[JaJ*W!d=W/ p&>#gq1f6ns8RQK!2nuGS*^4VJ*m79_s[O.J,lpldc,iFJaLPGj7Ij~> j7L7O!U&\7klL#2J``on!BEF!rrt_:X1-+"j/hAEF9"PtrrA5X,63i(rrdfOCY[p#k(DdtF9"Pu rr?6u,7'D2s*k"KUYt7'joGI3mJd0Jmf*H@!3j/]gO\L j7L=X"n(mAb-.K.S-,G!jmI]eWU0bl!5R@h"oqN)^ j7L=X"n(mAb-.K.S-,G!jmI]eWU0bl!5R@h"oqN)^ j7L=Q"m>.,_lB6sQNN_ghWKIPWTF8_!5R@h"oqK%]#iUdXl]\e!'g)T!0[1'!*T4!"FgDcg#7ME L#rGY!.XY@!*]1CrVm&9!!&_LjdbH6!.W]%"b6TU]?AoZk(2[9k2Z)a~> j7L@Y#4CNM?o/uDnJ;?J40[r;mXac+m/jNmJDC//J,lLR_U@DEm/[2Oo)A^;qP+& j7L@Y#4CNM?o/uDnJ;?J40[r;mXac+m/jNmJDC//J,lLR_U@DEm/[2Oo)A]oqHj6*qYp[`!2&04 JaJ6[!d=WooD\feqM,'gqu6da1tq!TSa?FXJ(ai+J,lgfcen)?m=FZHmGmhh~> j7L@R#3Xg;?8EZ?nJ;?J3ND*&kCMcrjoVUaJDC//J,lFM^ j7LF[#O^p+H7]a6!Up'm!Z@Tkn*YboWpKgI5huAD!)KW(`n'@Um/hJ3^[hC)dJK[=dJ3_E5VgmJ l%/5Ym/hJ3J+EU>Gkt)0rrp.;1YCaST'ZP?5hZ/AJ,lgecJRu>m=FZHmGmhh~> j7LF[#O^p+H7]a6!Up'm!Z@Tkn*YboWpKgI5huAD!)KW(`n'@Um/hJ3^[hC)Ykqc-Y5&#"5VgmJ l%/5Ym/hJ3J+EU>V>>uDrrp.;1YCaST'ZP?5hZ/AJ,lgecJRu>m=FZHmGmhh~> j7LFT#Nt0lFt=71!Up'm!Z7?_ki[NZWoa=@5huAD!)BK!_U.8DjoTW)^[hC)P5?I(OSJhY5V^U< j+6?LjoTW)J+EU>>5BK?rrp.;1=Ft>T&p&75hZ/AJ,l^^ak>d,k(2[:k2Z)a~> j7LF[#j]i\*kWU0bh!5RIk#CleOYIr*6lE9LUG6!-_rrCIDU\Fcl^Aqir iTl'^MX:EE5OnaRGl(,6V"t#o^Aqp"j6WE*!BWR"rrmp%YeAB?JaJ$UaR/$[J,~> j7LF[#j]i\*kWU0bh!5RIk#CleOYIr*6lE9LUG6!-_rrB;#?haR(^Aqir iTl'^MX:EE5OnaRV>H#J_tj<9^Aqp"j6WE*!BWR"rrmp%YeAB?JaJ$UaR/$[J,~> j7LFT#is0N*!66W0L5.t"A3LGjQD*VWTF8\!5RIk#CleNXLZC%j0%MGF9$g\rrA5Z,PV3A^Aqcj gZ3qJMWOp>5OnaR>5KNEOSSn[^Aqfnh;t9k!BNL!rrmp$XguU-J`_OGaQDOTJ,~> j7LF[#htRf!!YZ;V7#j(!kZ_>JaKB&!p_fGhZ!c[:T:_=dbq7p!<>@RrrCIEUB&^8rrbReRG$VO m>L?5G6#DJrr@ j7LF[#htRf!!YZ;V7#j(!kZ_>JaKB&!p_fGhZ!c[:T:_=dbq7p!<>@RrrB;$?N@8&rrbReRG$VO m>L?5G6#DJrrAnnLB+1FrrmlPRbHihT^;c(!87AU!)^#9d,9KBJaLPGj7Ij~> j7LFT#h>"\!!YT4TWdmm!k-/-J``lm!ou9?hZ!c[:8Y;2c.T5]!<>@RrrA5[,63i!rrbReQ.+Q? k)8@'F9')Grr?7#,61m@rrdfOQdjlajoKQ(g&D3U:p.FRfn&::k/c]FoRH~> j7LL]$L[/i9EG>^UY+l!md>YnVsOTQX8kNDrs+&SUp%DQg$,[,!d=WonG`L9r1ioa"9:.&hg"9:.&hdc#cEJaLPGj7Ij~> j7LL]$L[/i9EG>^UY+l!md>YnVsOTQX8kNDrs+&SUp%DQg$,[,!d=WonG`Kmr*T*r"9:.&hdc#cEJaLPGj7Ij~> j7LLV$KpEV8HK#YT$cT\mcT/`Vre*CVZ9!?rs+&SU9(iCe)IOm!d"ElnG`KOr$1k6"9:*ufA_AD NTL;E!.XM j7LL]$1?H$('>SIj6l>dm?6i=l>HZQir9:7!)KW'_p[H/Y3c765k"^QdJ]g?dIdGB5QJjTiTu-_ NpQjk!:TpfGl:88V"O`k^AsMJi9R*(!pfnPg]%J.!3*BEe`MGMJaLPGj7Ij~> j7LL]$1?H$('>SIj6l>dm?6i=l>HZQir9:7!)KW'_p[H/Y3c765k"^QYl.o/Y4V_t5QJjTiTu-_ NpQjk!:TpfV>Z/L_tF$5^AsMJi9R*(!pfnPg]%J.!3*BEe`MGMJaLPGj7Ij~> j7LLV$0T`i'a#GBh<3sPk*"j/j(n^Gir9:7!)BK!^WkHnY3#b,5k"^QP5QU*OS&PV5QJ^KgZ="K Nog@a!:Tpf>5]ZGOS/VW^AsDAg>nsi!p'DIg]%J.!2d';cej<9J`b&9j7Ij~> j7LO^#O^lc9)oB&gk"jQm/uKe!5R[q#_2nNVm3t\g?Gd-!jVfUn,EC8rhBJ@oD]!/!05=Yk^i,] m/[3:n,EB4rCcb^"9:*ug?#!p!BNL#rrrI"XLQ@(l%/5QmE"\ToRH~> j7LO^#O^lc9)oB&gk"jQm/uKe!5R[q#_2nNVm3t\g?Gd-!jVfUn,EBlra,Z.oD]!/!05=Yk^i,] m/[3:n,EBarJ1"E"9:*ug?#!p!BNL#rrrI"XLQ@(l%/5QmE"\ToRH~> j7LOW#j:6Q8H90"e`DAEO6-Q%B`Ps0rs4,T:8P,)aOK4PjoVXbJ+*C;P5Z[+ORrJU^As25f&D8C Nog?BJ+*C;>5]];rr`dc,i?J`b&9j7Ij~> j7LO^#k$B!(^)(\mI#PmO6m)9i\^_Ok5P_&!%4YI]#`I]XmH.5!:KjedJfm@dIR;@5V:"+iTu-_ OR3'm!:KjeGlC>9V"=Ti5QJ^Mhs@'(!jD[?h>[_1!)]o3bhME6m=FZGmGmhh~> j7LO^#k$B!(^)(\mI#PmO6m)9i\^_Ok5P_&!%4YI]#`I]XmH.5!:KjeYl7u0Y4DSr5V:"+iTu-_ OR3'm!:KjeV>c5M_t3m35QJ^Mhs@'(!jD[?h>[_1!)]o3bhME6m=FZGmGmhh~> j7LOW#NsNd(^)"UjalQSjojc"1]Y>Rrs6C?-D[`P`6d8@joKQ(mf*9MrZ_E)o)AkY/]#cfjalQU joKQ(mf*8jrZ_E)oD\tZ!5?_2je:f j7LR_#k%#d9E5W+i9pL0!Uf@DmXac'm0)V.1]W'irs=2U-`*uT`7!K,XmH-N5jnXSdA*Janc&bX !5?b6l%/5`m/]:umf*93r_)h^"ht%rbLu'Pm/lT55i)GEJ,lLR_U77lm=FZFmGmhh~> j7LR_#k%#d9E5W+i9pL0!Uf@DmXac'm0)V.1]W'irs=2U-`*uT`7!K,XmH-N5jnXSY[:b?nc&bX !5?b6l%/5`m/]:umf*9`reL(E"ht%rbLu'Pm/lT55i)GEJ,lLR_U77lm=FZFmGmhh~> j7LRX#j:9P8H9<$f]W.o!U&V6kCMcnjojYp1B;shrs=2U-DRWK^s1KkXl]XF5jnXSOs;35nc&bX !4p;'idp6RjoIMmmf*8jrZh"6"ht%o`m`k>joXU&5i)GEJ,lFL^ j7LR_#O^At)?VC_o).kXmd09$iUG_*jQ_<`WU0lUjkFDJJEm.@J,gsoY.)9pgZbg,!<@W;rrUTC U[\9e^As/4fB.YOOR3&KJ+!==GY:]*nc&bX/]6&qm%WkLm&U3&hu j7LR_#O^At)?VC_o).kXmd09$iUG_*jQ_<`WU0lUjkFDJJEm.@J,gsoY.)9pgZbg,!<@W;rrTE3 ?h"(!^As/4fB.YOOR3&KJ+!==V2'iDnc&bX/]6&qm%WkLm&U3&hu j7LRX#NsWc)$;4WlMTcIkN1d^g@3brh;a(KWTFBGhpu?=JEm.@J,gslX0fUaf&Edn!<@W;rrS?. ,Ok^:^As)-dGTTjei*ohu j7LU`#k%&e8H9?)h=(@1#4CsFc(&]Rnk]Kc:33l)mt'l+m0;n j7LU`#k%&e8H9?)h=(@1#4CsFc(&]Rnk]Kc:33l)mt'l+m0;n j7LUY#j:U( k(2[8k2Z)a~> j7LU`#O^Gr* j7LU`#O^Gr* j7LUY#Ns]`*!7a\kl0]LkNL'@3YVo,n-9(5CW>W%J``up#NadlR4nVkJF`^J^d.rJHE"-Y_TUKc X6'Jc!5S%&!Kn\qrriC']?/^eJ``-X!H\>/rrQ@KORW8R^At[Zdc$n^"6HB[JDpM5J,iEG[_pMJ J`_OG`oc=RJ,~> j7LXa'CP4o7g0Q5f'i"nmdS,,+pe8N"S)d%"p,rqe+)jWmA]IUlfHsOAGuT/JGK6F^^WR0!%<5q Yd_Eje(s0QX6fq3!:BgddI.#m/uTk!.a,0#_2nN WO0Ohh j7LXa'CP4o7g0Q5f'i"nmdS,,+pe8N"S)d%"p,rqe+)jWmA]IUlfHsOAGuT/JGK6F^^WR0!%<5q Yd_Eje(s0QX6fq3!:BdeYcdEo"TVT8bM2<5m?6i;G6%%1!N!^&rrp.;Lr7a&T^;jGDZG=Jrs4,T :T:_ j7LXZ'BeJ[6j46/d-0WSkNKWi+UJ/M"Ru["![)a4lK<`\WTF?FhUg%^rW)tKq>^K1%7'so-[M_n [(OB'fAPbkjoKQ(mJd3MORE,P!*-MJf&M>DP3)de!:Bde>*/4Q"ht%k^WtW-joaX[!.a,0#_2nM Vm3tZfAhGEJ`au7j7Ij~> j7LXa'CXb&*s4 j7LXa'CXb&*s4 j7LXZ'Bn"j*Wn0bk3(mhkfd]]":?V]GOg7fIt!ZthroXRVre3Fi7ZJu?X3O0r'C8]$7L;$Xg5IP ajJf"WTF5'!8.;T5Us1Ze)Gr@P3)de5jeRO>42Cp^ArkubLu!Njojf'B`N\Frs4,T:8P,)a4'%! k(2[6k2Z)a~> j7L[b'^k=q6jOH9d.$Gimcof`"VX_%`n'Y5!TE9omA0+hm-*H`eBGpn>Zr[/.k3#KJ:Xo0[CO&k bgbD,lDX(NZ2iPW"TVN2`n0@(m?6i;4$`35!7C67#/:.j^!>I#T^;mJiG&@CkPkj=!%4YI\]<7Y m"+PTmD\JQoRH~> j7L[b'^k=q6jOH9d.$Gimcof`"VX_%`n'Y5!TE9omA0+hm-*H`eBGpn>Zr[/.k3#KJ:Xo0[CO&k bgbD,lDX(NZ2iPW"TVN2`n0@(m?6i;4$`35!9*AG#/:.j^!>I#T^;mJiG&@CkPkj=!%4YI\]<7Y m"+PTmD\JQoRH~> j7L[['^+S]5mJ'2b3A'NkMqFM"VXUs_9h]$!SZOak+q,Hjl,"GcH!b[>$* j7L[b'CXe&*X"BklL+',nB+B5#tq8VmdKTEmXac!m2>9RiS2r1b/M32]XkYa]">\m`QHQXgYq>^ VsOPk!5R:f"TVN1`Ra1&m?6i;4$_L!#/:.i]?K'rTBudFhIut?l2M'?!)KMr\AcnQl@J>RmDSDP oRH~> j7L[b'CXe&*X"BklL+',nB+B5#tq8VmdKTEmXac!m2>9RiS2r1b/M32]XkYa]">\m`QHQXgYq>^ VsOPk!5R:f"TVN1`Ra1&m?6i;4$_L!#/:.i]?K'rTBudFhIut?l2M'?!)KMr\AcnQl@J>RmDSDP oRH~> j7L[['Bdti*<\6djQG[fl,6.&#t^rIkO7U7kCMchjr*:=gXa`p`PK7!\@/fQ[^`o^_8XUEe_B'E Vre&_!5R:f"TVH,^sV%jk*"j-3^DBu#/:.g\B!1]TB6:8fOb,6l2M'?!)B>k[D:&>j+6?Dk/?EB oRH~> j7L^c#k%)h56qp9cLCi##j[Em![s)dn*YboN9qAGkMtIRf$i!kbfe2RcHjtjgY_&Tm%s(NZ2h\n rriC%[`-h[JaJWf!BWR"rs$4 j7L^c#k%)h56qp9cLCi##j[Em![s)dn*YboN9qAGkMtIRf$i!kbfe2RcHjtjgY_&Tm%s(NZ2h\n rriC%[`-h[JaJWf!BWR"rs$4 j7L^\#j:ai_lUe_/d j7L^c'CXe&)?VpilL+',ma"B2#[P:6mXab^m1J^Mk2YFXh;$c>gtglHj6#XnU[8,g!5R=g#/:.; [`-k^JaJWf!I"Osrs$4 j7L^c'CXe&)?VpilL+',ma"B2#[P:6mXab^m1J^Mk2YFXh;$c>gtglHj6#XnU[8,g!5R=g#/:.; [`-k^JaJWf!I"Osrs$4 j7L^\'Bn(j)?Vgaj6,RekK-+"#[4k&kCMcPjq6_8i8*/@f@JL'f%8U0h;I>TUZMW[!5R=g#/:.: Zbb&JJ``-X!H\=prs$4 j7MF"mHt5AW\k%oCX)AJm0Dm<)$)%Ki9pL0mI#PmTC)[Fs69jRkiq?slKml0m/]75gA_A-!33QL f]dtSP3i:U!87AVJ,l^_bM2?Rm0)e j7MF"mHt5AW\k%oCX)AJm0Dm<)$)%Ki9pL0mI#PmTC)[Fs69jRkiq?slKml0m/]75gA_A-!33QL f]dtSP3i:U!87AVJ,l^_bM2?Rm0)e j7MEpmH4`3VDSVjBZKB:jp0n))$)%Fg#r7pmH9&_TB?18ro4%O J``-X!Nc@Trs!uRWjg"!jIPE9jl"h=@f??.JACpZs1f".!!#loXL#RYce%*+k(2[2k2Z)a~> j7MI#!UK"?kS<:Bfs"*X"ZW:Bn*TN/dR4BF?,Q'trU'XQ!UT+@kQ0r6JaJ$UZL%ZR5hc5BJ1.M1 akH!1m?6iHm-!W"Ghs#^ZJaL8? j7Ij~> j7MI#!UK"?kS<:Bfs"*X"ZW:Bn*TN/dR4BF?,Q'trU'XQ!UT+@kQ0r6JaJ$UZL%ZR5hc5BJ1.M1 akH!1m?6iHm-!W"Ghs#^ZJaL8? j7Ij~> j7MHq!T`81i=t5,d]5qJ"ZE"5kiV$hb/'+rk5XTFjl5dg!TN3lk(2[#joIJ-gA_A-.&s_p eDl,BP3)hJ!5R=g#!aT;\B!4_SE:%;iS)`%?sN[2rI>KqJ:KpO!!"EnW3!85^rasVidp6Ck.p-> oRH~> j7MO%"RkU&]!Cn3'X"*P0*D=qIamlSmI'&$&-3oCipQa3"R4[_]!Lq3!kHbFJaJ$UZg@h!!5R@h "oqN(^!,=!JaJWf!p]hOgA_?W;6[d\hs?g!#jLC(eBGm<>:fXO%4NcVJ?Q;g]u/%BgZ%LLm=FZ> mGmhh~> j7MO%"RkU&]!Cn3'X"*P0*D=qIamlSmI'&$&-3oCipQa3"R4[_]!Lq3!kHbFJaJ$UZg@h!!5R@h "oqN(^!,=!JaJWf!p]hOgA_?W;6[d\hs?g!#jLC(eBGm<>:fXO%4NcVJ?Q;g]u/%BgZ%LLm=FZ> mGmhh~> j7MNs"6eaf[IL=4WLIo["9KKfcfsTSkM;72!?[W^kPXHFimkrRY48RpZd%Q^k(2[$joVR`^t\kn !)Tc-a4KF(k*"j.jT%R"rrrI"Xh)[.jdP<;j4r21a1tq_/,K>H.1:QQXK]%A^W+RLgZ*kIJ`a`0 j7Ij~> j7MO%"S0ZI,80P^&ebZU!!3jMbO4fcmH`"a!s)ZJrTsjXm,+gQ1EI>&'a#*mC!V@tJaKf2!pfnP h#@S/!.;)gd,0EAOR3'05hl;CJ,iKQ_9q5/m2G?SiS2o.aMP[&\@/fP['mHT^;@q8e(WaDm"+PT mCquJoRH~> j7MO%"S0ZI,80P^&ebZU!!3jMbO4fcmH`"a!s)ZJrTsjXm,+gQ1EI>&'a#*mC!V@tJaKf2!pfnP h#@S/!.;)gd,0EAOR3'05hl;CJ,iKQ_9q5/m2G?SiS2o.aMP[&\@/fP['mHT^;@q8e(WaDm"+PT mCquJoRH~> j7MNs"REs8+qjG\&ebZU!!3jJ`TQFHk2aNJ!s)QArT4@Jjk6G;1E@5$'E\siB#oPcJ`a<$!p'DI h#@S/!.1o^bLqI/OQHR'5hl;CJ,iHM^!,5ujpU;/gXXWl_nN^k[Bok;$a[4K]"Z&&cICS,jalQF k.^! j7MO%"7N j7MO%"7N j7MNs"6cUB$1@lt!!=X!c2Hc:kLkt+!#V6]kN:pijMPLE"Tn,n!XUojJ`a)s!3P_SO(+`5g$Gdb!73jalQFk.Kj: oRH~> j7MO%"S:#f4=ftR"thf1J@a\*rTsgT`_$73>0['6$1@,r=:kT7$m-qh/-@C`Ib');XmH.p!9QKH !4&Hg!j)HQhZ!h2!)]o2bLu-2m?$]:lo78crrrI"XLcO,l'1QJm-*QjhqZr j7MO%"S:#f4=ftR"thf1J@a\*rTsgT`_$73>0['6$1@,r=:kT7$m-qh/-@C`Ib');XmH.p!9QKH !4&Hg!j)HQhZ!h2!)]o2bLu-2m?$]:lo78crrrI"XLcO,l'1QJm-*QjhqZr j7MNs"RO9T3[sVM"t_]-I'hSgrT4dS^dnJ+=2s@ak3(g< j7MO%"7PO*_XY8A]u&%Mkksf^mbO$>!>qNjmd9H2cYjO"#Y0cT](3K9]u&CeJaKT,!jVgAkl1_! !4&Hg"7%oH^u,.r!)BK!_U77lm>gQ8EWEo3rs+&SVQmn^h<^9k"RPI j7MO%"7PO*_XY8A]u&%Mkksf^mbO$>!>qNjmd9H2cYjO"#Y0cT](3K9]u&CeJaKT,!jVgAkl1_! !4&Hg"7%oH^u,.r!)BK!_U77lm>gQ8EWEo3rs+&SVQmn^h<^9k"RPI j7MNs"R+ml]smR@"Lu+bf&?Ph'^+&,'*/o?gumh_kf@QS!XM8MW3l-("1Pqij+6?pjoVXb_!(e" ^At@3joa['!5RLl"oqApYeJ?7J``!T!cA"RhZ!h2!2Qd0b1Ga;jojf,io/hphZVlrj5]6Pk(2[( k2Z)a~> j7ML$!q,F@nEL,AmI'E?m1\d":]^c#d-U/en)U&l!t8MEgZS(k!pf7AJaKT,!jVgAkl1_!!4&Ef !p0JJi;X%4!.(fYaOo^-m>gQ8kr1lbrs-=>:TLqDe`)MSrp54'JaKc1j7Ij~> j7ML$!q,F@nEL,AmI'E?m1\d":]^c#d-U/en)U&l!t8MEgZS(k!pf7AJaKT,!jVgAkl1_!!4&Ef !p0JJi;X%4!.(fYaOo^-m>gQ8kr1lbrs-=>:TLqDe`)MSrp54'JaKc1j7Ij~> j7MKr!pA\+nDaW3k3(q*jqHdc9`bGrbN.gJkhVUU!t8G?eDTiV!T`?nk,d\HXT60#rrTq8XJFt[ hZ,q"rs+&SGcS9kdc#c>NogE'2F-1"#J^=7W3a=dgPb4njalQFk-F.0oRH~> j7LO^s6^QTNZ%s;U?:n2mdS5*$jf,/kO.bhm@!>CZ2h](rrTq8ZD?jilDXc6ir9:7!)KT%_U76* JaJKb"73#f^u54t5ldde^!5 j7LO^s6^QTNZ%s;U?:n2mdS5*$jf,/kO.bhm@!>CZ2h](rrTq8ZD?jilDXc6ir9:7!)KT%_U76* JaJKb"73#f^u54t5ldde^!5 j7LOWs5t'FM&?@4S_rVmkNTfi$OJr&iTKBTk*b?5XT60#rrTq8XJFt\j.l[*ir9:7!)BGs^ j7LO^#O^lg;?I8)fDb.Cn)Tif!um'XmXablm/jQn_!(e"^AtR7m/uNg!.a/1#f$EfVQmhXf][nR N9p`12ZUYQrs+&S:oq1He_u)HJaJ$U\F&>KJ,~> j7LO^#O^lg;?I8)fDb.Cn)Tif!um'XmXablm/jQn_!(e"^AtR7m/uNg!.a/1#f$EfVQmhXf][nR N9p`12ZUYQrs+&S:oq1He_u)HJaJ$U\F&>KJ,~> j7LOW#Nt-S:BLr#dJi86kh_LR!ucjLkCMc^joVXb_!(e"^At@1joaOV!.a/1#f$EeUoq8Ke)Gr@ N916$2?:PPrs+&S:T:b j7LO^'COJ+*WmRWlgF0-n&8]E$:Ml#mXablm/XHQ!!'="m0)e92?89irs6C?-DdlUa49+!m>L?6 lf9r(j8TC8!)KT#_9^p$RdC*\jEH1BJaL/ j7LO^'COJ+*WmRWlgF0-n&8]E$:Ml#mXablm/XHQ!!'="m0)e92?89irs6C?-DdlUa49+!m>L?6 lf9r(j8TC8!)KT#_9^p$RdC*\jEH1BJaL/ j7LOW'Bdbo* j7M9ss6]mUnEptUg7:M3%o_0LmHsB)PV*%]L#h]Rm?[,>ZK-2&m0)\22#r0jrs?I@-@2f&_pI6( JaJ?^"6uca5ir"OJ,iBCZ+S93j-f97466GB4+ZdMmChoIoRH~> j7M9ss6]mUnEptUg7:M3%o_0LmHsB)PV*%]L#h]Rm?[,>ZK-2&m0)\22#r0jrs?I@-@2f&_pI6( JaJ?^"6uca5ir"OJ,iBCZ+S93j-f97466GB4+ZdMmChoIoRH~> j7M9lmH5/DeA3J$=Dk.Tp;oRH~> j7M:36m,b#C!5Rgu$%N"'VQRJLeDKPZm/]9ZqDnUln,EDR4+ZdMmChoIoRH~> j7M:36m,b#C!5Rgu$%N"'VQRJLeDKPZm/]:2qX4FUn,EDR4+ZdMmChoIoRH~> j7M j7M?u"Rb?bM/Mj%'4jQO"9p,oiU?1$mcorf"!iQ]mt'kWm?[,Dm,cu7!!%Q;rsHP-!)TVt\AHPF j+6TQm02h9UGDn#lMh0@!)KMr\\uqPl'Ui=3tM0K!,M''!C/_tm=FZ j7M?u"Rb?bM/Mj%'4jQO"9p,oiU?1$mcorf"!iQ]mt'kWm?[,Dm,cu7!!%Q;rsHP-!)TVt\AHPF j+6TQm02h9UGDn#lMh0@!)KMr\\uqPl'Ui=4$ j7M?n"R"UOKkp6q'Ph/F)?U0gF5#j?k3(cb/-@,IhWTOQJ``9\#3FanAH2^\mf*X05la]WXL,^` eD5] j7MC!"Rba(E>.a?"TeuXG1H-^m0N4*=UbX0_X6cBm=FYim0Dt=gsT[46%JP+%)>./-[Mi"]>`.M j6V?aL@#00in.iCJF3@D^]4@2WNj%RdG=&Rm/]9ZqDnUln,EDR4+ZdMmChoIoRH~> j7MC!"Rba(E>.a?"TeuXG1H-^m0N4*=UbX0_X6cBm=FYim0Dt=gsT[46%JP+%)>./-[Mi"]>`.M j6V?aL@#00in.iCJF3@D^]4@2WNj%RdG=&Rm/]:2qX4FUn,EDR4+ZdMmChoIoRH~> j7MBo"R#!hD%c7:"TerVEmO%Mjp:4j./-[DYo\%p2: h;s4ML?8["gsT[4JF3@D^]4@1VQRDEbh(j@joILRqDnUln,EDR3J$=Dk.Tp;oRH~> j7MC!"Rbd'D@u=="U,f>T&B):m0N.IOX^AXL#qcSm=FYgm0W+?hUQ0k!!%P"q#:i>5sYK!H`F9X ^W=jYk3RZdK^B$0in.iC!._lUrsQV.!%3/q[(XN.guk!j!BUhg3WNsarrGAHJaJ$U^$XkPJ,~> j7MC!"Rbd'D@u=="U,f>T&B):m0N.IOX^AXL#qcSm=FYgm0W+?hUQ0k!!%P"q#:i>5sYK!H`F9X ^W=jYk3RZdK^B$0in.iC!._lUrsQV.!%3/q[(XN.guk!j!BWF?mf;tJrrGAHJaJ$U^$XkPJ,~> j7MBo"R#!fC(]n9"U,c:RbI!)jp:/4N@FrRJ_oUAk(2ZYjpC/,f[""Z!!%P"q#:i>5sYJuHDmpO ]>VtFhrTFOK]WO"gXB[5!._lUrsQV.!%3)mZ+7csf&2kV!BLbf3WNsarrGAFJ`_OG^#nAIJ,~> j7M?u"RkKjNGe9*"F#m4f'3,"#k6/=$jKPDm-]GlJaJWf#41O2f?TRdr;c^\rW!9SIB9Z\]>MnE hWKIWJaEd/j4hsk!!#<8_"dp=^d.r#."//$\A?A=h<1'j!BU;W&+;db!'>c!JaL/ j7M?u"RkKjNGe9*"F#m4f'3,"#k6/=$jKPDm-]GlJaJWf#41O2f?TRdr;c^\rW!9SIB9Z\]>MnE hWKIWJaEd/j4hsk!!#<8_"dp=^d.r#."//$\A?A=h<1'j!BU;W4RYQ:!'>c!JaL/ j7M?n"R+aVM/2Zu"`8=l=["62gik.!qqqZF@Ti d+I9-k(2Y$j5&80?iU0mJACs[%DY70!%3)kY-ks_ce%*?joILCq%)p[!<>-2k(2[.k2Z)a~> j7Mrau-&CRKJaJ$UO6mnNj4r20`kYh_==YmO.TM"MYHbLG^;S4D gZ%LLm=F[)m0;n j7Mrau-&CRKJaJ$UO6mnNj4r20`kYh_==YmO.TM"MYHbLG^;S4D gZ%LLm=F[)m0;n j7M j7M6rm-ac<#k65;%L,qGm-]GlJaJKb)=6PGgt'ip`kfC%]",A_]=l&"b0SVli90HXm=F['m0i:C i7ZN#_i7l'.kCQJ%"No4Z*q3X`m*#gj6D3_JaN@%s6P=(\aAGLJ,~> j7M6rm-ac<#k65;%L,qGm-]GlJaJKb)=6PGgt'ip`kfC%]",A_]=l&"b0SVli90HXm=F['m0i:C i7ZN#_i7l'.kCQJ%"No4Z*q3X`m*#gj6D3_JaN@%s6P=(\aAGLJ,~> j7M6km-"9.#jKN,%L,h?jl_3WJ``!T)M(Dk(2[njp^A0 g=+9b^PZ/r.k3%J.1CZTXKJk;]>;S7f%f>;k(2ZGk-=(/oRH~> j7L=X#k%)D,Qf?NhX5s^JaJB_($k)EhqQ`2d*BkYbKS5Wdam./j65i\m=F[#m2>6OhqHT*aMYd+ ]=GG]\\#Pk`lcZZgZ%G`JaJ$UJaK]/j7Ij~> j7L=X#k%)D,Qf?NhX5s^JaJB_($k)EhqQ`2d*BkYbKS5Wdam./j65i\m=F[#m2>6OhqHT*aMYd+ ]=GG]\\#Pk`lcZZgZ%G`JaJ$UJaK]/j7Ij~> j7L=Q#j:?1,6K3Hf]RhJJ`_mQ($4E1g""KqbK7fE`lH0Cc-Xtmgu7@Gk(2[jjpL2,g!n j7L=X#O^)9%0fnHm"+PTm>1-Am-hVmMWl0NugJaN0u(@:ALin`8;dE]tZb0%oP cdLD!hrEqeJaJ$UJaKW-j7Ij~> j7L=X#O^)9%0fnHm"+PTm>1-Am-hVmMWl0NugJaN0u(@:ALin`8;dE]tZb0%oP cdLD!hrEqeJaJ$UJaKW-j7Ij~> j7L=Q#Ns?)$jK_@jalQFk(r.(jl>:TgY(3WecsaYgYLfJjalQFk4[pQjlG=Rf@/'iaiDE?`Q-'B c-b(ph;[OIk(2ZGk-!k,oRH~> j7L@Y#k$uE*WdOHhX5s^JaJ0Ys6TdN#O1I:l0@R#m"+PTmI9KIlfdHji838fgBQKjiT0.dm"+PT m=FZ+mGmhh~> j7L@Y#k$uE*WdOHhX5s^JaJ0Ys6TdN#O1I:l0@R#m"+PTmI9KIlfdHji838fgBQKjiT0.dm"+PT m=FZ+mGmhh~> j7L@R#j:63*WdLCfB7_IJ`_[Ks5aCEj5]+Xro=%?J`_OGnE'r9iSWGDf[g&P#M.MZgYLfJjalQF k(2Zrk2Z)a~> j7L@Y#jg&7#mFDFm-]GlJaJ$UJaJ$Ujm j7L@Y#jg&7#mFDFm-]GlJaJ$UJaJ$Ujm j7L@R#j'?(#m=8=jQD*VJ`_OGJ`_OGjlZU)s5X+;"QJJ%jlM'UJ`_OGWTN75J,~> j7L@Y#NVBj!ZdK]mXabVm=FYUm=FYUm=FYUm=FX)mGmhh~> j7L@Y#NVBj!ZdK]mXabVm=FYUm=FYUm=FYUm=FX)mGmhh~> j7L@R#2PU\!Z[9QJ`_OGJ`_OGJ`_OGJ`_OGJ`d!pj7Ij~> j7LCZ#OKo;"9VZAm"+PTm=FYUm=FYUm=FYUm=F[)mGmhh~> j7LCZ#OKo;"9VZAm"+PTm=FYUm=FYUm=FYUm=F[)mGmhh~> j7LCS#Na3+"9VQ8jalQFk(2ZGk(2ZGk(2ZGk(2[pk2Z)a~> j7LF[#k$up.g6iZhsQ'_JaJKbj?*FJJaJ$UJaJ$UJaJQdj7Ij~> j7LF[#k$up.g6iZhsQ'_JaJKbj?*FJJaJ$UJaJ$UJaJQdj7Ij~> j7LFT#j:6].KgWRf]RhJJ``!Tj>m:BJ`_OGJ`_OGJ``'Vj7Ij~> j7LCZ#40iH+(rmcJaJ$UNU6]Gk!&kHZ@VCom/cTuj?/"\JaJ$UYO1BBJ,~> j7LCZ#40iH+(rmcJaJ$UNU6]Gk!&kHZ@VCom/cTuj?/"\JaJ$UYO1BBJ,~> j7LCS#3F*7*b3:QJ`_OGNTL3@k!&kHXF]MbjoOUdj>qkSJ`_OGYNFm;J,~> j7L@Y"ROs_hX#g\JaJE`!p;oRH~> j7L@Y"ROs_hX#g\JaJE`!p;oRH~> j7L@R"Qe4Kf]@\HJ`_pR! j7L:W!:5:(JaJ?^! j7L:W!:5:(JaJ?^! j7L:P!9JdoJ`_jP! j7L:W!:GF*JaJ?^! j7L:W!:GF*JaJ?^! j7L:P!9\pqJ`_jP! j7IoiJaJ$Ubj>E2_!(e"^AtQjm=FX+m<8R%3WNshrrIWMJaJ$UJaKZ.j7Ij~> j7IoiJaJ$Ubj>E2_!(e"^AtQjm=FX+m<8R%mf;tQrrIWMJaJ$UJaKZ.j7Ij~> j7IobJ`_OGbiSp+_!(e"^At?dk(2Xrk&^Up3WNshrrIWMJ`_OGJ`a/uj7Ij~> j7IoiJaJ$UbjFHk!4%1CJaEO+G6%4q!,M<.!It0$m=FYUmB>p;oRH~> j7IoiJaJ$UbjFHk!4%1CJaEO+G6%7'!;#pa!It0$m=FYUmB>p;oRH~> j7IobJ`_OGbi[sd!3Cb6J`[$rF9(nn!,M<.!It0$k(2ZGk-*q-oRH~> j7IoiJaJ$UbjFJ7JaJ$Us6Tf1j8abud-^l!#jL^Clg4!(kj*feJaMdjj7Ij~> j7IoiJaJ$UbjFJ7JaJ$Us6Tf1j8abud-^l!#jL^Clg4!(kj*feJaMdjj7Ij~> j7IobJ`_OGbi[u,J`_OGs5j<'j8abnd,tAh#ik%/jQ5LaioG[QJ`c:\j7Ij~> j7IoiJaJ$UJaJ$UXmH+ojE?+AJaJ$UYO1BBJ,~> j7IoiJaJ$UJaJ$UXmH+ojE?+AJaJ$UYO1BBJ,~> j7IobJ`_OGJ`_OGXl]VcjDoh6J`_OGYNFm;J,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IoiJaJ$UJaJ$UJaJ$UJaJ$U^$XkPJ,~> j7IobJ`_OGJ`_OGJ`_OGJ`_OG^#nAIJ,~> j7IoiJaJ$UXR-&RD"m;tD!HT=JaKZ.s!n.&7^1J2r@ j7IoiJaJ$UXR-&T_tWO!_sDsAJaKZ.riH4/g",QhXb#kjmAB:2oRH~> j7IobJ`_OGX6'EPpZ;JGJ`_OGY3,`W!V>X.k5G")k(2Zlk2Z)a~> j7IoiJaJ$UhemB>m[/+)Z:37dHs.tR)= mA'(/oRH~> j7IoiJaJ$UhmB>m[XmPK2Z.A:BXb#kj mA'(/oRH~> j7IobJ`_OGh<"k'n+HAAioUC1oChe@q;q_EmJc5Rlh'oClfR?sm"+;Mk-*nMp?(N&o[`.)p4;@W k+h)!oRH~> j7IoiJaJ$Uhn*/+)W17I;TU/l0%F RRA_X/+&cn/Lr@=_+QCR_ j7IoiJaJ$Uhn*XmPK0[dC3!Y. j7IobJ`_OGh<"k.n`T*(ioUC6ki_=*q;q_Kl2KfNo]kW)o]G<#nq#qSk-*nNp?(N&o]H/H"S:sA mI^DO!p8nBrq6rdlLX`@lK7C'p@IS3p?hPFm/#iIp@S%Jk^hlIk,7A%oRH~> j7IoiJaJ$Uh j7IoiJaJ$Uh l*>m-JaKZ.riH4)gA^I^m,#)9Yg;@qmHq.(j6Q+"\('`m\*^ZSldN?1XjZOiZM j7IobJ`_OGh<"b1kl'WZlLX`@lK77&me-26p?hPFm,mU)p@ISDjoO]>q;q\MrT4CWlf[R)k2tk" J`_OGYNGiX!VPaVjr`m]k4S0,kN:q"mciosk4A!'m.9B2mH j7IoiJaJ$UhmBU:n22gk-AOeV9m4IJfjt?$Qk3_:`m47Jh m47qu!Tu+em=FZ&mGmhh~> j7IoiJaJ$Uhm=FZ&mGmhh~> j7IobJ`_OGh<"_2r8nddmHp)3#Yo(2PBk3MU0p$Uu9kN:t$k4\!#kP"'3roO4S pZ;M>p4;@Wk,7A%oRH~> j7IoiJaJ$Urp9]kr]L,Z"$CdOZL$rZ5LfZ!#VG3+jt?&g/*['#$7kE/m47QUk3_:`q<\:)7>L4) m/]<-rp9t^[HFnhmHrjpJaJ$Ue*R1A4T5<\5Q603'`\U?!<762m0YB2m(P@E0t-5O/,no;lb#"P m1ATr1@7].k+GB78ttI@9C48/m/S[3m/l=0<.XFfmAK@3oRH~> j7IoiJaJ$Urp9]kr]L,Z"$CdOZL$rZ[,h2Q#d3L\l`TXBXm>uR$E`[^mB,cYlg=#@q<\:C[`n+6 m/aNWrp:!"g?[J%mHs/DJaJ$Ue*R1A4T5<\5Q603 j7IobJ`_OGroO3_r]9uX"$1OGXmG0No]Z89#PR?CkP"'#pAO+RpAX1Yp?))3k3)I6joXf>meGoF p#l>:#kR?ElLX0#kOjDgJ`bGD!NeE^!!#7_J,Xcp4oPE^Ec^^3$MNWElLj<4lK@I+rT4.Gp&4"` lLj<2mH j7IoiJaJ$U!O=ie!<5:`J,U/_$N:#+G3n\b%>8BKiIgMiU?8+AmHlG&m/S[9m05*.8ttI@9D\#W U/So&m/^e:rp9tL[HP(YmHqf&JaJ$Uea3Na!!#6u^\n-0.fgb-rVutAm.0QD.j?N3VsTI&2j5cZ .k<2>Sa@pX/OZ/s^e-1N_ j7IoiJaJ$U!O=ie!<5:`J,WIK/,fMLG3n\b%ElTNl,rD&eEbSmmHq%Sm/X9fm09][\('`m\,,In e@ON2m/b#\rp9urg?[LumHrfHJaJ$Uea3Na!!#6u^\n-0XoX,TrVutAm.0QCXU)>cmEMo@c`QU6 XTGoYmJa3u[F4I!\$!3smG>=T\)-MSrp9]fqX+ j7IobJ`_OG!NeE^!<5:`J,Xcp4oPE^EolcP%I`iMk3qI(mHjmqR5p'C j7IoiJaJ*W"a<'/5JOu3rrC4>/,p@a!!.1bna-eKIQKEMmD)=G9#9o^/,ei8/,no?/+(,E/PRm=FZdmGmhh~> j7IoiJaJ*W"a<'/5JOu3rrDB_Y5_Q&!!.1bna-eOaKi%rmG>=T\)-N>Xo7VeXo@\lXmOuSY.*9a mJO'dlMBlKf$F?_#hQJd[Fjm6cdm[7JaM(V"L:,i5JR$oq6'`u!s!B)l@J>Rm=FZdmGmhh~> j7IobJ`_UI"`ud*5JOu3rrE#qp]#U?!!.([n`C;Dn+HAAk3DR3oBPK#pAO+RpAX1Yp?(Z3p@ISG k5=oQkPF j7IoiJaJ0Y"L:,i5JR'p!6Y*f"Vh";Ep2l-m/cKgp[%utl@J>RmF^e.G5skBp]'t<"b-LPEp)e? m=FYUmH j7IoiJaJ0Y"L:,i5JR'p!:0H`"]bU'Ep2l-m/cRkp[&!um"+PTmF^e.G5skBp]'uj"b-LPEp)e? m=FYUmH j7IobJ`_[K"Ka]b5JR'p!<)`f"a^4LDrKfpjoFrGjoFq&k(2[Hjo]]*5JQsmp\+i@!!$oqjalQF k(2[Xk2Z)a~> j7IoiJaJ3Z"*X`0^\Ig/b4dCiIfKJKkj3lfJaJNc!d=Wop&Fe;#F,8gIfOEJl@J>Rm=FZgmGmhh~> j7IoiJaJ3Z"*X`0^\Ig/lh@OcIfKJKkj3lfJaJNc!d=Wop&Ffi#M/qTIfOEJl@J>Rm=FZgmGmhh~> j7IobJ`_^L"*=N-^\Ig/rV-$iIfKJGiT5XQJ``$U!d"Elp&Fg]#Q+Q$IfO<@j+6?Dk(2[Yk2Z)a~> j7IoiJaJ6[!d=WopAY-8paZT6s8P=aE96D:m=FYem/fBMJ+Wd:.f`rTrrdfOD;sc1m=FYUmHO#u oRH~> j7IoiJaJ6[!d=WopAY-YpoXkQs8P=aE96D:m=FYem/fBMJ+Wd:XoQ?;rrdfOD;sc1m=FYUmHO#u oRH~> j7IobJ`_aM!d"ElpAY-kq"G$js8P=aD;O?)k(2ZWjoRRDJ+Wd:pAk$frrdfOCYRg!k(2ZGk3;$g oRH~> j7IoiJaJ9\!^$I4p&>$7q'uH0rVm"M!,gi\JaJ$UPO/Eq!.XV?q'uH0r;QnL!,UQTJaJ$UJaMai j7Ij~> j7IoiJaJ9\!^$I4p&>$Xq5s_KrVm"M!,gi\JaJ$UPO/Eq!.XV?q5s_Kr;QnL!,UQTJaJ$UJaMai j7Ij~> j7IobJ`_dN!]g=2p&>$jq=amdrVm"M!,LBLJ`_OGPNDpg!.XV?q=amdr;QnL!,:-EJ`_OGJ`c7[ j7Ij~> j7IoiJaJ<]!d=XZo`"p6q'uHer;QnL!2AE8JaJ$UQ0eWs!.XS>qC;Q1qYp\J!1r'1JaJ$UJaMdj j7Ij~> j7IoiJaJ<]!d=XZo`"pWq5s__r;QnL!2AE8JaJ$UQ0eWs!.XS>qQ9hLqYp\J!1r'1JaJ$UJaMdj j7Ij~> j7IobJ`_gO!d"FWo`"piq=amfr;QnL!1hj&J`_OGQ0&-i!.XS>qY(!eqYp\J!1MR!J`_OGJ`c:\ j7Ij~> j7IoiJaJ?^!d=WooD\g5qC;Q1qYp[`!2/96JaJ$UQgFiu!.XP=qCD@@"@)t9hs#^ZJaJ$Um-Wj* J,~> j7IoiJaJ?^!d=WooD\gVqQ9hLqYp[`!2/96JaJ$UQgFiu!.XP=qQBVn"@)t9hs#^ZJaJ$Um-Wj* J,~> j7IobJ`_jP!d"EloD\ghqY(!eqYp[`!1_a$J`_OGQf\?k!.XP=qY0db"@)t4g#@SFJ`_OGm,m@# J,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> Jb4NcJb4NcJb4NcJb4NcJb4NcJ,~> %%EndData showpage %%Trailer end %%EOF ssr-0.4.2/doc/manual/images/screenshot.png000066400000000000000000002121031236416011200204710ustar00rootroot00000000000000PNG  IHDRQ.gAMA atIME  ;QR&IDATx^|睿J.ɥʼnN{/7p X4{ #QEBQ$@tQl.wWzxئݙݙg?i5>;ﳿ}_9uiEï @D`˖-s+wWA @.6ڣκgs  @@p 9A{|4 @  cILόk[bƅ˫\et͛wu849 @#pe׷-tig+c)h$ysavȾstÀeo(4ojʚJ}oy~uo8L(2 @@Phv!*]:!~ǵ8K۷G;GTum>tv3_4vZfcA~9"C 5YԦCTt%Op6oy֬Yѷo]?5jX}}]j7{fk)[6n.MP)HHEŊYўí_۹f?ߧ; /^?rӾ/mw?x{Z&{)gxtno7X~ŢY {1Lk6ܗ?g'/,Yֵ_ѯodIMͦGO΋త@ >ZԦCtޔt?yTk߶m(9t+Z~J64p5qke1ī]^~w'ߚx֫yK۹!~u ];v7zx-wK+Cty7wx%տrb;]L-KyxkUmSUaZqp_vU)YP>c7V:,A @ cxr#]+tqsn=es-:_=oOJ&NtN:DDu >漽<[ۜg~g ozo^fvH'!;iz{qUVOLw{^敮:7KnkrlJ._=Q|e!蜻.ce/whE{Kuԧ+9oSg7ycum~ߔnkl\{" 3_leO/_U9oQPP袻]-9e5/uX v @@f\_ԦCt-щDitE{I{C"?yZ=ƾIcJK=C=oޣGŋDϾ}_:pח?Zx3z{\ۂ_=,{>҇~zً=01މ"e벣} }~?I=ϼ?⩲s }cd/f~y;2߼+6wѪߵ{}.z`ҕmV̂Y+ι9,A @ 3.zhʓtqϺ%:7́N잏=C"?yιؾYz{UNy6{0o>oZÁ;ݻPO֯6W{o{e?ڟv2\|U.\A?Non?]WNwz}s66\O/}6s춓|_YYWi3I%Ȟu7{= t=ξw>g 7kŪKڝ|g9鐻&L'WwM}}@ dIm:DY8gHě@'[\<\[A9{<]X#~=3quyz٫?=뾟=||ο?/<{';Ϗ.yԛzubek錻~|E%;ow꥔O':hS:gfLYg)_e˖5k?GǞy3n䎧?vEwwv٫l*-`V#C @ cλw]o.Mj!^ā?8D"4:٢suɯvC"?y~l6v숙3Gt\d}Z@ؿyx~8uC\?m&=w7%Ns?wqSɗzz}Ͱ?O/j7mAڳqk_N>ny`y[O:)t {n;C{.Z_q1SK~{vmXK[[o֯sc6h_/y_J'!tͧ^y~]nI;qV?jmAڳq\|ս:y?wOW~oie {Y9_p{8r~J=~T 5z6fS~[;={=媷Ͼs`<\@ 8xμ~SĦ7e53:ٞ3WV'8eOͷo)S?dRM8~Y -ٷl~ɏ&L}P?Dnz#ϼ|k;wOiz\i tί%N:~rEӮw_%ϼK)Ǘ<7'C"$]'h⇧p|\`ڇ^xo]['[.\ϸuXv @@f~[_ZԦCtR?y~f}3nmۦIƍ1c"􃒺~;_pK\_Ȕ/+w3~՛?\Nr./Ϲb\䬋7A]׾uuqco]:}vt?3=!@ 2$e,*O\V-cyb~ln3{Ĵ7PpEW>^^˧g__<6 h=WmwRf-O~r-].zp. ? @9Snxt N sM%-Y8<ܿ^;^.]Zػӯxj/.W_^oM63ox{pát @A$pڝ CTҔtB)Op_Y+.];lrݯN׽$?Z1oI 0^|ru/-v~ @@pig+c)hi&~ 6n|mͼCgWoX8wes< 輤E *tԙKo}o|wwSnx) @H s[+ْ"p˛+k;WO6hk׮۰rMuѼ!3ya~nͯ/I*}v @$pj }ٵ\Ku=/ܗo\^;+O @"p_ @ qp$% @byh @@  @}6ld;˛_!Wzkjj̿_R)--2"b~;y֣yWUޣG{ׯyb !{\>l? xA`„ L7W4DjzȒi8/;)zzS )߂ rpsW^m׽{}CCC_Xg y]}dA~zر~)ʉ\%f6t b 1_:j Mw__ثWϞ= Dmt0|饗om۶8OfA4vX 4^sȽW[fw˂9-4?,>gqr}1_eA#w-l8iHwy| __[έI%&Bey+KsEw>pF}}vŔI|48́ooݟ$W<IW9Chرc y6k[mͶ8OX? -!.XR < &sa_9Om;vhVCb bq9xB=gg7|s"C<> eƶmHqt_M Vf~ӟhJN+6 F~ <^>u}<09\";dOeKEZ3n8/:҇[D|@@pnH牨'3vi_j*ͤ(GTf;8Ϻu+Ep뎞Yl8q.Cv:w{G(K&NcHZN$cn>d dyuba{įU93L'(?vÉ~'Zv(if[Y22kRyHx/EyU${ ͛~8KΖКc$'4f'&mѯtb>NI:wOLI8OP>ȧG[k\0}Ÿ5_s_xy+0)Ӌ=o#]Q_c:g}V=&~/vJKi[f.Zfġ___%bgm"~nGSzkw>OL1v/H}'5!Q8Ov?#8{ lL<_e;%-++~СC+ԟ"Y^ /do֭]v__D?bISICo9<1&o1bĉ?JWLI4fY|m󘞨h߿KAzi>w5<)"c' p KjyBn]9.`!HxG_7Z Yvqj+X;t“$wϟ;κ5Ǫ}y2Sm-o!.՟1Uܹs0X`ίrxl8Oĸ4y&><o%VtVc>_5U+kz?Rx(<-[4-1Sqvpt$< ' r 0f3g9xnzSH*WZ9ORL9&NC'qKfd^R;Γp-ўZy d}ڒa~qqkq*n&$8 nCp]ti\I<Iyb46$Ul<^9Om;v=zTm}8IϓU)p6:wǕ6,F{\d"†eˆD;vĕoJns}}c;Oγ`.ؒ%s7hD;5@Ζ:!e8ӭwavǭ%@ k޷Sw 6/[li^:(?UbbͶoGK@ Cׯ_p}YIv!@ pVY ?{既71s8Xc}ݧz G B8O+"A<3fǶ[o~s|>9#  '+"C @ <9T @9HJ @!8OU6E @@yr)2 @rΓCMQ!@ pt @ "PeST@  '+"C @ y>`mgAl)93Ug8'eg=3ARn IGΓfI8 @@4V@ ڥl @C @LPi~ed9|8g Yhp d@ +_JviӦ}ؾ}{jg(1 r؈DYR@9msZ7.p2ISSSs%ԮT[oժU> FcLPoxڵk /Ҋ8'՘Ŝ??o߾妡!C~}_Ma)Xwg9gO@ <^{{1p}uCOgΕW^;ry[ܒKos=C*iPZJgCTKۿ]h!jo[@hyb~m~KD+̯7}Ywܑ;>:z7OE`@׳gO}>CMG~W;(_|ɿw2r{noGɘjnJ3@p6e7׈[AкXǭ`Ե_XX[m[/_{wwZ8rȝwީ{4 뮻3|wiZ/s8ZPY=/R:<*;C&Sill}κU]h 0g| /zv/{|qɒ%˗/<YA[g̘ɢѣ[nEmޞ#j!0+#'䣏>Æ fψ_:\7x)]B/`~UȂ/29ѣG@!?s;7 |;5kt-,\ZʔZL\@v y~_髬Cn,X f[;|k_۵kWW+VЇ9Žo18<˖-+//vzjP?>ͯ?V"uvq~njꇘm^u 27ʪ `„ _#~sn޼Y7-W/y/^l59S/_9O=k`xs0=qCV]DԂ)>}ygO@ <צMU8^AmZc _SO=O{·Σ<5 "Йm Iٟ,QEcǎ!"f,/q,WK?Dw; :/Q %~,vIp:W8;'`:|=z;U75ت'>lBo:wlwɆs8ט}0as:q N d'+ >Y4&XkU+zy g,)8g5l!D~]ОgH @p%w'N1Y[uI'eeBTEKpsY p @ f8OkA @8m @3'̵K @ 6@ ڥl @C @L gGL}ԒsRws:IJyg8.SY7,8ϑ# @  x]8@ dΓ:!@ <8 @ f8Ok;W&e@ ΃@ @a&vb @yp@ @ p0׮wL @  @L sż' @#< @@ !@  @L szʤ @ yp@ @ p0nP̛|B @;8@ ΕI @A!< @@ !@  @L szʤ @ yp@ @ p0nP̛|B @;8@ ΕI @A!< @@ !@  @L szʤ @ yp@ @ p0nP̛|B @;8@ ΕI @A!< @@ {s  @)X9G~SN!4C̲{?4O\'=зݣKd/7^I-9X[gM *39  @"Pzq[zY2x]1U0Moei8<t|oɊ<@)=kv)׷Σ5ڵt¤g/OyOVN>{ۧҿc|O >[ώ6γvlEGyɟ7zhJ~e E V |xoikU޾iA^^;+Fw_znϮ3;1S ^mb9gΛ̹U띝xƱb}Tt{3;nlؼt<#=\n\nK?/lcJ]>sum=vHCS>sȑz\q@#GvY)DvFN @Pϡ;Ʒ>kW?W1vm\>3;U:SqpQѴ_˴}ėyS>M=4 g*_kN;;OC2_E> IB9 i1Nw{AzAN5fg*J]HΣ6V:R 1ZemGtyp߶y)ɣM!Yǧ B]f]vɹWy7+.]^y]aᔔ/;д*v1c9v'a꣇&FpL:<*yΓI!@ xI z}ݿrdk|_yJ'{e4"<EyGt yΎoޠAqG%pI -w^f i<)S`CLlǨs;e<|Sdqe0U%e WI/(y,'^PT[%yphZBَy?:lQ &kyq'@@nf$;y/(IkVyqznqh:ROޱD(̞ZRO?dm={'XӴbwlC ])|%v^)K,gyp̥bxǡcD櫎'xtÆ/cMv~! ?pn>;O ? tp=@WsORO< X׼ўG}r#l )@ @B/(XA j82.s"ўGyh;Xc;ء4z @v '^PID5Ei뀦ڥy @A'`QtzAb'A/(xKyZN"0)o=H{͂<@BInx3yv]wvI{ n3Qyvss, @Vϊx&Y]:p'6K/UVizK:om[j7xz>ϡoyi @"(Sn^^T\:p'64%kO^G;wfuc 7jݶt;B я%a/T\:p'6ƽ]l o3ΓQ}&N[Z:oӦ*&ss, @1 e eՇ .M8{MB=f`& :pld4v @ @vbt{8OC|:u#֭k>=5dƺu$< l qch ,^Ё'ypjx٧31£ Vn~2ŋ*qǡ eK.K8ΓΝ[.L.stҏ?#^0j԰Y ׬Y~p+P!@@m>NJm.;n ;RGnV`@>}E_ 5yfO@ &#ָ2^cCݒe''zN=ɧ9Vͻ{O;T|Z\McONzZZ-K=nȢ%KjH F̞ dswMb>U7rq>-^2Z@kTV\xAIIVxoSN9Y/5t^_s=ӻw΀}Vر#d;%%3,Y폟9<O @zAe5cbi>faOL\8Oh{YUSQB棘fL8f̘Z`ЁhWa1b;'-,2w̥KjέcMx'0Tv @pT1 9< U:%h'Γ'耏:tAS[^p48Mՙ?֬Y3fL3v2e6Ʀ_Qi޼YrUVW۰o9 @ LP$ꉥx|)/Ó~0<ԛzM0[-v=TcV^V^xe˚+dDk,ןjj*:z tOAM'@ "^--^PuٴO*N2b>XGH{,t-)F.m߾i۶M?&3U;hOc;f~w( @i0A%b= 94=!g7aGcԮ=[c='M&: hI&xf1ۑCEѸwY[엊IZ.XW @ j;qe&clv-Z]]`Mssr~lyCg: uW]ѡ}}  Г=K:mqZfJgyrC1e+uf񙉷&jժR6X:ë2P 2Iy ^P2=}\&uq' y.;vYm S2Z̈́}6חC +ugso~[%jLygJ|8nEO3@@ ^a/gyp$8S\q+ueqCD @$`קSGo;d IkXyy㺷^[>Or9 +L@ mMt4?y|X_oǦ<=gXE}6, Kq9.wR lK{^87G[1o_?j߹Ss^lRsΌ~+_Ť'N?" @p '{(yXOWstuO6FN'ŇJy @ q< <ģ6P7>aP.@ <J!@“xlhϣ>\Yrǎh@<8 @ “ybN2#][s'd8ן9:)> Ԅ'Dy,2s{=0@ 4pK,KxA᱖48.nݪ]Þ@q8?siR|@)yyZt'=Gu}Ϋԣ{@ Hj䱯^//֌p A_"@y|IOӄ LLaR&9{v&44lB@y @ DmsB=?)'+ C '>%Ň L-8O޽yn&͜9}ժezhNy @wOjAkJOv/N0zѢ96U1-Ǜ%ŇMy @_dW'ek2F 3zuc@ޓy@L'}.E@H3- 7\?lؠ₊{젂 @ pEI9c8 7\7dȀ)ҳ{ր~ғm@yIO 8_7xpi&-_^Z__G@@@ <8 ځM2aE;wlCOzZ$ n[c]';eKmj @ pE?Ӧ&γlq56 -87q@<:tH3V晤=z|xz>9WN'}Л >t%ckO^G;wfuw_.R yp@ƽ]l o3ΓQ}&N[Z:oӦ|HsR@pOzWD {MB=f`& :pw⁤|\B0'  ԩ On]驁m%%3֭[u. Ky @y٧31£ Vn~2ŋoR܏yr@"񓞦 @"s /tKZ{62c"7DpG|E&A߬w~z3fI56>;ϴS;t'Zq}qm6Vi7N0qqG^~[)9:p2,yB~Cb a2[Z~u1`\PY[v~9ɂ~>;Zp7wtj$pդlϏ٧oѽSiأ5F{\ ^^{V۷"3}E֭lNd5C&)M9sFIIQצ|c*KQ2 LdVY߱Sq5*&_aζ~9?6=`%3?> Z]V6=S-iqU7y::'?9gZkMjw,q# ,Y!mwoK@G%9#FK@˵7ڬ=g(\Zi\YiI*vh 羞,.H繣iVY\n}[ꖨ q טiћIn׺G^Qma.Z7-bnϨˠ riu<8 cT'zq[m<)T$xb;7֝{KOQ+>ф1G߉|d,]ӞA?h:= 8O?פ(@,ZpH[c"[S%hXH!8+ 2$eDsy=kN 8@j“ Γ`73oZgn>ȃo &< 'i ԟ0Z"n^| ֙gZzD/2LtD <1<ogLw#/<}vR#0sٱˬy%qLy&O8OrUof 6k3) [8@A8ORA3F=^{pg܌<4<q<:ׯf@ن+p\i $@򦇐:7zO)p)<~K6lX˺ծ7'uIpȐA)^ITs\nyp@ $f$103DjĈ!Z[l͚ ynp]qd<4۸6~6\!󄤷Jk 4EIdkx+2eҥ 6?sBm!.:Ojצַoo(<8 f2OYYYj]"sԭϿNsS XӮ cǎ\`f>/{URʵ㭑2@ QtH/ud,7`RvV-khؖ0"eq.\I,' my_:tHD!CL>Y'ݵ:@pyRG2ڵ3fFpt(kӺ&q΃@!! hժUr yzXs &X!iZ~Dti92++pmIxD# K/8Σ''鹇Æ 2kU۷ӣ{7B @];uĵ6CV!:'$=[ B p^}4u~|I#kBd#3gfwJpmzT$ @yp@ $Od*vy;v7lX{@|IxJrUOM4Gm\V1Cp!& K<|{5Wwm~'[da]L]|Љk3MLB;8UǏ6޽{ >XyVf2OHڕw+-_y4k3w%@L8@xh̀̇ztƓNq~G}7n%Z|@;}?|Gg|6ic+ȣU 8}KmDu@]K.+õISp{wnDF13y ϼy֭[u.>` ;]&7#;)iksР|yf%'PW h@Cöf)}8G}23{GM>}zQ<&=H !D 9F~ _:uFsr΃@9AqϮ][5Mҹ2)S&;o喛oƛn 7\1;zر#NY|˖z؈兀]=zw#|uѦ;\VA <9˅L!Tzn%K̝;hڴi53GJl#F ѯ"iԉZMCY\DX pC\A r"3])g__QQB)t`l=TTѦ%V)n6ݺu[Mx'@\4@;8OnkI Ј Nl۶Ijj*5?r"94z|ӟv|kT'pLHk3F!s8:Xjٮ孵gmzSBu|IFJk35K y8O28# @2Iy @ 0y\g@ yp@ ڷջ͟ur@=ػ-$ I8O{{lI $/_,2@ td$L@+uٱ˜l?9{jJ)gf͹h}IR#G:Ki*LsA 󄹷&H&!)Sn%^QQ>y򄼼fK3YwdI3[ypCc2Iy 0y2ڹ֬YᮥJ-WQySnL d^&[炀? <\ t?Npu Dud&8Of8s@yp@ 9xӪ|E`K󫨨8eKсd~M9ʏ^ʛɡ[E&9yp@Hfu43e ?u"@R!Pl)$CL -yA%t%y9p?i4 `&k4{$I8.\k?3A.+٤""<&zbl:@yp@H@I8nO "vOmF>kOSR# @Γ'=M@㮒jdXzM?hK,}Ʌ>qWRiZE-?L*? @  <8 #CʽmXq4~"4])J:وyɛbGnL /,t*uP$@pE=@;RmI0*̻6V *52-2@pp[=f"VNPjIK[RjMܭ"EqVVH,{=rro/E?[yYcZ%O>-)fҚV&IY'x%V95(Y%x,^Óc!\ <k=WUUgfr2*^rmmmkLۗGkFKURDKTG")RDCKaRDgݺ 9aO@9Hq"C :cuV:W0*'K zk4K'ԴY-NA#H-nu%R$[-B@ <8 Ax|c6 xmbpnI-Zm_cb2;̛lH=KNV50o\&ΏL@4E8!:sDqAY 2yED-aԒǩ4XGSS`۴~tшR^;BK\/Zc꺴'^h!c`|r @yp@MD O^uz,%L@ <8 ><~M32IvՋ/潨YGXeN'5ϊ}s20<'k,2!"W F:ͨN˘Kr8 yF A: ̏b;Tz^OăA Fkrҝx SwsD?Դ.wOjCy ,H4̓usM-'ljZI-G31^-G,QיDg,A<'5EI|Tji:m>^ f xJBo%q@0AV7-bs3ẖNquKD$qn R=0y]' ,8d@bbTUn1s Xt7~Ě| A$(}9i'%5VZ-AmqZQR2|82Gho/p CEN89JyR:S: ?auikL_).M+b`~Xoηϱ=5g)&/'/=yw'σr/CHi"<'[a msx>}Bb+ ?;+9Y]J $m s 8 AG@n0_io[L Mi0ۼ#&rvU k5] FDs"ziڰQzjm{SO@;8dw6l{NaB {EfY8D=2,56$ I8O{{]ph߲f~jj֫Ga;]vqKxL1Ip.iBpWXk^Gף1y2O ٳcjmiӦ@΃@2pewJ?bCo:`bypbQ_4=Y# kp{{֤(/,_>`>'U^Exİ'k5udif6+dEc'ц1l&veMӈ  M[ "G[jqA:0"<"ъDO Xn)2 8W==92@ l9;2u( $XBpi mB~~~mm-A t8=@u-s5A\J  +@ZV#wXI%G?K~C׮]&nyAh;ٻh9'Ì=]$P{Yfl+|`tZs $@"ۃ 3ze{I4qۋ#L>"SU֋&  $G]4?9<ߗ{Mfłtacžt#kOܬMJ < @|<[ʟ.:)8 4OT& AGך @Rpz{Zv&$uvv[j)s!`N`>F{?=8O:5ű@8=@-z;|Z@7Snoh\Bms o4 8:NS_Az8Z!YɆztS{R@&! y !3(Ⱦ5t='}CJ,6ls$Pfks9@ &@ >֖Kω)!t6Ayp@"`pZW HI."_v 'e <8 @.zBF{y©ϻSd9DžArh@hqłpQI8  O֭'O;xJ h1cvVtE#MJt PyrK 󪪪p~ {BIhQ{ԵYZI)=Fx IFy  O~~~mmO<=>(Zq8O{{9)>D'!<d+$@r ΃@9A@!'Td;=.ƭ!pLj1q 5@ p텲R($K` iKC Wr9njv>޷/u=UUk'B'<@Ow+Q@HP[|?z]{}Rd%JoϷMA c,{1 D uKyHppQ pylb>F~StYup]gN9/Bɕ^PZ$wgs$َ,C8Ov @!&`[uM(!&F <8 @!4-3|:v$d.Os @ 06 W<8 @Y# ԓ`1W@$'kwid @ _BH΃@ M9毉4H'ı@.yyυF!@-:l-.d@ xH[{!I),?d p>nEy찄@]$!h8O {{nsd,EEEŦW׮]O "df̘9:֜D;sm>?qݻٳ]OvEܯ/z)ays?sϵ;;V/bYVp٩sqۂ"C/y֏3Quȋ u#??Ҥźf@ p}ek+؅GL)LpxΕ_2;z6/(Lϡ= <w]]3~p5I{4QM5XuEf$<о4g{o֭\ùΣRiҞ%='fڏɏc~$-LBiyp@ xM:.i~px{ [ϱOTӦMkE~nX__&%-VTٵfc\i=Ws{v{ &cYۧˌ&S0v`Όs)nbØɶ7+o=k.d'Z`΃@$`Ta݂@6Z }19|*h7-=2fG|L>F. E,~mgxTM$ ygc"W$y2yRΕ# [DC;{w|=Z@BI0 y!g8@H 3“3I[c>v9眳/jR==F5.sA$'}&[ CUڼVrNԞi17+n:l|2  "0}dkTGsxYaǾUzP3mM_ŅښYxԏ$YΓ[AiG4> a8%ݒ] sٗ4h֞-`c}v=f p'r'@$< +jkg9*I9OAў?dBܞ;k)+r)\$' ɓ'h(qdl݂Ƕiժѹ:s޼Y+V[vVqh=^VJ( I@ <8 _oNz'HN&Qm^[^_hNy5zV&*cEs{"&hjC^^nA܅ZfEDz [8_z{m"d !X4i|f2YM@W^rT[:du8-M1O=H˸Y\#kC5dA(AkX|ݑ̯rp_#&<@ X£Oln]4Ly?'YU̳:cΜK&hۮ]!fVZ_|rEtIDzciOmṁPO\ }Cö^ uǜcǎM:AV(3V<;@toNPO:gX@p~%@&~\'< 6ny"apݬxر#5g)=6Uڵ^tbAǘh hO  Γ q'yƌ1}yf/ܹsKL1mޡA8@a# ^|9?,f9O[nq[O羜'r‰ 7O(Wh<8 !8Oh>9|^/gn%gرp)'1df.^`ݺUuu5-Cpi_UVY|NNOm/9rГydkfёtvM E8@$'m:t`z M7ݨƎ'̛7K۪h!Cڣك8O8?=@7󔞚 .8hL 񖬜ҼoQkvk6 l[h55MpW}3+(ypyNy @ ̊Zm9=zԩՊ}JTg?sgcDl@P<ܰ OTTu ʭ|Vo۶m:V#B= i3jŶ+lXZ^Ks>On^d4! X5lʔ y<($>4|os{uW47F(BIJ%<}WA4g3B )|VOp 74lؠqF)ȳ`+cyXeb_ؑOX^f NXS>?? Ky t Kq^TTd}uqnEjHkL}BꪫLo-fy,YXQb˖ꆆmaͣ-y|t47F\(7IRjdΓno/UH uZ_m2-B z̢՚cXړ<==ӿ#L4GAat)..6F J8yp@ ]X pM"n8p`wsg, 23yN8{vђ% *+Wn\Mn!͑t991ǶHA'x'x(|Ij^$!dExPGO?ֲe_L#Y](];B="< [޽;m$YjY^ 7(=zĉc$Ѷcfۙ0a̴ix%KJҪmгVܝf6B  '?R' O H{iyz> .Gh~rOLivʫ4bĐqFM2^)SZ:W3GvփGyO&>Э;ZM?{&IpΓ[FR  0ޣc勵ۼy3xh'e؈٬gM;F]X8QxGR)ـ@Nyp@ [(o^^W}uɩ&52=T13m˖jIȺu SɏD~6&#i1 4}d-*=uD}:uDOaTstB:Z{z9z(:e6hv4NsŜ#v]TwT~Sz8Oз  ^fפI[< tVGi)"ѐ3bFyZTMd/rYܹ33ԟI͕/ieyI()%(թ]/R|Iۿ'1Z%3a%<@,>q~vZ2iykEfhX4ж|y6F(RuIFiƎTG+Pg#ω]7h5 3rV4 '^@ ֧֗>SHC h|{ј7h؛IHejj*ִj GarQ*hVf?I2yopV4  `yBa@qR#`G~"Zj2F^0 Mőȅ̦MIuIӨNju$61Ƕ[-bg#H27ՌeA g 8]@ :y,DI9O$y$<8Oઞ Γj64'ycu䟘з 8$"`wk9Γ5'@QѴ/E7CI9>Mw5xqH . ЂpLysv hfGL[_*ZSk.|#͆bfCpJ@F{27kΘӕ&S# VuI >GA9io9S@ t%C\( +s Gmx޴t8*&{ <@rGR3s[ժ EES] 9R:؋})=( ye8;@ j<$ `َǷUkak1c/oh8R@"< *f 6c-M'xSzVis8H) t8O>? X|<O _~H!!?d<@ <8 @px[O ox[Y=(8@P<J##` l3a x2}YOpd]p.$< `y"5@ cpO 6B=Rp"@%޷mzB=EP&`` }uG}5Ť:z8Ood @ oZh,㴷8H'Yn-l#ԓZ0΃F ΃@q TT7`Ty^3cbl~a8(cDyb*-p:myBq Xb/ B !œD\$`wN;ixOAR.<)$`X ȵM'3Ӥy\GJIy <`01"bl o5B~J '}X% +sͬe>O*l$=vAxh6%`wiIfA pppD)|d8fGGxr  byTdק D7Q')yy̓猹Fy $g]{@TYg !q $@}p @H 4ar8 pyp@N׮]pT( !ك yy]U @yp@ 'Odd KuGE{|Rd#p$z{n -0ƒ 8!`G/]>@ph>Ԭ78O7\ `'`}jA4@:^yBX_CY3ix/G @ ؝/r)l A$`98O(BCh?7nr?27Xҭ/Q4JyBN8n"'FJ硿 @@wv*)=m@!`Q k6d@ Lp@% t,BE &'^n6J hj4 @]T!t0X8'< @ U@pLү3R @yp@ @ p0׮seO@ ΃@ @a&vj  @ <8 @ f8Ok׹' @Jy @ 0y\a5u@ p @3'̵} @a%< @@ u'@v XΣhqqzyv+'^.4<@0wQ/RK pH:ӺJ\ryxc$$dp.!I- @|N@QV|yyp@ @ p0neA @! @L s:_v @@ <8 @ f8Ok7IJN @ ΃@ @a&v/A @ p @3'̵bYh @pHy @ 0y\ŗ @ b8I`͚!cR4@஘,1p7{{^iB X&O|B-1/w2 ΃@nhݺ5GwE }ES@y!<%`>,$@ (-oR_3ppg6| 8'`} 7AΡ'<"zH PQQ5ŗ22 dyu;8/ <8 .9ZB=|@0aQQ3p ye8)|E@Cǹy|U5d{>hҤ@yp@ ]Z 0-?T(> `X_h}< -8Ol煀XSSP@Y!` 2-+UI!`'< UMf`[׮]cM27PjdBo/ǩ!g<ϵC mǻ2 ΃@ @a&v/{B @ p @3'̵VS\ @pNy @ 0y\ݗ=!@ V8@  S.@ 8'< @@ !Spo{{9՘(,z!02ڵ{텻Q:8!z2'; O ^xJ!18} p"@ "@ pޘ?$PUUa0L&L$`<楻Sq8 ]8d@EEy^^"~w PRΓ^ȚŁ@j̊mz+U`1xr QmvƓ 8ڵ!_ 3' 4N?XfE֭3I (z!K΃@$`G/Oo$uIjA '=4 )㒗8dnIru_%&D ޞߚ@ R[[oE{2̟A £Fqq1_3R#<@XuY,at~j!`-^EEE|]#UO1!`<p!H1yy8bݻwF@yp@ xnALa=袘Hn w ('U P,~&_DН̍V@y˝II!:.|GK R#G$5typ@ @ p0n@ElC @E8@ uQI  @%< @@ 8K }ޙ;l+vuKgo?YJXd̘Q|{ܸ1\IV4m!C9bӧOu3fL2qĈa={+ElQ=q{"s‚s޼'GR9jsv>>tG̶!;懨ٞ~3d~Rή_ 8@$e /C 3g3g?vĈ+p/z5l޼~"w6TV6l+GUWWeʺuk߾\xzQQA^|EANѣ:p %8]˗^ڵk׽{=uw>gN߶m[֭Z<*;T~Zm_yQ?M?ʯƫUо;o~*oU|m_Su|ќI$iN @ ƾ;ti9q8;C[\,{XtQnQ<'ROG@$W %Z*NPObQ3gp<˾}#)4,=n8W6V=7x[[,&E5'[wWmόа?xvoGG=5l<|snpmhئ]u\כՊ޷7fdl' @ h[oʕ,AmK/)gw))}EM,^7tyƕ)cuvs^f%R4'{ 4˗/6F;\p8Nb*MMw8P+:DgQΣSX􃒵EZRiT ͇(SQNvb`wʟ]w:q#<GՐ[szu2vig3C~wOcOj[n9s[o[jժկ}gN ر 9ml:ҦI{F"j@ h)@C )j![ GyШ^{M -l)@z@PEJ͟n\Vhƌ™3׬YiFKϞ=kƌ&MԢpTW=z)|ѫWϥKM) EQdiܸ&׼-[6_T肆IHq;ڵ֭[%&)iʹR 4HcҔ-*,Ԟ%KMft6TJ$6n4i ^ 7gDŽݭ/RA#'OS\#vΒJQoXkuAN"?믿!j-3]P0?DМB:R#M0+J-^& W`63TV֔} {woPս{wMQF҅򖂂)5ٝGJ6lPuo+5}AJu;w\RRl=,>ʒ42\@k>mzS' *F Y#Z۱cGi kL1ƍӞz"vp%~|0a(vFS}Ŗ-5JYRh&,# ѩg͚rAx7zKzԢ ZOUSZd*ի;=aky|l}۝[Z< {O\Zsz~x-$<8γs `{bvxW^y{$5[_xṩS'hlk#mm\}\KRHzzu|҇I $N^^Wì*(>о/zJthifb2Zޒ߯waK3mkƆt˗ڰa:!giS]K3&Yj~&jtllܸG|,yRYPrԀW\}d zH^Y90HB_-Aj'P[BfM&P݊8OaGu\sFinݳV\x҃VZVV6p}۴y7_uoh[m>A/*N $C%ޱ>eLPNG%+Pn"0ѣGi$m+TҼX@ӳ߾VXӬ={ YΣ70h.uk:WCdPJg)!~ɈP?^ڼV&kW**VYΣ| В5M0N#<1( J:JR/>*]b1<&gT#~p@8iƔJqhK' Q1G6_f $q0 h ШQ#_)dMkOyLG_4i275EG~eժV)-`m#(? [,Qp!v kV3'Jo*犪)bf1<6e$՗\EO*%W5ݻ,i\ [n}Qf={5M[ReX,wQҴ_9uێ|BΣRٵzMqmwhD{"pm}|N v԰7nl1 G`uWTU1k7՚[ر<ʌV?Ӄ;VE)RTTS+={v*!PΦaNfW;H0,瑡 L隝go,M4R/_֨/J1]Q ӭG$zklZ~Ge?>T3gNq6OwߥinbwD3ϴ6opǡGjI{ ,3째6|X*Yfe+??_[3i)"=P\(ifkF3Dk)P!X("Ғq:&hmswdAfɄ-bx3qDR*T4[IxΣƦ9N~0d['ű)97ԆէצVmo4o(,du%^b<C6r)"1GymTfdըi2OwZ;!eQH.QRۦ KZZ)bizQ8Dc۴'4ƶiY6aWHG3Ff-QK<(|8,γ'nsy6ʿhWwS˚8~yZ+ʴޚ hA˛V'LyӘv^4=*"y^J\kyémn>ի=;Ԟsr19:= h#uFn>|eLDGñdf&ƍU-:d3P[Y`Hߦe#0"`o޾A]]&0"6)4Ck%ti-{í}}O$5;DmS)4L9hviRPbH<4̞D <\̟?ǚ=M<:y%/쁑"bIf!3a]Uٽx8zz%q}1m>rۦ# uΓF}\;u}L,%PJQy4>Jʬ?Czf@2=iǬf&TnzNs}ZIMf^5Z^;ke%a]5j3=ЇV2PbСgIr'Q\%Q>6O_{l)9Nzd ZZMqA`Y&Uмys4J%J_ 9XÑh^ً( *&uQ-H2u4Hh:؆ =zR(\r @u58reK9ٽKe-L rsyd}U;oj]sJO ͨ{1\)@@4Iuz:*l :}bb9zʥ؂VIcdVBiuȑ#k֬(YقBѨ-*Gj)5u޽{k!> _ǖ.(*RiM4K'T:m*֭[LVO棸"k^ ƕiJ)$f +yS1qR&@mFQʏ}Ds\ֺ:ܼi=DVmD,3ZٲPjm߶LV9<]~hM||[-Օt4Dx$<h܏D ֳP0?TGX`=c4p3 "W.G:S˗Eoz,,w^Qޞsffn\̅/v3Us;ٞNɁhc L6  sHB !PV+n[Qt+$-~UT{/"}S#44);m{G0&" 9vc ajSG Ո@8LQ,#I]HR;]iB|񨑍HAhk99ý]QQ~60:20UM~EE<+k2SfO>\^^"vB666=#m|۟YwvVH-PDq y%+2Cɓ'mڵ+͢2G`k0e?qh?7os3yV^>* Pc쨳k9Lӵ1N|jfW "8 nl*$o˾́wi붜LJgM۬Si.4ʚ997h\=++K 3`7̵Ds9OΙ i .uK ???Neu;l lg=w#v Djɤy"EInG'/s\[oed=}˗Nn%#hVTCOWWhœ.Xs摼ex4;+N[e+-[?h<.a"\itpY Qjl7_޾]g nE[KMM^R+*ۻwo`kQQVӁM~QQ<F$+ރy*=*gYJ&47ׇe6Rg33lqqܝq:ٖTg]'L'y;+/YeӎDٽ۟~:TH p.#i'"גp "6iD[dl#E|Hk MeeEGmm5fUõnHX4UVYUkUM~}vt8Uݣ:YD` 'hc pcmc`:hoE'X+ss b0g۶my0LI^HpTV?L^^23b^"]*CTɲp".zR 5\o-eCh6ip?,$066 n9 wF %qqgVZ73w~ϗɹ쉹/s;w=F\9E|k!HDеmoGͳ!њA W57uZ,COϽW0l3X1H'^t####J)Q>_.//ŀ).jeFmmw-ѓNY7QԘ?RV"Z3#JO@͛k3UWW˗;kF~v}ZZ {k2z{bw! RlEp%22RL8όN7)Z[OwCiiJ >̐LJPht]]]A?k=TIrW_/-TI|iGynJXP:77/>7>x,忖ĪN~:9<쮚qVłY'qnڱ^l|;5˦{ъ8}TJ <ޱc/ϞJ pH.?yb$hhs~juAe&7N8Nz=]P(u`gaN@;\[?x_w}ͺÇqhyy|YsѣGfQ]דxES4hԗ/y"'0^Ǜͮo:e^MH!_ Z <##:+srA7=D 4*O06#u W!_I`xX JbͪFOy2ޚWH BȐ p7w [.x] 3CĂ>9u'omA*Aj$~B#3Q(>Clg[x{>L۝;hę_Knh͐ABΐs;.$y\7hw@̓W*==ո(cmڜ=~:y>E\'hyjKpfU9#mP~5@$]gE]?55xL{fJ]͓(x8Ow]Hmu{i`" _d3vT,yȩudxNO~wnss#y5yxWP8V111 рdꫯ%'_%y OY7y?N%VENG;7Avs׷\sC0[d,-a)_ͣECx.YFjǬ'At`$dgg&''y6ziO̓il(! D6)nqσeӒQ)Y\+h~K@xPT7,̓A Z^`K &Gff.ki!1wj |XNNmiCݶBS%lѓMT;k w/tm"T[v J`eۼC#NvvzR;@^mFF~c'pz}ON=NMmkm|P3H^4ԗ2e7SkhKc phOf@YYIqq!GEEYuM7+Jx͛ ġBիBak,w6ujj v@E%D54hCCMz $oHڛwr9u SHsaۜӌҙ3*tD1h&f$ڤPrsV':Bi|BCGB)abS3iRUUeqqr(@1|ه̝,&j E'|OB\VT34r"-[gKxzkW?,m>sσl2dݶu;/}1is\=" &@H8?u `^j$ᬪ?~j!_K֐ Ԉ_1_-q ~WʣM&'< iX x9h3FZ<<O9;^.dA|ч)tD ?|kocm2 w ِtsijUG е"`'fϟp&fhAVtT\ctNȪ)κVD@dNDpsرo:*_ܲ[*wOMh8^z%Z^*n=19竦]NA8v&ML& #&"  ),NrR'd…dՂ' n)߱c*W_`Ur& ΋/hmT#YW==7ܚqrцAG_~QmAP)?=a>(-F$ M&??rF/Anѧ+*X]C+A%Q /, /PwCE)hj؍*m+d(f{אpjvΖ"( "ND\WhOӌ s$\D@D d<+SP̎=d[Ned{FT*|օoIMM>[Hx΋cs韧 AI=eԝFаW'Ɩ޺';F@ ]8lOٚ 0tF$@R>)}F(e4ޝy&g@T<&'(o@YAX1VG4`;wo1^3NaRz'IDATLM!Ж R} ry 6學Ny*vApqE KI֎K؃ 寊Unљp?yeϽVli`5=kzǦMoc~^}͛?ڿ'ĉ#}f*Yg>3C\nnF[ {9F870rmX*uH gNPC(%}WXR#Jhbw r^k{ze6' x/*' v(ݖ8>ϧωٻ.Ws`=rLv?}]05;.eם38I;aY晘6'P/1mlɋakN#vaWt1uPęw0}7_G(i^тvN-G <Ο?YL XI4bb_T e:Brw'sW1aojg8~I\;\8>>A1C$Mq-zK@D ^g/ΥE5'05Slʵ 0E&MtĀym !a:[TTpN#b!B*++ؘ3Ilޯ̂Ow 3޼aWpgUVgʮ<(|gcųdPύ5깑=|:7o#!7>o-ïWADD@D@D`0r% AˋbbO\bcc'&~N=73N̈JƴH?uTJʍ̣G^\XXwС 6gR,rݻMU/^8p@ii1<0r111'NG\ ʕcǎedaץP2T`4䤥96L޽/?~Ņ}}ttK.dP0` ?`o73hu'hlfa\ hi; K6lz9\RRFctT\\qq1g4,+v氬IIؼ:u&={Ni@iyyf$fgΜYz())ֱyEo @>:ᐤyB|pϞo}}s {{̓B&09sdNuB'!?bxb󒓯v0srb̸ȿܹH䔔xAUPsz̀ZJsssmE5I1#6N @GsG=taHZd5W1HAo0YTE )Z4 by|=|0X;-d>ڇK|5h³q ѣGL [G]PVDvn½ tNee)n+B$&&Zg:nddD> r!L(=dZ WM͂ 4e-y 25m6݂B0eZqtt8!|) >؃3 ^B!xHCb\zN uT+"Jrޫjkj*i 8ƍ$\tt'x2 o4ͫyB Bg/4OgF"" " "uv'ƍd,W⠰<<ƅBD!_-Ci9x6k 4@GY<]]E}d}/d&‰z񌄨3WVV&/C_̓%F0jzz  0(%< 9ʂB|nnnomMMg|^MUMRr ]{#9B_? ݅2h8|4}?&P5DQ 0`)d%1Bd CEp-fPpԘ~xN@'"֖} yXnon;d d[L,‰thكЂ-JM 44D=k2"' #do|Vr 6E@D@D@D@D`c3i;< <<" " " " " v& c}*Ym" ##" " " " "`g JcC<$JcFU,J,~> JcC<$JcFU,J,~> !<7W;JaJ$UhsGp~> !<7W;JaJ$UhsGp~> !<7W4J`_OGhr]F~> !<7W;JaJ$UhsGp~> !<7W;JaJ$UhsGp~> !<7W4J`_OGhr]F~> !<7W;JaJ$UhsGp~> !<7W;JaJ$UhsGp~> !<7W4J`_OGhr]F~> !<7W;JaJ$UhsGp~> !<7W;JaJ$UhsGp~> !<7W4J`_OGhr]F~> !<7W;p[%u4m"+PTmHa.i~> !<7W;p[%s`JaJ$UlKs*~> !<7W4pZ;JMJ`_OGlK3T~> !<8PU"R(G!23RRu!%[U,!A*Sgm=FZjm=5~> !<8PU"RF4:Z-W%/!3YkZ!3Un?JaMaiJ,~> !<8PN"R#:Ip@%V8!;H$G!;D',J`c7[J,~> !<:^=s6T]hrc\:Fs6BULrp8;)"G^XCiIgah!%[U,!A*Sgm=FZjm=5~> !<:^=s6T]hrc\:Fs6BULrp8;)"O+^El-&\c!3YkZ!3Un?JaMaiJ,~> !<:^6s5j3\s)S0\ri>tXroMep"RkmEkj\3+!;H$G!;D',J`c7[J,~> !<:jA"739S47`FY3d#T0l0@R"m-NNp!C5-4m0ga(/ka"I/=AOYU$_e/rp:m[L,2Zn/1W>K\a:r& 0hK:M/1W>K\a>hQ/Lr@ !<:jA"739S47`FY3d#T0l0@R"m-NNp!O_"em0hd]YINHVY0jS]eF!4;rp:l3bcn\GY-+t>h!M8g YINKWY-+t>h!NOuYHG%qd^H?^Y(>tkm/ZO(~> !<:j:"6HUF3qE=X3H9*!ioB+\jlP:[!VP[TjpUJIp[mb5p[.\ImH4BAroO@Xn+QA8rq?]ilK7j0 p@RV3rq?QelK7L,rq?KVmIgJQJ`_OG!9Se~> !<:sD!jXpMl2V$0FQhuSlg3Kq!A<[9m0u^_kFbK7/=bn347)>br9XS:QK3)5O9qnkFtZ: mHs2S5O:7icKOZ_23H2JJaJ$Us6Y=~> !<:sD!jXpMl2V$0FQhuSlg3Kq!O(kgm1$SUldWH3Y1)^k[-d7`r9XThdH\gPm0N(8[-b$Fld`QF m1&F=[-b-Fj6Q+"Z-_OsJaJ$Us6Y=~> !<:s=!j+LEl2V$-ET,p;jQ57\!;HER%J9&Nm.BH7m-![-k2uF9jobAFkOAH4#O;!Ep[%)1o_mn\ lLj<5n*0#tk4\9.kCMcHk5XRo~> !<;'G!pk+mq#L@Xs*k)B'c$]Tp](M=XQSg4lg3Tt!C5-4m0t]1mH-(g/EuQh/F;aSr9XM8l2L&i S4"qZ/F4qMmH-+hS4"qZ/F9ZR7l?4lZ=QN`Mh18Hm/ZO(~> !<;'G!pk+mq#L@Xs*k)c<`]2Mp](M=XQSg4lg3Tt!O_"em1$8bmH^tCY3YKBY3kVZr9XNfli-8P d^K.X#jne\mH_"Dd^K.X$glW`\$=3Fftk/&c%585m/ZO(~> !<;'@!p+Jaq#L@Xs*k)uH[G`Tp](M:Vr6OojQ5@_!VP[Tjp(SCk3)L$p\s:WpZCW4r8n"RrT4.I p&=[kk4e'$kP"')p&=[nk3MU1p@%/0o(;YEJ`_OG!9Se~> !<;-I!O=f`!!5C"^\[s1@/;DR(&@n7EH:1(l0@Tpm1b7eg>R_P9;:UB9C+4rmHlIkm4d&gm/S[8 m2[phkHlf,/>;7$:$aUBkHlf,mHs5P1[OMrlb#%?JaJ$UJ,~> !<;-I!O=f`!!5C"^\[s1_YUG8 !<;-B!N\?Y!!5C"^\[s1q>0jcHhm^HDK"FjioB+[jp^_KkND?so^1i.o]GoIjokG@k4\$;joG/S jq.7OkN_g(p[%)1o]Gl4kN_g?joOcArT4.GpOVIXjoFOo~> !<;3K!O=ic!!5C"^\7[-@.l,N(&It7ETH !<;3K!O=ic!!5C"^\7[-_Y1/4 !<;3D!NeE\!!5C"^\7[-q=aR_Hi!dHDVa71jQ5Lc%-R-Qp@%//p%S+Dk4el;"o%0An+QMTjoG/S jq7%Tp$_Y;p[InKl/qF-p$_Y;rq?KemH4EB!VPi2k(2Xpk(!~> !<;6L!'C)Y!C+?jrr?Hu.f^=^!!YB\j6#Rjm"+PTmAoW)~> !<;6L!'C)Y!C+?jrrBq-XoMN#!!YB\j6#Rjm"+PTmAoW)~> !<;6E!':#X!C+?jrrDldpAfR !<;i8`t`l@J>RmB#]*~> !<;i8`t`l@J>RmB#]*~> !<;1]HjFQHEk,d]q~> !<;BP!O=fc!!%N:rr?Hq.f^=^!!R<"jQGgpJaJ$UY3g`~> !<;BP!O=fc!!%N:rrBq)XoMN#!!R<"jQGgpJaJ$UY3g`~> !<;BI!NeE]!!%N:rrDl`pAfR !<;EQ!-eAA!C+?drr?Hp.fo["r;["=hW!Y\l[eGSmB>o-~> !<;EQ!-eAA!C+?drrBq(Xo\QPr;["=hW!Y\l[eGSmB>o-~> !<;EJ!-A)=!C+?drrDl_pAt;Dr;[":f\> !<;HR!-eAA!5Ik"!+>7G"I/rdImEu5"`<&QjQPl[m=FZ/m=5~> !<;HR!-eAA!5Ik"!5e3-"P3VQImEu5"`<&QjQPl[m=FZ/m=5~> !<;HK!-A)=!5Ik"!;Z*X"T/6!ImEu5"_uWBhVmLGk(2[!k(!~> !<;KS!-eDB!'f`J!+>7Gr;QbHrW!*[gu.8Vl@J>RmBQ&/~> !<;KS!-eDB!'f`J!5e3-r;QbHrW!*[gu.8Vl@J>RmBQ&/~> !<;KL!-A,>!'f`J!;Z*Xr;QbHrW!*Yf%T!>j+6?Dk-='!~> !<;NT!'L5\!.X54!+>:H!0mB\!.XqI"Jai6k37HaJaKc1J,~> !<;NT!'L5\!.X54!5e6.!7q&I!.XqI"Jai6k37HaJaKc1J,~> !<;NM!':)Z!.X54!;Z-Y!;lZn!.XqI"J=?%i8T=MJ`a9#J,~> !<;QU!'C/[!.X23!+>=I!+5R%!.XqI"JX`2jlq?`JaKf2J,~> !<;QU!'C/[!.X23!5e9/!5eR3!.XqI"JX`2jlq?`JaKf2J,~> !<;QN!':)Z!.X23!;Z0Z!;ZHj!.XqI"J46"hr94LJ`a<$J,~> !<;TV!'L5\!.X/2!+>=I!0m9Y!5JI4"JOW0k3@NbJaKi3J,~> !<;TV!'L5\!.X/2!5e9/!7prF!5JI4"JOW0k3@NbJaKi3J,~> !<;TO!':)Z!.X/2!;Z0Z!;lQk!5JI4"J+,uhrB:MJ`a?%J,~> !<;WW!-eDB!5IXq!+>@J!0m0V#XA@i1tLI@jm%EaJaKl4J,~> !<;WW!-eDB!5IXq!5e<0!7piC#XA@i1tLI@jm%EaJaKl4J,~> !<;WP!-A,>!5IXq!;Z3[!;lHh#XA@i1XO_-hrB:MJ`aB&J,~> !<;ZX!-\>A!.X)0!+>@Jo`"p+rW!*Yf%f6Hl@J>RmC)D4~> !<;ZX!-\>A!.X)0!5e<0o`"p+rW!*Yf%f6Hl@J>RmC)D4~> !<;ZQ!-A,>!.X)0!;Z3[o`"p+rW!*XdFR(0j+6?Dk-jE&~> !<;]Y!-eDB!.X&/!+>CK!0m*T!5JL5"_lTDiofWYm=FZ6m=5~> !<;]Y!-eDB!.X&/!5e?1!7pcA!5JL5"_lTDiofWYm=FZ6m=5~> !<;]R!-A,>!.X&/!;Z6\!;lBf!5JL5"_Q05gYh.Dk(2[(k(!~> !<;`Z!4)S(!.X#.!+>CKnc&T=rW!+9fA5KMl[eGSmC;P6~> !<;`Z!4)S(!.X#.!5e?1nc&T=rW!+9fA5KMl[eGSmC;P6~> !<;`S!3Q5#!.X#.!;Z6\nc&T=rW!+7db!:4jFQHEk.'Q(~> !<;`Z"$6J_J)UD-@.YrLPjn\QJ,TENS_2fZkCN#OmC;P6~> !<;`Z"$6J_J)UD-_Xsu2f'r\>J,TENS_2fZkCN#OmC;P6~> !<;`S"$-D^J)UD-q=OC]qsaUcJ,TENRaTjEiIU-Bk.'Q(~> !<;c[!-\>Ai;Wdbod^$,mf*PX!!"h(g>CuUJaJ$U\a=n~> !<;c[!-\>Ai;Weoor\;Gmf*PX!!"h(g>CuUJaJ$U\a=n~> !<;cT!-A,>i;WfQp%JI`mf*PX!!"e"eCi[;J`_OG\`SD~> !<;f\!:9[b!.Wr,!+>IM!0lpO!5JL5"e*uqi8s9Um=FZ9m=5~> !<;f\!:9[b!.Wr,!5eE3!7pT !<;fU!9O1[!.Wr,!;Z<^!;l3a!5JL5"ddQbg>CtBk(2[+k(!~> !<;f\"*OYEJ)C8+@.l)NPjJDVIfKI(d+7"3kNmceJaL):J,~> !<;f\"*OYEJ)C8+_Y1,4f'NDCIfKI(d+7"3kNmceJaL):J,~> !<;fU"*+AAJ)C8+q=aO_qs==gIfKI'bL"hpi.:$Ak.9]*~> !<;i]!-eDBhZ!R`p+,G.#XA@iB$o8fjQV6_JaL):J,~> !<;i]!-eDBhZ!Smp9*]\#XA@iB$o8fjQV6_JaL):J,~> !<;iV!-A,>hZ!TOp@mkP#XA@iAB`KSh;a(KJ`aT,J,~> !<;i]rVuqJhZ!R`pF?6.lMgk!rW!+je(`jDm"+PTmC_h:~> !<;i]rVuqJhZ!SmpT=MIlMgk!rW!+je(`jDm"+PTmC_h:~> !<;iVrVuqJhZ!TOp\+[blMgk!rW!+fcIL\-jalQFk.Ki,~> !<;l^!-\>Ah>[I_pFGJ-#XA@iA^Auaj6;-^JaL,;J,~> !<;l^!-\>Ah>[JlpTE`[#XA@iA^Auaj6;-^JaL,;J,~> !<;lW!-A,>h>[KNp\3nO#XA@iA'33Nh;X"JJ`aW-J,~> !<;l^"*FSDJ)1,)@/)5PPj&,I^]"3:bL>2'jm7QcJaL/ !<;l^"*FSDJ)1,)_YC86f'*,6^]"3:bL>2'jm7QcJaL/ !<;lW"*+AAJ)1,)q=s[aqrn%[^]"3:`m3)fhrTFOJ`aZ.J,~> !<;o_!:'O`h#@@^q'uH0k5P]P!!&80f%oBNJaJ$U^$U=~> !<;o_!:'O`h#@Akq5s_Kk5P]P!!&80f%oBNJaJ$U^$U=~> !<;oX!9F+Zh#@BMq=amdk5P]P!!&/(dF[15J`_OG^#jh~> !<;o_"*FSDJ)(&(@/2;Qal`U0^An6gbL>/&jm7QcJaL2=J,~> !<;o_"*FSDJ)(&(_YL>7lK8*Q^An6gbL>/&jm7QcJaL2=J,~> !<;oX"*+AAJ)(&(q>'abrT=+d^An6e`m*#dhrTFOJ`a]/J,~> !<;r`!:0Ua!5RCi!+>XR!0lRErW!+ecdpq4l%/5QmCqt<~> !<;r`!:0Ua!5RCi!5eT8!7p62rW!+ecdpq4l%/5QmCqt<~> !<;rY!9F+Z!5RCi!;ZKc!;kjWrW!+ab0\bridp6Ck.]u.~> !<;r`"*OYE5hl;=@/;D6rs1ji!&;[Gg>M+Em=FZ=m=5~> !<;r`"*OYE5hl;=_YUFqrs1ji!&;[Gg>M+Em=FZ=m=5~> !<;rY"*+AA5hl;=q>0jGrs1ji!&2I=eCrf2k(2[/k(!~> !<;r`"*FSDJ(su'@/DGSPiMcM^An6faO&PqjQqHbJaL5>J,~> !<;r`"*FSDJ(su'_Y^J9f&Qc:^An6faO&PqjQqHbJaL5>J,~> !<;rY"*+AAJ(su'q>9mdqr@\_^An6e_opH[hW9=NJ`a`0J,~> !<;ua!:'O`gA_.\q^^Y*#XA@iPKhV3iou$]JaL5>J,~> !<;ua!:'O`gA_/iql\oX#XA@iPKhV3iou$]JaL5>J,~> !<;uZ!9F+ZgA_0KqtK(L#XA@iON>_ugu !<;ua!:'O`gA_.\r$qc3iVs0K!!&/(dFmIAJaJ$U^[6O~> !<;ua!:'O`gA_/ir2p%NiVs0K!!&/(dFmIAJaJ$U^[6O~> !<;uZ!9=%YgA_0Kr:^3giVs0K!!&%ubgY;*J`_OG^ZL%~> !<;ua"*FSDJ(jo&@/VSUPi2QJ^An6fa3N8mjm7QcJaL8?J,~> !<;ua"*FSDJ(jo&_YpV;f&6Q7^An6fa3N8mjm7QcJaL8?J,~> !<;uZ"*+AAJ(jo&q>L$fqr%J\^An6e_TC3XhW9=NJ`ac1J,~> !<<#b"RC11!.W`&!+>aUhu="t!!"[kbgkP0m"+PTmD/+>~> !<<#b"RC11!.W`&!5e];hu="t!!"[kbgkP0m"+PTmD/+>~> !<<#["QXP&!.W`&!;ZTfhu="t!!"Xea3WAnjalQFk.p,0~> !<<#b"R8#J!5R=g!+>dV!0l@?rW!,=b0o&(l%/5QmD/+>~> !<<#b"R8#J!5R=g!5e`~> !<<#["QVNB!5R=g!;ZWg!;kXQrW!,9`Qcrfidp6Ck.p,0~> !<<#b!:'O`g&D%[r[Zh)#XA@iON>f$i95aZJaL8?J,~> !<<#b!:'O`g&D&hriY)W#XA@iON>f$i95aZJaL8?J,~> !<<#[!9=%Yg&D'JrqG7K#XA@iNPirgg>RVFJ`ac1J,~> !<<#b"6fdc5hZ/>?n3alh>[b2!!&&!cI^q7JaJ$U_!QX~> !<<#b"6fdc5hZ/>_QL&mh>[b2!!&&!cI^q7JaJ$U_!QX~> !<<#["6':\5hZ/>q=jX\h>[b2!!%qnajJbtJ`_OG^ug.~> !<<#b"0MV(5hZ/=?n5U>rs=2U!+!Lef%oERJaJ$U_ !<<#b"0MV(5hZ/=_QLc2rs=2U!+!Lef%oERJaJ$U_ !<<#["/u8#5hZ/=q=j^?rs=2U!*m:[d+@+8J`_OG_<-7~> !<<&c"RE2j!.W]%!amOCh#@\q!!$-m"+PTmD81?~> !<<&c"RE2j!.W]%!lD+kh#@\q!!$-m"+PTmD81?~> !<<&\"QZN^!.W]%!r;Tfh#@\q!!$6;`m3/kjalQFk/$21~> !<<&c"RE/i!.W]%!FV/Hrs?F?!%u+1e(`mKJaJ$U_ !<<&c"RE/i!.W]%!Q*FBrs?F?!%u+1e(`mKJaJ$U_ !<<&\"QZN^!.W]%!VuWIrs?F?!%kn'c.1V2J`_OG_<-7~> !<<&c"RC.0!.W]%!+=O\$,6H?0#5QVg>V=Jm=FZ@m=5~> !<<&c"RC.0!.W]%!5dIi$,6H?0#5QVg>V=Jm=FZ@m=5~> !<<&\"QXP&!.W]%!;Y@K$,6H?/\K$HeD'#7k(2[2k(!~> !<<&c"R:(/!.W]%!6Nsp$,6H?0#,KTg#;1Hm=FZ@m=5~> !<<&c"R:(/!.W]%!:&;<$,6H?0#,KTg#;1Hm=FZ@m=5~> !<<&\"QOG$!.W]%!<(XO$,6H?/\ApEeD'#7k(2[2k(!~> !<<&c"R0t-!5Pi=rr<6U^;nURi98MSJcC<$s8VcZJ,~> !<<&c"R0t-!5Pi=rr<6U^;nURi98MSJcC<$s8VcZJ,~> !<<&\"QFA#!5Pi=rr<6T]#2_?g>UB?JcC<$s8VcSJ,~> !<<&c"R0t-!.V9R$,6H?/\]9Pf\kr=m=G:gs8W,lm=5~> !<<&c"R0t-!.V9R$,6H?/\]9Pf\kr=m=G:gs8W,lm=5~> !<<&\"QFA#!.V9R$,6H?/\8gBe(Wd,k(3P`s8W,lk(!~> #lj;ZlfkJN!.V9R$,6H?>/&#'f\kr=m=G:gs8W,lm=5~> #lj;ZlfkJN!.V9R$,6H?>/&#'f\kr=m=G:gs8W,lm=5~> #lj&LjPm';!.V9R$,6H?=h;Jne(Wd,k(3P`s8W,lk(!~> !<<&c"R2uf!'mgh$%N!U>/&#'f\kr=m=G:gs8W,lm=5~> !<<&c"R2uf!'mgh$%N!U>/&#'f\kr=m=G:gs8W,lm=5~> !<<&\"QH !<<&c"R2uf!'mgh$%N!U>/&&(g#2,@m=G:gs8W,lm=5~> !<<&c"R2uf!'mgh$%N!U>/&&(g#2,@m=G:gs8W,lm=5~> !<<&\"QH !<<&c"R2uf!'mgh$%N!U>JA/)g#2,@m=G:gs8W,lm=5~> !<<&c"R2uf!'mgh$%N!U>JA/)g#2,@m=G:gs8W,lm=5~> !<<&\"QH !<<&c"RG1M!'mgh$%N!UMSI6[g>V;Bm=G:gs8W,lm=5~> !<<&c"RG1M!'mgh$%N!UMSI6[g>V;Bm=G:gs8W,lm=5~> !<<&\"Q\G?!'mgh$%N!ULV(IJeD'$0k(3P`s8W,lk(!~> !<<&c"RG20!!';(#s\Ij\\Q>8g>V>Cm=G:gs8W,lm=5~> !<<&c"RG20!!';(#s\Ij\\Q>8g>V>Cm=G:gs8W,lm=5~> !<<&\"Q\K"!!';(#s\Ij[CjH&eD'$0k(3P`s8W,lk(!~> !<<&c"mb>2!!'g?s8E!&]"uP !<<&c"mb>2!!'g?s8E!&]"uP !<<&\"m"T#!!'g?s8E!&[_9Z*f%fB5k(3P`s8W,lk(!~> !<<&c"mkHQ!!#9jrsF8V!*QqNbLG>-m*G'Ms+14Ms7Pc0~> !<<&c"mkHQ!!#9jrsF8V!*QqNbLG>-m*G'Ms+14Ms7Pc0~> !<<&\"m+^>!!#9jrsF8V!*H_Ea3W>mjj3(?s+14Ms7PN)~> !<<#b"R)ic!'mmj$%N!U>.hbtf%oE4m=G:gs8W,lm=5~> !<<#b"R)ic!'mmj$%N!U>.hbtf%oE4m=G:gs8W,lm=5~> !<<#["QH9Y!'mmj$%N!U=Ll2fd+@."k(3P`s8W,lk(!~> !<<#b!U8\E!42\35QCe?^;eIMhri;PJcC<$s8VcZJ,~> !<<#b!U8\E!42\35QCe?^;eIMhri;PJcC<$s8VcZJ,~> !<<#[!TMu8!42\35QCe=]"uM9g#:6=JcC<$s8VcSJ,~> !<<#b"mY2/!!'gAs8E!&\\Q;5f\l#>m=G:gs8W,lm=5~> !<<#b"mY2/!!'gAs8E!&\\Q;5f\l#>m=G:gs8W,lm=5~> !<<#["lnJu!!'gAs8E!&[CjE#e(Wj-k(3P`s8W,lk(!~> !<<#b"mb?N!!#9lrsF8V!*QqNb0o&(lH\dJs+14Ms7Pc0~> !<<#b"mb?N!!#9lrsF8V!*QqNb0o&(lH\dJs+14Ms7Pc0~> !<<#["m"U;!!#9lrsF8V!*?YD`Qcrfj3He !<<#b!po.1rW'A+#s\Ij=hVbteD0*/m=G:gs8W,lm=5~> !<<#b!po.1rW'A+#s\Ij=hVbteD0*/m=G:gs8W,lm=5~> !<<#[!p/CrrW'A+#s\Ij=Ll5fcdppsk(3P`s8W,lk(!~> !<;ua"mP),!!%PYrr@TK!!]mHa3N8mkKWCFs+14Ms7Pc0~> !<;ua"mP),!!%PYrr@TK!!]mHa3N8mkKWCFs+14Ms7Pc0~> !<;uZ"le>r!!%PYrr@TK!!]dA_o^ !<;ua"mY3I!!#9nrsF8V!*HeJb0el"l-8Vgrt^F7OH@ihLkpq[huAM*Z'_frm%]H+idq.%rr<&' s7Pc0~> !<;ua"mY3I!!#9nrsF8V!*HeJb0el"l-8Vgrt^F7OH@ihLkpq[huAM*Z'_frm%]H+idq.%rr<&' s7Pc0~> !<;uZ"lnL8!!#9nrsF8V!*?SA`QZfaim$WYrt^F7OH@ihLkpq[huAM*Z'_frm%]H+idq.%rr<&' s7PN)~> !<;ua#4(I22ZNin\GuO.#IV4`c.(M-lHS_hrre/'qq[D6rso%gPlHR)M!C$#T[itsR@+!Wq/I,Y M"CWH!:g'h!3lLrm=5~> !<;ua#4(I22ZNin\GuO.#IV4`c.(M-lHS_hrre/'qq[D6rso%gPlHR)M!C$#T[itsR@+!Wq/I,Y M"CWH!:g'h!3lLrm=5~> !<;uZ#3=at2#mWl\GuO.#I1hTaNi>kj3?`Zrre/'qq[D6rso%gPlHR)M!C$#T[itsR@+!Wq/I,Y M"CWH!:g'h!3lLrk(!~> !<;r`"mP'E!!#9prsF8V!*HbHaO&PqjNR#art:.ds8RoTcBa !<;r`"mP'E!!#9prsF8V!*HbHaO&PqjNR#art:.ds8RoTcBa !<;rY"le@3!!#9prsF8V!*?P?_opH[hTY-Trt:.ds8RoTcBa !<;r`!pet+rVur6])Va0#?naWbgY;(l-/Pfrr@lSrt("dN8=BdM#W;2M#W;2U"oO2rrF(qrVm8E .Kh6?rrA/o&Y&h!!<3!A!-\f)rr<$%0>%5aG6o\>rr<$%0>%7I+9MTDs,nK,!!(%=p$I8~> !<;r`!pet+rVur6])Va0#?naWbgY;(l-/Pfrr@lSrt("dN8=BdM#W;2M#W;2U"oO2rrF(qrVm8E .Kh6?rrA/o&Y&h!!<3!A!-\f)rr<$%0>%5aG6o\>rr<$%0>%7I+9MTDs,nK,!!(%=p$I8~> !<;rY!p&7mrVur6])Va0#?\IMa3N2gj36ZYrr@lSrt("dN8=BdM#W;2M#W;2U"oO2rrF(qrVm8E .Kh6?rrA/o&Y&h!!<3!A!-\f)rr<$%0>%5aG6o\>rr<$%0>%7I+9MTDs,nK,!!(%=p#^c~> !<;r`#41O5D?'Z%]Di4g!!$-2^W4^Qh;c]Gp&>#Jrr3Ues29tZaGBkPs+p]2s6Z\BLkqGu!<`8u rs^EIp6mK2!1!8e'`\15r;Qj!NV$o`rs8P@)?0]*p73c6rsenE)?1,"f(o@G%#3@/`W,VuJ,~> !<;r`#41O5D?'Z%]Di4g!!$-2^W4^Qh;c]Gp&>#Jrr3Ues29tZaGBkPs+p]2s6Z\BLkqGu!<`8u rs^EIp6mK2!1!8e'`\15r;Qj!NV$o`rs8P@)?0]*p73c6rsenE)?1,"f(o@G%#3@/`W,VuJ,~> !<;rY#3Fh!CB+?"]Di4g!!$*-]>Mh>fA4X4p&>#Jrr3Ues29tZaGBkPs+p]2s6Z\BLkqGu!<`8u rs^EIp6mK2!1!8e'`\15r;Qj!NV$o`rs8P@)?0]*p73c6rsenE)?1,"f(o@G%#3@/`W,VnJ,~> !<;o_#3k7-2?3`-^&J(_rW!22]>Mh@g>M5=m=G<1rrF(qrVm !<;o_#3k7-2?3`-^&J(_rW!22]>Mh@g>M5=m=G<1rrF(qrVm !<;oX#3+Oo2#mW,^&J(_rW!2/\A#u.eCij)k(3R*rrF(qrVm !<;o_"7,12VZ$Pq^qTjJ!!l`<^W4[Oh;dkFm=G<1rsX$HkO !<;o_"7,12VZ$Pq^qTjJ!!l`<^W4[Oh;dkFm=G<1rsX$HkO !<;oX"6AFsU&G#l^qTjJ!!lZ6]>D_ !<;l^#3k:.2ZNhC^]+:arW!23]u8+Cg>M5&.jZurr<$#+d`3/!!4=1`rG`!J,~> !<;l^#3k:.2ZNhC^]+:arW!23]u8+Cg>M5&.jZurr<$#+d`3/!!4=1`rG`!J,~> !<;lW#3+Oo2#mVA^]+:arW!20\\Q82eCrp)k(3R*rs\[?'Fflu`$kd;`W#r;rriE&s)A;Err2ru rVmjg&.jZurr@0I-cuC>&.jZurr<$#+d`3/!!4=1`rG_oJ,~> !<;l^"7,12h>RBSJAM6c_#=<>=h;Jld+@.8lH8LFs+14Ms7Pc0~> !<;l^"7,12h>RBSJAM6c_#=<>=h;Jld+@.8lH8LFs+14Ms7Pc0~> !<;lW"6AItfDYaMJAM6c_#=<>=LZ#_bL5&!j3$M8s+14Ms7PN)~> !<;i]"7,+-D>jM8JA_Be_#=<>=Lc,dcIL\.lH/FEs+14Ms7Pc0~> !<;i]"7,+-D>jM8JA_Be_#=<>=Lc,dcIL\.lH/FEs+14Ms7Pc0~> !<;iV"6ACoCAn25JA_Be_#=<>=1,ZXaj8Mlj2pG7s+14Ms7PN)~> !<;f\"6nn(C]4;6^r66\5l^m9[_9Z(db3O>aR+QYJcGcMp$I8~> !<;f\"6nn(C]4;6^r66\5l^m9[_9Z(db3O>aR+QYJcGcMp$I8~> !<;fU"6/1jB`7u3^r66\5l^m8Zamllc-tA&aQA'RJcGcMp#^c~> !<;f\"RG71gGSs1`W#pgrW!53]>D\:f%oBOaR+QYJcGcMp$I8~> !<;f\"RG71gGSs1`W#pgrW!53]>D\:f%oBOaR+QYJcGcMp$I8~> !<;fU"Q\Osehm@+`W#pgrW!5/\%]i)dF[16aQA'RJcGcMp#^c~> !<;c[#O1@-feia05f`m)JGoNSL:P+>cIL\-l,W1Bs+14Ms7Pc0~> !<;c[#O1@-feia05f`m)JGoNSL:P+>cIL\-l,W1Bs+14Ms7Pc0~> !<;cT#NFUndk^t(5f`m)JGoNSK=/A/aj8MkilC24s+14Ms7PN)~> !<;c["RP@4hP'oK!'n[+!.b%K$!=^O`luochrO.Em=G:gs8W,lm=5~> !<;c["RP@4hP'oK!'n[+!.b%K$!=^O`luochrO.Em=G:gs8W,lm=5~> !<;cT"QeY!fUi'B!'n[+!.b%K$!4LF_8jjNg"kc1k(3P`s8W,lk(!~> !<;`Z"RG71gnFZH!5QhY!($Sa$!FaO`66T]hrF"Am=G:gs8W,lm=5~> !<;`Z"RG71gnFZH!5QhY!($Sa$!FaO`66T]hrF"Am=G:gs8W,lm=5~> !<;`S"Q\Lret2g?!5QhY!($Sa$!4LF^rO^Kg"k].k(3P`s8W,lk(!~> !<;]Y"R>..ghZig!.`Dq!Ph##!!uf<]u/%@f\PTQ`U/6VJcGcMp$I8~> !<;]Y"R>..ghZig!.`Dq!Ph##!!uf<]u/%@f\PTQ`U/6VJcGcMp$I8~> !<;]R"QSCoenG!^!.`Dq!Ph##!!u`7\\H/.db!=8`TDaOJcGcMp#^c~> !<;ZX"R5%,gnFZH!.`Js!5SO5$!FgQ`6-HYh;[Y:m=G:gs8W,lm=5~> !<;ZX"R5%,gnFZH!.`Js!5SO5$!FgQ`6-HYh;[Y:m=G:gs8W,lm=5~> !<;ZQ"QJ=nesu[=!.`Js!5SO5$!=UH^r=LEfA,?'k(3P`s8W,lk(!~> !<;ZX#41U9i7lg>rVuqKe,KDur;[.Z]"c5-d+7"2kJQ\ !<;ZX#41U9i7lg>rVuqKe,KDur;[.Z]"c5-d+7"2kJQ\ !<;ZQ#3Fk%g=FV,rVuqKe,KDur;[.W[_'AqbL+nqi5=].s+14Ms7PN)~> !<;WW#41U9i7lgrr;ZhJfDble62gil/;UpS`lui]gu@J__sN$TJcGcMp$I8~> !<;WW#41U9i7lgrr;ZhJfDble62gil/;UpS`lui]gu@J__sN$TJcGcMp$I8~> !<;WP#3Fk%g==S^r;ZhJfDble62gil.u(OG_T0mJf%]*D_rcOMJcGcMp#^c~> !<;TV#41U9i7usur;Zj`^tSeg62^ck/@iI2aN`2dgu7D]_X2pSJcGcMp$I8~> !<;TV#41U9i7usur;Zj`^tSeg62^ck/@iI2aN`2dgu7D]_X2pSJcGcMp$I8~> !<;TO#3Fk%g=F\ar;Zj`^tSeg62^ck/@E%&_oU*Nf%]*C_WHFLJcGcMp#^c~> !<;QU#OL^:iSE.XB)M]0JDL5*JGfHV/;UpR`Q?HUg"kWLm(r(?s+14Ms7Pc0~> !<;QU#OL^:iSE.XB)M]0JDL5*JGfHV/;UpR`Q?HUg"kWLm(r(?s+14Ms7Pc0~> !<;QN#Nat&gXjoDAGlK.JDL5*JGfHV.u(OG^r=IAe(<@3jh^)1s+14Ms7PN)~> !<;NT#OL^;j5/M?BD_c0JE-Y1^d7ku$sL3T_T0mIf%T$AkNp"OJcG6>rW)`nr;Zj3s8;ot'D2>( !;QQspi$uK!<3!!L&goH!!(XNp$I8~> !<;NT#OL^;j5/M?BD_c0JE-Y1^d7ku$sL3T_T0mIf%T$AkNp"OJcG6>rW)`nr;Zj3s8;ot'D2>( !;QQspi$uK!<3!!L&goH!!(XNp$I8~> !<;NM#Nat&h:U6'Ac)Q.JE-Y1^d7ku$s9pJ^;@q6d+$b(iT7l;JcG6>rW)`nr;Zj3s8;ot'D2>( !;QQspi$uK!<3!!L&goH!!(XNp#^c~> !<;KS#jgg=j58SAct3(S!.a84!Ph#!!":E8]"c2)cI1:uhrEn6m=G<*rr<&lrrHn]rVln6HLh(9 !;QQrF>2;Frr3&G('+%-!!(XNp$I8~> !<;KS#jgg=j58SAct3(S!.a84!Ph#!!":E8]"c2)cI1:uhrEn6m=G<*rr<&lrrHn]rVln6HLh(9 !;QQrF>2;Frr3&G('+%-!!(XNp$I8~> !<;KL#j(((h:^<)b@CDL!.a84!Ph#!!":E5\%BGoaj&2_g"kW$k(3R#rr<&lrrHn]rVln6HLh(9 !;QQrF>2;Frr3&G('+%-!!(XNp#^c~> !<;EQ#jUU6i7ur5Rq;Hs!^V?en,EI)J3j&4%5!FK^;@q6d+-e(i8j1:mIgJ^\[;CC](H"*U>Q"% UQPQV!;?Eomh+[\rrVX$jmrEO!;QQp)Sl=O"8FFC!;uj#!&Re%=TA="rr39)G7-,)Orsi,!9X=S m=5~> !<;EQ#jUU6i7ur5Rq;Hs!^V?en,EI)J3j&4%5!FK^;@q6d+-e(i8j1:mIgJ^\[;CC](H"*U>Q"% UQPQV!;?Eomh+[\rrVX$jmrEO!;QQp)Sl=O"8FFC!;uj#!&Re%=TA="rr39)G7-,)Orsi,!9X=S m=5~> !<;EJ#ijn"g"+TsQX]gl!^V?en,EI)J3j&4%4m:D]"Z&%bKnYgg>:l'k4S`W\[;CC](H"*U>Q"% UQPQV!;?Eomh+[\rrVX$jmrEO!;QQp)Sl=O"8FFC!;uj#!&Re%=TA="rr39)G7-,)Orsi,!9X=S k(!~> !<;BP$1$g:iniD?e';M!!<.NJ_#OH5^]K$P62:Kj>)Ku._oBmGe(<@3io]O=mIgJ^rM[<+rUg*m f!57JgQDL9!;?EnM-KrY!Jt%Nrr<&ors8dP$6?2IF&r;@r;R!%df@i*s8N)srs/UaoP"75L=Z1^ s7Pc0~> !<;BP$1$g:iniD?e';M!!<.NJ_#OH5^]K$P62:Kj>)Ku._oBmGe(<@3io]O=mIgJ^rM[<+rUg*m f!57JgQDL9!;?EnM-KrY!Jt%Nrr<&ors8dP$6?2IF&r;@r;R!%df@i*s8N)srs/UaoP"75L=Z1^ s7Pc0~> !<;BI$0:+'gXt$'c,jGi!<.NJ_#OH5^]K$P62:Kj=bjQ#^;@n4cI1:sgu%2*k4S`WrM[<+rUg*m f!57JgQDL9!;?EnM-KrY!Jt%Nrr<&ors8dP$6?2IF&r;@r;R!%df@i*s8N)srs/UaoP"75L=Z1^ s7PN)~> !<;?O$gd0AjP\kJf$_lUA*3h'/W.3T_SsU>cdUJ!hW!Y\m(;ZWrrVA>kO\]VrL)<-rfR3\!;HKp q]3lZrrW' !<;?O$gd0AjP\kJf$_lUA*3h'/W.3T_SsU>cdUJ!hW!Y\m(;ZWrrVA>kO\]VrL)<-rfR3\!;HKp q]3lZrrW' !<;?H$g$F-h:gK1d*9^C@HRV%/;UgI^;7b.b0A>`f\>9Bjh'[IrrVA>kO\]VrL)<-rfR3\!;HKp q]3lZrrW' !<;9M%I<6?j5AbHf@/*kQXaIm!"pnhN5!*E`Q--Hd*pV#h;RGXlacEUrri4jX.K4[rrU6#d#e8- !;HKoW&stX!N8*Lrr<&orrc*(q7@@>rr<&srs/W)!<3'!rs%BZrr<&rrrW/5$KhB]m=5~> !<;9M%I<6?j5AbHf@/*kQXaIm!"pnhN5!*E`Q--Hd*pV#h;RGXlacEUrri4jX.K4[rrU6#d#e8- !;HKoW&stX!N8*Lrr<&orrc*(q7@@>rr<&srs/W)!<3'!rs%BZrr<&rrrW/5$KhB]m=5~> !<;9F%HZU,h:gK1dE]qVP@.bd!"pkeM7U@6^r+.5bKeMbfA#->jLOFGrri4jX.K4[rrU6#d#e8- !;HKoW&stX!N8*Lrr<&orrc*(q7@@>rr<&srs/W)!<3'!rs%BZrr<&rrrW/5$KhB]k(!~> !<;6L'C>#Jk2YCUgXt')d*9bTP[S!.?i2SV?5&?[^qmq-a2lHLd*pRugYLiLkN]PDp&>9)ZN%5= s8RoKrrVnVqi_!W!<<'!!;uit2lloW!B7(Xrr<&ns82lsrr<&trrh+9" !<;6L'C>#Jk2YCUgXt')d*9bTP[S!.?i2SV?5&?[^qmq-a2lHLd*pRugYLiLkN]PDp&>9)ZN%5= s8RoKrrVnVqi_!W!<<'!!;uit2lloW!B7(Xrr<&ns82lsrr<&trrh+9" !<;6E'BS96hqd#S>SE$S]Y2%p_SjI9bK\D_e^rR3i8_9)ZN%5= s8RoKrrVnVqi_!W!<<'!!;uit2lloW!B7(Xrr<&ns82lsrr<&trrh+9" !<;0J'^Y/Mk2bOZh:gN3e'ZLebfe,LaN+8!'?A28aihoRd*^@of\>6?ioK:fm'c !<;0J'^Y/Mk2bOZh:gN3e'ZLebfe,LaN+8!'?A28aihoRd*^@of\>6?ioK:fm'c !<;0C'BS<7i838Bf@86qcHOGQa2Z*9_u%:`_o0O6a2uNKcHt%jf@o$:i8`q-k3i6LM"goNrlr-- s7lTo!36(lk(!~> !<;*H'("oJkiLj`hqd&?f[eR$da?Fgrm1Sj&(/hSe^iC+gtglHj5oLim'Q0Jrr@lLrrS"JoR[&; rr<&!s7Pc0~> !<;*H'("oJkiLj`hqd&?f[eR$da?Fgrm1Sj&(/hSe^iC+gtglHj5oLim'Q0Jrr@lLrrS"JoR[&; rr<&!s7Pc0~> !<;*A''836iSWJGg"4d(da6=cc-4ASrlY5`&'W;Dcd:+if%8U0h;@2Ojg=1 !<;!E%d`KEkN1a`iSWJHgY:E7qq)A2g=k<;hV[;PjQ5Rhm'5r/s+14Ms7Pc0~> !<;!E%d`KEkN1a`iSWJHgY:E7qq)A2g=k<;hV[;PjQ5Rhm'5r/s+14Ms7Pc0~> !<;!>%cua1iSWJHgY(30f%&:!r6kVos3qD,f@\d2gtprIj5mQ.JcC<$s8VcSJ,~> !<:pCs6TdN#O(@5jQ#:[iVDF3i<\H'jlYajlKdf?m=G:gs8W,lm=5~> !<:pCs6TdN#O(@5jQ#:[iVDF3i<\H'jlYajlKdf?m=G:gs8W,lm=5~> !<:p !<:g@s6L!VlKRQski_.+k5XWEkQL/8lKml=m=G:gs8W,lm=5~> !<:g@s6L!VlKRQski_.+k5XWEkQL/8lKml=m=G:gs8W,lm=5~> !<:g9roF.?ro3t:s5 !<:X;r9OFLrTa=IVsT'8JcGcMp$I8~> !<:X;r9OFLrTa=IVsT'8JcGcMp$I8~> !<:X4r8mk;r8jj=JcC<$s8VcSJ,~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<;KSa*.Nh]Bt1LJcGcMp$I8~> !<;KSa*.Nh]Bt1LJcGcMp$I8~> !<;KLa)h<_]B4\EJcGcMp#^c~> !<;KS`rH+F]C#.g!/UXS!/Q4+PlLFDJ,~> !<;KS`rH+F]C#.g!/UXS!/Q4+PlLFDJ,~> !<;KL`rH+@]B8Y`!/UXS!/Q4+PlLF=J,~> !<;KS`rH+F]C#.g!/U7H!/Q4+k5PRZ,R#p:q#:AL!;6?l!8%8Dm=5~> !<;KS`rH+F]C#.g!/U7H!/Q4+k5PRZ,R#p:q#:AL!;6?l!8%8Dm=5~> !<;KL`rH+@]B8Y`!/U7H!/Q4+k5PRZ,R#p:q#:AL!;6?l!8%8Dk(!~> !<;KSrW(V%!0-]e"$chdZFfMorr@lHrr@k+s5s=a<7:N9'`%b1cP?NRrr<&Ns7Pc0~> !<;KSrW(WX!5%s>"$chdZFfMorr@lHrr@k+s5s=a<7:N9'`%b1cP?NRrr<&Ns7Pc0~> !<;KLrW(V%!0-]e"$chdXLmWbrr@lHrr@k+s5s=a<7:N9'`%b1cP?NRrr<&Ns7PN)~> !<;KSrW(V4!7(<<"$chdZFfMsrs7bVN5(2Ks+lL1rrpW5M26ufrJ:LRs+hAHT&9R(b`Iq5s6-)3 M#[MAUPbK!JcG`L!ojI\qu6cn/>N:DrriBZ=<^lIr;cis#lo/W0`R:Y!rr>`s7Pc0~> !<;KSrW(X?!;lKi"$chdZFfMsrs7bVN5(2Ks+lL1rrpW5M26ufrJ:LRs+hAHT&9R(b`Iq5s6-)3 M#[MAUPbK!JcG`L!ojI\qu6cn/>N:DrriBZ=<^lIr;cis#lo/W0`R:Y!rr>`s7Pc0~> !<;KLrW(V4!7(<<"$chdXLmWfrs7bVN5(2Ks+lL1rrpW5M26ufrJ:LRs+hAHT&9R(b`Iq5s6-)3 M#[MAUPbK!JcG`L!ojI\qu6cn/>N:DrriBZ=<^lIr;cis#lo/W0`R:Y!rr>`s7PN)~> !<;KSrW(V4!7(<<"$chdZFfMsrrek>qp1)trr@lSrr\;$m/6k_!/UOP%0#:Os+ntth3%`.c2@S@ "JF-r;R$&NV$oa%#3@/jo>#@J,~> !<;KSrW(X?!;lKi"$chdZFfMsrrek>qp1)trr@lSrr\;$m/6k_!/UOP%0#:Os+ntth3%`.c2@S@ "JF-r;R$&NV$oa%#3@/jo>#@J,~> !<;KLrW(V4!7(<<"$chdXLmWfrrek>qp1)trr@lSrr\;$m/6k_!/UOP%0#:Os+ntth3%`.c2@S@ "JF-r;R$&NV$oa%#3@/jo>#9J,~> !<;KSrW(V4!7(<<"$chdZFfMsrre5Vs8%QNrr@lSrs.;ZR?#2bs+pXR%F18rLku(erVunRs,-[O rr@rTLk5R/s32a4rVHNo!rW,trs&Q(!<3'!rrDus! !<;KSrW(X?!;lKi"$chdZFfMsrre5Vs8%QNrr@lSrs.;ZR?#2bs+pXR%F18rLku(erVunRs,-[O rr@rTLk5R/s32a4rVHNo!rW,trs&Q(!<3'!rrDus! !<;KLrW(V4!7(<<"$chdXLmWfrre5Vs8%QNrr@lSrs.;ZR?#2bs+pXR%F18rLku(erVunRs,-[O rr@rTLk5R/s32a4rVHNo!rW,trs&Q(!<3'!rrDus! !<;KSrW(V4!7(<<"$chdZFfMsrrek>qp:/urr@lQrs&J]NW9$:nG`FlN8=BdM#W;1rre/XS^%*3 rrSnLqgneIrrS`K`V'62!;uj&!<3'!!<<''hu3QT!;lctrOrX$s7Pc0~> !<;KSrW(X?!;lKi"$chdZFfMsrrek>qp:/urr@lQrs&J]NW9$:nG`FlN8=BdM#W;1rre/XS^%*3 rrSnLqgneIrrS`K`V'62!;uj&!<3'!!<<''hu3QT!;lctrOrX$s7Pc0~> !<;KLrW(V4!7(<<"$chdXLmWfrrek>qp:/urr@lQrs&J]NW9$:nG`FlN8=BdM#W;1rre/XS^%*3 rrSnLqgneIrrS`K`V'62!;uj&!<3'!!<<''hu3QT!;lctrOrX$s7PN)~> !<;KSrW(V4!7(<<"$chdZFfMsrrqJPN5(2Kr.l$dLkpnScN!pXN/7N6`/fu#M#W;1rsX_`kFRNd s8VJ`N/3:@s82iuoD\gerr<&srs8]*!<3'!s)A;Err2rurVult!WjO3k5Y,AJ,~> !<;KSrW(X?!;lKi"$chdZFfMsrrqJPN5(2Kr.l$dLkpnScN!pXN/7N6`/fu#M#W;1rsX_`kFRNd s8VJ`N/3:@s82iuoD\gerr<&srs8]*!<3'!s)A;Err2rurVult!WjO3k5Y,AJ,~> !<;KLrW(V4!7(<<"$chdXLmWfrrqJPN5(2Kr.l$dLkpnScN!pXN/7N6`/fu#M#W;1rsX_`kFRNd s8VJ`N/3:@s82iuoD\gerr<&srs8]*!<3'!s)A;Err2rurVult!WjO3k5Y,:J,~> !<;KSrW(V4!7(<<"$chdZFfLTs+14Ms7Pc0~> !<;KSrW(X?!;lKi"$chdZFfLTs+14Ms7Pc0~> !<;KLrW(V4!7(<<"$chdXLmVGs+14Ms7PN)~> !<;KSrW(V4!7(<<"$chdZFfLTs+14Ms7Pc0~> !<;KSrW(X?!;lKi"$chdZFfLTs+14Ms7Pc0~> !<;KLrW(V4!7(<<"$chdXLmVGs+14Ms7PN)~> !<;KSrW(V4!7(<<"$chdZFfLTs+14Ms7Pc0~> !<;KSrW(X?!;lKi"$chdZFfLTs+14Ms7Pc0~> !<;KLrW(V4!7(<<"$chdXLmVGs+14Ms7PN)~> !<;KS`rH+F]Bt1LJcGcMp$I8~> !<;KS`rH+F]Bt1LJcGcMp$I8~> !<;KL`rH+@]B4\EJcGcMp#^c~> !<;KS`rH+F]Bt1LJcGcMp$I8~> !<;KS`rH+F]Bt1LJcGcMp$I8~> !<;KL`rH+@]B4\EJcGcMp#^c~> !<;KS`rH+F]Bt1LJcGcMp$I8~> !<;KS`rH+F]Bt1LJcGcMp$I8~> !<;KL`rH+@]B4\EJcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<9n&!-bU5JcC<$s8VcZJ,~> !<9n&!-bU5JcC<$s8VcZJ,~> !<9mt!-GC+JcC<$s8VcSJ,~> !<9q'!I"OHm=G:gs8W,lm=5~> !<9q'!I"OHm=G:gs8W,lm=5~> !<9pu!H\=Ek(3P`s8W,lk(!~> !<9q'rr<%CYO-o@JcGcMp$I8~> !<9q'rr<%CYO-o@JcGcMp$I8~> !<9purr<%@YNCE9JcGcMp#^c~> !<9t(!-eDBYO-o@JcGcMp$I8~> !<9t(!-eDBYO-o@JcGcMp$I8~> !<9t!!-J2?YNCE9JcGcMp#^c~> !<9t(r;ZhAYjI#AJcGcMp$I8~> !<9t(r;ZhAYjI#AJcGcMp$I8~> !<9t!r;Zh>Yi^N:JcGcMp#^c~> !<:")!-e>@YjI#AJcGcMp$I8~> !<:")!-e>@YjI#AJcGcMp$I8~> !<:""!-J,=Yi^N:JcGcMp#^c~> !<:")qZ$V?Z0g`S!/U"A!/Q4+hZ!QUeGo4/J,~> !<:")qZ$V?Z0g`S!/U"A!/Q4+hZ!QUeGo4/J,~> !<:""qZ$V !<:%*!-e8>Z0g-B!/UUR!/Q4+qu6qV!<<)$.g@fGqZ$Npp\t3neGo4/J,~> !<:%*!-e8>Z0g-B!/UUR!/Q4+qu6qV!<<)$.g@fGqZ$Npp\t3neGo4/J,~> !<:%#!-J&;Z0'X;!/UUR!/Q4+qu6qV!<<)$.g@fGqZ$Npp\t3neGo4(J,~> !<:%*q0'%1f)G`+rVlkRJcGZJ$-s@es8Q0OpoYG.rr<&nrr<&Ks7Pc0~> !<:%*q0'%1f)G`+rVlkRJcGZJ$-s@es8Q0OpoYG.rr<&nrr<&Ks7Pc0~> !<:%#q/Wb&f)G`+rVlkRJcGZJ$-s@es8Q0OpoYG.rr<&nrr<&Ks7PN)~> !<7W;mHsTHs+gc7T&9SHL]ibiI/>N:ErrVno ./`s7Pc0~> !<7W;mHsTHs+gc7T&9SHL]ibiI/>N:ErrVno ./`s7Pc0~> !<7W4mH4*As+gc7T&9SHL]ibiI/>N:ErrVno ./`s7PN)~> !<7W;mHsNF!rh5Err3&GNo^3>!/UXS#)MQ#lAkn#rr3(Vs8RoRrre/'qq[Bcrr[=CrrE&urr<'5 c1q;=!;ld2/"QC$!<4jOpp_XM/sl7#!<3I^f(nG-p$I8~> !<7W;mHsNF!rh5Err3&GNo^3>!/UXS#)MQ#lAkn#rr3(Vs8RoRrre/'qq[Bcrr[=CrrE&urr<'5 c1q;=!;ld2/"QC$!<4jOpp_XM/sl7#!<3I^f(nG-p$I8~> !<7W4mH4$?!rh5Err3&GNo^3>!/UXS#)MQ#lAkn#rr3(Vs8RoRrre/'qq[Bcrr[=CrrE&urr<'5 c1q;=!;ld2/"QC$!<4jOpp_XM/sl7#!<3I^f(nG-p#^c~> !<7W;mHsTH$-ninLku*2er]"lrr@lSrsaeaM#W;2M#[MRM#[L2rVm"VrVunRLB%5Qs8N'#r;cis !qiHBq#:p$I8~> !<7W;mHsTH$-ninLku*2er]"lrr@lSrsaeaM#W;2M#[MRM#[L2rVm"VrVunRLB%5Qs8N'#r;cis !qiHBq#:p$I8~> !<7W4mH4*A$-ninLku*2er]"lrr@lSrsaeaM#W;2M#[MRM#[L2rVm"VrVunRLB%5Qs8N'#r;cis !qiHBq#:p#^c~> !<7W;mHsTH#`RU:gkc!dP32]B!/UXS%Z(.@s+p];hYYTSs8S#Hrr2tSrr2tSJcG`L#6=f(@H%!] ');J,!;ld%/=lL'!<4p_p\t0q/Xc=(!<3!"rOrX$s7Pc0~> !<7W;mHsTH#`RU:gkc!dP32]B!/UXS%Z(.@s+p];hYYTSs8S#Hrr2tSrr2tSJcG`L#6=f(@H%!] ');J,!;ld%/=lL'!<4p_p\t0q/Xc=(!<3!"rOrX$s7Pc0~> !<7W4mH4*A#`RU:gkc!dP32]B!/UXS%Z(.@s+p];hYYTSs8S#Hrr2tSrr2tSJcG`L#6=f(@H%!] ');J,!;ld%/=lL'!<4p_p\t0q/Xc=(!<3!"rOrX$s7PN)~> !<7W;mHsTH#/lR`aGBkUrJ1IRr.l$dM#W;2M#Yh_ONiU]s1OAIs8RoSrr@k+s8N$*!<<(m+p9I* s8N)ts82lqrt`U%#^6:KfKfcC!<:.g#^-:L!!!'DQfe>@m=5~> !<7W;mHsTH#/lR`aGBkUrJ1IRr.l$dM#W;2M#Yh_ONiU]s1OAIs8RoSrr@k+s8N$*!<<(m+p9I* s8N)ts82lqrt`U%#^6:KfKfcC!<:.g#^-:L!!!'DQfe>@m=5~> !<7W4mH4*A#/lR`aGBkUrJ1IRr.l$dM#W;2M#Yh_ONiU]s1OAIs8RoSrr@k+s8N$*!<<(m+p9I* s8N)ts82lqrt`U%#^6:KfKfcC!<:.g#^-:L!!!'DQfe>@k(!~> !<7W;mHoK)U]1DoY:/?/p$I8~> !<7W;mHoK)U]1DoY:/?/p$I8~> !<7W4mH0!"U]1DoY:/?/p#^c~> !<7W;mHoK)V#LMs!@,A;s7Pc0~> !<7W;mHoK)V#LMs!@,A;s7Pc0~> !<7W4mH0!"V#LMs!@,A;s7PN)~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHsQG!mcm_rVlkRmf*9Ck5PF;pAY,KJcF*sp$I8~> !<7W;mHsQG!mcm_rVlkRmf*9Ck5PF;pAY,KJcF*sp$I8~> !<7W4mH4'@!mcm_rVlkRmf*9Ck5PF;pAY,KJcF*sp#^c~> !<7W;mHsQG!L2j4rr@l6rr@k+s8;lsbk@G)bhN0jm=5~> !<7W;mHsQG!L2j4rr@l6rr@k+s8;lsbk@G)bhN0jm=5~> !<7W4mH4'@!L2j4rr@l6rr@k+s8;lsbk@G)bhN0jk(!~> !<7W;mHsQG!/Th !<7W;mHsQG!/Th !<7W4mH4'@!/Th !<7W;mHsTHrJ:IQs+p[S&@o1JZb$2'UPbK!s5of0^PMad'SsM_S*'b3S;Nits3-gdLku*2Lku'1 rJ:IQs+p[S$KR_7SE0^+b`Iq5OoGELpA=oYg]-s6J,~> !<7W;mHsTHrJ:IQs+p[S&@o1JZb$2'UPbK!s5of0^PMad'SsM_S*'b3S;Nits3-gdLku*2Lku'1 rJ:IQs+p[S$KR_7SE0^+b`Iq5OoGELpA=oYg]-s6J,~> !<7W4mH4*ArJ:IQs+p[S&@o1JZb$2'UPbK!s5of0^PMad'SsM_S*'b3S;Nits3-gdLku*2Lku'1 rJ:IQs+p[S$KR_7SE0^+b`Iq5OoGELpA=oYg]-s/J,~> !<7W;mHsQG!/UOP!/UXS&H/Q=W;Zahebn9ls.%L6c\VGt%uAU0d@L<,dJM:\s,P+cr;HWqM#I>R M#72PM#RD]S'h,[SH"Djqq[BsrrAJ\qZ*&=s7Pc0~> !<7W;mHsQG!/UOP!/UXS&H/Q=W;Zahebn9ls.%L6c\VGt%uAU0d@L<,dJM:\s,P+cr;HWqM#I>R M#72PM#RD]S'h,[SH"Djqq[BsrrAJ\qZ*&=s7Pc0~> !<7W4mH4'@!/UOP!/UXS&H/Q=W;Zahebn9ls.%L6c\VGt%uAU0d@L<,dJM:\s,P+cr;HWqM#I>R M#72PM#RD]S'h,[SH"Djqq[BsrrAJ\qZ*&=s7PN)~> !<7W;mHsQG!/UOP!/UUR"m:^)s8RuTL^!g9qu?MKqYq+_qu?PNs,$XQqMP=-X-o!crr2tSrVlkR qu6YPrr3=^qu?MMs+pXRs+lg;!1EQW!1DUAp$I8~> !<7W;mHsQG!/UOP!/UUR"m:^)s8RuTL^!g9qu?MKqYq+_qu?PNs,$XQqMP=-X-o!crr2tSrVlkR qu6YPrr3=^qu?MMs+pXRs+lg;!1EQW!1DUAp$I8~> !<7W4mH4'@!/UOP!/UUR"m:^)s8RuTL^!g9qu?MKqYq+_qu?PNs,$XQqMP=-X-o!crr2tSrVlkR qu6YPrr3=^qu?MMs+pXRs+lg;!1EQW!1DUAp#^c~> !<7W;mHsQG!/UOP!/UXS#lC3oT)8P]h#%*N"J!m:d"qPu$Ad+,d@C6+dJM:\rr3'!iJmj:!/UUR !K6IFrr@lSrs"t?qp:l5M#RDSLlq];RJ-FVRG7c7m=5~> !<7W;mHsQG!/UOP!/UXS#lC3oT)8P]h#%*N"J!m:d"qPu$Ad+,d@C6+dJM:\rr3'!iJmj:!/UUR !K6IFrr@lSrs"t?qp:l5M#RDSLlq];RJ-FVRG7c7m=5~> !<7W4mH4'@!/UOP!/UXS#lC3oT)8P]h#%*N"J!m:d"qPu$Ad+,d@C6+dJM:\rr3'!iJmj:!/UUR !K6IFrr@lSrs"t?qp:l5M#RDSLlq];RJ-FVRG7c7k(!~> !<7W;mHsQG!/UURr.ksb[Bfh<[f>`lN/3;ijI2%BM#.,`Lq\h:jT"80M4 !<7W;mHsQG!/UURr.ksb[Bfh<[f>`lN/3;ijI2%BM#.,`Lq\h:jT"80M4 !<7W4mH4'@!/UURr.ksb[Bfh<[f>`lN/3;ijI2%BM#.,`Lq\h:jT"80M4 !<7W;mHr=$!/Q4+j8T+Ip@eQTg]-s6J,~> !<7W;mHr=$!/Q4+j8T+Ip@eQTg]-s6J,~> !<7W4mH2gr!/Q4+j8T+Ip@eQTg]-s/J,~> !<7W;mHr=$!/Q4+j8T+Ip@\KSg]-s6J,~> !<7W;mHr=$!/Q4+j8T+Ip@\KSg]-s6J,~> !<7W4mH2gr!/Q4+j8T+Ip@\KSg]-s/J,~> !<7W;mHoK)ZMsomp@SERg]-s6J,~> !<7W;mHoK)ZMsomp@SERg]-s6J,~> !<7W4mH0!"ZMsomp@SERg]-s/J,~> !<7W;mHoK)ZMsomp@J?Qg]-s6J,~> !<7W;mHoK)ZMsomp@J?Qg]-s6J,~> !<7W4mH0!"ZMsomp@J?Qg]-s/J,~> !<7W;mHoK)ZMsomp@A9Pg]-s6J,~> !<7W;mHoK)ZMsomp@A9Pg]-s6J,~> !<7W4mH0!"ZMsomp@A9Pg]-s/J,~> !<7W;mHoK)ZMspKp6bp)g]-s6J,~> !<7W;mHoK)ZMspKp6bp)g]-s6J,~> !<7W4mH0!"ZMspKp6bp)g]-s/J,~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHs3=reQ.*a8Z,>eGo4/J,~> !<7W;mHs3=reQ.*a8Z,>eGo4/J,~> !<7W4mH3^6reQ.*a8Z,>eGo4(J,~> !<7W;mHs-;!/Q4+eGf[13UX!rr<$#,d,l$p$I8~> !<7W;mHs-;!/Q4+eGf[13UX!rr<$#,d,l$p$I8~> !<7W4mH3X4!/Q4+eGf[13UX!rr<$#,d,l$p#^c~> !<7W;mHs-;!/Q4+eGfYNGkZGaq>UX!rrE)t_@bN%p$I8~> !<7W;mHs-;!/Q4+eGfYNGkZGaq>UX!rrE)t_@bN%p$I8~> !<7W4mH3X4!/Q4+eGfYNGkZGaq>UX!rrE)t_@bN%p#^c~> !<7W;mHsTH%A)JWhk0r0S;Nits8RoRrr@lSrsX_`LnM#?Z2a0cMOEITs69Od'BoJ['`J%=aXIL< !<3'!qmRd's7Pc0~> !<7W;mHsTH%A)JWhk0r0S;Nits8RoRrr@lSrsX_`LnM#?Z2a0cMOEITs69Od'BoJ['`J%=aXIL< !<3'!qmRd's7Pc0~> !<7W4mH4*A%A)JWhk0r0S;Nits8RoRrr@lSrsX_`LnM#?Z2a0cMOEITs69Od'BoJ['`J%=aXIL< !<3'!qmRd's7PN)~> !<7W;mHsTH%Eu*WZ-E< !<7W;mHsTH%Eu*WZ-E< !<7W4mH4*A%Eu*WZ-E< !<7W;mHsTH%JPqQO8&[2qu?MMs8RoRrsX_`s8@cRM#W;2M#WA2Lk5Tjrra#"s6(0qrsAo&s7HBl !<;r*-eeUnm=5~> !<7W;mHsTH%JPqQO8&[2qu?MMs8RoRrsX_`s8@cRM#W;2M#WA2Lk5Tjrra#"s6(0qrsAo&s7HBl !<;r*-eeUnm=5~> !<7W4mH4*A%JPqQO8&[2qu?MMs8RoRrsX_`s8@cRM#W;2M#WA2Lk5Tjrra#"s6(0qrsAo&s7HBl !<;r*-eeUnk(!~> !<7W;mHsQG%C)P1s8SS9qp:l5s-2aHrsk1Jqp(#tM#W;2M#X1(qgne1rrc8Hpj-!>rsC !<7W;mHsQG%C)P1s8SS9qp:l5s-2aHrsk1Jqp(#tM#W;2M#X1(qgne1rrc8Hpj-!>rsC !<7W4mH4'@%C)P1s8SS9qp:l5s-2aHrsk1Jqp(#tM#W;2M#X1(qgne1rrc8Hpj-!>rsC !<7W;mHsQG+lA+5s8V,KM4N/3:@s69Odh_t]uiVic``[;%9 !<3$!!?\&is7Pc0~> !<7W;mHsQG+lA+5s8V,KM4N/3:@s69Odh_t]uiVic``[;%9 !<3$!!?\&is7Pc0~> !<7W4mH4'@+lA+5s8V,KM4N/3:@s69Odh_t]uiVic``[;%9 !<3$!!?\&is7PN)~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHrU,!/Q4+W;lPXJ,~> !<7W;mHrU,!/Q4+W;lPXJ,~> !<7W4mH3+%!/Q4+W;lPQJ,~> !<7W;mHs-;!/U7H!/Q4+g]%8tp6bp)g]-s6J,~> !<7W;mHs-;!/U7H!/Q4+g]%8tp6bp)g]-s6J,~> !<7W4mH3X4!/U7H!/Q4+g]%8tp6bp)g]-s/J,~> !<7W;mHs-;!/U7H!/Q4+g]%8ApAFuZg]-s6J,~> !<7W;mHs-;!/U7H!/Q4+g]%8ApAFuZg]-s6J,~> !<7W4mH3X4!/U7H!/Q4+g]%8ApAFuZg]-s/J,~> !<7W;mHsTH#)JqWR'H[Irr3"Ts8@cRrsJ+fMOEK(k*q=ELk5T\rrAJ\quE/>s7Pc0~> !<7W;mHsTH#)JqWR'H[Irr3"Ts8@cRrsJ+fMOEK(k*q=ELk5T\rrAJ\quE/>s7Pc0~> !<7W4mH4*A#)JqWR'H[Irr3"Ts8@cRrsJ+fMOEK(k*q=ELk5T\rrAJ\quE/>s7PN)~> !<7W;mHsTH#)MQ#lAkn#rr3(Vs8RoRrsGFLr8*kASC.5[Lk5T\rrAJ\qZ*&=s7Pc0~> !<7W;mHsTH#)MQ#lAkn#rr3(Vs8RoRrsGFLr8*kASC.5[Lk5T\rrAJ\qZ*&=s7Pc0~> !<7W4mH4*A#)MQ#lAkn#rr3(Vs8RoRrsGFLr8*kASC.5[Lk5T\rrAJ\qZ*&=s7PN)~> !<7W;mHsTH%>b%?s+p]2s8W%Rs8RoRrr@rTL^!g9qu?MKJcFL)!1EQW!1DUAp$I8~> !<7W;mHsTH%>b%?s+p]2s8W%Rs8RoRrr@rTL^!g9qu?MKJcFL)!1EQW!1DUAp$I8~> !<7W4mH4*A%>b%?s+p]2s8W%Rs8RoRrr@rTL^!g9qu?MKJcFL)!1EQW!1DUAp#^c~> !<7W;mHsTH%Z(.@s+p];hYYTSs8S#Hrr3%nh#%*N"J!m:d"m8Qg]%8ApA"]Vg]-s6J,~> !<7W;mHsTH%Z(.@s+p];hYYTSs8S#Hrr3%nh#%*N"J!m:d"m8Qg]%8ApA"]Vg]-s6J,~> !<7W4mH4*A%Z(.@s+p];hYYTSs8S#Hrr3%nh#%*N"J!m:d"m8Qg]%8ApA"]Vg]-s/J,~> !<7W;mHsTH*/OWNs+p^%O-%VBs8TckM#[MBVMp=es5]W-^PII@g]%8Ap@nWUg]-s6J,~> !<7W;mHsTH*/OWNs+p^%O-%VBs8TckM#[MBVMp=es5]W-^PII@g]%8Ap@nWUg]-s6J,~> !<7W4mH4*A*/OWNs+p^%O-%VBs8TckM#[MBVMp=es5]W-^PII@g]%8Ap@nWUg]-s/J,~> !<7W;mHoK)ZMsomp@eQTg]-s6J,~> !<7W;mHoK)ZMsomp@eQTg]-s6J,~> !<7W4mH0!"ZMsomp@eQTg]-s/J,~> !<7W;mHoK)ZMsomp@\KSg]-s6J,~> !<7W;mHoK)ZMsomp@\KSg]-s6J,~> !<7W4mH0!"ZMsomp@\KSg]-s/J,~> !<7W;mHoK)ZMsomp@SERg]-s6J,~> !<7W;mHoK)ZMsomp@SERg]-s6J,~> !<7W4mH0!"ZMsomp@SERg]-s/J,~> !<7W;mHoK)ZMsomp@J?Qg]-s6J,~> !<7W;mHoK)ZMsomp@J?Qg]-s6J,~> !<7W4mH0!"ZMsomp@J?Qg]-s/J,~> !<7W;mHoK)ZMsomp@A9Pg]-s6J,~> !<7W;mHoK)ZMsomp@A9Pg]-s6J,~> !<7W4mH0!"ZMsomp@A9Pg]-s/J,~> !<7W;mHoK)ZMspKp6bp)g]-s6J,~> !<7W;mHoK)ZMspKp6bp)g]-s6J,~> !<7W4mH0!"ZMspKp6bp)g]-s/J,~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHpPG!Um@GO7822Zg[Vfp$I8~> !<7W;mHpPG!Um@GO7822Zg[Vfp$I8~> !<7W4mH1&@!Um@GO7822Zg[Vfp#^c~> !<7W;mHpPG!OIi !<7W;mHpPG!OIi !<7W4mH1&@!OIi !<7W;mHs$8!/UOPreSPn!KZAoirA! !<7W;mHs$8!/UOPreSPn!KZJrjT"9@Pk3r:glM1/p$I8~> !<7W4mH3O1!/UOPreSPn!KZZ"lMp,LR.KSDiK*^4p#^c~> !<7W;mHs$8!/UIN!/SVo!KZAoi;_^8P4RT4foPk,p$I8~> !<7W;mHs$8!/UIN!/SVo!KZGqirA! !<7W4mH3O1!/UIN!/SVo!KZW!kl9iHQh0DAi/dU3p#^c~> !<7W;mHs$8!/UIN!/SVo!KZ>nhuDR6On7H2fT5b+p$I8~> !<7W;mHs$8!/UIN!/SVo!KZGqiW%j:P4RW5g5kt-p$I8~> !<7W4mH3O1!/UIN!/SVo!KZW!kPs]FQLj8?hiIL2p#^c~> !<7W;mHsTH)2OrjR'H\PS;Nits5of0^PMpVUPbK!s8RnorrJ2An(n$EP262$oA0N"O7rVHm=5~> !<7W;mHsTH)2OrjR'H\PS;Nits5of0^PMpVUPbK!s8RnorrJ2Dn)+0GP2H>'oABZ&O7rVHm=5~> !<7W4mH4*A)2OrjR'H\PS;Nits5of0^PMpVUPbK!s8RnorrJ2In)XNLP2u\/oAp#0O7rVHk(!~> !<7W;mHsTH)2RR6lAkn6dJM:\s.%L6c\VUoebn9ls8RnorrJ2AjP0_8S_O4+o@sAtO7rVHm=5~> !<7W;mHsTH)2RR6lAkn6dJM:\s.%L6c\VUoebn9ls8RnorrJ2DjPBk:S_a@/oA0N#O7rVHm=5~> !<7W4mH4*A)2RR6lAkn6dJM:\s.%L6c\VUoebn9ls8RnorrJ2IjQ$:@S`Bd8oAfr.O7rVHk(!~> !<7W;mHsTH&r?RDs+p]3qu?MMs,-^RqM>0/rJ:LR!/SVo!KZ:%gAh5EgAm<+gB#icpAagQJ,~> !<7W;mHsTH&r?RDs+p]3qu?MMs,-^RqM>0/rJ:LR!/SVo!KZC(h#IGGh#NQ.h#Z,gpAagQJ,~> !<7W4mH4*A&r?RDs+p]3qu?MMs,-^RqM>0/rJ:LR!/SVo!KZR-iW&tLiW,26iW7hqpAagJJ,~> !<7W;mHsTH'SudFs+p]EdJM:\s.%L6d"q^qh#%'M!L2ffrrJ2@rRhY\A-X !<7W;mHsTH'SudFs+p]EdJM:\s.%L6d"q^qh#%'M!L2ffrrJ2BrS%e^A-X<\gSlG#,cTPQ!!*#Q "TWr]%u&d:!;Y@XI2`7g!8FIu"!jl#!<1^c!8H6.AdKp@YpohUgY97o" !<7W4mH4*A'SudFs+p]EdJM:\s.%L6d"q^qh#%'M!L2ffrrJ2GrS\4dB*TZdiN=I0-*H4c!!*#W "TX&a%uK9D!;YR^JK+al!9((,"=:50!<1pi!9)l:BaQ?K[OVF[iShL,"iVEQRi;q\o pAagJJ,~> !<7W;mHsTH)i4NMs+p^8Ru*Wqs5]W-^PMpWVMp=es8UE)Lr]MrO5g,j$35\)KdPFsK[>;rrn%2N rn%GUIaFPrf[p2ufF/+-[F !<7W;mHsTH)i4NMs+p^8Ru*Wqs5]W-^PMpWVMp=es8UE)Lr]MrO6->o$35b-L*kV!L!kW"rn7>P rn7SWJ'si!g=cQ$g'e=0\(03d,DFR!,h`7tg'Ri6g&QEG=kY6PeGe/)/ !<7W4mH4*A)i4NMs+p^8Ru*Wqs5]W-^PMpWVMp=es8UE)Lr]MrO6Z]$$35k5M'q1+Lt@D.rnd\U rndq\K%HY-hqnG.h[Bj7]@u0",`('--/S_%h[0A@hZ/#R>Mgi[g&Bk3/WeYZ+NiRAOo4,7!<(dU g5kt-p#^c~> !<7W;mHpPG!KZ7=ed;E&f$4EL"4#KC"9-jO!<1OS!7TNM!7o^$pXU%D5T^AKecX4Kc3)Vtf%/:M f%'iLrmh)PcMc;o"9&9#!QrsnMu2*)!;tCKdZ=,%p$I8~> !<7W;mHpPG!KZ=?fEqW*f['cR"45]I"9-pQ!<1UU!7f`S!8,p(pXg1H5T^ALfE9LQci_o#f\"^S f[p2Rrn%5Rd/DSs"9&9#!Qs$pNVhB-!;tIMe;s>'p$I8~> !<7W4mH1&@!KZLDh?j86hUVhd"4l>Z"9.-W!<1g[!8HAe!8cQ4pYHUR66HYRh?2?ceH=Y.hVQue hVJ7drn[YXec">)"9&9#!Qs7!Oo+#7!;t[SfoPk,p#^c~> !<7W;mHpPG!KZ4 !<7W;mHpPG!KZ=?f*VM2c\*Rq,D=Bp,Q?9p!<1RM!<1RQ!8#g+]_9qheY!3",D=Bp,M*Ntd!buH f)Yd$![` !<7W4mH1&@!KZLDg^4%:e;#@*,DXd&,lZR!!<1aR!<1aV!8Q?5_"QOqg8#&1,DXd&,hs!)eU[_U g]7<.![iKLrnIQ-RaoL7!0@!3!!)oO!SEJ,s7PN)~> !<7W;mHpPG!KZ4 !<7W;mHpPG!KZ:>eI)@c#9fqoU`ombV:GJD!S%2LeHGs'eQrI\q:5KF&HWr$eYFAk,,Nh@%1ddc e^XZueI&<(!W^d$Zo/-h!6WglMt>I"dZ=,%p$I8~> !<7W4mH1&@!KZICgC"!l#9g,%W$;BhWS@=N!S[VRgB@T3gL1!,H9FN%M+!g gY2`,gBt,4!W_!0\i9op!6X$rO7V*,f8oY*p#^c~> !<7W;mHpPG!KZ1;dJs9#dK#-qdK.[TpAagQJ,~> !<7W;mHpPG!KZ7=e,TK%e,YBte,dsXpAagQJ,~> !<7W4mH1&@!KZFBf`2#*f`7$'f`BZbpAagJJ,~> !<7W;mHpPG!KZ1;ci='!ciAjmciMFQpAagQJ,~> !<7W;mHpPG!KZ7=dJs9#dK#-qdK.^UpAagQJ,~> !<7W4mH1&@!KZFBfDko)fDpp&fE'N`pAagJJ,~> !<7W;mHpPG!KZ,hci;;kL\&gmc&_Sup$I8~> !<7W;mHpPG!KZ2jdJqSoM"B!pc]@f"p$I8~> !<7W4mH1&@!KZAof)O;$Mt>L#e;s>'p#^c~> !<7W;mHpPG!KZ)gcMu/iL@`[kb`DJtp$I8~> !<7W;mHpPG!KZ2jd/VGmL\&jncB%]!p$I8~> !<7W4mH1&@!KZAoec4/"Mt>I"duX5&p#^c~> !<7W;mHpPG!KZ)gbl>leL%ELhbE)Asp$I8~> !<7W;mHpPG!KZ/icMu/iL@`[kc&_Sup$I8~> !<7W4mH1&@!KZ>ne,RksM=]0sdZ=,%p#^c~> !<7W;mHpPG!OIi<_Z.IQIe1DWO0eQ[p$I8~> !<7W;mHpPG!OIi<_uIUSJ+LPYO0eQ[p$I8~> !<7W4mH1&@!OIi !<7W;mHpPG!Um@GO7822Zg[Vfp$I8~> !<7W;mHpPG!Um@GO7822Zg[Vfp$I8~> !<7W4mH1&@!Um@GO7822Zg[Vfp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHs$8!/UXS!/Q4+T`=]PJ,~> !<7W;mHs$8!/UXS!/Q4+T`=]PJ,~> !<7W4mH3O1!/UXS!/Q4+T`=]IJ,~> !<7W;mHs$8!/Q4+SH&9LJ,~> !<7W;mHs$8!/Q4+SH&9LJ,~> !<7W4mH3O1!/Q4+SH&9EJ,~> !<7W;mHs$8!/Q4+SH&9LJ,~> !<7W;mHs$8!/Q4+SH&9LJ,~> !<7W4mH3O1!/Q4+SH&9EJ,~> !<7W;mHsTHs+gf8T&9R(rr3=]s5of0^PMpiLku'1"QZ)1SE0PH%*Y&pLku)lS;Nits+p[S&r?Sf Lqeq@s6-)3M#[MAUPbK!bPqPB`r?#=jo>;[jT"o?J,~> !<7W;mHsTHs+gf8T&9R(rr3=]s5of0^PMpiLku'1"QZ)1SE0PH%*Y&pLku)lS;Nits+p[S&r?Sf Lqeq@s6-)3M#[MAUPbK!bPqPB`r?#=jo>;[jT"o?J,~> !<7W4mH4*As+gf8T&9R(rr3=]s5of0^PMpiLku'1"QZ)1SE0PH%*Y&pLku)lS;Nits+p[S&r?Sf Lqeq@s6-)3M#[MAUPbK!bPqPB`r?#=jo>;[jT"o8J,~> !<7W;mHsNF"TIGGs+p[S#)N;LdJM7Grr2tSrr3+jdJM:\qYpta`pEX's-qF5d%(,lrr3=]s8Rnt qRHQrc2@S@"JF !<7W;mHsNF"TIGGs+p[S#)N;LdJM7Grr2tSrr3+jdJM:\qYpta`pEX's-qF5d%(,lrr3=]s8Rnt qRHQrc2@S@"JF !<7W4mH4$?"TIGGs+p[S#)N;LdJM7Grr2tSrr3+jdJM:\qYpta`pEX's-qF5d%(,lrr3=]s8Rnt qRHQrc2@S@"JF !<7W;mHsTH&C-SuLku(es8W%Rs,-^RqM>.N!/UXS"H!$UqMP.L'\f[VNl1mRqu?MMs+p^TrJ:LR M#I>Q!K-aPrr@rTLtr"1!:g'h!65$=!9=(Z!;6?l!:g*^m=5~> !<7W;mHsTH&C-SuLku(es8W%Rs,-^RqM>.N!/UXS"H!$UqMP.L'\f[VNl1mRqu?MMs+p^TrJ:LR M#I>Q!K-aPrr@rTLtr"1!:g'h!65$=!9=(Z!;6?l!:g*^m=5~> !<7W4mH4*A&C-SuLku(es8W%Rs,-^RqM>.N!/UXS"H!$UqMP.L'\f[VNl1mRqu?MMs+p^TrJ:LR M#I>Q!K-aPrr@rTLtr"1!:g'h!65$=!9=(Z!;6?l!:g*^k(!~> !<7W;mHsTH&<,HBgkbumhYYTSs.%L6d"q]$!/UXS"Img9d%'n2&cUaKs-qF5d%(,uhYYTSs8RoR rrSb9r;Q]tU"oP4rt3O2!s&B$`@;!?`W#q#&eUW5r;cis+TQ]o0`M+Q">@Ce!-\_tao;>@">@Ce Orsi,!<83)!rr>trt?O3s4ngt!!!-T]DjRss10rbrrE#t--e4I!<<'!OV/7qrr<$%0>%5aOV/7q rrA/o&Y&gP3X$hGs8N)lrr<&hs7Pc0~> !<7W;mHsTH&<,HBgkbumhYYTSs.%L6d"q]$!/UXS"Img9d%'n2&cUaKs-qF5d%(,uhYYTSs8RoR rrSb9r;Q]tU"oP4rt3O2!s&B$`@;!?`W#q#&eUW5r;cis+TQ]o0`M+Q">@Ce!-\_tao;>@">@Ce Orsi,!<83)!rr>trt?O3s4ngt!!!-T]DjRss10rbrrE#t--e4I!<<'!OV/7qrr<$%0>%5aOV/7q rrA/o&Y&gP3X$hGs8N)lrr<&hs7Pc0~> !<7W4mH4*A&<,HBgkbumhYYTSs.%L6d"q]$!/UXS"Img9d%'n2&cUaKs-qF5d%(,uhYYTSs8RoR rrSb9r;Q]tU"oP4rt3O2!s&B$`@;!?`W#q#&eUW5r;cis+TQ]o0`M+Q">@Ce!-\_tao;>@">@Ce Orsi,!<83)!rr>trt?O3s4ngt!!!-T]DjRss10rbrrE#t--e4I!<<'!OV/7qrr<$%0>%5aOV/7q rrA/o&Y&gP3X$hGs8N)lrr<&hs7PN)~> !<7W;mHsTH&]B`kaGBlCO-%VBs5]W-^PMpfL^!h=Ru*WqqZ$SO&W)k"s5TT*S)jUrO-%VBs8RoR rsIq\M2;33mA>huM!t?F/sH)0rs^EIp6mK2!1!8e'`\15r;Qj!NV$o`rs8P@)?0]*p73c6rsenE )?1,"f(o@G%#3@/q>US+R/iadrr39%`#8T<=oVTfqYpULPPG.W"9=_\[hSiA(];2hrrA>X[hSiB Q1h[$s"gMH\0_1T!;6?l!:g*^m=5~> !<7W;mHsTH&]B`kaGBlCO-%VBs5]W-^PMpfL^!h=Ru*WqqZ$SO&W)k"s5TT*S)jUrO-%VBs8RoR rsIq\M2;33mA>huM!t?F/sH)0rs^EIp6mK2!1!8e'`\15r;Qj!NV$o`rs8P@)?0]*p73c6rsenE )?1,"f(o@G%#3@/q>US+R/iadrr39%`#8T<=oVTfqYpULPPG.W"9=_\[hSiA(];2hrrA>X[hSiB Q1h[$s"gMH\0_1T!;6?l!:g*^m=5~> !<7W4mH4*A&]B`kaGBlCO-%VBs5]W-^PMpfL^!h=Ru*WqqZ$SO&W)k"s5TT*S)jUrO-%VBs8RoR rsIq\M2;33mA>huM!t?F/sH)0rs^EIp6mK2!1!8e'`\15r;Qj!NV$o`rs8P@)?0]*p73c6rsenE )?1,"f(o@G%#3@/q>US+R/iadrr39%`#8T<=oVTfqYpULPPG.W"9=_\[hSiA(];2hrrA>X[hSiB Q1h[$s"gMH\0_1T!;6?l!:g*^k(!~> !<7W;mHpGD!' Z:lPRIes'UUJ*L>V#Q]%!WW9$m1A+cmeZqa"SM`n(]aI5rrE(a*s)ECrrDrrrrE'!qu?Zrrri<$ !s/H%!;6?l!:g*^m=5~> !<7W;mHpGD!' Z:lPRIes'UUJ*L>V#Q]%!WW9$m1A+cmeZqa"SM`n(]aI5rrE(a*s)ECrrDrrrrE'!qu?Zrrri<$ !s/H%!;6?l!:g*^m=5~> !<7W4mH0r=!' Z:lPRIes'UUJ*L>V#Q]%!WW9$m1A+cmeZqa"SM`n(]aI5rrE(a*s)ECrrDrrrrE'!qu?Zrrri<$ !s/H%!;6?l!:g*^k(!~> !<7W;mHpGD!\KW3rr30OSG&C$rrE&u"9AK%#2fCZ!!)ut&.#c=ZiL++SG&L*rs6+-ZiL+*!r]h? rr3&u^'a`:&*+%U$K_9n\+[0/s8Q.U=S_su/X-#0rr<&urs/W)#e0N7!<3&urrN3#!<3!$!<4p_ p\k*m)9qmT!!)Qhp$I8~> !<7W;mHpGD!\KW3rr30OSG&C$rrE&u"9AK%#2fCZ!!)ut&.#c=ZiL++SG&L*rs6+-ZiL+*!r]h? rr3&u^'a`:&*+%U$K_9n\+[0/s8Q.U=S_su/X-#0rr<&urs/W)#e0N7!<3&urrN3#!<3!$!<4p_ p\k*m)9qmT!!)Qhp$I8~> !<7W4mH0r=!\KW3rr30OSG&C$rrE&u"9AK%#2fCZ!!)ut&.#c=ZiL++SG&L*rs6+-ZiL+*!r]h? rr3&u^'a`:&*+%U$K_9n\+[0/s8Q.U=S_su/X-#0rr<&urs/W)#e0N7!<3&urrN3#!<3!$!<4p_ p\k*m)9qmT!!)Qhp#^c~> !<7W;mHpGD%*U@+!<<);.0D$ !<7W;mHpGD%*U@+!<<);.0D$ !<7W4mH0r=%*U@+!<<);.0D$ !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHpPG!!%TMr;ZZpm/QbGJ,~> !<7W;mHpPG!!%TMr;ZZpm/QbGJ,~> !<7W4mH1&@!!%TMr;ZZpm/Qb@J,~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHr$q!/Q4+\Gu6hJ,~> !<7W;mHr$q!/Q4+\Gu6hJ,~> !<7W4mH2Oj!/Q4+\Gu6aJ,~> !<7W;mHr:#!/Q4+Z2aLaJ,~> !<7W;mHr:#!/Q4+Z2aLaJ,~> !<7W4mH2dq!/Q4+Z2aLZJ,~> !<7W;mHr:#!/Q4+Z2aLaJ,~> !<7W;mHr:#!/Q4+Z2aLaJ,~> !<7W4mH2dq!/Q4+Z2aLZJ,~> !<7W;mHsTH,Da*nS*'bNLqeq@s5]]-SE0^+^PnZEs6QM !<7W;mHsTH,Da*nS*'bNLqeq@s5]]-SE0^+^PnZEs6QM !<7W4mH4*A,Da*nS*'bNLqeq@s5]]-SE0^+^PnZEs6QM !<7W;mHsTH-&BqFd@L=;Ls#S(s-qF5d%(,ld/24]s.Ip?hO4EDLs#S(s8RoPrr@lSrs>@Kr8*kA NlLG`_#FN;MZ<\VoD\h/chIG>!7LlJ):.aN!!)?bp$I8~> !<7W;mHsTH-&BqFd@L=;Ls#S(s-qF5d%(,ld/24]s.Ip?hO4EDLs#S(s8RoPrr@lSrs>@Kr8*kA NlLG`_#FN;MZ<\VoD\h/chIG>!7LlJ):.aN!!)?bp$I8~> !<7W4mH4*A-&BqFd@L=;Ls#S(s-qF5d%(,ld/24]s.Ip?hO4EDLs#S(s8RoPrr@lSrs>@Kr8*kA NlLG`_#FN;MZ<\VoD\h/chIG>!7LlJ):.aN!!)?bp#^c~> !<7W;mHsTH#`/BXqhkFRM#I>Q%#Ol]qMP7s4aTB Nl/^5"99h?rrDZj!!2fn!!(II!!2Ke!!)?bp$I8~> !<7W;mHsTH#`/BXqhkFRM#I>Q%#Ol]qMP7s4aTB Nl/^5"99h?rrDZj!!2fn!!(II!!2Ke!!)?bp$I8~> !<7W4mH4*A#`/BXqhkFRM#I>Q%#Ol]qMP7s4aTB Nl/^5"99h?rrDZj!!2fn!!(II!!2Ke!!)?bp#^c~> !<7W;mHsTH#Dge)d@C7:M#I>`S'h,[SH"DmqpCr6U"oPDrr@lQrrJ&Ir;QbQrr3%nh#%$L!rh/< _>aiBL4]F*s2?Gj.`M8ar;cisrrE#t!!)ut"P5[<-,KHX'p9[A!!*$!FU0D;s49:8-,KW,0`qFT s8N)us8;rss8N)ursI@="XV"baXIL !<7W;mHsTH#Dge)d@C7:M#I>`S'h,[SH"DmqpCr6U"oPDrr@lQrrJ&Ir;QbQrr3%nh#%$L!rh/< _>aiBL4]F*s2?Gj.`M8ar;cisrrE#t!!)ut"P5[<-,KHX'p9[A!!*$!FU0D;s49:8-,KW,0`qFT s8N)us8;rss8N)ursI@="XV"baXIL !<7W4mH4*A#Dge)d@C7:M#I>`S'h,[SH"DmqpCr6U"oPDrr@lQrrJ&Ir;QbQrr3%nh#%$L!rh/< _>aiBL4]F*s2?Gj.`M8ar;cisrrE#t!!)ut"P5[<-,KHX'p9[A!!*$!FU0D;s49:8-,KW,0`qFT s8N)us8;rss8N)ursI@="XV"baXIL !<7W;mHsTH#Dg*QRcOMKM#I>ej-tmpjSt%MN1/i-mA>huM#[L2r;Qo5N/7N6r.ka\mA>huM#W9e M3lN(rsAbt,jta5.@g+#.f')E!;lcr!<)ot!<)p#1SslV-2RWU%#3@/s8N(fp73f81SslV-3#T] q#(-l!<)ot!;lcr!<3!*1SslV-3#Kdp6YjGs7Pc0~> !<7W;mHsTH#Dg*QRcOMKM#I>ej-tmpjSt%MN1/i-mA>huM#[L2r;Qo5N/7N6r.ka\mA>huM#W9e M3lN(rsAbt,jta5.@g+#.f')E!;lcr!<)ot!<)p#1SslV-2RWU%#3@/s8N(fp73f81SslV-3#T] q#(-l!<)ot!;lcr!<3!*1SslV-3#Kdp6YjGs7Pc0~> !<7W4mH4*A#Dg*QRcOMKM#I>ej-tmpjSt%MN1/i-mA>huM#[L2r;Qo5N/7N6r.ka\mA>huM#W9e M3lN(rsAbt,jta5.@g+#.f')E!;lcr!<)ot!<)p#1SslV-2RWU%#3@/s8N(fp73f81SslV-3#T] q#(-l!<)ot!;lcr!<3!*1SslV-3#Kdp6YjGs7PN)~> !<7W;mHsTH!/U.E!/R'C$3:*^HN='JpAadgq>UEpqu6WrrVlitrVlj#rVup!qYpu09I1-srrDcm p'(I!rW!$$rri#lrr<&trr<&rrr<&urr<3$!!WK("SVlg!:0[Xm=5~> !<7W;mHsTH!/U.E!/R'C$3:*^HN='JpAadgq>UEpqu6WrrVlitrVlj#rVup!qYpu09I1-srrDcm p'(I!rW!$$rri#lrr<&trr<&rrr<&urr<3$!!WK("SVlg!:0[Xm=5~> !<7W4mH4*A!/U.E!/R'C$3:*^HN='JpAadgq>UEpqu6WrrVlitrVlj#rVup!qYpu09I1-srrDcm p'(I!rW!$$rri#lrr<&trr<&rrr<&urr<3$!!WK("SVlg!:0[Xk(!~> !<7W;mHsTH!/U.E!/R'C$3:+u+TVNkSG&C$q>UEpqu6WrrVlm9c2R_E27*&Nrs\k?$NC*rp73`6 27*&VrrOd9q>C6m!<)ot!;lcr!<3!"27*&VrramDp6l!Is7Pc0~> !<7W;mHsTH!/U.E!/R'C$3:+u+TVNkSG&C$q>UEpqu6WrrVlm9c2R_E27*&Nrs\k?$NC*rp73`6 27*&VrrOd9q>C6m!<)ot!;lcr!<3!"27*&VrramDp6l!Is7Pc0~> !<7W4mH4*A!/U.E!/R'C$3:+u+TVNkSG&C$q>UEpqu6WrrVlm9c2R_E27*&Nrs\k?$NC*rp73`6 27*&VrrOd9q>C6m!<)ot!;lcr!<3!"27*&VrramDp6l!Is7PN)~> !<7W;mHoK)qYpm&s8RrVs26>g.E2,_!!)utquHcs$(2/$s8U_;#QOl$s8N'6![3p1!-AJo`rF[W #QOl)bV'"ts8Vuss8N'!rVucq%fb0I#QOl)`[;%9!<<'!mf2tIJ,~> !<7W;mHoK)qYpm&s8RrVs26>g.E2,_!!)utquHcs$(2/$s8U_;#QOl$s8N'6![3p1!-AJo`rF[W #QOl)bV'"ts8Vuss8N'!rVucq%fb0I#QOl)`[;%9!<<'!mf2tIJ,~> !<7W4mH0!"qYpm&s8RrVs26>g.E2,_!!)utquHcs$(2/$s8U_;#QOl$s8N'6![3p1!-AJo`rF[W #QOl)bV'"ts8Vuss8N'!rVucq%fb0I#QOl)`[;%9!<<'!mf2tBJ,~> !<7W;mHoK)`;]f;^&RcmJ,~> !<7W;mHoK)`;]f;^&RcmJ,~> !<7W4mH0!"`;]f;^&RcfJ,~> !<7W;mHoK)`;]f;^&RcmJ,~> !<7W;mHoK)`;]f;^&RcmJ,~> !<7W4mH0!"`;]f;^&RcfJ,~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHsECrW%NLPQ1=CJ,~> !<7W;mHsECrW%NLPQ1=CJ,~> !<7W4mH3p !<7W;mHsTH#Pi?S%>b&a!.k06s7Pc0~> !<7W;mHsTH#Pi?S%>b&a!.k06s7Pc0~> !<7W4mH4*A#Pi?S%>b&a!.k06s7PN)~> !<7W;mHsTH#Bs#,mrJI.!.k06s7Pc0~> !<7W;mHsTH#Bs#,mrJI.!.k06s7Pc0~> !<7W4mH4*A#Bs#,mrJI.!.k06s7PN)~> !<7W;mHsTH!?9nnrr<&trt*.!"=hCjOrsi,!<:Y3"XV!9s/>tjm=5~> !<7W;mHsTH!?9nnrr<&trt*.!"=hCjOrsi,!<:Y3"XV!9s/>tjm=5~> !<7W4mH4*A!?9nnrr<&trt*.!"=hCjOrsi,!<:Y3"XV!9s/>tjk(!~> !<7W;mHsTH!<`8trr<&trt$WLp6mK2%#3@/s8OmOpp_W$s/>tjm=5~> !<7W;mHsTH!<`8trr<&trt$WLp6mK2%#3@/s8OmOpp_W$s/>tjm=5~> !<7W4mH4*A!<`8trr<&trt$WLp6mK2%#3@/s8OmOpp_W$s/>tjk(!~> !<7W;mHsTH!?9nnrr<&trsT#'s7HNpZ:lPRIfBQM!!!%Ns/>tjm=5~> !<7W;mHsTH!?9nnrr<&trsT#'s7HNpZ:lPRIfBQM!!!%Ns/>tjm=5~> !<7W4mH4*A!?9nnrr<&trsT#'s7HNpZ:lPRIfBQM!!!%Ns/>tjk(!~> !<7W;mHsTH#^&u,n9"^1)9r0\"=]l7RO\=:#5u7Cs#%.WJcDVIp$I8~> !<7W;mHsTH#^&u,n9"^1)9r0\"=]l7RO\=:#5u7Cs#%.WJcDVIp$I8~> !<7W4mH4*A#^&u,n9"^1)9r0\"=]l7RO\=:#5u7Cs#%.WJcDVIp#^c~> !<7W;mHsTH*;FCc%>b&aR0s#ps26>g.E2;e!!4=1s4BUC!!%TMW;lPXJ,~> !<7W;mHsTH*;FCc%>b&aR0s#ps26>g.E2;e!!4=1s4BUC!!%TMW;lPXJ,~> !<7W4mH4*A*;FCc%>b&aR0s#ps26>g.E2;e!!4=1s4BUC!!%TMW;lPQJ,~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;mHoK)JcGcMp$I8~> !<7W;mHoK)JcGcMp$I8~> !<7W4mH0!"JcGcMp#^c~> !<7W;JaJ$UhsGp~> !<7W;JaJ$UhsGp~> !<7W4J`_OGhr]F~> !<7W;JaJ$UhsGp~> !<7W;JaJ$UhsGp~> !<7W4J`_OGhr]F~> !<7W;JaJ$UhsGp~> !<7W;JaJ$UhsGp~> !<7W4J`_OGhr]F~> !<7W;JaJ$UhsGp~> !<7W;JaJ$UhsGp~> !<7W4J`_OGhr]F~> !<7W;JaJ$UhsGp~> !<7W;JaJ$UhsGp~> !<7W4J`_OGhr]F~> !<7W;JaJ$UhsGp~> !<7W;JaJ$UhsGp~> !<7W4J`_OGhr]F~> !<7W;JaJ$UhsGp~> !<7W;JaJ$UhsGp~> !<7W4J`_OGhr]F~> !<7W;JaJ$UhsGp~> !<7W;JaJ$UhsGp~> !<7W4J`_OGhr]F~> !<7W;JaJ$UhsGp~> !<7W;JaJ$UhsGp~> !<7W4J`_OGhr]F~> !<7W;JaJ$UhsGp~> !<7W;JaJ$UhsGp~> !<7W4J`_OGhr]F~> !<7W;JaJ$UhsGp~> !<7W;JaJ$UhsGp~> !<7W4J`_OGhr]F~> !<7W;JaJ$UhsGp~> !<7W;JaJ$UhsGp~> !<7W4J`_OGhr]F~> !<7W;JaJ$UhsGp~> !<7W;JaJ$UhsGp~> !<7W4J`_OGhr]F~> JcC<$JcFU,J,~> JcC<$JcFU,J,~> JcC<$JcFU,J,~> JcC<$JcFU,J,~> JcC<$JcFU,J,~> JcC<$JcFU,J,~> %%EndData showpage %%Trailer end %%EOF ssr-0.4.2/doc/manual/images/signal_processing.eps000066400000000000000000000536771236416011200220530ustar00rootroot00000000000000%!PS-Adobe-3.0 EPSF-3.0 %%Creator: 0.46 %%Pages: 1 %%Orientation: Portrait %%BoundingBox: 46 263 585 794 %%HiResBoundingBox: 46.354688 263.2 584.8 793.6 %%EndComments %%Page: 1 1 0 842 translate 0.8 -0.8 scale 0 0 0 setrgbcolor [] 0 setdash 1 setlinewidth 0 setlinejoin 0 setlinecap gsave [1 0 0 1 0 0] concat gsave [-1 0 0 1 0 0] concat gsave 1 1 1 setrgbcolor newpath -280 672.36218 moveto -190 672.36218 lineto -190 722.36218 lineto -280 722.36218 lineto -280 672.36218 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 2 setlinewidth 0 setlinejoin 0 setlinecap newpath -280 672.36218 moveto -190 672.36218 lineto -190 722.36218 lineto -280 722.36218 lineto -280 672.36218 lineto closepath stroke grestore gsave [-1 0 0 1 0 0] concat gsave 1 1 1 setrgbcolor newpath -730 672.36218 moveto -640 672.36218 lineto -640 722.36218 lineto -730 722.36218 lineto -730 672.36218 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 2 setlinewidth 0 setlinejoin 0 setlinecap newpath -730 672.36218 moveto -640 672.36218 lineto -640 722.36218 lineto -730 722.36218 lineto -730 672.36218 lineto closepath stroke grestore gsave [-1 0 0 1 0 0] concat gsave 1 1 1 setrgbcolor newpath -280 92.362183 moveto -190 92.362183 lineto -190 142.36218 lineto -280 142.36218 lineto -280 92.362183 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 2 setlinewidth 0 setlinejoin 0 setlinecap newpath -280 92.362183 moveto -190 92.362183 lineto -190 142.36218 lineto -280 142.36218 lineto -280 92.362183 lineto closepath stroke grestore gsave [-1 0 0 1 0 0] concat gsave 1 1 1 setrgbcolor newpath -730 92.362183 moveto -640 92.362183 lineto -640 142.36218 lineto -730 142.36218 lineto -730 92.362183 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 2 setlinewidth 0 setlinejoin 0 setlinecap newpath -730 92.362183 moveto -640 92.362183 lineto -640 142.36218 lineto -730 142.36218 lineto -730 92.362183 lineto closepath stroke grestore gsave [1 0 0 1 0 0] concat gsave 1 1 1 setrgbcolor newpath 370 382.36218 moveto 460 382.36218 lineto 460 432.36218 lineto 370 432.36218 lineto 370 382.36218 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 2 setlinewidth 0 setlinejoin 0 setlinecap newpath 370 382.36218 moveto 460 382.36218 lineto 460 432.36218 lineto 370 432.36218 lineto 370 382.36218 lineto closepath stroke grestore 0 0 0 setrgbcolor [1 3] 0 setdash 1 setlinewidth 0 setlinejoin 0 setlinecap newpath 370.00001 432.36215 moveto 395.00001 432.36215 415.00001 407.36215 415.00001 407.36215 curveto 435.00001 382.36215 460.00001 382.36215 460.00001 382.36215 curveto stroke gsave [-1 0 0 1 0 0] concat gsave 1 1 1 setrgbcolor newpath -550 382.36218 moveto -460 382.36218 lineto -460 432.36218 lineto -550 432.36218 lineto -550 382.36218 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 2 setlinewidth 0 setlinejoin 0 setlinecap newpath -550 382.36218 moveto -460 382.36218 lineto -460 432.36218 lineto -550 432.36218 lineto -550 382.36218 lineto closepath stroke grestore 0 0 0 setrgbcolor [1 3] 0 setdash 1 setlinewidth 0 setlinejoin 0 setlinecap newpath 550.00001 432.36216 moveto 525.00001 432.36216 505.00001 407.36216 505.00001 407.36216 curveto 485.00001 382.36216 460.00001 382.36216 460.00001 382.36216 curveto stroke gsave [1 0 0 1 0 0] concat gsave 1 1 1 setrgbcolor newpath 280 292.36215 moveto 370 292.36215 lineto 370 342.36215 lineto 280 342.36215 lineto 280 292.36215 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 2 setlinewidth 0 setlinejoin 0 setlinecap newpath 280 292.36215 moveto 370 292.36215 lineto 370 342.36215 lineto 280 342.36215 lineto 280 292.36215 lineto closepath stroke grestore 0 0 0 setrgbcolor [1 3] 0 setdash 1 setlinewidth 0 setlinejoin 0 setlinecap newpath 280.00001 342.36215 moveto 305.00001 342.36215 325.00001 317.36215 325.00001 317.36215 curveto 345.00001 292.36215 370.00001 292.36215 370.00001 292.36215 curveto stroke gsave [-1 0 0 1 0 0] concat gsave 1 1 1 setrgbcolor newpath -460 292.36215 moveto -370 292.36215 lineto -370 342.36215 lineto -460 342.36215 lineto -460 292.36215 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 2 setlinewidth 0 setlinejoin 0 setlinecap newpath -460 292.36215 moveto -370 292.36215 lineto -370 342.36215 lineto -460 342.36215 lineto -460 292.36215 lineto closepath stroke grestore 0 0 0 setrgbcolor [1 3] 0 setdash 1 setlinewidth 0 setlinejoin 0 setlinecap newpath 460.00001 342.36216 moveto 435.00001 342.36216 415.00001 317.36216 415.00001 317.36216 curveto 395.00001 292.36216 370.00001 292.36216 370.00001 292.36216 curveto stroke gsave [1 0 0 1 0 0] concat gsave 1 1 1 setrgbcolor newpath 460 472.36218 moveto 550 472.36218 lineto 550 522.36218 lineto 460 522.36218 lineto 460 472.36218 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 2 setlinewidth 0 setlinejoin 0 setlinecap newpath 460 472.36218 moveto 550 472.36218 lineto 550 522.36218 lineto 460 522.36218 lineto 460 472.36218 lineto closepath stroke grestore 0 0 0 setrgbcolor [1 3] 0 setdash 1 setlinewidth 0 setlinejoin 0 setlinecap newpath 460.00001 522.36215 moveto 485.00001 522.36215 505.00001 497.36215 505.00001 497.36215 curveto 525.00001 472.36215 550.00001 472.36215 550.00001 472.36215 curveto stroke gsave [-1 0 0 1 0 0] concat gsave 1 1 1 setrgbcolor newpath -640 472.36218 moveto -550 472.36218 lineto -550 522.36218 lineto -640 522.36218 lineto -640 472.36218 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 2 setlinewidth 0 setlinejoin 0 setlinecap newpath -640 472.36218 moveto -550 472.36218 lineto -550 522.36218 lineto -640 522.36218 lineto -640 472.36218 lineto closepath stroke grestore 0 0 0 setrgbcolor [1 3] 0 setdash 1 setlinewidth 0 setlinejoin 0 setlinecap newpath 640.00001 522.36216 moveto 615.00001 522.36216 595.00001 497.36216 595.00001 497.36216 curveto 575.00001 472.36216 550.00001 472.36216 550.00001 472.36216 curveto stroke gsave [1 0 0 1 0 0] concat gsave 1 1 1 setrgbcolor newpath 550 562.36218 moveto 640 562.36218 lineto 640 612.36218 lineto 550 612.36218 lineto 550 562.36218 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 2 setlinewidth 0 setlinejoin 0 setlinecap newpath 550 562.36218 moveto 640 562.36218 lineto 640 612.36218 lineto 550 612.36218 lineto 550 562.36218 lineto closepath stroke grestore 0 0 0 setrgbcolor [1 3] 0 setdash 1 setlinewidth 0 setlinejoin 0 setlinecap newpath 550.00001 612.36214 moveto 575.00001 612.36214 595.00001 587.36214 595.00001 587.36214 curveto 615.00001 562.36214 640.00001 562.36214 640.00001 562.36214 curveto stroke gsave [-1 0 0 1 0 0] concat gsave 1 1 1 setrgbcolor newpath -370 202.36214 moveto -280 202.36214 lineto -280 252.36214 lineto -370 252.36214 lineto -370 202.36214 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 2 setlinewidth 0 setlinejoin 0 setlinecap newpath -370 202.36214 moveto -280 202.36214 lineto -280 252.36214 lineto -370 252.36214 lineto -370 202.36214 lineto closepath stroke grestore 0 0 0 setrgbcolor [1 3] 0 setdash 1 setlinewidth 0 setlinejoin 0 setlinecap newpath 370.00001 252.36216 moveto 345.00001 252.36216 325.00001 227.36216 325.00001 227.36216 curveto 305.00001 202.36216 280.00001 202.36216 280.00001 202.36216 curveto stroke gsave [1 0 0 1 140.00001 81.999982] concat gsave [1 0 0 -1 179.28125 196.93022] concat gsave /newlatin1font {findfont dup length dict copy dup /Encoding ISOLatin1Encoding put definefont} def /Arial-ISOLatin1 /Arial newlatin1font 20 scalefont setfont 0 0 0 setrgbcolor newpath 0 0 moveto (+) show grestore grestore grestore gsave [1 0 0 1 230.5932 172.04927] concat gsave [1 0 0 -1 179.28125 196.93022] concat gsave /Arial-ISOLatin1 findfont 20 scalefont setfont 0 0 0 setrgbcolor newpath 0 0 moveto (+) show grestore grestore grestore gsave [1 0 0 1 319.30749 262.76356] concat gsave [1 0 0 -1 179.28125 196.93022] concat gsave /Arial-ISOLatin1 findfont 20 scalefont setfont 0 0 0 setrgbcolor newpath 0 0 moveto (+) show grestore grestore grestore gsave [1 0 0 1 405.30749 352.76356] concat gsave [1 0 0 -1 179.28125 196.93022] concat gsave /Arial-ISOLatin1 findfont 20 scalefont setfont 0 0 0 setrgbcolor newpath 0 0 moveto (+) show grestore grestore grestore gsave [1 0 0 1 136.04115 -26.029236] concat gsave [1 0 0 -1 84.84375 148.83647] concat gsave /Arial-ISOLatin1 findfont 20 scalefont setfont 0 0 0 setrgbcolor newpath 0 0 moveto (dots) show grestore grestore grestore gsave [1 0 0 1 576.04115 441.41998] concat gsave [1 0 0 -1 84.84375 148.83647] concat gsave /Arial-ISOLatin1 findfont 20 scalefont setfont 0 0 0 setrgbcolor newpath 0 0 moveto (dots) show grestore grestore grestore gsave [-1 0 0 1 0 0] concat gsave 1 1 1 setrgbcolor newpath -370 92.362144 moveto -280 92.362144 lineto -280 142.36214 lineto -370 142.36214 lineto -370 92.362144 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 2 setlinewidth 0 setlinejoin 0 setlinecap newpath -370 92.362144 moveto -280 92.362144 lineto -280 142.36214 lineto -370 142.36214 lineto -370 92.362144 lineto closepath stroke grestore gsave [-1 0 0 1 0 0] concat gsave 1 1 1 setrgbcolor newpath -460 92.362167 moveto -370 92.362167 lineto -370 142.36217 lineto -460 142.36217 lineto -460 92.362167 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 2 setlinewidth 0 setlinejoin 0 setlinecap newpath -460 92.362167 moveto -370 92.362167 lineto -370 142.36217 lineto -460 142.36217 lineto -460 92.362167 lineto closepath stroke grestore gsave [-1 0 0 1 0 0] concat gsave 1 1 1 setrgbcolor newpath -550 92.362167 moveto -460 92.362167 lineto -460 142.36217 lineto -550 142.36217 lineto -550 92.362167 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 2 setlinewidth 0 setlinejoin 0 setlinecap newpath -550 92.362167 moveto -460 92.362167 lineto -460 142.36217 lineto -550 142.36217 lineto -550 92.362167 lineto closepath stroke grestore gsave [-1 0 0 1 0 0] concat gsave 1 1 1 setrgbcolor newpath -640 92.362183 moveto -550 92.362183 lineto -550 142.36218 lineto -640 142.36218 lineto -640 92.362183 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 2 setlinewidth 0 setlinejoin 0 setlinecap newpath -640 92.362183 moveto -550 92.362183 lineto -550 142.36218 lineto -640 142.36218 lineto -640 92.362183 lineto closepath stroke grestore gsave [1 0 0 1 142.47266 80.208912] concat gsave [1 0 0 -1 84.84375 148.83647] concat gsave /Arial-ISOLatin1 findfont 20 scalefont setfont 0 0 0 setrgbcolor newpath 0 0 moveto (dots) show grestore grestore grestore gsave [1 0 0 1 583.68372 -26.029236] concat gsave [1 0 0 -1 84.84375 148.83647] concat gsave /Arial-ISOLatin1 findfont 20 scalefont setfont 0 0 0 setrgbcolor newpath 0 0 moveto (dots) show grestore grestore grestore gsave [1 0 0 1 138.04115 553.97076] concat gsave [1 0 0 -1 84.84375 148.83647] concat gsave /Arial-ISOLatin1 findfont 20 scalefont setfont 0 0 0 setrgbcolor newpath 0 0 moveto (dots) show grestore grestore grestore gsave [-1 0 0 1 0 0] concat gsave 1 1 1 setrgbcolor newpath -370 672.36218 moveto -280 672.36218 lineto -280 722.36218 lineto -370 722.36218 lineto -370 672.36218 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 2 setlinewidth 0 setlinejoin 0 setlinecap newpath -370 672.36218 moveto -280 672.36218 lineto -280 722.36218 lineto -370 722.36218 lineto -370 672.36218 lineto closepath stroke grestore gsave [-1 0 0 1 0 0] concat gsave 1 1 1 setrgbcolor newpath -460 672.36218 moveto -370 672.36218 lineto -370 722.36218 lineto -460 722.36218 lineto -460 672.36218 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 2 setlinewidth 0 setlinejoin 0 setlinecap newpath -460 672.36218 moveto -370 672.36218 lineto -370 722.36218 lineto -460 722.36218 lineto -460 672.36218 lineto closepath stroke grestore gsave [-1 0 0 1 0 0] concat gsave 1 1 1 setrgbcolor newpath -550 672.36218 moveto -460 672.36218 lineto -460 722.36218 lineto -550 722.36218 lineto -550 672.36218 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 2 setlinewidth 0 setlinejoin 0 setlinecap newpath -550 672.36218 moveto -460 672.36218 lineto -460 722.36218 lineto -550 722.36218 lineto -550 672.36218 lineto closepath stroke grestore gsave [-1 0 0 1 0 0] concat gsave 1 1 1 setrgbcolor newpath -640 672.36218 moveto -550 672.36218 lineto -550 722.36218 lineto -640 722.36218 lineto -640 672.36218 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 2 setlinewidth 0 setlinejoin 0 setlinecap newpath -640 672.36218 moveto -550 672.36218 lineto -550 722.36218 lineto -640 722.36218 lineto -640 672.36218 lineto closepath stroke grestore gsave [1 0 0 1 581.68372 553.97076] concat gsave [1 0 0 -1 84.84375 148.83647] concat gsave /Arial-ISOLatin1 findfont 20 scalefont setfont 0 0 0 setrgbcolor newpath 0 0 moveto (dots) show grestore grestore grestore gsave [1 0 0 1 -113.7832 10.717119] concat gsave [1 0 0 -1 280.8125 63.96147] concat gsave /Arial-ISOLatin1 findfont 20 scalefont setfont 0 0 0 setrgbcolor newpath 0 0 moveto (input) show grestore grestore grestore gsave [1 0 0 1 -121.26953 -107.6364] concat gsave [1 0 0 -1 275.78125 761.99272] concat gsave /Arial-ISOLatin1 findfont 20 scalefont setfont 0 0 0 setrgbcolor newpath 0 0 moveto (output) show grestore grestore grestore gsave [1 0 0 1 -12 -2] concat gsave [1 0 0 -1 304.0625 124.58647] concat gsave /Arial-ISOLatin1 findfont 20 scalefont setfont 0 0 0 setrgbcolor newpath 0 0 moveto (n) show grestore grestore grestore gsave [1 0 0 1 -20 -2] concat gsave [1 0 0 -1 394.96875 124.58647] concat gsave /Arial-ISOLatin1 findfont 20 scalefont setfont 0 0 0 setrgbcolor newpath 0 0 moveto (n+1) show grestore grestore grestore gsave [1 0 0 1 -26 -6] concat gsave [1 0 0 -1 490.9375 128.61772] concat gsave /Arial-ISOLatin1 findfont 20 scalefont setfont 0 0 0 setrgbcolor newpath 0 0 moveto (n+2) show grestore grestore grestore gsave [1 0 0 1 -22 -6] concat gsave [1 0 0 -1 576.8125 128.61772] concat gsave /Arial-ISOLatin1 findfont 20 scalefont setfont 0 0 0 setrgbcolor newpath 0 0 moveto (n+3) show grestore grestore grestore gsave [1 0 0 1 -6 4] concat gsave [1 0 0 -1 297 699.36772] concat gsave /Arial-ISOLatin1 findfont 20 scalefont setfont 0 0 0 setrgbcolor newpath 0 0 moveto (n) show grestore grestore grestore gsave [1 0 0 1 -18 2] concat gsave [1 0 0 -1 391.9375 701.36772] concat gsave /Arial-ISOLatin1 findfont 20 scalefont setfont 0 0 0 setrgbcolor newpath 0 0 moveto (n+1) show grestore grestore grestore gsave [1 0 0 1 -14 -2] concat gsave [1 0 0 -1 478.8125 705.43022] concat gsave /Arial-ISOLatin1 findfont 20 scalefont setfont 0 0 0 setrgbcolor newpath 0 0 moveto (n+2) show grestore grestore grestore gsave [1 0 0 1 -8 1.0003068] concat gsave [1 0 0 -1 562.65625 702.39897] concat gsave /Arial-ISOLatin1 findfont 20 scalefont setfont 0 0 0 setrgbcolor newpath 0 0 moveto (n+3) show grestore grestore grestore 0 0 0 setrgbcolor [6 6] 0 setdash 1 setlinewidth 0 setlinejoin 0 setlinecap newpath 325 152.36218 moveto 325 192.36218 325 192.36218 325 192.36218 curveto stroke gsave [-2.4492127e-17 -0.4 0.4 -2.4492127e-17 325 188.36218] concat gsave 0 0 0 setrgbcolor newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 1.25 setlinewidth 0 setlinejoin 0 setlinecap newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath stroke grestore 0 0 0 setrgbcolor [6 6] 0 setdash 1 setlinewidth 0 setlinejoin 0 setlinecap newpath 415 152.36218 moveto 415 282.36218 415 282.36218 415 282.36218 curveto stroke gsave [-2.4492127e-17 -0.4 0.4 -2.4492127e-17 415 278.36218] concat gsave 0 0 0 setrgbcolor newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 1.25 setlinewidth 0 setlinejoin 0 setlinecap newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath stroke grestore 0 0 0 setrgbcolor [6 6] 0 setdash 1 setlinewidth 0 setlinejoin 0 setlinecap newpath 505 147.36218 moveto 505 372.36218 505 372.36218 505 372.36218 curveto stroke gsave [-2.4492127e-17 -0.4 0.4 -2.4492127e-17 505 368.36218] concat gsave 0 0 0 setrgbcolor newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 1.25 setlinewidth 0 setlinejoin 0 setlinecap newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath stroke grestore 0 0 0 setrgbcolor [6 6] 0 setdash 1 setlinewidth 0 setlinejoin 0 setlinecap newpath 590 152.36218 moveto 590 462.36218 590 462.36218 590 462.36218 curveto stroke gsave [-2.4492127e-17 -0.4 0.4 -2.4492127e-17 590 458.36218] concat gsave 0 0 0 setrgbcolor newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 1.25 setlinewidth 0 setlinejoin 0 setlinecap newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath stroke grestore 0 0 0 setrgbcolor [6 6] 0 setdash 1 setlinewidth 0 setlinejoin 0 setlinecap newpath 325 347.36218 moveto 325 662.36218 325 662.36218 325 662.36218 curveto stroke gsave [-2.4492127e-17 -0.4 0.4 -2.4492127e-17 325 658.36218] concat gsave 0 0 0 setrgbcolor newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 1.25 setlinewidth 0 setlinejoin 0 setlinecap newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath stroke grestore 0 0 0 setrgbcolor [6 6] 0 setdash 1 setlinewidth 0 setlinejoin 0 setlinecap newpath 415 442.36218 moveto 415 662.36218 415 662.36218 415 662.36218 curveto stroke gsave [-2.4492127e-17 -0.4 0.4 -2.4492127e-17 415 658.36218] concat gsave 0 0 0 setrgbcolor newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 1.25 setlinewidth 0 setlinejoin 0 setlinecap newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath stroke grestore 0 0 0 setrgbcolor [6 6] 0 setdash 1 setlinewidth 0 setlinejoin 0 setlinecap newpath 505 532.36218 moveto 505 662.36218 505 662.36218 505 662.36218 curveto stroke gsave [-2.4492127e-17 -0.4 0.4 -2.4492127e-17 505 658.36218] concat gsave 0 0 0 setrgbcolor newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 1.25 setlinewidth 0 setlinejoin 0 setlinecap newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath stroke grestore 0 0 0 setrgbcolor [6 6] 0 setdash 1 setlinewidth 0 setlinejoin 0 setlinecap newpath 590 622.36218 moveto 590 662.36218 590 662.36218 590 662.36218 curveto stroke gsave [-2.4492127e-17 -0.4 0.4 -2.4492127e-17 590 658.36218] concat gsave 0 0 0 setrgbcolor newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 1.25 setlinewidth 0 setlinejoin 0 setlinecap newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath stroke grestore 0 0 0 setrgbcolor [] 0 setdash 1 setlinewidth 0 setlinejoin 0 setlinecap newpath 180 242.36218 moveto 270 242.36218 270 242.36218 270 242.36218 curveto stroke gsave [-0.4 0 0 -0.4 266 242.36218] concat gsave 0 0 0 setrgbcolor newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 1.25 setlinewidth 0 setlinejoin 0 setlinecap newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath stroke grestore 0 0 0 setrgbcolor [] 0 setdash 1 setlinewidth 0 setlinejoin 0 setlinecap newpath 180 332.36218 moveto 270 332.36218 270 332.36218 270 332.36218 curveto stroke gsave [-0.4 0 0 -0.4 266 332.36218] concat gsave 0 0 0 setrgbcolor newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 1.25 setlinewidth 0 setlinejoin 0 setlinecap newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath stroke grestore 0 0 0 setrgbcolor [] 0 setdash 1 setlinewidth 0 setlinejoin 0 setlinecap newpath 180 422.36218 moveto 360 422.36218 360 422.36218 360 422.36218 curveto stroke gsave [-0.4 0 0 -0.4 356 422.36218] concat gsave 0 0 0 setrgbcolor newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 1.25 setlinewidth 0 setlinejoin 0 setlinecap newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath stroke grestore 0 0 0 setrgbcolor [] 0 setdash 1 setlinewidth 0 setlinejoin 0 setlinecap newpath 180 512.36218 moveto 450 512.36218 450 512.36218 450 512.36218 curveto stroke gsave [-0.4 0 0 -0.4 446 512.36218] concat gsave 0 0 0 setrgbcolor newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 1.25 setlinewidth 0 setlinejoin 0 setlinecap newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath stroke grestore 0 0 0 setrgbcolor [] 0 setdash 1 setlinewidth 0 setlinejoin 0 setlinecap newpath 180 602.36218 moveto 540 602.36218 540 602.36218 540 602.36218 curveto stroke gsave [-0.4 0 0 -0.4 536 602.36218] concat gsave 0 0 0 setrgbcolor newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath eofill grestore 0 0 0 setrgbcolor [] 0 setdash 1.25 setlinewidth 0 setlinejoin 0 setlinecap newpath 0 0 moveto 5 -5 lineto -12.5 0 lineto 5 5 lineto 0 0 lineto closepath stroke grestore gsave [1 0 0 1 -26 16] concat gsave [1 0 0 -1 87.875 230.64897] concat gsave /Arial-ISOLatin1 findfont 20 scalefont setfont 0 0 0 setrgbcolor newpath 0 0 moveto (pn-1) show grestore grestore grestore gsave [1 0 0 1 -6 18] concat gsave [1 0 0 -1 67.6875 317.52397] concat gsave /Arial-ISOLatin1 findfont 20 scalefont setfont 0 0 0 setrgbcolor newpath 0 0 moveto (pn) show grestore grestore grestore gsave [1 0 0 1 2 8] concat gsave [1 0 0 -1 56.5625 419.55522] concat gsave /Arial-ISOLatin1 findfont 20 scalefont setfont 0 0 0 setrgbcolor newpath 0 0 moveto (pn+1) show grestore grestore grestore gsave [1 0 0 1 -8 16] concat gsave [1 0 0 -1 65.65625 503.39897] concat gsave /Arial-ISOLatin1 findfont 20 scalefont setfont 0 0 0 setrgbcolor newpath 0 0 moveto (pn+2) show grestore grestore grestore gsave [1 0 0 1 -6 -6] concat gsave [1 0 0 -1 62.625 615.52397] concat gsave /Arial-ISOLatin1 findfont 20 scalefont setfont 0 0 0 setrgbcolor newpath 0 0 moveto (pn+3) show grestore grestore grestore grestore showpage %%EOF ssr-0.4.2/doc/manual/images/signal_processing.svg000066400000000000000000001024211236416011200220410ustar00rootroot00000000000000 image/svg+xml + + + + dots dots dots dots dots dots input output n n+1 n+2 n+3 n n+1 n+2 n+3 pn-1 pn pn+1 pn+2 pn+3 ssr-0.4.2/doc/manual/images/ssr_logo.mps000066400000000000000000000016571236416011200201700ustar00rootroot00000000000000%!PS-Adobe-3.0 EPSF-3.0 %%BoundingBox: -3 -31 88 31 %%HiResBoundingBox: -2.1259 -30.47235 87.16525 30.47235 %%Creator: MetaPost 0.993 %%CreationDate: 2008.11.12:1733 %%Pages: 1 %%BeginProlog %%EndProlog %%Page: 1 1 0 0 0 setrgbcolor 0 4.2518 dtransform truncate idtransform setlinewidth pop [] 0 setdash 1 setlinecap 0 setlinejoin 10 setmiterlimit newpath 0 0 moveto 22.67723 0 lineto 26.15166 0 29.32791 1.96297 30.88173 5.07059 curveto 39.98436 23.2758 lineto 41.5382 26.38344 44.71445 28.34645 48.1889 28.34645 curveto 70.86613 28.34645 lineto stroke newpath 7.08661 -14.17323 moveto 29.76384 -14.17323 lineto 33.23827 -14.17323 36.41452 -12.21027 37.96834 -9.10265 curveto 47.07097 9.10257 lineto 48.6248 12.2102 51.80106 14.17322 55.27551 14.17322 curveto 77.95274 14.17322 lineto stroke newpath 42.51967 -28.34645 moveto 54.15762 -5.07059 lineto 55.71144 -1.96297 58.8877 0 62.36212 0 curveto 85.03935 0 lineto stroke showpage %%EOF ssr-0.4.2/doc/manual/latexmkrc000066400000000000000000000002471236416011200162620ustar00rootroot00000000000000@default_files = ('SoundScapeRenderer'); $pdf_mode = 2; # ps -> pdf $ps2pdf = 'ps2pdf -dPDFSETTINGS=/prepress'; # Get rid of compression artefacts $clean_ext = "bbl"; ssr-0.4.2/doc/manual/network.tex000066400000000000000000000514211236416011200165600ustar00rootroot00000000000000% This is the section about the network interface. % It is included by SoundScapeRenderer.tex \section{Network Interface} \label{sec:network} This is just a short overview about the XML messages which can be sent to the SSR via TCP/IP. The messages have to be terminated with a binary zero (\verb|\0|). \textbf{WARNING:} We did not evaluate the network interface in terms of security. So please be sure that you are in a safe network when using it. % The SSR provides a network interface to enable a remote control of the audio % processing part. A typical usage could be to dynamically modify a sound scene % using any kind of interaction tool. This interaction tool (e.g. a head-tracker) % then communicates with the SSR via the SSR's network interface. % \begin{figure} % %\vspace{0.3cm} % \begin{center} % \includegraphics[width=\textwidth]{ssrsystem} % %\vspace{-2.0cm} % \caption{\label{fig:ssrsystem}{The SoundScape Renderer Build.}} % \end{center} % %\vspace{-0.7cm} % \end{figure} % \subsection{General Layout} % % The network server is connected to the main component of the SSR which is the audio % processing part. The server communicates with its clients such as GUIs or trackers % and transfers the received data to the core part of the SSR. Note that the % communication is bidirectional, i.e. clients can also receive messages from the % server.\\ % The basic server thread initializes the network socket and waits for an incomming connection. % When a new client joins the server, the server starts the handling routines. These handling % routines are splitted into two seperate threads, one for receiving, the other for sending data. % Like this, it is possible to receive and send at the same % time.\\ % Prepared but not finished are file handling routines, so that e.g. a sound file on the audio % processing computer can be opened from a remote GUI. The (intended) functinoality is comparable % to that of an ftp server.\\ % The network protocol is TCP/IP. The data transport is in plaintext. The application protocol % is a simpler format than the ftp protokoll. Messages begin with a code number followed by the % payload.\\ % The complete client-server-communication flow is shown in figure \ref{fig:networkflow}. % \begin{figure} % %\vspace{0.3cm} % \begin{center} % \includegraphics[width=\textwidth]{networkflow} % %\vspace{-2.0cm} % \caption{\label{fig:networkflow}{Network Subsystem.}} % \end{center} % %\vspace{-0.7cm} % \end{figure} % \subsection{Class Description} % % % \subsubsection{Files} % % % All the network related source files are stored in the folder \texttt{src/network} (see table \ref{tab:network_files}). % % % \begin{table} % \begin{center} % \begin{tabular}{ll} % \hline % \textsc{Name} & \textsc{Description} \\ % \hline%\hline % Client.cpp & Client class implementation\\ % % \hline % Client.h & Client class declaration\\ % % \hline % ClientConfigFile.cpp & config-file parser for client\\ % % \hline % FileHandle.cpp & File handling routines for using like ftp\\ % % \hline % FileHandle.h & Class declaration\\ % % \hline % NetworkInterface.cpp & SSR Server Network Interface\\ % % \hline % NetworkInterface.h & Interface declaration\\ % % \hline % ParseXMLCommand.cpp & XML-String Parser for Server and Client\\ % % \hline % ParseXMLCommand.h & XML Parser declaration\\ % % \hline % RequestHandle.cpp & Client request handling at server side\\ % % \hline % RequestHandle.h & Class declaration\\ % % \hline % SSRClientNetworkInterface.cpp & SSR Client Network Interface\\ % % \hline % SSRClientNetworkInterface.h & Classe declaration\\ % % \hline % Server.cpp & SSR Server routines\\ % % \hline % Server.h & Class declaration\\ % % \hline % Socket.cpp & Fundamental Network routines\\ % % \hline % Socket.h & Class declaration\\ % % \hline % server\_codes.h & Server Codes for Messages\\ % \hline % \end{tabular} % \label{tab:network_files} % \end{center} % \caption{Source files in folder \texttt{src/network}} % \end{table} % % % % % \subsubsection{Class ``Socket''} % % % This is the fundamental network class. It provides the functions to initialize the network, % requesting, binding, connecting and closing sockets. For further information, confer to the header file % \texttt{src/nework/Socket.h}. % % % % % \subsubsection{Class ``Server''} % % % The Server class is derived from the socket class. The Server is written for running standalone. It % can thus easily be reused in other projects. The initialization function expects four % optional arguments at startup. These are % % % \begin{enumerate} % \item Server Name % \item Network Port % \item IP-Address % \item XML File Directory % \end{enumerate} % % % The Server Name is a string which is send to each client when it joins. This is usefull % for the client to identify the server and see that the connection is established.\\ % The Network Port specifies the network port on which server listens for incomming messages.\\ % With the IP-Address option, it is possible to bind the server to one specific IP-Address. If % no IP-address is specified, the server listens on all network cards respectively IP-addresses.\\ % Finally, the XML File Directory is not yet in use.\\ % Note that all parameters are optional. When a parameter is not specified, default value are used. % These are defined in the header file \texttt{src/network/Server.h}. A client data structure is % also defined in the header. This structure contains status information about the clients.\\ % % The main thread looks for incomming clients, sets the client structure % and starts the client request handling via a callback function. Upon successful connection, it stores the client object % in a list. Clients are removed from the list, upon disconnection. SSR creats aunique % ID for each connected client. These client identfiers are used in the SSR-subsystem.\\ % % Figure \ref{fig:serverflow} illustrates the workflow of the server algorithm.\\ % % The main job is the accepting and the administrating from clients. Therefore, a list is used. % The server thread assigns the client id's and creates a client list entry. % The list entry are objects from the structure ``client\_param\_r'' which is defined in {\em Server.h}. % The client list is used to address the clients. % After that the thread starts the client request handling threads via the callback % function {\em start\_request\_thread(void* arg)}. The argument is a pointer to the new list entry. % Another job is to find ``zombie'' enties in the list. Zombie means that these are entry from clients % which are not longer connected to the server. % %\newpage % % \begin{figure} % %\vspace{0.3cm} % \begin{center} % \includegraphics[width=0.7\textwidth]{serverflowa} % %\vspace{-2.0cm} % \caption{\label{fig:serverflow}{Server Flowchart}} % \end{center} % %\vspace{-0.7cm} % \end{figure} % % %\newpage % % \begin{figure} % %\vspace{0.3cm} % \begin{center} % \includegraphics[width=0.7\textwidth]{serverflowb} % %\vspace{-2.0cm} % \caption{{Server Flowchart}} % \end{center} % %\vspace{-0.7cm} % \end{figure} % % % \subsubsection{Class ``RequestHandle''} % % The class {\em RequestHandle} is derived form the server class. The basic job of that class is the cummunication handling % between clients and the server computer. This class provides two threads per client. Thread one is only for receiving % client message and handle them. % % In general there is a problem when receiving. Data doesn't arrive as they send. The sequence is correct but it is possible % that the data are splitted or joined with previous messages. Therefor there is a search for complete messages implemented. % The search looks for the beginning of a message, the server code. And there is a search for the end of a message: {\em newline}. % If the code is inside the range between 100 and 900 (that means, a 3-digit number is found) and a message end is found, % the message will copied into another message buffer. The following switch-construct decides what is to do with the message. % If the message contains an xml-string the callback function {\em recv\_data\_callback(client-$>$id,client-$>$message)} % is called. % % Thread two is only for sending. Therefor exists one message queue per client. If the queue is empty, the transmitter thread % is sleeping. This is done with the help of {\em pthread\_cond\_wait()}. When {\em send\_msg()} is called, a condition is % set and the transmitter thread can work (figure \ref{fig:sendmsgflow}). % % The workflow of these two threads is illustrated in figure \ref{fig:requestflow}. % % The class implements the virtual ``Server''-class functions {\em start\_request\_thread(void* arg)} % and {\em stop\_all\_request\_thread()}. This allows the server class to handle the request thread % when the server stops. % % \begin{figure} % %\vspace{0.3cm} % \begin{center} % \includegraphics[width=\textwidth]{requestflow} % %\vspace{-2.0cm} % \caption{\label{fig:requestflow}{Request Handle Flowchart}} % \end{center} % %\vspace{-0.7cm} % \end{figure} % % \begin{figure} % %\vspace{0.3cm} % \begin{center} % \includegraphics[width=\textwidth]{sendmsgflow} % %\vspace{-2.0cm} % \caption{\label{fig:sendmsgflow}{send\_msg()}} % \end{center} % %\vspace{-0.7cm} % \end{figure} % % % \subsubsection{Class ``Network Interface''} % % The network interface class is the link between the renderer system and the network server system. % The interface is derived from the Request Handle class and the Subscriber class. The interface class % has two basic jobs. One is to implment the virtual Subscriber function to generate xml strings and send them % to the clients. The other job is to forwarding the incomming messages to the XML-Parser. % % When creating a new Network Interface object the constructor needs a reference to a {\em controller} object. This reference is % used for the XML-Parsing class. In the same time when creating the interface object, the constructor builds a xml-parser object % The XML-Parser uses the {\em controller} reference to call the corresponding functions to the received xml-strings. % % % % \subsubsection{Class ``ParseXMLCommand''} % % This class is the only class with access to the renderer system. The XML-Parser checks the xml strings for correctness % and extracts the information. Then he calls the corresponding functions. % % The XML-Parser is implemented as thread. A xml-data-queue contains all xml strings which has to parse. % If the queue is empty the thread waits. To add new data into this queue there must be call % {\em set\_new\_server\_data(int id, std::string data)} or {\em set\_new\_client\_data(std::string data)}. % With the Compiler switches ``BUILD\_CLIENT'' and ``BUILD\_SERVER'' it is possible to select between the xml parsing threads. % The difference is that the server parser thread is for messages from a client to the server and % the client parser thread for messages from the server to a client. Thereby this class % can be used for server and client implementations. % % \subsection{Client Description} % % The reference client is based on the same source code like the server system. The xml parsing function % are also in the file {\em ParseXMLCommand.cpp}. Even the Socket classe is reused. The {\em main()} function % is located in the file {\em SSRClientMain.cpp} in the directory {\em client\_gui}. In this folder there is also the % Makefile. % % The classes for the client software are located in the {\em /network} folder. The files {\em Client.h} and % {\em Client.cpp} contains the basic network functionality. The Client class looks similiar to the Request Handle class. % The class is derived from the Socket class. In operation there is a running thread only for receiving the server messages. % The thead is nearly the same implementation like the receiving thread from the Request Handle class. % For sending messages there is no extra thread but a {\em send\_msg()} function. % % The interface class for the link between GUI and network is called {\em SSRClientNetworkInterface}. % The client can be configured with commandline arguments or via a configure file. %\subsection{Messages} % In general a message looks like following example: % % \begin{verbatim} % 500 message-string\n\r % \end{verbatim} % The first element in this string is the server code. This number says the server what payload is following and what % is to deal with it. Next character is an empty space. % % After that is following the payload string. This string is depend on the message code. % % % \subsubsection{The Message Codes} % % Table \ref{tab:message_codes} shows the message codes which are used to detect what is to do. % \begin{table} % \begin{center} % \begin{tabular}{lcll} % \hline % \textsc{Code Name} & \textsc{Code} & \textsc{Description} & \textsc{Payload}\\ % \hline%\hline % WELCOME & 100 & Server Welcome Message & optional\\ % CLIENT\_READY & 120 & Client is ready to receive & optional\\ % KEEP\_ALIVE & 150 & to look, if the opponent is alive & optional\\ % ILIVE & 160 & Answer to a Keep alive message & optional\\ % COMMAND & 200 & Send a Command (for file handling only) & command string\\ % COMMAND\_OK & 210 & Command successful & optional \\ % COMMAND\_RES & 220 & Command result & result string\\ % COMMAND\_ERROR & 300 & Command does not exists or not successful & optional\\ % CONN\_CLOSE & 400 & Close connection & optional\\ % SVR\_SHTDWN & 410 & Server Shutdown & optional\\ % XML\_DATA & 500 & send/receive XML Data & XML-Data String\\ % ERROR & 900 & Unknown error happend & optional\\ % \hline % \end{tabular} % \caption{Message codes} % \label{tab:message_codes} % \end{center} % \end{table} % % The names are not important but the numbers are relevant. % The Codes are defined in the the file server\_codes.h % % \subsubsection{Buildup XML Messages} % % In general a message for the Sound Scrape Rendering System is build as a xml-message. % There is a differenz in the message from a client to the server and from the server to a client. % In the first case the key word is {\em request} in the second case it's {\em update}. % \subsubsection{Server Messages} % Server messages means xml-strings from a client to the server. The basic xml-string contains % % {\em $<$request$>$ ... $<$request$>$}. \subsection{Scene} \begin{itemize} \item Load Scene:\\ \verb|| \item Clear Scene (remove all sources):\\ \verb|| \item Set Master Volume (in dB):\\ \verb|| \end{itemize} \subsection{State} \begin{itemize} \item Start processing:\\ \verb|| \item Stop processing:\\ \verb|| \item Transport Start (Play):\\ \verb|| \item Transport Stop (Pause):\\ \verb|| \item Transport Rewind:\\ \verb|| \item Transport Locate:\\ \verb||\\ \verb||\\ \verb|| \emph{(seconds)}\\ \verb|| \item Reset/Calibrate Head-Tracker:\\ \verb|| \end{itemize} \subsection{Source} \begin{itemize} \item Set Source Position (in meters):\\ \verb|| \item Fixed Position (\verb|true|/\verb|false|):\\ \verb|| \begin{verbatim} \end{verbatim} \item Set Source Orientation (in degrees, zero in positive x-direction):\\ \verb|| \item Set Source Gain (Volume in dB):\\ \verb|| \item Set Source Mute (\verb|true|/\verb|false|):\\ \verb|| \item Set Source Name:\\ \verb|| \item Set Source Model (\verb|point|/\verb|plane|):\\ \verb|| \item Set Source Port Name (any JACK port):\\ \verb|| \item New Source (some of the parameters are optional): \begin{verbatim} \end{verbatim} \begin{verbatim} \end{verbatim} \item Delete Source:\\ \verb|| \end{itemize} \subsection{Reference} \begin{itemize} \item Set Reference Position (in meters):\\ \verb|| \item Set Reference Orientation (in degrees, zero in positive x-direction):\\ \verb|| \end{itemize} %\subsubsection{Client Messages} % Client messages means xml-strings from the server to a client. The basic xml-string contains % % {\em $<$update$>$ ... $<$update$>$}. % % \begin{itemize} % \item New Source:\\ % $<$update$>$$<$source id='{\em ID}'/$>$$<$/update$>$ % % \item Delete Source:\\ % $<$update$>$$<$delete$>$$<$source id='{\em ID}'/$>$$<$/delete$>$$<$/update$>$ % % \item Delete All Sources:\\ % $<$update$>$$<$delete$>$$<$source id='0'/$>$$<$/delete$>$$<$/update$>$ % % \item Set Source Position:\\ % $<$update$>$$<$source id='{\em ID}'$>$$<$position x='{\em VALUE}' y='{\em VALUE}'/$>$$<$/source$>$$<$/update$>$ % % \item Set Source Orientation:\\ % $<$update$>$$<$source id='{\em ID}'$>$$<$orientation azimuth='{\em VALUE}'/$>$$<$/source$>$$<$/update$>$ % % \item Set Source Gain (Volume):\\ % $<$update$>$$<$source id='{\em ID}' volume='{\em VALUE}'/$>$$<$/update$>$ % % \item Set Source Mute:\\ % $<$update$>$$<$source id='{\em ID}' mute='{\em VALUE}'/$>$$<$/update$>$ % % \item Set Source Name:\\ % $<$update$>$$<$source id='{\em ID}' name='{\em VALUE}'/$>$$<$/update$>$ % % \item Set Source Model:\\ % $<$update$>$$<$source id='{\em ID}' model='{\em VALUE}'/$>$$<$/update$>$ % % \item Set Source Port Name:\\ % $<$update$>$$<$source id='{\em ID}' port\_name='{\em VALUE}'/$>$$<$/update$>$ % % \item Set Source File Length:\\ % $<$update$>$$<$source id='{\em ID}' file\_length='{\em VALUE}'/$>$$<$/update$>$ % % \item Set Reference Position:\\ % $<$update$>$$<$reference$>$$<$position x='{\em VALUE}' y='{\em VALUE}'/$>$$<$/reference$>$$<$/update$>$ % % \item Set Reference Orientation:\\ % $<$update$>$$<$reference$>$$<$orientation azimuth='{\em VALUE}'/$>$$<$/reference$>$$<$/update$>$ % % \item Set Master Volume:\\ % $<$update$>$$<$volume$>${\em VALUE}$<$/volume$>$$<$/update$>$ % % \item Set CPU Load:\\ % $<$update$>$$<$cpu load='{\em VALUE}'/$>$$<$/update$>$ % % \item Set Master Signal Level:\\ % $<$update$>$$<$master level='{\em VALUE}'/$>$$<$/update$>$ % % \item Set Source Signal Level:\\ % $<$update$>$$<$source id='{\em ID}' level='{\em VALUE}'/$>$$<$/update$>$ % % \end{itemize} % % A special thing is the loudspeaker setup. In the beginning of the communication the server transmit the % loudspeaker adjustment. This is defined with following xml-commands: % % $<$update$>$$<$loadspeaker model="{\em VALUE}"$>$$<$position x="{\em VALUE}" y="{\em VALUE}"/$>$$<$orientation azimuth="{\em VALUE}"/$>$$<$/loudspeaker$>$...$<$/update$>$. % % All loudspeaker are set inside the $<$update$>$ tags. % In the Attachment is the complete arrangement for the circual loudspeaker setup with 56 loudspeakers and one subwoofer. % \subsection{XML-Setup for a 56-Loudspeaker Circle} % % % For the complete loudspeaker setup look at the file {\em loadspeaker.txt}. % % % \subsection{Building Client} % % % The Makefile is stored in the subfolder {\em /client\_gui}.\\ % Open this file and adjust the preference for the actually operating system.\\ % To building the reference client with Network Interface and % Graphical User Interface only type:\\ % % {\em make client}\\ % \subsection{Starting the Client} % % % If the building is successful the binary in located in the {\em /bin} folder above the {\em /src} directory. % It is possible to use a configuration file or use comandline parameters.\\ % Type {\em ./ssr\_client --help} to see the programm options.\\ % % Example: {\em ./ssr\_client -i 192.168.1.1 -p 5555} % Settings for Vim (http://www.vim.org/), please do not remove: % vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80 ssr-0.4.2/doc/manual/operation.tex000066400000000000000000000324671236416011200171000ustar00rootroot00000000000000\section{Compiling and running the SSR} \subsection{Mac OSX App-Bundle} The following sections are relevant if you want to build the SSR from its source code. This is the default procedure for GNU/Linux systems. If you want to use the SSR on a Mac, you can also use the pre-compiled app bundle. For further information visit the SSR website~\cite{ssr}. \subsection{Getting the Source} If you didn't receive this manual with the source code of the SSR, you can download it from the SSR website~\cite{ssr}. After downloading, you can unpack the tarball with the command \verb+tar xvzf ssr-x.x.x.tar.gz+ in a shell. This will extract the source code to a directory of the form \verb+ssr-x.x.x+ where ``x'' stands for the version numbers. \verb+cd+ to this directory and proceed with section \ref{sec:configuring} to configure the SSR. \subsection{Configuring} \label{sec:configuring} To build the SSR from source you have to configure first. Open a shell and \verb+cd+ to the directory containing the source code %\footnote{Note that the %top directory of the SSR-package distributed e.g. in a tar-ball is intended.} of the package and type: \begin{verbatim} ./configure \end{verbatim} This script will check your system for dependencies and prepare the \verb+Makefile+ required for compilation. Section \ref{sec:dependencies} lists the dependencies that must be installed on your system. The \texttt{configure} script will signal if dependencies are missing. At successful termination of the \texttt{configure} script a summary will show up. Section \ref{sec:hints_conf} is intended to help you troubleshooting. \subsubsection{Dependencies} \label{sec:dependencies} At least the following software (libraries and headers) including their development packages (\emph{dev} or \emph{devel}), where available, are required for a full installation of the SSR: \begin{itemize} \item[-] JACK Audio Connection Kit \cite{jack} \item[-] FFTW3 compiled for single precision (\texttt{fftw3f}) version 3.0 or higher \cite{fftw3} \item[-] libsndfile \cite{sndfile} \item[-] Ecasound \cite{ecasound} \item[-] Trolltech's Qt 4.2.2 or higher with OpenGL (QtCore, QtGui and QtOpenGL) \cite{qt4} %\item[-] GLUT \cite{glut} or freeglut \cite{freeglut} \item[-] libxml2 \cite{libxml2} \item[-] Boost.Asio \cite{boost}, included since Boost version 1.35. \end{itemize} We provide a simple integration of several head tracking systems. Please read section %\ref{sec:configuring} for an actual configuration or read section \ref{sec:head_tracking} for further informations about head tracking. \subsubsection{Hints on Configuration} \label{sec:hints_conf} If you encounter problems configuring the SSR these hints could help: \begin{itemize} \item Ensure that you really installed all libraries (\verb+lib+) with devel-package (\verb+devel+ or \verb+dev+, where available) mentioned in section \ref{sec:dependencies}. \item It may be necessary to run \verb+ldconfig+ after installing new libraries. \item Ensure that \verb+/etc/ld.so.conf+ or \verb+LD_LIBRARY_PATH+ are set properly, and run \verb+ldconfig+ after changes. \item If a header is not installed in the standard paths of your system you can pass its location to the configure script using \verb+./configure CPPFLAGS=-Iyourpath+. \end{itemize} Note that with \verb+./configure --help+ all configure-options are displayed, e.g. in section ``Optional Features'' you will find how to %enable optimization by compiler, how to disable compilation of the head trackers %or how to start the SSR with a different renderer. and many other things. Setting the influential environment variables with \verb+./configure VARNAME=value+ can be useful for debugging dependencies. \subsection{Compiling and Installing} \label{sec:comp_inst} If the configure script terminates with success, it creates a file named \texttt{Makefile}. You can build the SSR by typing \begin{quote} \texttt{make}\\ \texttt{make install} \end{quote} % This will compile the SSR and install it to your system. %Section \ref{sec:configuring} contains more information about configuring. \subsection{Uninstalling} If the SSR didn't meet your expectations, we are very sorry, and of course you can easily remove it from your system with \begin{quote} \texttt{make uninstall} \end{quote} \subsection{Running the SSR} \label{sec:running_ssr} Before you start the SSR, start JACK \cite{jack}, e.g.~by typing\\ \verb+jackd -d alsa -r 44100+ in a shell or using the graphical user interface ``qjackctl'' \cite{qjackctl}. Now, the easiest way to get a signal out of the SSR is by passing a sound-file directly: \begin{quote} \begin{verbatim} ssr YOUR_AUDIO_FILE \end{verbatim} \end{quote} By default, the SSR starts with the binaural renderer; please use headphones for listening with this renderer. Type \verb+ssr --help+ to get an overview of the command line options and various renderers: {\footnotesize \begin{verbatim} USAGE: ssr [OPTIONS] The SoundScape Renderer (SSR) is a tool for real-time spatial audio reproduction providing a variety of rendering algorithms. OPTIONS: Choose a rendering algorithm: --binaural Binaural (using HRIRs) --brs Binaural Room Synthesis (using BRIRs) --wfs Wave Field Synthesis --aap Ambisonics Amplitude Panning --vbap Stereophonic (Vector Base Amplitude Panning) --generic Generic Renderer --nfc-hoa Near-field-corrected Higher Order Ambisonics (experimental!) Renderer-specific options: --hrirs=FILE Load the HRIRs for binaural renderer from FILE --hrir-size=VALUE Maximum IR length (binaural and BRS renderer) --prefilter=FILE Load WFS prefilter from FILE -o, --ambisonics-order=VALUE Ambisonics order to use (default: maximum) --in-phase-rendering Use in-phase rendering for Ambisonics JACK options: -n, --name=NAME Set JACK client name to NAME --input-prefix=PREFIX Input port prefix (default: "system:capture_") --output-prefix=PREFIX Output port prefix (default: "system:playback_") -f, --freewheel Use JACK in freewheeling mode General options: -c, --config=FILE Read configuration from FILE -s, --setup=FILE Load reproduction setup from FILE --threads=N Number of audio threads (default N=1) -r, --record=FILE Record the audio output of the renderer to FILE --loop Loop all audio files --master-volume-correction=VALUE Correction of the master volume in dB (default: 0 dB) -i, --ip-server[=PORT] Start IP server (default on) A port can be specified: --ip-server=5555 -I, --no-ip-server Don't start IP server -g, --gui Start GUI (default) -G, --no-gui Don't start GUI -t, --tracker=TYPE Start tracker, possible value(s): polhemus vrpn razor --tracker-port=PORT A serial port can be specified, e.g. /dev/ttyS1 -T, --no-tracker Don't start tracker -h, --help Show this very help information. You just typed that! -v, --verbose Increase verbosity level (up to -vvv) -V, --version Show version information and exit \end{verbatim} } Choose the appropriate arguments and make sure that your amplifiers are not turned too loud\dots To stop the SSR use either the options provided by the GUI (section \ref{sec:gui}) or type \texttt{Crtl+c} in the shell in which you started the SSR. \paragraph{Keyboard actions in non-GUI mode} If you start SSR without GUI (option \verb+--no-gui+), it starts automatically replaying the scene you have loaded. You can have some interaction via the shell. Currently implemented actions are (all followed by \texttt{Return}): \begin{itemize} \item[] \texttt{c}: calibrate tracker (if available) \item[] \texttt{p}: start playback \item[] \texttt{q}: quit application \item[] \texttt{r}: ``rewind''; go back to the beginning of the current scene \item[] \texttt{s}: stop (pause) playback \end{itemize} % Note that in non-GUI mode, audio processing is always taking place. Live inputs are processed even if you pause playback. \paragraph{Recording the SSR output} You can record the audio output of the SSR using the \texttt{--record=FILE} command line option. All output signals (i.e.\ the loudspeaker signals) will be recorded to a multichannel wav-file named \texttt{FILE}. The order of channels corresponds to the order of loudspeakers specifed in the reproduction setup (see sections \ref{sec:reproduction_setups} and \ref{sec:asdf}). The recording can then be used to analyze the SSR output or to replay it without the SSR using a software player like \texttt{ecaplay}~\cite{ecasound}. \subsection{Configuration File} \label{sec:ssr_configuration_file} The general configuration of the SSR (if GUI is enabled, which tracker to use etc.) can be specified in a configuration file (e.g. \texttt{ssr.conf}). By specifying your settings in such a file, you avoid having to give explicit command line options every time you start the SSR. We have added the example \texttt{data/ssr.conf.example} which mentions all possible parameters. Take a look inside, it is rather self-explanatory. There are three possibilities to specify a configuration file: \begin{itemize} \item put it in \texttt{/etc/ssr.conf} \item put it in your home directory in \texttt{\$HOME/.ssr/ssr.conf} \item specify it on the command line with \texttt{ssr -c my\_config.file} \end{itemize} We explicitly mention one parameter here which might be of immediate interest for you: \texttt{MASTER\_VOLUME\_CORRECTION}. This a correction in dB~(!) which is applied -- as you might guess -- to the master volume. The motivation is to have means to adopt the general perceived loudness of the reproduction of a given system. Factors like the distance of the loudspeakers to the listener or the typical distance of virtual sound sources influence the resulting loudness which can be adjusted to the desired level by means of the \texttt{MASTER\_VOLUME\_CORRECTION}. Of course, there's also a command line alternative (\texttt{--master-volume-correction}). \subsection{Head Tracking} % TODO: update this section. \label{sec:head_tracking} We provide integration of the \emph{InterSense InertiaCube3} tracking sensor \cite{intersense} and the \emph{Polhemus Fastrak}~\cite{fastrack}. They are used to update the orientation of the reference (in binaural reproduction this is the listener) in real-time. Please read sections \ref{sec:prep_isense} and \ref{sec:prep_pol} if you want to compile the SSR with the support for these trackers. Note that on startup, the SSR tries to find the tracker. If it fails, it continues without it. If you use a tracker, make sure that you have the appropriate rights to read from the respective port. You can calibrate the tracker while the SSR is running by pressing \texttt{Return}. The instantaneous orientation will then be interpreted as straight forward ($\alpha = 90^\circ$). \subsubsection{Preparing InterSense InertiaCube3} \label{sec:prep_isense} If you want to compile the SSR with support for the \emph{InterSense InertiaCube3} tracking sensor~\cite{intersense}, please download the \emph{InterSense Software Development Kit} (SDK) from the InterSense website~\cite{intersense}. Unpack the archive and place the files \begin{itemize} \item \verb+isense.h+ and \verb+types.h+ to \verb+/usr/local/include+, and \item \verb+libisense.so+ %(either from folder \texttt{x86} or \texttt{x86\_64}) (the version appropriate for your processor type) to \verb+usr/local/lib+. \end{itemize} % The SSR \texttt{configuration} script will automatically detect the presence of the files described above and if they are found, enable the compilation for the support of this tracker. To disable this tracker, use \verb+./configure --disable-intersense+ and recompile. If you encounter an error-message similar to \texttt{libisense.so: cannot open shared object file: No such file or directory}, but the file is placed correctly, run \verb|ldconfig|. \subsubsection{Preparing Polhemus Fastrack} \label{sec:prep_pol} For incorporation of the \emph{Polhemus Fastrack}~\cite{fastrack} with serial connection, no additional libraries are required. %The files %\verb+termios.h,unistd.h, fcntl.h and poll.h+ should be already installed on %your system. If they are found, the configure-script will enable the %compilation for the support of this tracker. If you want to disable this tracker, use \verb+ ./configure --disable-polhemus+ and recompile. \subsection{Using the SSR with DAWs} As stated before, the SSR is currently not able to dynamically replay audio files (refer to section~\ref{sec:asdf}). If your audio scenes are complex, you might want to consider using the SSR together with a digital audio work station (DAW). To do so, you simply have to create as many sources in the SSR as you have audio tracks in your respective DAW project and assign live inputs to the sources. Amongst the ASDF examples we provide at~\cite{ssr} you find an example scene description which does exactly this. DAWs like Ardour~\cite{ardour} support JACK and their use is therefore straightforward. DAWs which do not run on Linux or do not support JACK can be connected via the input of the sound card. In the future we will provide a VST plug-in which will allow you to dynamically operate all virtual source's properties (like e.g.~a source's position or level etc.). You will then be able to have the full SSR functionality controlled from your DAW. ssr-0.4.2/doc/manual/references.bib000066400000000000000000000175201236416011200171460ustar00rootroot00000000000000@Misc{ardour, OPTkey = {}, author = {Davis, Paul}, title = {Ardour}, howpublished = {\url{http://www.ardour.org}}, OPTmonth = {}, OPTyear = {}, OPTnote = {}, OPTannote = {} } @Misc{ambiophonics, OPTkey = {}, author = {Glasgal, Ralph}, title = {Ambiophonics}, howpublished = {\url{http://www.ambiophonics.org}}, OPTmonth = {}, OPTyear = {}, OPTnote = {}, OPTannote = {} } @Misc{brutefir, OPTkey = {}, author = {Torger, Anders}, title = {BruteFIR}, howpublished = {http://www.ludd.luth.se/\textasciitilde torger/brutefir.html}, OPTmonth = {}, OPTyear = {}, OPTnote = {}, OPTannote = {} } @Misc{jack, OPTkey = {}, author = {Davis, Paul and others}, title = {{JACK Audio Connection Kit}}, howpublished = {\url{http://jackaudio.org}}, OPTmonth = {}, OPTyear = {}, OPTnote = {}, OPTannote = {} } @Misc{qjackctl, OPTkey = {}, author = {Rui Nuno Capela and others}, title = {{JACK Audio Connection Kit - Qt GUI Interface}}, howpublished = {\url{http://qjackctl.sourceforge.net/}}, OPTmonth = {}, OPTyear = {}, OPTnote = {}, OPTannote = {} } @Misc{fftw3, OPTkey = {}, author = {Frigo, Matteo and Johnson, Steven G.}, title = {{FFTW3}}, howpublished = {\url{http://www.fftw.org}}, OPTmonth = {}, OPTyear = {}, OPTnote = {}, OPTannote = {} } @Misc{ecasound, OPTkey = {}, author = {Vehmanen, Kai}, title = {Ecasound}, howpublished = {\url{http://eca.cx/ecasound}}, OPTmonth = {}, OPTyear = {}, OPTnote = {}, OPTannote = {} } @Misc{qt4, OPTkey = {}, author = {Trolltech}, title = {Qt4}, howpublished = {\url{http://doc.trolltech.com/4.2}}, OPTmonth = {}, OPTyear = {}, OPTnote = {}, OPTannote = {} } @Misc{freeglut, OPTkey = {}, author = {}, title = {The Free OpenGL Utility Toolkit}, howpublished = {\url{http://freeglut.sourceforge.net/}}, OPTmonth = {}, OPTyear = {}, OPTnote = {}, OPTannote = {} } @Misc{GLUT, OPTkey = {}, author = {OpenGL.org}, title = {GLUT - The OpenGL Utility Toolkit}, howpublished = {\url{http://www.opengl.org/resources/libraries/glut}}, OPTmonth = {}, OPTyear = {}, OPTnote = {}, OPTannote = {} } @Misc{libxml2, OPTkey = {}, author = {Veillard, Daniel}, title = {Libxml2}, howpublished = {\url{http://xmlsoft.org}}, OPTmonth = {}, OPTyear = {}, OPTnote = {}, OPTannote = {} } @Misc{sndfile, OPTkey = {}, author = {de Castro Lopo, Erik}, title = {libsndfile}, howpublished = {\url{http://www.mega-nerd.com/libsndfile}}, OPTmonth = {}, OPTyear = {}, OPTnote = {}, OPTannote = {} } @Misc{intersense, OPTkey = {}, OPTauthor = {}, title = {{InterSense Inc.}}, howpublished = {\url{http://www.isense.com}}, OPTmonth = {}, OPTyear = {}, OPTnote = {}, OPTannote = {} } @Misc{fastrack, OPTkey = {}, OPTauthor = {}, title = {{Polhemus Fastrack}}, howpublished = {\url{http://www.polhemus.com/?page=Motion\_Fastrak}}, OPTmonth = {}, OPTyear = {}, OPTnote = {}, OPTannote = {} } @Misc{boost, OPTkey = {}, OPTauthor = {}, title = {{Boost C++ Libraries}}, howpublished = {\url{http://www.boost.org}}, OPTmonth = {}, OPTyear = {}, OPTnote = {}, OPTannote = {} } @Misc{ssr, OPTkey = {}, author = {{The SSR Team}}, title = {{The \mbox{SoundScape} Renderer}}, howpublished = {\url{http://spatialaudio.net/ssr/}}, OPTmonth = {}, OPTyear = {}, OPTnote = {}, OPTannote = {} } @inproceedings{Pulkki97:JAES, author={Pulkki, V.}, title={Virtual Sound Source Positioning Using {Vector Base Amplitude Panning}}, booktitle={Journal of the Audio Engineering Society (JAES)}, year={Vol.45(6), June 1997}, address={}, organization={} } @inproceedings{Ahrens08:MOVING_AES, author={Ahrens, J. and Spors, S.}, title={Reproduction of Moving Virtual Sound Sources with Special Attention to the Doppler Effect}, booktitle={124th Convention of the AES, Amsterdam, The Netherlands}, year={May 17--20, 2008}, address={}, organization={} } @inproceedings{Ahrens08:SUPERSONIC_AES, author={Ahrens, J. and Spors, S.}, title={Reproduction of Virtual Sound Sources Moving at Supersonic Speeds in {Wave Field Synthesis}}, booktitle={125th Convention of the AES, San Francisco, CA}, year={Oct.~2--5, 2008}, address={}, organization={} } @inproceedings{Ahrens08:HOA_AES, author={Ahrens, J. and Spors, S.}, title={Focusing of Virtual Sound Sources in Higher Order Ambisonics}, booktitle={124th Convention of the AES, Amsterdam, The Netherlands}, year={May 17--20, 2008}, address={}, organization={} } @inproceedings{Neukom07, author={Neukom, M.}, title={Ambisonic Panning}, booktitle={123th Convention of the AES, New York, NY, USA}, year={Oct.~5--8, 2007}, address={}, organization={} } @inproceedings{Spors06:Aliasing_AES, author={Spors, S. and Rabenstein, R.}, title={Spatial Aliasing Artifacts Produced by Linear and Circular Loudspeaker Arrays used for {Wave Field Synthesis}}, booktitle={120th Convention of the AES, Paris, France}, year={May 20--23, 2006}, address={}, organization={} } @inproceedings{Spors08:WFS_AES, author={Spors, S. and Rabenstein, R. and Ahrens, J.}, title={The Theory of {Wave Field Synthesis} Revisited}, booktitle={124th Convention of the AES, Amsterdam, The Netherlands}, year={May 17--20, 2008}, address={}, organization={} } @Misc{matlab, OPTkey = {}, author = {{The MathWorks, Inc.}}, title = {MATLAB}, howpublished = {\url{http://www.mathworks.com}}, OPTmonth = {}, OPTyear = {}, OPTnote = {}, OPTannote = {} } @Misc{cipic, OPTkey = {}, author = {R. Algazi}, title = {The {CIPIC} {HRTF} Database}, howpublished = {\\\url{http://interface.cipic.ucdavis.edu/CIL\_html/CIL\_HRTF\_database.htm}}, OPTmonth = {}, OPTyear = {}, OPTnote = {}, OPTannote = {} } @inproceedings{Geier08:DAGA, Author = "Geier, M. and Ahrens, J. and Spors, S.", Title = "{ASDF}: Ein {XML} {F}ormat zur {B}eschreibung von virtuellen {3D-A}udioszenen", Booktitle = "34rd German Annual Conference on Acoustics (DAGA)", Pages = "", Year = "2008", Month = "March", address = "Dresden, Germany", } @inproceedings{Geier08:AES, Author = "Geier, M. and Ahrens, J. and Spors, S.", Title = "The {SoundScape Renderer}: A unified spatial audio reproduction framework for arbitrary rendering methods", Booktitle = "124th AES Convention", organization = {Audio Engineering Society (AES)}, Pages = "", Year = "2008", Month = "May", address = {Amsterdam, The Netherlands}, } @inproceedings{fabian, author={Lindau, Alexander and Weinzierl, Stefan}, title={F{A}{B}{I}{A}{N} - {S}chnelle {E}rfassung binauraler {R}aumimpulsantworten in mehreren {F}reiheitsgraden}, booktitle={Fortschritte der Akustik, DAGA Stuttgart}, year={2007}, address={}, organization={} } @inproceedings{geier2012ssr, title = {Spatial Audio Reproduction with the {SoundScape Renderer}}, booktitle = {27\textsuperscript{th} Tonmeistertagung -- VDT International Convention}, author = {Geier, Matthias and Spors, Sascha}, year = 2012, } ssr-0.4.2/doc/manual/renderers.tex000066400000000000000000001006131236416011200170560ustar00rootroot00000000000000\section{The Renderers} \label{sec:renderers} \subsection{General} \subsubsection{Reproduction Setups} \label{sec:reproduction_setups} The geometry of the actual reproduction setup is specified in \texttt{.asd} files, just like sound scenes. By default, it is loaded from the file \texttt{/usr/local/share/ssr/default\_setup.asd}. Use the \texttt{--setup} command line option to load another reproduction setup file. Note that the loudspeaker setups have to be convex. This is not checked by the SSR. The loudspeakers appear at the outputs of your sound card in the same order as they are specified in the \texttt{.asd} file, starting with channel 1. \noindent A sample reproduction setup description: \begin{verbatim}
    Circular Loudspeaker Array
    \end{verbatim} \noindent We provide the following setups in the directory \verb+data/reproduction_setups/+: \begin{itemize} \item[-] \texttt{2.0.asd}: standard stereo setup at 1.5 mtrs distance \item[-] \texttt{2.1.asd}: standard stereo setup at 1.5 mtrs distance plus subwoofer \item[-] \texttt{5.1.asd}: standard 5.1 setup on circle with a diameter of 3 mtrs \item[-] \texttt{rounded\_rectangle.asd}: Demonstrates how to combine circular arcs and linear array segments. \item[-] \texttt{circle.asd}: This is a circular array of 3 mtrs diameter composed of 56 loudspeakers. \item[-] \texttt{loudspeaker\_setup\_with\_nearly\_all\_features.asd}: This setup describes all supported options, open it with your favorite text editor and have a look inside. \end{itemize} \noindent Note that outputs specified as subwoofers receive a signal having full bandwidth. There is some limited freedom in assigning channels to loudspeakers: If you insert the element \texttt{}, the specified number of output channels are skipped and the following loudspeakers get higher channel numbers accordingly. Of course, the binaural and BRS renderers do not load a loudspeaker setup. By default, they assume the listener to reside in the coordinate origin looking straight forward. \subsubsection{A Note on the Timing of the Audio Signals} The WFS renderer is the only renderer in which the timing of the audio signals is somewhat peculiar. None of the other renderers imposes any algorithmic delay on individual source signals. Of course, if you use a renderer which is convolution based such as the BRS renderer, the employed HRIRs do alter the timing of the signals due to their inherent properties. This is different with the WFS renderer. Here, also the propagation duration of sound from the position of the virtual source to the loudspeaker array is considered. That means that the farther a virtual source is located, the longer is the delay imposed on its input signal. This also holds true for plane waves: Theoretically, plane waves do originate from infinity. Though, the SSR does consider the origin point of the plane wave which is specified in ASDF. This origin point also specifies the location of the symbol which represents the respective plane wave in the GUI. We are aware that this procedure can cause confusion and reduces the ability of a given scene of translating well between different types of renderers. In the upcoming version~0.4 of the SSR we will implement an option that will allow you specifying for each individual source whether the propagation duration of sound shall be considered by a renderer or not. \subsubsection{Distance Attenuation} Note that in all renderers -- except the BRS renderer -- distance attenuation is handled as $\nicefrac{1}{r}$ with respect to the distance $r$ of the respective virtual source to the reference position. Sources closer than 0.5 mtrs to the reference position do not experience any increase of amplitude. Virtual plane waves do not experience any algorithmic distance attenuation in any renderer. In future versions of the SSR more freedom in specifying the distance attenuation will be provided. The amplitude reference distance, i.e.~the distance from the reference at which plane waves are as loud as the other source types (like point sources), can be set in the SSR configuration file (Section~\ref{sec:ssr_configuration_file}). The desired amplitude reference distance for a given sound scene can be specified in the scene description (Section~\ref{sec:asdf}). The default value is 3~m. \subsubsection{Doppler Effect} In the current version of the SSR the Doppler Effect in moving sources is not supported by any of the renderers. \subsubsection{Signal Processing} All rendering algorithms are implemented on a frame-wise basis with an internal precision of 32 bit floating point. The signal processing is illustrated in Fig.~\ref{fig:signal_processing}. The input signal is divided into individual frames of size \emph{nframes}, whereby \emph{nframes} is the frame size with which JACK is running. Then e.g.\ frame number $n+1$ is processed both with previous rendering parameters $n$ as well as with current parameters $n+1$. It is then crossfaded between both processed frames with cosine-shaped slopes. In other words the effective frame size of the signal processing is $2\cdot\text{\emph{nframes}}$ with 50\% overlap. Due to the fade-in of the frame processed with the current parameters $n+1$, the algorithmic latency is slightly higher than for processing done with frames purely of size \emph{nframes} and no crossfade. \begin{figure} \footnotesize \psfrag{input}{\bf input signal} \psfrag{output}{\bf output signal} \psfrag{dots}{\bf \dots} \psfrag{+}{\bf +} \psfrag{n}{frame $n$} \psfrag{n+1}{frame $n\!+\!1$} \psfrag{n+2}{frame $n\!+\!2$} \psfrag{n+3}{frame $n\!+\!3$} \psfrag{pn-1}{parameters $n\!-\!1$} \psfrag{pn}{parameters $n$} \psfrag{pn+1}{parameters $n\!+\!1$} \psfrag{pn+2}{parameters $n\!+\!2$} \psfrag{pn+3}{parameters $n\!+\!3$} \hfill \includegraphics[width=.95\linewidth]{signal_processing} \caption{\label{fig:signal_processing}{Illustration of the frame-wise signal processing as implemented in the SSR renderers (see text).}} \end{figure} The implementation approach described above is one version of the standard way of implementing time-varying audio processing. Note however that this means that with \emph{all} renderers, moving sources are not physically correctly reproduced. The physically correct reproduction of moving virtual sources as in \cite{Ahrens08:MOVING_AES,Ahrens08:SUPERSONIC_AES} requires a different implementation approach which is computationally significantly more costly. \subsection{Binaural Renderer} \label{sec:binaural_renderer} Binaural rendering is a technique where the acoustical influence of the human head is electronically simulated to position virtual sound sources in space. {\bf Be sure that you use headphones to listen.} Note that the current binaural renderer reproduces all virtual sources exclusively as point sources. The acoustical influence of the human head is coded in so-called head-related impulse responses (HRIRs). The HRIRs are loaded from the file \texttt{/usr/local/share/ssr/default\_hrirs.wav}. If you want to use different HRIRs then use the \texttt{--hrirs=FILE} command line option or the SSR configuration file (Section~\ref{sec:ssr_configuration_file}) to specify your custom location. The SSR connects its outputs automatically to outputs 1 and 2 of your sound card. For virtual sound sources which are closer to the reference position (= the listener position) than 0.5 mtrs, the HRTFs are interpolated with a Dirac impulse. This ensures a smooth transition of virtual sources from the outside of the listener's head to the inside. SSR uses HRIRs with an angular resolution of 1$^\circ$. Thus, the HRIR file contains 720 impulse responses (360 for each ear) stored as a 720-channel .wav-file. The HRIRs all have to be of equal length and have to be arranged in the following order: % \begin{itemize} \item[-] 1st channel: left ear, virtual source position 0$^\circ$ \item[-] 2nd channel: right ear, virtual source position 0$^\circ$ \item[-] 3rd channel: left ear, virtual source position 1$^\circ$ \item[-] 4th channel: right ear, virtual source position 1$^\circ$ \item[] \dots \item[-] 720th channel: right ear, virtual source position 359$^\circ$ \end{itemize} % If your HRIRs have lower angular resolution you have to interpolate them to the target resolution or use the same HRIR for serveral adjacent directions in order to fulfill the format requirements. Higher resolution is not supported. Make sure that the sampling rate of the HRIRs matches that of JACK. So far, we know that both 16bit and 24bit word lengths work. The SSR automatically loads and uses all HRIR coefficients it finds in the specified file. You can use the \texttt{--hrir-size=VALUE} command line option in order to limit the number of HRIR coefficients read and used to \texttt{VALUE}. You don't need to worry if your specified HRIR length \texttt{VALUE} exceeds the one stored in the file. You will receive a warning telling you what the score is. The SSR will render the audio in any case. The actual size of the HRIRs is not restricted (apart from processing power). The SSR cuts them into partitions of size equal to the JACK frame buffer size and zero-pads the last partition if necessary. Note that there's some potential to optimize the performance of the SSR by adjusting the JACK frame size and accordingly the number of partitions when a specific number of HRIR taps are desired. The least computational load arises when the audio frames have the same size like the HRIRs. By choosing shorter frames and thus using partitioned convolution the system latency is reduced but computational load is increased. The HRIRs \texttt{impulse\_responses/hrirs/hrirs\_fabian.wav} we have included in the SSR are HRIRs of 512 taps of the FABIAN mannequin~\cite{fabian} in an anechoic environment. See the file \texttt{hrirs\_fabian\_documentation.pdf} for details of the measurement. % \paragraph{Preparing HRIR sets}% % You can easily prepare your own HRIR sets for use with the SSR by adopting the MATLAB \cite{matlab} script \texttt{data/matlab\_scripts/prepare\_hrirs\_kemar.m} to your needs. This script converts the HRIRs of the KEMAR mannequin included in the CIPIC database \cite{cipic} to the format which the SSR expects. See the script for further information and how to obtain the raw HRIRs. \subsection{\label{sec:brs}Binaural Room Synthesis Renderer} The Binaural Room Synthesis (BRS) renderer is a binaural renderer (refer to Section~\ref{sec:binaural_renderer}) which uses one dedicated HRIR set of each individual sound source. The motivation is to have more realistic reproduction than in simple binaural rendering. In this context HRIRs are typically referred to as binaural room impulse responses (BRIRs). Note that the BRS renderer does not consider any specification of a virtual source's position. The positions of the virtual sources (including their distance) are exclusively coded in the BRIRs. Consequently, the BRS renderer does not apply any distance attenuation. It only applies the respective source's gain and the master volume. No interpolation with a Dirac as in the binaural renderer is performed for very close virtual sources. The only quantity which is explicitely considered is the orientation of the receiver, i.e.~the reference. Therefore, specification of meaningful source and receiver positions is only necessary when a correct graphical illustration is desired. The BRIRs are stored in the a format similar to the one for the HRIRs for the binaural renderer (refer to Section~\ref{sec:binaural_renderer}). However, there is a fundamental difference: In order to be consequent, the different channels do not hold the data for different positions of the virtual sound source but they hold the information for different head orientations. Explicitely, % \begin{itemize} \item[-] 1st channel: left ear, head orientation 0$^\circ$ \item[-] 2nd channel: right ear, head orientation 0$^\circ$ \item[-] 3rd channel: left ear, head orientation 1$^\circ$ \item[-] 4th channel: right ear, head orientation 1$^\circ$ \item[] \dots \item[-] 720th channel: right ear, head orientation 359$^\circ$ \end{itemize} % In order to assign a set of BRIRs to a given sound source an appropriate scene description in \texttt{.asd}-format has to be prepared (refer also to Section~\ref{sec:audio_scenes}). As shown in \texttt{brs\_example.asd} (from the example scenes), a virtual source has the optional property \texttt{properties\_file} which holds the location of the file containing the desired BRIR set. The location to be specified is relative to the folder of the scene file. Note that -- as described above -- specification of the virtual source's position does not affect the audio processing. If you do not specify a BRIR set for each virtual source, then the renderer will complain and refuse processing the respective source. We have measured the binaural room impulse responses of the FABIAN mannequin~\cite{fabian} in one of our mid-size meeting rooms called Sputnik with 8 different source positions. Due to the file size, we have not included them in the release. Please contact \contactadress\ to obtain the data. %\subsection{Binaural Playback Renderer} % %The binaural playback (BPB) renderer is actually not a renderer but %a playback engine that enables real-time head-tracking in headphone %playback. It is similar to BRS with the only difference that it does %not employ impulse responses that are applied to the input signal. %It is rather such that the entire signals for the two ears for all %desired possible head orientations have to be precomputed and are then %loaded into the memory. During playback, depending on the %instantaneous head orientation of the listener as measured by the %tracking system, the corresponding audio data are replayed. If a %change in head orientation occurs then a crossfade is applied over %the duration of one JACK frame. Playing is automatically looped. To %stop replay, mute the source. When the source is unmuted, replay %starts at the beginning of the data. % %The BPB renderer was designed for the simulation of time-varying %systems, which are complicated to implement in real-time. The %audio signals can be prepared in any desired software and also %costly algorithms that do not run in real-time can be replayed with %head-tracking. % %As shown in the example \texttt{bin/scenes/bpb\_example.asd} and %similar to the description of a BRS scene, a virtual source has the %optional property \texttt{properties\_file}, which holds the location %of the file containing the audio data. By default, it is assumed %that the data are stored in a 720-channel audio file the channels of %which are arranged similarly to BRS impulse responses. % %Loading all 720 channels into memory can result in hundreds of %megabytes even for signals of moderate length. In order to avoid %restrictions due to the available memory caused by possibly unrequired %data it is possible to restrict the interval of head %orientations. This restriction has to be applied symmetrically, %e.g.~$\pm60^\circ$. The resolution between the limits is still %1$^\circ$. The channel arrangement for the $\pm60^\circ$ example %would then be %% %\begin{itemize} %\item[-] 1st channel: left ear, head orientation 0$^\circ$ %\item[-] 2nd channel: right ear, head orientation 0$^\circ$ %\item[-] 3rd channel: left ear, head orientation 1$^\circ$ %\item[-] 4th channel: right ear, head orientation 1$^\circ$ %\item[] \dots %\item[-] 121st channel: left ear, head orientation 60$^\circ$ %\item[-] 122nd channel: right ear, head orientation 60$^\circ$ %\item[-] 123rd channel: left ear, head orientation 300$^\circ$ (i.e.~-60$^\circ$) %\item[-] 124th channel: right ear, head orientation 300$^\circ$ (i.e.~-60$^\circ$) %\item[-] 125th channel: left ear, head orientation 301$^\circ$ (i.e.~-59$^\circ$) %\item[-] 126th channel: right ear, head orientation 301$^\circ$ (i.e.~-59$^\circ$) %\item[] \dots %\item[-] 242nd channel: right ear, head orientation 359$^\circ$ (i.e.~-1$^ \circ$) %\end{itemize} %% %resulting thus in 242 channels. It is not necessary to explicitly %specify the desired interval of possible head orientations. The SSR deduces it %directly from the number of channels of the %\texttt{properties\_file}. If the listener turns the head to %orientations for which no data are available the BPB renderer %automatically replays the data for the closest orientation available. %We assume that this is less disturbing in practice than a full %dropout of the signal. % %To fulfill the ASDF syntax, the specification of an input signal is %required. In order to avoid the unnecessary opening and replaying of %an audio file, we propose to specify an arbitrary input port such as % %\begin{verbatim} % % % 0 % % % %\end{verbatim} \subsection{Vector Base Amplitude Panning Renderer} The Vector Base Amplitude Panning (VBAP) renderer uses the algorithm described in \cite{Pulkki97:JAES}. It tries to find a loudspeaker pair between which the phantom source is located (in VBAP you speak of a phantom source rather than a virtual one). If it does find a loudspeaker pair whose angle is smaller than $180^\circ$ then it calculates the weights $g_l$ and $g_r$ for the left and right loudspeaker as % \begin{equation} g_{l,r} = \frac{\cos\phi \sin \phi_0 \pm \sin \phi \cos \phi_0} {2\cos \phi_0 \sin \phi_0} \ . \nonumber \end{equation} % $\phi_0$ is half the angle between the two loudspeakers with respect to the listening position, $\phi$ is the angle between the position of the phantom source and the direction ``between the loudspeakers''. If the VBAP renderer can not find a loudspeaker pair whose angle is smaller than $180^\circ$ then it uses the closest loudspeaker provided that the latter is situated within $30^\circ$. If not, then it does not render the source. If you are in verbosity level 2 (i.e.~start the SSR with the \texttt{-vv} option) you'll see a notification about what's happening. Note that all virtual source types (i.e.~point and plane sources) are rendered as phantom sources. Contrary to WFS, non-uniform distributions of loudspeakers are ok here. Ideally, the loudspeakers should be placed on a circle around the reference position. You can optionally specify a delay for each loudspeakers in order to compensate some amount of misplacement. In the ASDF (refer to Section~\ref{sec:asdf}), each loudspeaker has the optional attribute \texttt{delay} which determines the delay in seconds to be applied to the respective loudspeaker. Note that the specified delay will be rounded to an integer factor of the temporal sampling period. With 44.1 kHz sampling frequency this corresponds to an accuracy of 22.676 $\mu$s, respectively an accuracy of 7.78 mm in terms of loudspeaker placement. Additionally, you can specify a weight for each loudspeaker in order to compensate for irregular setups. In the ASDF format (refer to Section~\ref{sec:asdf}), each loudspeaker has the optional attribute \texttt{weight} which determines the linear~(!) weight to be applied to the respective loudspeaker. An example would be % \begin{verbatim} \end{verbatim} % Delay defaults to 0 if not specified, weight defaults to~1. Although principally suitable, we do not recommend to use our amplitude panning algorithm for dedicated 5.1 (or comparable) mixdowns. Our VBAP renderer only uses adjacent loudspeaker pairs for panning which does not exploit all potentials of such a loudspeaker setup. For the mentioned formats specialized panning processes have been developed also employing non-adjacent loudspeaker pairs if desired. The VBAP renderer is rather meant to be used with non-standardized setups. % \subsection{Wave Field Synthesis Renderer} The Wave Field Synthesis (WFS) renderer is the only renderer so far which discriminates between virtual point sources and plane waves. It implements the simple driving function given in~\cite{Spors08:WFS_AES}. Note that we have only implemented a temporary solution to reduce artifacts when virtual sound sources are moved. This topic is subject to ongoing research. We will work on that in the future. In the SSR configuration file (Section~\ref{sec:ssr_configuration_file}) you can specify an overall predelay (this is necessary to render focused sources) and the overall length of the involved delay lines. Both values are given in samples. % \paragraph{Prefiltering}% % As you might know, WFS requires a spectral correction additionally to the delay and weighting of the input signal. Since this spectral correction is equal for all loudspeakers, it needs to be performed only once on the input. We are working on an automatic generation of the required filter. Until then, we load the impulse response of the desired filter from a .wav-file which is specified via the \texttt{--prefilter=FILE} command line option (see Section~\ref{sec:running_ssr}) or in the SSR configuration file (Section~\ref{sec:ssr_configuration_file}). Make sure that the specified audio file contains only one channel. Files with a differing number of channels will not be loaded. Of course, the sampling rate of the file also has to match that of the JACK server. Note that the filter will be zero-padded to the next highest power of 2. If the resulting filter is then shorter than the current JACK frame size, each incoming audio frame will be divided into subframes for prefiltering. That means, if you load a filter of 100 taps and JACK frame size is 1024, the filter will be padded to 128 taps and prefiltering will be done in 8 cycles. This is done in order to save processing power since typical prefilters are much shorter than typical JACK frame sizes. Zero-padding the prefilter to the JACK frame size usually produces large overhead. If the prefilter is longer than the JACK frame buffer size, the filter will be divided into partitions whose length is equal to the JACK frame buffer size. If you do not specify a filter, then no prefiltering is performed. This results in a boost of bass frequencies in the reproduced sound field. In order to assist you in the design of an appropriate prefilter, we have included the MATLAB \cite{matlab} script \texttt{data/matlab\_scripts/make\_wfs\_prefilter.m} which does the job. In the very top of the file, you can specify the sampling frequency, the desired length of the filter as well as the lower and upper frequency limits of the spectral correction. The lower limit should be chosen such that the subwoofer of your system receives a signal which is not spectrally altered. This is due to the fact that only loudspeakers which are part of an array of loudspeakers need to be corrected. The lower limit is typically around 100 Hz. The upper limit is given by the spatial aliasing frequency. The spatial aliasing is dependent on the mutual distance of the loudspeakers, the distance of the considered listening position to the loudspeakers, and the array geometry. See \cite{Spors06:Aliasing_AES} for detailed information on how to determine the spatial aliasing frequency of a given loudspeaker setup. The spatial aliasing frequency is typically between 1000 Hz and 2000 Hz. For a theoretical treatment of WFS in general and also the prefiltering, see \cite{Spors08:WFS_AES}. The script \texttt{make\_wfs\_prefilter.m} will save the impulse response of the designed filter in a file like \texttt{wfs\_prefilter\_120\_1500\_44100.wav}. From the file name you can extract that the spectral correction starts at 120 Hz and goes up to 1500 Hz at a sampling frequency of 44100 Hz. Check the folder \texttt{data/impules\_responses/wfs\_prefilters} for a small selection of prefilters. % \paragraph{Tapering}% % When the listening area is not enclosed by the loudspeaker setup, artifacts arise in the reproduced sound field due to the limited aperture. This problem of spatial truncation can be reduced by so-called tapering. Tapering is essentially an attenuation of the loudspeakers towards the ends of the setup. As a consequence, the boundaries of the aperture become smoother which reduces the artifacts. Of course, no benefit comes without a cost. In this case the cost is amplitude errors for which the human ear fortunately does not seem to be too sensitive. In order to taper, you can assign the optional attribute \texttt{weight} to each loudspeaker in ASDF format (refer to Section~\ref{sec:asdf}). The \texttt{weight} determines the linear~(!) weight to be applied to the respective loudspeaker. It defaults to 1 if it is not specified. \subsection{Ambisonics Amplitude Panning Renderer} The Ambisonics Amplitude Panning (AAP) renderer does very simple Ambisonics rendering. It does amplitude panning by simultaneously using all loudspeakers which are not subwoofers to reproduce a virtual source (contrary to the VBAP renderer which uses only two loudspeakers at a time). Note that the loudspeakers should ideally be arranged on a circle and the reference should be the center of the circle. The renderer checks for that and applies delays and amplitude corrections to all loudspeakers which are closer to the reference than the farthest. This also includes subwoofers. If you do not want close loudspeakers to be delayed, then simply specify their location in the same direction like its actual position but at a larger distance from the reference. Then the graphical illustration will not be perfectly aligned with the real setup, but the audio processing will take place as intended. Note that the AAP renderer ignores delays assigned to an individual loudspeaker in ASDF. On the other hand, it does consider weights assigned to the loudspeakers. This allows you to compensate for irregular loudspeaker placement. Note finally that AAP does not allow to encode the distance of a virtual sound source since it is a simple panning renderer. All sources will appear at the distance of the loudspeakers. If you do not explicitly specify an Ambisonics order, then the maximum order which makes sense on the given loudspeaker setup will be used. The automatically chosen order will be one of \nicefrac{(L-1)}{2} for an odd number $L$ of loudspeakers and accordingly for even numbers. You can manually set the order via a command line option (Section~\ref{sec:running_ssr}) or the SSR configuration file (Section~\ref{sec:ssr_configuration_file}). We therefore do not explicitly discriminate between ``higher order'' and ``lower order'' Ambisonics since this is not a fundamental property. And where does ``lower order'' end and ``higher order'' start anyway? Note that the graphical user interface will not indicate the activity of the loudspeakers since theoretically all loudspeakers contribute to the sound field of a virtual source at any time. \paragraph{Conventional driving function} By default we use the standard Ambisonics panning function outlined e.g.~in \cite{Neukom07} reading % \begin{equation} d(\alpha_0) = \frac{\sin\left ( \frac{2M+1}{2} \ (\alpha_0 - \alpha_\textnormal{s})\right )} {(2M+1) \ \sin \left ( \frac{\alpha_0 - \alpha_\textnormal{s}}{2} \right ) } \ , \nonumber \end{equation} % whereby $\alpha_0$ is the polar angle of the position of the considered secondary source, $\alpha_\textnormal{s}$ is the polar angle of the position of the virtual source, and $M$ is the Ambisonics order. \paragraph{In-phase driving function} The conventional driving function leads to both positive and negative weights for individual loudspeakers. An object (e.g.~a listener) introduced into the listening area can lead to an imperfect interference of the wave fields of the individual loudspeakers and therefore to an inconsistent perception. Furthermore, conventional Ambisonics panning can lead to audible artifacts for fast source motions since it can happen that the weights of two adjacent audio frames have a different algebraic sign. These problems can be worked around when only positive weights are applied on the input signal (\emph{in-phase} rendering). This can be accomplished via the in-phase driving function given e.g.~in \cite{Neukom07} reading % \begin{equation} d(\alpha_0) = \cos^{2M} \left (\frac{\alpha_0 - \alpha_\textnormal{s}}{2} \right ) \ . \nonumber \end{equation} % Note that in-phase rendering leads to a less precise localization of the virtual source and other unwanted perceptions. You can enable in-phase rendering via the according command-line option or you can set the \texttt{IN\_PHASE\_RENDERING} property in the SSR configuration file (see section~\ref{sec:ssr_configuration_file}) to be ``\texttt{TRUE}'' or ``\texttt{true}''. \subsection{Generic Renderer} The generic renderer turns the SSR into a multiple-input-multiple-output convolution engine. You have to use an ASDF file in which the attribute \texttt{properties\_file} of the individual sound source has to be set properly. That means that the indicated file has to be a multichannel file with the same number of channels like loudspeakers in the setup. The impulse response in the file at channel 1 represents the driving function for loudspeaker~1 and so on. Be sure that you load a reproduction setup with the corresponding number of loudspeakers. It is obviously not possible to move virtual sound sources since the loaded impulse responses are static. We use this renderer in order to test advanced methods before implementing them in real-time or to compare two different rendering methods by having one sound source in one method and another sound source in the other method. Download the ASDF examples from~\cite{ssr} and check out the file \texttt{generic\_renderer\_example.asd} which comes with all required data. \begin{table}%[htbp] \begin{center} \begin{tabular}{| l | c | c |} \hline & individual delay & weight \\ \hline binaural renderer & - & - \\ BRS renderer & - & - \\ VBAP renderer & + & + \\ WFS renderer & - & + \\ AAP renderer & autom. & + \\ generic renderer & - & - \\\hline \end{tabular} \caption{\label{tab:loudspeaker_properties}Loudspeaker properties considered by the different renderers.} \end{center} \end{table} \begin{table}%[htbp] \begin{center}% \begin{minipage}{\textwidth}% to enable footnotes \begin{tabular}{| l | c | c | c | c | c |} \hline & gain & mute & position & orientation\footnote{So far, only plane waves have a defined orientation.} & model\\ \hline binaural renderer & + & + & + & - & only ampl.\\ BRS renderer & + & + & - & - & -\\ VBAP renderer & + & + & + & - & only ampl.\\ WFS renderer & + & + & + & + & +\\ AAP renderer & + & + & + & - & only ampl.\\ generic renderer & + & + & - & - & -\\\hline \end{tabular} \end{minipage} \caption{\label{tab:source_properties}Virtual source's properties considered by the different renderers.} \end{center} \end{table} %\subsection{Parallel Processing Renderers} % %The renderers as described above do not support parallel processing. We are %currently redesigning the architecture of the SSR in order to support audio %processing in multiple threads so that the power of multi-processor/multi-core machines can be %properly exploited. The current SSR release contains versions of the WFS, the %VBAP, and the binaural renderer which support parallel processing. These %versions are disabled at compile time by default. %If you want to enable these renderers use the option \texttt{./configure %--enable-newrenderer} (Section~\ref{sec:comp_inst}) when configuring. All %renderers other than WFS, VBAP, and binaural will then not be available. % %\textbf{WARNING:} The parallel processing renderers are under heavy development. %If you encounter unexpected behaviour or bugs, please report them to %\emph{SoundScapeRenderer@telekom.de}. Thank you. \subsection{Summary} Tables~\ref{tab:loudspeaker_properties} and~\ref{tab:source_properties} summarize the functionality of the SSR renderers. ssr-0.4.2/doc/manual/todo.tex000066400000000000000000000032061236416011200160320ustar00rootroot00000000000000\section{TODO} % % \subsection{General} % % % \begin{itemize} \item {\bf SPLIT THE APPLICATION INTO LIBRARIES. THE EXECUTABLE HAS MORE THAN 8 MB!!!} \item Whenever an error is stated because a file was not found, then specify the entire path to the respective file. \item This means that we have to properly treat relative and absolute paths. \item Make sure that the \emph{asd} scheme is found when SSR is started from a different working directory than \emph{SSR/bin}. \item Either orientation or position should be sufficient as specification for plane waves in ASDF. \item HOA \item Implementation of directional sources \item {\bf Dynamic scene handling} \item InterSense client \item More flexible assignment of virtual loudspeakers to physical outputs. \item proper handling of moving sources (audible artefacts!!!) \item optimize the convolver in terms of efficiency \end{itemize} % % \subsection{GUI} % \begin{itemize} \item shadows \item color gradients \item interpolated textures \end{itemize} % % \subsection{WFS Renderer} % \begin{itemize} \item Implementation of the 3dB/oct prefilter. \end{itemize} % % % \subsection{VBAP Renderer} % \begin{itemize} \item compensate for imperfect circular loudspeaker layouts via delay??? \end{itemize} % % \subsection{Network Interface} % \begin{itemize} \item Remote file dialog (to open files on the server from then client) \end{itemize} % % % \subsection{(possible) bugs} % % \begin{itemize} \item There might be a bug in the loudspeaker selection for WFS. The setup file for the linear array in flores works. But if you change the loudspeaker orientation from 180 to -180 degrees it won't work. \end{itemize} %ssr-0.4.2/flext/000077500000000000000000000000001236416011200134425ustar00rootroot00000000000000ssr-0.4.2/flext/8channels.asd000077700000000000000000000000001236416011200256312../data/reproduction_setups/8channels.asdustar00rootroot00000000000000ssr-0.4.2/flext/Makefile000066400000000000000000000016421236416011200151050ustar00rootroot00000000000000# Makefile to generate externals for Puredata # see also package.txt # The flext build system doesn't support compiling multiple externals at once, # so we work around this with PD_TARGETS and specify NAME here. EXTERNALS ?= ssr_binaural ssr_nfc_hoa ssr_aap ssr_wfs ssr_vbap PD_TARGETS := $(EXTERNALS:%=%_pd) FLEXTPATH ?= /usr/local/src/flext all: pd pd: $(PD_TARGETS) %_pd: %.cpp check_flext_path NAME='$(@:%_pd=%~)' MAIN_SOURCE='$<' $(FLEXTPATH)/build.sh pd gcc check_flext_path: @test -n '$(FLEXTPATH)' || ( echo \"FLEXTPATH\" is empty! ; false ) @test -d '$(FLEXTPATH)' || \ ( echo \"$(FLEXTPATH)\" not found! Set FLEXTPATH! ; false ) clean: $(EXTERNALS:%=%_pd_clean) %_pd_clean: check_flext_path NAME='$(@:%_pd_clean=%~)' $(FLEXTPATH)/build.sh pd gcc clean @rmdir pd-linux 2> /dev/null || true .PHONY: all pd clean check_flext_path # TODO: When adding $(PD_TARGETS) to .PHONY, it ceases to work ... why? ssr-0.4.2/flext/circle.asd000077700000000000000000000000001236416011200245652../data/reproduction_setups/circle.asdustar00rootroot00000000000000ssr-0.4.2/flext/hrirs_fabian.wav000077700000000000000000000000001236416011200300032../data/impulse_responses/hrirs/hrirs_fabian.wavustar00rootroot00000000000000ssr-0.4.2/flext/package.txt000066400000000000000000000006131236416011200155760ustar00rootroot00000000000000# NAME and MAIN_SOURCE are set in Makefile! vpath %.cpp ../src SSR_SRCS = position.cpp orientation.cpp directionalpoint.cpp \ ssr_global.cpp xmlparser.cpp SRCS = $(MAIN_SOURCE) $(SSR_SRCS) INCPATH = -I../apf -I../src PKG_CONFIG ?= pkg-config INCPATH += `$(PKG_CONFIG) --cflags libxml-2.0` CXXFLAGS = -std=c++11 LIBS = -lfftw3f -lsndfile -lxml2 # vim:filetype=make ssr-0.4.2/flext/ssr_aap.cpp000066400000000000000000000045451236416011200156060ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ // Ambisonics Amplitude Panning renderer as Puredata/Max external. // Force copy of buffers because input buffers are re-used as output buffers: #define SSR_SHARED_IO_BUFFERS #include "ssr_flext.h" #include "aaprenderer.h" SSR_FLEXT_INSTANCE(aap, ssr::AapRenderer) // Settings for Vim (http://www.vim.org/), please do not remove: // vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent ssr-0.4.2/flext/ssr_aap~-help.pd000066400000000000000000000010031236416011200165350ustar00rootroot00000000000000#N canvas 1105 534 324 223 10; #X obj 47 77 osc~ 440; #X obj 47 101 *~ 0.2; #X obj 171 107 *~ 0.1; #X obj 170 83 noise~; #X text 115 21 <-- open this!; #X obj 30 22 ssr_messages; #X obj 30 135 ssr_aap~ 2 8channels.asd; #X obj 29 162 dac~ 1 2 3 4 5 6 7 8; #X connect 0 0 1 0; #X connect 1 0 6 0; #X connect 2 0 6 1; #X connect 3 0 2 0; #X connect 5 0 6 0; #X connect 6 0 7 0; #X connect 6 1 7 1; #X connect 6 2 7 2; #X connect 6 3 7 3; #X connect 6 4 7 4; #X connect 6 5 7 5; #X connect 6 6 7 6; #X connect 6 7 7 7; ssr-0.4.2/flext/ssr_binaural.cpp000066400000000000000000000043631236416011200166400ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ // Binaural renderer as Puredata/Max external. #include "ssr_flext.h" #include "binauralrenderer.h" SSR_FLEXT_INSTANCE(binaural, ssr::BinauralRenderer) // Settings for Vim (http://www.vim.org/), please do not remove: // vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent ssr-0.4.2/flext/ssr_binaural~-help.pd000066400000000000000000000006021236416011200175750ustar00rootroot00000000000000#N canvas 403 410 333 223 10; #X obj 29 162 dac~; #X obj 47 77 osc~ 440; #X obj 47 101 *~ 0.2; #X obj 219 100 *~ 0.1; #X obj 220 76 noise~; #X obj 30 135 ssr_binaural~ 2 hrirs_fabian.wav; #X text 115 21 <-- open this!; #X obj 30 22 ssr_messages; #X connect 1 0 2 0; #X connect 2 0 5 0; #X connect 3 0 5 1; #X connect 4 0 3 0; #X connect 5 0 0 0; #X connect 5 1 0 1; #X connect 7 0 5 0; ssr-0.4.2/flext/ssr_flext.h000066400000000000000000000346451236416011200156400ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// SSR renderers as Puredata/Max externals. #ifndef SSR_FLEXT_H #define SSR_FLEXT_H #include #include // 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 #define APF_MIMOPROCESSOR_SAMPLE_TYPE t_sample #define SSR_FLEXT_INSTANCE(name, renderer) \ class ssr_ ## name : public SsrFlext { \ using SsrFlext::SsrFlext; \ FLEXT_HEADER_S(ssr_ ## name, flext_dsp, setup) }; \ FLEXT_NEW_DSP_V("ssr_" #name "~", ssr_ ## name) #include "apf/pointer_policy.h" #include "apf/default_thread_policy.h" #include "../src/source.h" template class SsrFlext : public flext_dsp { FLEXT_HEADER_S(SsrFlext, flext_dsp, setup) private: // TODO: put these functions somewhere else? static bool _get(int& argc, const t_atom*& argv, int& out) { if (!IsFloat(*argv)) return false; int result = GetInt(*argv); if (result != GetFloat(*argv)) return false; out = result; --argc; ++argv; return true; } static bool _get(int& argc, const t_atom*& argv, float& out) { if (!IsFloat(*argv)) return false; out = GetFloat(*argv); --argc; ++argv; return true; } static bool _get(int& argc, const t_atom*& argv, bool& out) { if (!CanbeBool(*argv)) return false; out = GetBool(*argv); --argc; ++argv; return true; } static const char* _get(int& argc, const t_atom*& argv) { const char* result = ""; if (!IsSymbol(*argv)) return result; result = GetString(*argv); --argc; ++argv; return result; } apf::parameter_map _get_parameters(int argc, const t_atom* argv) { _in_channels = 0; int threads = 0; std::string first_string = "", second_string = ""; while (argc > 0) { // Note: IsInt(*argv) doesn't work in Pd if (IsFloat(*argv)) { int arg; if (!_get(argc, argv, arg)) { throw std::invalid_argument("Numeric arguments must be integers!"); } if (!_in_channels) { if (arg < 0) { throw std::invalid_argument("A negative number of sources ... " "how is this supposed to work?"); } _in_channels = arg; } else if (!threads) { if (arg < 0) { throw std::invalid_argument("A negative number of threads ... " "how is this supposed to work?"); } threads = arg; } else { throw std::invalid_argument("Too many numeric arguments!"); } } else if (IsSymbol(*argv)) { if (first_string == "") { first_string = _get(argc, argv); } else if (second_string == "") { second_string = _get(argc, argv); } else { throw std::invalid_argument("Too many string arguments!"); } } else { throw std::invalid_argument("Unsupported argument type!"); } } if (!_in_channels) { post("%s - first numeric argument should specify number of sources! " "None given, creating one source by default ...", thisName()); _in_channels = 1; } if (first_string == "") { throw std::invalid_argument( "At least one string must be specified as argument!"); } apf::parameter_map params; params.set("reproduction_setup", first_string); params.set("hrir_file", first_string); // TODO: At some point, "prefilter_file" should become part of the // configuration file for the reproduction setup. // As soon as this happens, "second_string" should be removed! if (second_string != "") { params.set("prefilter_file", second_string); } if (threads) { params.set("threads", threads); } params.set("block_size", Blocksize()); params.set("sample_rate", Samplerate()); std::string info; for (auto it: params) { info += "\n"; info += " * "; info += it.first; info += ": "; info += it.second; } post("%s - trying to start with following options:\n * sources: %d%s" , thisName(), _in_channels, info.c_str()); return params; } public: SsrFlext(int argc, const t_atom* argv) : _engine(_get_parameters(argc, argv)) { _engine.load_reproduction_setup(); for (size_t i = 0; i < _in_channels; ++i) { _engine.add_source(); AddInSignal(); } AddOutSignal(_engine.get_output_list().size()); _engine.activate(); // start parallel processing (if threads > 1) post("%s - initialization of %s completed, %d outputs available" , thisName(), _engine.name(), CntOut()); } static void setup(t_classid c) { FLEXT_CADDMETHOD(c, 0, _handle_messages); post("Thanks for using the SoundScape Renderer (SSR)!"); post("For more information, visit http://spatialaudio.net/ssr/"); } private: virtual void CbSignal() { _engine.audio_callback(Blocksize(), InSig(), OutSig()); } FLEXT_CALLBACK_A(_handle_messages) void _handle_messages(const t_symbol* s, int argc, const t_atom* argv) { std::string cmd1 = GetString(s); if (cmd1 == "src") { if (argc < 1) { error("%s - too few arguments for %s", thisName(), cmd1.c_str()); return; } int src_id; if (!_get(argc, argv, src_id)) { error("%s - src expects integer source ID (starting with 1)" , thisName()); return; } auto* source = _engine.get_source(src_id); if (!source) { error("%s - couldn't find source %d", thisName(), src_id); return; } std::string cmd2 = _get(argc, argv); if (cmd2 == "") { error("%s - %s %d must be followed by a string!" , thisName(), cmd1.c_str(), src_id); return; } else if (cmd2 == "pos") { if (argc != 2) { error("%s - src %d pos must be followed by exactly 2 coordinates!" , thisName(), src_id); return; } float x, y; if (!_get(argc, argv, x)) { error("%s - x must be a float value!", thisName()); return; } if (!_get(argc, argv, y)) { error("%s - y must be a float value!", thisName()); return; } source->position = Position(x, y); } else if (cmd2 == "azi") { if (argc != 1) { error("%s - src %d azi must be followed by exactly 1 angle!" , thisName(), src_id); return; } float azi; if (!_get(argc, argv, azi)) { error("%s - src azi expects a float value!", thisName()); return; } source->orientation = Orientation(azi); } else if (cmd2 == "gain") { if (argc != 1) { error("%s - src %d gain must be followed by exactly 1 value!" , thisName(), src_id); return; } float gain; if (!_get(argc, argv, gain)) { error("%s - src %d gain expects a float value!" , thisName(), src_id); return; } source->gain = gain; } else if (cmd2 == "mute") { if (argc != 1) { error("%s - src %d mute must be followed by exactly 1 argument!" , thisName(), src_id); return; } bool mute; if (!_get(argc, argv, mute)) { error("%s - src mute expects a boolean value!", thisName()); return; } source->mute = mute; } else if (cmd2 == "model") { if (argc != 1) { error("%s - src %d model must be followed by exactly 1 argument!" , thisName(), src_id); return; } std::string model_str = _get(argc, argv); if (model_str == "") { error("%s - src model expects a string value!", thisName()); return; } Source::model_t model = Source::unknown; if (!apf::str::S2A(model_str, model)) { error("%s - couldn't convert model string: %s" , thisName(), model_str.c_str()); return; } source->model = model; } else { error("%s - unknown command: src %d %s", thisName() , src_id, cmd2.c_str()); return; } } else if (cmd1 == "ref") { if (argc < 1) { error("%s - too few arguments for %s", thisName(), cmd1.c_str()); return; } bool offset = false; std::string cmd2 = _get(argc, argv); if (cmd2 == "offset") { offset = true; cmd2 = _get(argc, argv); } const char* offset_str = offset ? " offset" : ""; if (cmd2 == "") { error("%s - ref%s must be followed by a string!" , thisName(), offset_str); return; } else if (cmd2 == "pos") { if (argc != 2) { error("%s - ref%s pos must be followed by exactly 2 coordinates!" , thisName(), offset_str); return; } float x, y; if (!_get(argc, argv, x)) { error("%s - x must be a float value!", thisName()); return; } if (!_get(argc, argv, y)) { error("%s - y must be a float value!", thisName()); return; } if (offset) { _engine.state.reference_offset_position = Position(x, y); } else { _engine.state.reference_position = Position(x, y); } } else if (cmd2 == "azi") { if (argc != 1) { error("%s - ref%s azi must be followed by exactly 1 angle!" , thisName(), offset_str); return; } float azi; if (!_get(argc, argv, azi)) { error("%s - ref%s azi expects a float value!" , thisName(), offset_str); return; } if (offset) { _engine.state.reference_offset_orientation = Orientation(azi); } else { _engine.state.reference_orientation = Orientation(azi); } } else { error("%s - invalid string for ref%s: %s" , thisName(), offset_str, cmd2.c_str()); return; } } else if (cmd1 == "vol") { if (argc != 1) { error("%s - vol must be followed by exactly 1 value (in dB)!" , thisName()); return; } float master_volume; if (!_get(argc, argv, master_volume)) { error("%s - vol expects a float value!", thisName()); return; } _engine.state.master_volume = master_volume; } else if (cmd1 == "processing") { if (argc != 1) { error("%s - processing must be followed by exactly 1 value!" , thisName()); return; } bool processing; if (!_get(argc, argv, processing)) { error("%s - processing expects a boolean value!", thisName()); return; } _engine.state.processing = processing; } else { // TODO: amplitude_reference_distance? error("%s - unknown command: %s", thisName(), cmd1.c_str()); return; } } int _in_channels; Renderer _engine; }; #endif // Settings for Vim (http://www.vim.org/), please do not remove: // vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent ssr-0.4.2/flext/ssr_messages.pd000066400000000000000000000164071236416011200164750ustar00rootroot00000000000000#N canvas 573 53 1036 923 10; #X msg 33 93 src 1 pos -1 1; #X msg 33 116 src 1 pos 0 1; #X msg 35 139 src 1 pos 1 1; #X msg 49 169 src 2 pos -1 1; #X msg 50 192 src 2 pos 0 1; #X msg 51 215 src 2 pos 1 1; #X obj 21 251 s \$0_msg; #X obj 12 12 r \$0_msg; #X obj 12 33 outlet; #X msg 59 720 nonsense 1 pos 1 1; #X obj 33 945 s \$0_msg; #X msg 65 742 1 1 pos 1 1; #X msg 75 787 src 0 pos 1 1; #X msg 79 809 src -1 pos 1 1; #X msg 70 766 src 1.1 pos 1 1; #X msg 33 633 src; #X msg 41 655 src 1; #X msg 47 675 src 1 pos; #X msg 53 697 src 1 pos 1; #X msg 84 831 src 1 nonsense 1 1; #X msg 90 855 src 1 1 1 1; #X msg 97 879 src 1 pos nonsense 1; #X msg 101 903 src 1 pos 1 nonsense; #X msg 109 923 src 1 pos 1 1 1; #X msg 77 404 ref azi 90; #X text 21 68 SOURCE POSITION; #X text 183 67 SOURCE AZIMUTH; #X obj 190 208 s \$0_msg; #X msg 202 153 src 1 azi -90; #X msg 187 109 src 1 azi 90; #X msg 195 131 src 1 azi 180; #X msg 209 177 src 1 azi 0; #X obj 270 943 s \$0_msg; #X msg 218 637 src 1 azi; #X msg 237 679 src 1 azi nonsense; #X msg 228 659 src 1 azi 0 0; #X obj 342 185 s \$0_msg; #X text 330 67 SOURCE MUTE; #X msg 333 94 src 1 mute 1; #X msg 342 114 src 1 mute 0; #X msg 353 134 src 2 mute 1; #X msg 362 154 src 2 mute 0; #X obj 897 16 loadbang; #X obj 897 57 s \$0_msg; #X msg 897 37 src 1 pos -1 1 \, src 2 pos 1 1 \,; #X obj 663 184 s \$0_msg; #X msg 650 93 src 1 model plane; #X msg 662 115 src 1 model point; #X msg 674 137 src 2 model plane; #X msg 686 159 src 2 model point; #X text 649 68 SOURCE MODEL (only ssr_wfs~); #X text 167 85 (only for plane waves); #X msg 250 703 src 1 mute; #X msg 258 723 src 1 mute nonsense; #X msg 267 745 src 1 mute true; #X msg 276 768 src 1 mute 1 1; #X msg 286 810 src 1 mute 1.1; #X text 284 792 this actually works:; #X msg 309 873 src 1 model 0; #X msg 313 895 src 1 model nonsense; #X msg 318 916 src 1 model point source; #X obj 489 185 s \$0_msg; #X text 473 66 SOURCE GAIN; #X text 474 82 (linear! from 0..1); #X msg 508 153 src 1 gain 2; #X msg 496 129 src 1 gain 1; #X msg 486 105 src 1 gain 0.5; #X msg 295 831 src 1 gain nonsense; #X msg 303 852 src 1 gain 0 0; #X obj 24 498 s \$0_msg; #X text 20 296 REFERENCE CONTROLS; #X msg 49 349 ref pos 0 0; #X msg 44 328 ref pos -2 0; #X msg 55 370 ref pos 2 0; #X msg 82 426 ref azi 180; #X msg 89 446 ref azi -90; #X msg 96 468 ref azi 0; #X text 162 403 <-- default!; #X obj 513 957 s \$0_msg; #X msg 512 609 ref 0; #X msg 519 629 ref nonsense; #X msg 525 649 ref pos nonsense; #X msg 533 669 ref pos 0; #X msg 540 689 ref pos 0 0 0; #X msg 547 710 ref azi nonsense; #X msg 552 731 ref azi 0 0; #X msg 560 761 ref offset nonsense; #X msg 565 784 ref offset 0; #X msg 571 806 ref offset pos 0; #X msg 579 828 ref offset pos 0 0 0; #X msg 586 850 ref offset azi nonsense; #X msg 594 872 ref offset azi 0 0; #X msg 609 906 vol nonsense; #X msg 616 929 vol 0 0; #X text 288 299 REFERENCE OFFSET; #X text 288 319 (may not work in some renderers!); #X obj 308 515 s \$0_msg; #X text 483 487 <-- default!; #X msg 305 346 ref offset pos -2 0; #X msg 310 367 ref offset pos 0 0; #X msg 316 388 ref offset pos 2 0; #X msg 338 422 ref offset azi 90; #X msg 343 444 ref offset azi 180; #X msg 349 464 ref offset azi -90; #X msg 357 486 ref offset azi 0; #X obj 593 408 s \$0_msg; #X text 567 292 MASTER VOLUME; #X text 729 291 PROCESSING ON/OFF; #X text 84 27 All parameter changes happen smoothly within one audio block!; #X obj 752 387 s \$0_msg; #X text 729 310 (= inverse master mute); #X msg 753 338 processing 0; #X msg 761 360 processing 1; #X text 561 309 (linear! from 0..1); #X msg 599 338 vol 0.5; #X msg 605 360 vol 1; #X msg 612 383 vol 2; #X obj 746 526 s \$0_msg; #X obj 746 473 bng 15 250 50 0 empty empty empty 17 7 0 10 -262144 -1 -1; #X text 36 596 INVALID MESSAGES; #N canvas 261 385 521 399 circle 0; #X obj 121 306 outlet; #X obj 158 200 cos; #X obj 126 201 sin; #X obj 126 154 line; #X obj 105 83 metro; #X obj 126 109 f; #X obj 137 61 t f f; #X msg 126 131 0 \, 6.28319 \$1; #X text 194 200 angle in radians; #X obj 94 -3 tgl 15 0 empty empty empty 17 7 0 10 -262144 -1 -1 0 1 ; #X msg 30 -14 0; #X obj 94 19 t f b; #X obj 94 -34 inlet; #X obj 30 -35 loadbang; #X obj 221 -32 inlet; #X text 278 -34 time (msec) for 1 rotation; #X text 145 -34 on/off; #X obj 126 176 t f f; #X text 215 23 first argument: source number; #X text 217 43 second argument: circle radius (meters); #X obj 117 234 * 3; #X obj 158 233 * 3; #X text 201 233 radius; #X obj 121 263 pack f f; #X msg 121 285 src 2 pos \$1 \$2; #X obj 155 -9 t b f; #X obj 151 14 0; #X msg 32 131 stop; #X obj 32 110 select 0; #X text 252 285 source nr 2 is used; #X obj 137 40 2000; #X connect 1 0 21 0; #X connect 2 0 20 0; #X connect 3 0 17 0; #X connect 4 0 5 0; #X connect 5 0 7 0; #X connect 6 0 4 1; #X connect 6 1 5 1; #X connect 7 0 3 0; #X connect 9 0 11 0; #X connect 9 0 26 1; #X connect 9 0 28 0; #X connect 10 0 9 0; #X connect 11 0 4 0; #X connect 11 1 30 0; #X connect 12 0 9 0; #X connect 13 0 10 0; #X connect 14 0 25 0; #X connect 17 0 2 0; #X connect 17 1 1 0; #X connect 20 0 23 0; #X connect 21 0 23 1; #X connect 23 0 24 0; #X connect 24 0 0 0; #X connect 25 0 26 0; #X connect 25 1 30 1; #X connect 26 0 11 0; #X connect 27 0 3 0; #X connect 28 0 27 0; #X connect 30 0 6 0; #X restore 746 500 pd circle; #X text 703 449 start/stop; #X text 816 437 time for one turn (msec); #X msg 819 479 4000; #X msg 814 458 2000; #X text 81 10 this abstraction is used in ssr_*~-help.pd; #X connect 0 0 6 0; #X connect 1 0 6 0; #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 7 0 8 0; #X connect 9 0 10 0; #X connect 11 0 10 0; #X connect 12 0 10 0; #X connect 13 0 10 0; #X connect 14 0 10 0; #X connect 15 0 10 0; #X connect 16 0 10 0; #X connect 17 0 10 0; #X connect 18 0 10 0; #X connect 19 0 10 0; #X connect 20 0 10 0; #X connect 21 0 10 0; #X connect 22 0 10 0; #X connect 23 0 10 0; #X connect 24 0 69 0; #X connect 28 0 27 0; #X connect 29 0 27 0; #X connect 30 0 27 0; #X connect 31 0 27 0; #X connect 33 0 32 0; #X connect 34 0 32 0; #X connect 35 0 32 0; #X connect 38 0 36 0; #X connect 39 0 36 0; #X connect 40 0 36 0; #X connect 41 0 36 0; #X connect 42 0 44 0; #X connect 44 0 43 0; #X connect 46 0 45 0; #X connect 47 0 45 0; #X connect 48 0 45 0; #X connect 49 0 45 0; #X connect 52 0 32 0; #X connect 53 0 32 0; #X connect 54 0 32 0; #X connect 55 0 32 0; #X connect 56 0 32 0; #X connect 58 0 32 0; #X connect 59 0 32 0; #X connect 60 0 32 0; #X connect 64 0 61 0; #X connect 65 0 61 0; #X connect 66 0 61 0; #X connect 67 0 32 0; #X connect 68 0 32 0; #X connect 71 0 69 0; #X connect 72 0 69 0; #X connect 73 0 69 0; #X connect 74 0 69 0; #X connect 75 0 69 0; #X connect 76 0 69 0; #X connect 79 0 78 0; #X connect 80 0 78 0; #X connect 81 0 78 0; #X connect 82 0 78 0; #X connect 83 0 78 0; #X connect 84 0 78 0; #X connect 85 0 78 0; #X connect 86 0 78 0; #X connect 87 0 78 0; #X connect 88 0 78 0; #X connect 89 0 78 0; #X connect 90 0 78 0; #X connect 91 0 78 0; #X connect 92 0 78 0; #X connect 93 0 78 0; #X connect 98 0 96 0; #X connect 99 0 96 0; #X connect 100 0 96 0; #X connect 101 0 96 0; #X connect 102 0 96 0; #X connect 103 0 96 0; #X connect 104 0 96 0; #X connect 111 0 109 0; #X connect 112 0 109 0; #X connect 114 0 105 0; #X connect 115 0 105 0; #X connect 116 0 105 0; #X connect 118 0 120 0; #X connect 120 0 117 0; #X connect 123 0 120 1; #X connect 124 0 120 1; ssr-0.4.2/flext/ssr_nfc_hoa.cpp000066400000000000000000000043551236416011200164410ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ // NFC-HOA renderer as Puredata/Max external. #include "ssr_flext.h" #include "nfchoarenderer.h" SSR_FLEXT_INSTANCE(nfc_hoa, ssr::NfcHoaRenderer) // Settings for Vim (http://www.vim.org/), please do not remove: // vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent ssr-0.4.2/flext/ssr_nfc_hoa~-help.pd000066400000000000000000000032561236416011200174050ustar00rootroot00000000000000#N canvas 877 651 707 188 10; #X obj 18 127 dac~ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56; #X obj 30 50 osc~ 440; #X obj 165 51 noise~; #X obj 30 71 *~ 0.01; #X obj 165 73 *~ 0.01; #X obj 18 100 ssr_nfc_hoa~ 2 circle.asd; #X text 104 14 <-- open this!; #X obj 19 15 ssr_messages; #X connect 1 0 3 0; #X connect 2 0 4 0; #X connect 3 0 5 0; #X connect 4 0 5 1; #X connect 5 0 0 0; #X connect 5 1 0 1; #X connect 5 2 0 2; #X connect 5 3 0 3; #X connect 5 4 0 4; #X connect 5 5 0 5; #X connect 5 6 0 6; #X connect 5 7 0 7; #X connect 5 8 0 8; #X connect 5 9 0 9; #X connect 5 10 0 10; #X connect 5 11 0 11; #X connect 5 12 0 12; #X connect 5 13 0 13; #X connect 5 14 0 14; #X connect 5 15 0 15; #X connect 5 16 0 16; #X connect 5 17 0 17; #X connect 5 18 0 18; #X connect 5 19 0 19; #X connect 5 20 0 20; #X connect 5 21 0 21; #X connect 5 22 0 22; #X connect 5 23 0 23; #X connect 5 24 0 24; #X connect 5 25 0 25; #X connect 5 26 0 26; #X connect 5 27 0 27; #X connect 5 28 0 28; #X connect 5 29 0 29; #X connect 5 30 0 30; #X connect 5 31 0 31; #X connect 5 32 0 32; #X connect 5 33 0 33; #X connect 5 34 0 34; #X connect 5 35 0 35; #X connect 5 36 0 36; #X connect 5 37 0 37; #X connect 5 38 0 38; #X connect 5 39 0 39; #X connect 5 40 0 40; #X connect 5 41 0 41; #X connect 5 42 0 42; #X connect 5 43 0 43; #X connect 5 44 0 44; #X connect 5 45 0 45; #X connect 5 46 0 46; #X connect 5 47 0 47; #X connect 5 48 0 48; #X connect 5 49 0 49; #X connect 5 50 0 50; #X connect 5 51 0 51; #X connect 5 52 0 52; #X connect 5 53 0 53; #X connect 5 54 0 54; #X connect 5 55 0 55; #X connect 7 0 5 0; ssr-0.4.2/flext/ssr_vbap.cpp000066400000000000000000000045511236416011200157720ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ // Vector Base Amplitude Panning renderer as Puredata/Max external. // Force copy of buffers because input buffers are re-used as output buffers: #define SSR_SHARED_IO_BUFFERS #include "ssr_flext.h" #include "vbaprenderer.h" SSR_FLEXT_INSTANCE(vbap, ssr::VbapRenderer) // Settings for Vim (http://www.vim.org/), please do not remove: // vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent ssr-0.4.2/flext/ssr_vbap~-help.pd000066400000000000000000000010041236416011200167250ustar00rootroot00000000000000#N canvas 1106 534 324 223 10; #X obj 47 77 osc~ 440; #X obj 47 101 *~ 0.2; #X obj 177 106 *~ 0.1; #X obj 176 82 noise~; #X text 115 21 <-- open this!; #X obj 30 22 ssr_messages; #X obj 29 162 dac~ 1 2 3 4 5 6 7 8; #X obj 30 135 ssr_vbap~ 2 8channels.asd; #X connect 0 0 1 0; #X connect 1 0 7 0; #X connect 2 0 7 1; #X connect 3 0 2 0; #X connect 5 0 7 0; #X connect 7 0 6 0; #X connect 7 1 6 1; #X connect 7 2 6 2; #X connect 7 3 6 3; #X connect 7 4 6 4; #X connect 7 5 6 5; #X connect 7 6 6 6; #X connect 7 7 6 7; ssr-0.4.2/flext/ssr_wfs.cpp000066400000000000000000000043601236416011200156370ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ // Wave Field Synthesis renderer as Puredata/Max external. #include "ssr_flext.h" #include "wfsrenderer.h" SSR_FLEXT_INSTANCE(wfs, ssr::WfsRenderer) // Settings for Vim (http://www.vim.org/), please do not remove: // vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent ssr-0.4.2/flext/ssr_wfs~-help.pd000066400000000000000000000033131236416011200166010ustar00rootroot00000000000000#N canvas 1152 654 707 188 10; #X obj 18 127 dac~ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56; #X obj 30 50 osc~ 440; #X obj 340 48 noise~; #X text 104 14 <-- open this!; #X obj 19 15 ssr_messages; #X obj 30 71 *~ 0.2; #X obj 18 100 ssr_wfs~ 2 circle.asd wfs_prefilter_120_1500_44100.wav ; #X obj 340 70 *~ 0.1; #X connect 1 0 5 0; #X connect 2 0 7 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; #X connect 6 2 0 2; #X connect 6 3 0 3; #X connect 6 4 0 4; #X connect 6 5 0 5; #X connect 6 6 0 6; #X connect 6 7 0 7; #X connect 6 8 0 8; #X connect 6 9 0 9; #X connect 6 10 0 10; #X connect 6 11 0 11; #X connect 6 12 0 12; #X connect 6 13 0 13; #X connect 6 14 0 14; #X connect 6 15 0 15; #X connect 6 16 0 16; #X connect 6 17 0 17; #X connect 6 18 0 18; #X connect 6 19 0 19; #X connect 6 20 0 20; #X connect 6 21 0 21; #X connect 6 22 0 22; #X connect 6 23 0 23; #X connect 6 24 0 24; #X connect 6 25 0 25; #X connect 6 26 0 26; #X connect 6 27 0 27; #X connect 6 28 0 28; #X connect 6 29 0 29; #X connect 6 30 0 30; #X connect 6 31 0 31; #X connect 6 32 0 32; #X connect 6 33 0 33; #X connect 6 34 0 34; #X connect 6 35 0 35; #X connect 6 36 0 36; #X connect 6 37 0 37; #X connect 6 38 0 38; #X connect 6 39 0 39; #X connect 6 40 0 40; #X connect 6 41 0 41; #X connect 6 42 0 42; #X connect 6 43 0 43; #X connect 6 44 0 44; #X connect 6 45 0 45; #X connect 6 46 0 46; #X connect 6 47 0 47; #X connect 6 48 0 48; #X connect 6 49 0 49; #X connect 6 50 0 50; #X connect 6 51 0 51; #X connect 6 52 0 52; #X connect 6 53 0 53; #X connect 6 54 0 54; #X connect 6 55 0 55; #X connect 7 0 6 1; ssr-0.4.2/flext/startpd.sh000077500000000000000000000003511236416011200154610ustar00rootroot00000000000000#!/bin/sh # if no patch is specified on the command line, this one is opened: PD_PATCH=ssr_binaural~-help.pd if [ $# -gt 0 ] then PD_PATCH="$@" fi cd "$(dirname "$0")" pd -path pd-linux/release-single -helppath . "$PD_PATCH" & ssr-0.4.2/flext/virtual_aap.pd000066400000000000000000000017051236416011200163010ustar00rootroot00000000000000#N canvas 159 578 895 235 10; #X obj 47 77 osc~ 440; #X obj 47 101 *~ 0.2; #X obj 177 106 *~ 0.1; #X obj 176 82 noise~; #X text 115 21 <-- open this!; #X obj 30 22 ssr_messages; #X obj 30 196 dac~; #X obj 29 162 ssr_binaural~ 8 hrirs_fabian.wav; #X msg 427 106 src 1 pos 0 1.5 \, src 2 pos -1.06 1.06 \, src 3 pos -1.5 0 \, src 4 pos -1.06 -1.06 \, src 5 pos 0 -1.5 \, src 6 pos 1.06 -1.06 \, src 7 pos 1.5 0 \, src 8 pos 1.06 1.06; #X obj 427 84 loadbang; #X text 320 84 set positions; #X text 320 100 of virtual; #X text 320 116 loudspeakers; #X text 320 132 on startup:; #X obj 30 135 ssr_aap~ 2 8channels.asd; #X connect 0 0 1 0; #X connect 1 0 14 0; #X connect 2 0 14 1; #X connect 3 0 2 0; #X connect 5 0 14 0; #X connect 7 0 6 0; #X connect 7 1 6 1; #X connect 8 0 7 0; #X connect 9 0 8 0; #X connect 14 0 7 0; #X connect 14 1 7 1; #X connect 14 2 7 2; #X connect 14 3 7 3; #X connect 14 4 7 4; #X connect 14 5 7 5; #X connect 14 6 7 6; #X connect 14 7 7 7; ssr-0.4.2/flext/virtual_nfc_hoa.pd000066400000000000000000000071271236416011200171410ustar00rootroot00000000000000#N canvas 549 345 1034 474 10; #X obj 30 50 osc~ 440; #X obj 340 48 noise~; #X text 116 14 <-- open this!; #X obj 19 15 ssr_messages; #X obj 17 228 ssr_binaural~ 56 hrirs_fabian.wav; #X obj 16 259 dac~; #X obj 549 26 loadbang; #X text 442 26 set positions; #X text 442 42 of virtual; #X text 442 58 loudspeakers; #X text 442 74 on startup:; #X msg 549 48 src 1 pos 0 1 \, src 2 pos -0.111964 0.993712 \, src 3 pos -0.222521 0.974928 \, src 4 pos -0.330279 0.943883 \, src 5 pos -0.433884 0.900969 \, src 6 pos -0.532032 0.846724 \, src 7 pos -0.62349 0.781832 \, src 8 pos -0.707107 0.707107 \, src 9 pos -0.781832 0.62349 \, src 10 pos -0.846724 0.532032 \, src 11 pos -0.900969 0.433884 \, src 12 pos -0.943883 0.330279 \, src 13 pos -0.974928 0.222521 \, src 14 pos -0.993712 0.111964 \, src 15 pos -1 0 \, src 16 pos -0.993712 -0.111964 \, src 17 pos -0.974928 -0.222521 \, src 18 pos -0.943883 -0.330279 \, src 19 pos -0.900969 -0.433884 \, src 20 pos -0.846724 -0.532032 \, src 21 pos -0.781832 -0.62349 \, src 22 pos -0.707107 -0.707107 \, src 23 pos -0.62349 -0.781831 \, src 24 pos -0.532032 -0.846724 \, src 25 pos -0.433884 -0.900969 \, src 26 pos -0.330279 -0.943883 \, src 27 pos -0.222521 -0.974928 \, src 28 pos -0.111965 -0.993712 \, src 29 pos 0 -1 \, src 30 pos 0.111964 -0.993712 \, src 31 pos 0.222521 -0.974928 \, src 32 pos 0.330279 -0.943883 \, src 33 pos 0.433883 -0.900969 \, src 34 pos 0.532032 -0.846724 \, src 35 pos 0.62349 -0.781832 \, src 36 pos 0.707107 -0.707107 \, src 37 pos 0.781831 -0.62349 \, src 38 pos 0.846724 -0.532032 \, src 39 pos 0.900969 -0.433884 \, src 40 pos 0.943883 -0.330279 \, src 41 pos 0.974928 -0.222521 \, src 42 pos 0.993712 -0.111965 \, src 43 pos 1 0 \, src 44 pos 0.993712 0.111964 \, src 45 pos 0.974928 0.222521 \, src 46 pos 0.943883 0.330279 \, src 47 pos 0.900969 0.433884 \, src 48 pos 0.846724 0.532032 \, src 49 pos 0.781832 0.62349 \, src 50 pos 0.707107 0.707107 \, src 51 pos 0.62349 0.781831 \, src 52 pos 0.532032 0.846724 \, src 53 pos 0.433884 0.900969 \, src 54 pos 0.330279 0.943883 \, src 55 pos 0.222521 0.974928 \, src 56 pos 0.111964 0.993712; #X obj 18 115 ssr_nfc_hoa~ 2 circle.asd; #X obj 30 71 *~ 0.002; #X obj 340 70 *~ 0.001; #X connect 0 0 13 0; #X connect 1 0 14 0; #X connect 3 0 12 0; #X connect 4 0 5 0; #X connect 4 1 5 1; #X connect 6 0 11 0; #X connect 11 0 4 0; #X connect 12 0 4 0; #X connect 12 1 4 1; #X connect 12 2 4 2; #X connect 12 3 4 3; #X connect 12 4 4 4; #X connect 12 5 4 5; #X connect 12 6 4 6; #X connect 12 7 4 7; #X connect 12 8 4 8; #X connect 12 9 4 9; #X connect 12 10 4 10; #X connect 12 11 4 11; #X connect 12 12 4 12; #X connect 12 13 4 13; #X connect 12 14 4 14; #X connect 12 15 4 15; #X connect 12 16 4 16; #X connect 12 17 4 17; #X connect 12 18 4 18; #X connect 12 19 4 19; #X connect 12 20 4 20; #X connect 12 21 4 21; #X connect 12 22 4 22; #X connect 12 23 4 23; #X connect 12 24 4 24; #X connect 12 25 4 25; #X connect 12 26 4 26; #X connect 12 27 4 27; #X connect 12 28 4 28; #X connect 12 29 4 29; #X connect 12 30 4 30; #X connect 12 31 4 31; #X connect 12 32 4 32; #X connect 12 33 4 33; #X connect 12 34 4 34; #X connect 12 35 4 35; #X connect 12 36 4 36; #X connect 12 37 4 37; #X connect 12 38 4 38; #X connect 12 39 4 39; #X connect 12 40 4 40; #X connect 12 41 4 41; #X connect 12 42 4 42; #X connect 12 43 4 43; #X connect 12 44 4 44; #X connect 12 45 4 45; #X connect 12 46 4 46; #X connect 12 47 4 47; #X connect 12 48 4 48; #X connect 12 49 4 49; #X connect 12 50 4 50; #X connect 12 51 4 51; #X connect 12 52 4 52; #X connect 12 53 4 53; #X connect 12 54 4 54; #X connect 12 55 4 55; #X connect 13 0 12 0; #X connect 14 0 12 1; ssr-0.4.2/flext/virtual_vbap.pd000066400000000000000000000016741236416011200164750ustar00rootroot00000000000000#N canvas 158 578 895 235 10; #X obj 47 77 osc~ 440; #X obj 47 101 *~ 0.2; #X obj 177 106 *~ 0.1; #X obj 176 82 noise~; #X text 115 21 <-- open this!; #X obj 30 22 ssr_messages; #X obj 30 135 ssr_vbap~ 2 8channels.asd; #X obj 30 196 dac~; #X obj 29 162 ssr_binaural~ 8 hrirs_fabian.wav; #X msg 427 106 src 1 pos 0 1.5 \, src 2 pos -1.06 1.06 \, src 3 pos -1.5 0 \, src 4 pos -1.06 -1.06 \, src 5 pos 0 -1.5 \, src 6 pos 1.06 -1.06 \, src 7 pos 1.5 0 \, src 8 pos 1.06 1.06; #X obj 427 84 loadbang; #X text 320 84 set positions; #X text 320 100 of virtual; #X text 320 116 loudspeakers; #X text 320 132 on startup:; #X connect 0 0 1 0; #X connect 1 0 6 0; #X connect 2 0 6 1; #X connect 3 0 2 0; #X connect 5 0 6 0; #X connect 6 0 8 0; #X connect 6 1 8 1; #X connect 6 2 8 2; #X connect 6 3 8 3; #X connect 6 4 8 4; #X connect 6 5 8 5; #X connect 6 6 8 6; #X connect 6 7 8 7; #X connect 8 0 7 0; #X connect 8 1 7 1; #X connect 9 0 8 0; #X connect 10 0 9 0; ssr-0.4.2/flext/virtual_wfs.pd000066400000000000000000000070621236416011200163410ustar00rootroot00000000000000#N canvas 209 234 1034 474 10; #X obj 30 50 osc~ 440; #X obj 340 48 noise~; #X text 116 14 <-- open this!; #X obj 19 15 ssr_messages; #X obj 30 71 *~ 0.2; #X obj 340 70 *~ 0.1; #X obj 17 228 ssr_binaural~ 56 hrirs_fabian.wav; #X obj 16 259 dac~; #X obj 18 115 ssr_wfs~ 2 circle.asd wfs_prefilter_120_1500_44100.wav ; #X obj 549 26 loadbang; #X text 442 26 set positions; #X text 442 42 of virtual; #X text 442 58 loudspeakers; #X text 442 74 on startup:; #X msg 549 48 src 1 pos 0 1 \, src 2 pos -0.111964 0.993712 \, src 3 pos -0.222521 0.974928 \, src 4 pos -0.330279 0.943883 \, src 5 pos -0.433884 0.900969 \, src 6 pos -0.532032 0.846724 \, src 7 pos -0.62349 0.781832 \, src 8 pos -0.707107 0.707107 \, src 9 pos -0.781832 0.62349 \, src 10 pos -0.846724 0.532032 \, src 11 pos -0.900969 0.433884 \, src 12 pos -0.943883 0.330279 \, src 13 pos -0.974928 0.222521 \, src 14 pos -0.993712 0.111964 \, src 15 pos -1 0 \, src 16 pos -0.993712 -0.111964 \, src 17 pos -0.974928 -0.222521 \, src 18 pos -0.943883 -0.330279 \, src 19 pos -0.900969 -0.433884 \, src 20 pos -0.846724 -0.532032 \, src 21 pos -0.781832 -0.62349 \, src 22 pos -0.707107 -0.707107 \, src 23 pos -0.62349 -0.781831 \, src 24 pos -0.532032 -0.846724 \, src 25 pos -0.433884 -0.900969 \, src 26 pos -0.330279 -0.943883 \, src 27 pos -0.222521 -0.974928 \, src 28 pos -0.111965 -0.993712 \, src 29 pos 0 -1 \, src 30 pos 0.111964 -0.993712 \, src 31 pos 0.222521 -0.974928 \, src 32 pos 0.330279 -0.943883 \, src 33 pos 0.433883 -0.900969 \, src 34 pos 0.532032 -0.846724 \, src 35 pos 0.62349 -0.781832 \, src 36 pos 0.707107 -0.707107 \, src 37 pos 0.781831 -0.62349 \, src 38 pos 0.846724 -0.532032 \, src 39 pos 0.900969 -0.433884 \, src 40 pos 0.943883 -0.330279 \, src 41 pos 0.974928 -0.222521 \, src 42 pos 0.993712 -0.111965 \, src 43 pos 1 0 \, src 44 pos 0.993712 0.111964 \, src 45 pos 0.974928 0.222521 \, src 46 pos 0.943883 0.330279 \, src 47 pos 0.900969 0.433884 \, src 48 pos 0.846724 0.532032 \, src 49 pos 0.781832 0.62349 \, src 50 pos 0.707107 0.707107 \, src 51 pos 0.62349 0.781831 \, src 52 pos 0.532032 0.846724 \, src 53 pos 0.433884 0.900969 \, src 54 pos 0.330279 0.943883 \, src 55 pos 0.222521 0.974928 \, src 56 pos 0.111964 0.993712; #X connect 0 0 4 0; #X connect 1 0 5 0; #X connect 3 0 8 0; #X connect 4 0 8 0; #X connect 5 0 8 1; #X connect 6 0 7 0; #X connect 6 1 7 1; #X connect 8 0 6 0; #X connect 8 1 6 1; #X connect 8 2 6 2; #X connect 8 3 6 3; #X connect 8 4 6 4; #X connect 8 5 6 5; #X connect 8 6 6 6; #X connect 8 7 6 7; #X connect 8 8 6 8; #X connect 8 9 6 9; #X connect 8 10 6 10; #X connect 8 11 6 11; #X connect 8 12 6 12; #X connect 8 13 6 13; #X connect 8 14 6 14; #X connect 8 15 6 15; #X connect 8 16 6 16; #X connect 8 17 6 17; #X connect 8 18 6 18; #X connect 8 19 6 19; #X connect 8 20 6 20; #X connect 8 21 6 21; #X connect 8 22 6 22; #X connect 8 23 6 23; #X connect 8 24 6 24; #X connect 8 25 6 25; #X connect 8 26 6 26; #X connect 8 27 6 27; #X connect 8 28 6 28; #X connect 8 29 6 29; #X connect 8 30 6 30; #X connect 8 31 6 31; #X connect 8 32 6 32; #X connect 8 33 6 33; #X connect 8 34 6 34; #X connect 8 35 6 35; #X connect 8 36 6 36; #X connect 8 37 6 37; #X connect 8 38 6 38; #X connect 8 39 6 39; #X connect 8 40 6 40; #X connect 8 41 6 41; #X connect 8 42 6 42; #X connect 8 43 6 43; #X connect 8 44 6 44; #X connect 8 45 6 45; #X connect 8 46 6 46; #X connect 8 47 6 47; #X connect 8 48 6 48; #X connect 8 49 6 49; #X connect 8 50 6 50; #X connect 8 51 6 51; #X connect 8 52 6 52; #X connect 8 53 6 53; #X connect 8 54 6 54; #X connect 8 55 6 55; #X connect 9 0 14 0; #X connect 14 0 6 0; ssr-0.4.2/flext/wfs_prefilter_120_1500_44100.wav000077700000000000000000000000001236416011200357202../data/impulse_responses/wfs_prefilters/wfs_prefilter_120_1500_44100.wavustar00rootroot00000000000000ssr-0.4.2/macros.m4000066400000000000000000000106201236416011200140450ustar00rootroot00000000000000dnl Some macros for the use in configure.ac. dnl ENABLE_FEATURE(): macro to enable/disable features dnl Usage: dnl ENABLE_FEATURE([auto/explicit/forced], [name], [description], [test code]) dnl dnl "auto": if available, activate feature. dnl if not available -> warning (but continue). dnl "explicit": if specified with --enable-bla and available, activate feature. dnl if specified with --enable-bla and not available -> error! dnl if not specified, don't check. dnl "forced": always activate feature except if disabled with --disable-bla. dnl if not available -> error! dnl dnl If an argument ARG was given to --enable-bla=ARG, it is available as the dnl value of $enable_bla within the test but it is deleted afterwards. dnl dnl The test code must set have_bla=no in case of failure. dnl AC_DEFUN([ENABLE_FEATURE], [ AC_ARG_ENABLE($2, AS_HELP_STRING( ifelse([$1],[explicit], [--enable-$2], [--disable-$2]), ifelse([$1],[explicit], [Enable $3 (default=no)], [Disable $3]))) dnl define FEATURE "locally", replace - by _ pushdef([FEATURE], patsubst([$2], -, _)) AS_IF(ifelse([$1],[explicit], [test x$enable_]FEATURE[ != xno -a x$enable_]FEATURE[ != x], [test x$enable_]FEATURE[ != xno]), [ # set have_bla to "yes" have_]FEATURE[=yes # run the actual test (if a test was specified) $4 # if the test passed (= have_bla is still "yes") ... AS_IF([test x$have_]FEATURE[ = xyes], [ # ... set the preprocessor variable ENABLE_BLA to 1 AC_DEFINE(translit([enable_]FEATURE, [a-z], [A-Z]), 1, $3) ], # else (= the test failed): [ AS_IF([test x$enable_]FEATURE[ = x], [ ifelse([$1], [forced], [ # for failures in forced settings, we generate an error AC_MSG_ERROR([$2 ($3) not available! Use --disable-$2 to deactivate.]) ], [ # for failures in default (auto) settings, we generate a warning AC_MSG_WARN( [--enable-$2 ($3) requested but not available!]) ]) ], [ # for failures in explicit settings, we generate an error AC_MSG_ERROR([--enable-$2 ($3) requested but not available!]) ]) ]) ], [ have_]FEATURE[=no ]) # undefine enable_bla because it only creates confusion, use have_bla instead AS_UNSET([enable_]FEATURE) # set automake conditional ENABLE_BLA (for Makefile.am) AM_CONDITIONAL(translit([enable_]FEATURE, [a-z], [A-Z]), [test x$have_]FEATURE[ = xyes]) popdef([FEATURE]) ]) dnl ENABLE_AUTO(): autoconf macro for configure options (default=auto) dnl Usage: ENABLE_AUTO([name of feature], [description], [test for feature]) dnl dnl A test for the given feature can be specified as (optional) third parameter dnl (e.g. some shell code or another macro). dnl The only thing it must do is "have_=no" if the feature is not possible dnl dnl Examples: dnl ENABLE_AUTO([bla], [support for bla-feature], dnl AC_CHECK_HEADER([bla.h], , [have_bla=no])) dnl dnl The macro provides several things: dnl 1) a configure switch --disable-bla including help text (second parameter) dnl 2) an autoconf variable $have_bla (yes/no) dnl 3) a preprocessor define ENABLE_BLA dnl 4) an automake conditional ENABLE_BLA (use in Makefile.am) dnl dnl The autoconf variable $enable_bla is deleted afterwards because it only dnl causes confusion (former values: yes/no//). dnl dnl Hyphens in "name" will be changed to underscores, e.g.: dnl all-goodies --> $have_all_goodies, ENABLE_ALL_GOODIES dnl dnl If a test fails, a warning message is shown in case of the default actions, dnl in case of failed explicit choices, an error message is shown. dnl ENABLE_EXPLICIT(): autoconf macro for configure options (default=no) dnl Usage: ENABLE_EXPLICIT([name of feature], [description], [test for feature]) dnl dnl The macro provides several things: dnl 1) a configure switch --enable-bla including help text (second parameter) dnl dnl For the rest of the features see ENABLE_AUTO() above. dnl ENABLE_FORCED(): autoconf macro for configure options (default=yes) dnl Same as ENABLE_AUTO, except if the feature is not available, it's an error. AC_DEFUN([ENABLE_AUTO], [ENABLE_FEATURE([auto], [[$1]], [[$2]], [$3])]) AC_DEFUN([ENABLE_EXPLICIT], [ENABLE_FEATURE([explicit], [[$1]], [[$2]], [$3])]) AC_DEFUN([ENABLE_FORCED], [ENABLE_FEATURE([forced], [[$1]], [[$2]], [$3])]) ssr-0.4.2/man/000077500000000000000000000000001236416011200130735ustar00rootroot00000000000000ssr-0.4.2/man/Makefile.am000066400000000000000000000021001236416011200151200ustar00rootroot00000000000000## This file will be processed by automake (which is called by autogen.sh) to ## generate Makefile.in, which in turn will be processed by configure to ## generate Makefile. ## comments starting with a single # are copied to Makefile.in (and afterwards ## to Makefile), comments with ## are dropped. dist_man_MANS = $(SSR_executables:=.1) MAINTAINERCLEANFILES = $(dist_man_MANS) RENDERER = $(@:ssr-%.1=%) RENDERER_TEXT = "manual page for the \ `test $(RENDERER) = binaural -o $(RENDERER) = generic \ && echo $(RENDERER) \ || echo $(RENDERER) | tr 'a-z' 'A-Z'` renderer" # help2man must be called after the binaries are built in the src/ folder! # Note the order of SUBDIRS in the top-level Makefile. # # "make" must be called before "make dist", there is no automatic dependency! $(dist_man_MANS): $(top_srcdir)/src/configuration.cpp $(top_srcdir)/configure.ac $(HELP2MAN) --no-info --name=$(RENDERER_TEXT) --output=$@ --locale=en \ $(top_builddir)/src/$(@:.1=$(EXEEXT)) ## Settings for Vim (http://www.vim.org/), please do not remove: ## vim:textwidth=80:comments+=bO\:## ssr-0.4.2/mex/000077500000000000000000000000001236416011200131115ustar00rootroot00000000000000ssr-0.4.2/mex/COPYING000066400000000000000000001045131236416011200141500ustar00rootroot00000000000000 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 . ssr-0.4.2/mex/DESCRIPTION000066400000000000000000000003541236416011200146210ustar00rootroot00000000000000Name: SoundScape Renderer Version: Date: Author: Maintainer: Title: SSR as MEX files Description: Categories: Problems: Url: http://spatialaudio.net/ssr/ Autoload: Depends: License: GPLv3+ SystemRequirements: BuildRequires: SVNRelease: ssr-0.4.2/mex/Makefile000066400000000000000000000056241236416011200145600ustar00rootroot00000000000000# Makefile for building MEX-files # Makefile example: # http://www.klab.caltech.edu/~harel/share/gbvs_scale/sift/Makefile MEXFILES ?= ssr_nfc_hoa ssr_binaural ssr_vbap ssr_aap ssr_wfs OBJECTS := ssr_global position orientation directionalpoint xmlparser LIBRARIES += libxml-2.0 fftw3f sndfile APF_DIR ?= ../apf SRC_DIR ?= ../src MEX ?= mex -v OCT ?= mkoctfile --mex --verbose CXXFLAGS += -std=c++11 # optimization: CXXFLAGS_OPT += -O3 #CXXFLAGS_OPT += -fomit-frame-pointer -ffast-math -funroll-loops #CXXFLAGS_OPT += -march=native CPPFLAGS += -DNDEBUG # TODO: check for SSE? CXXFLAGS += -pthread # TODO: this works for 64bit systems, check if it also works on 32bit CXXFLAGS += -fPIC # maybe for Mac OS X: #CFLAGS += -fno-common -no-cpp-precomp -fexceptions #CPPFLAGS += -DSSR_MEX_USE_DOUBLE # show many 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 += -Wlong-long #CXXFLAGS += -Wconversion #CXXFLAGS += -Wsign-conversion #CXXFLAGS += -Wshadow #CXXFLAGS += -Wold-style-cast #CXXFLAGS += -Winline PKG_CONFIG ?= pkg-config LDLIBS += `$(PKG_CONFIG) --libs $(LIBRARIES)` CPPFLAGS += `$(PKG_CONFIG) --cflags $(LIBRARIES)` CPPFLAGS += -I$(APF_DIR) -I$(SRC_DIR) OCTAVE_TARGETS := $(MEXFILES:%=%.octave) MATLAB_TARGETS := $(MEXFILES:%=%.matlab) OBJECTS := $(OBJECTS:%=%.o) CXXFLAGS += $(CXXFLAGS_OPT) # prevent make from deleting intermediate files: .SECONDARY: vpath %.cpp $(SRC_DIR) all: $(MAKE) octave $(MAKE) matlab octave: $(OCTAVE_TARGETS) matlab: $(MATLAB_TARGETS) # Rebuild objects if Makefile has changed: $(OBJECTS): Makefile # Targets have different extension depending on the platform. # To avoid finding out the extension, we use empty *.stamp files. $(OCTAVE_TARGETS) $(MATLAB_TARGETS): %: %.stamp STAMPFILES := $(OCTAVE_TARGETS:%=%.stamp) $(MATLAB_TARGETS:%=%.stamp) %.octave.stamp: %.cpp $(OBJECTS) | Makefile CXX="$(CXX)" CXXFLAGS="$(CXXFLAGS)" $(OCT) $(CPPFLAGS) $(LDLIBS) $< \ $(filter %.o, $^) @touch $@ # TODO: somehow add -O3 to linking step %.matlab.stamp: %.cpp $(OBJECTS) | Makefile $(MEX) $(CPPFLAGS) $(LDLIBS) CXX="$(CXX)" CXXFLAGS="$(CXXFLAGS)" \ CXXOPTIMFLAGS="$(CXXFLAGS_OPT)" LDOPTIMFLAGS="$(CXXFLAGS_OPT)" $< \ $(filter %.o, $^) @touch $@ # Reminder: to specify MEX file name: -output bla clean: $(RM) $(MEXFILES:%=%.mexa64) $(RM) $(MEXFILES:%=%.mexglx) $(RM) $(MEXFILES:%=%.mexmaci) $(RM) $(MEXFILES:%=%.mexmaci64) $(RM) $(MEXFILES:%=%.mex) $(RM) $(MEXFILES:%=%.o) # mkoctfile creates these $(RM) $(OBJECTS) $(RM) $(STAMPFILES) .PHONY: all octave matlab clean $(OCTAVE_TARGETS) $(MATLAB_TARGETS) .DELETE_ON_ERROR: DEPENDENCIES := $(OBJECTS) $(STAMPFILES) BINARY_EXTENSIONS := .o .octave.stamp .matlab.stamp include ../apf/misc/Makefile.dependencies ssr-0.4.2/mex/README.md000066400000000000000000000026031236416011200143710ustar00rootroot00000000000000Building the SSR as MEX-file for GNU Octave and MATLAB ====================================================== Compile for GNU Octave: make octave Compile for Matlab: make matlab By default, all available renderers are compiled, specific renderers can be selected with the variable MEXFILES, e.g. make octave MEXFILES="ssr_binaural ssr_wfs" Matlab comes with a very old default GCC, so you may have to create a symbolic link in `$MATLAB_DIR/sys/os/glnxa64/` pointing to `/usr/lib/x86_64-linux-gnu/libstdc++.so.6` (or similar). Remove all generated files: make clean Usage example: ``` octave inputblock = transpose(single([0 0 1 0 0 0 0 0])); sources = size(inputblock, 2); params.block_size = size(inputblock, 1); params.sample_rate = 44100; params.reproduction_setup = '../data/reproduction_setups/circle.asd'; params.threads = 4; ssr_nfc_hoa('init', sources, params) positions = [0; 2]; % one column for each source ssr_nfc_hoa('source_position', positions) % for more parameters see test_ssr.m % process (and discard) one block for interpolation: outputblock = ssr_nfc_hoa('process', single(zeros(params.block_size, sources))); % now the source parameters have reached their desired values outputblock = ssr_nfc_hoa('process', inputblock); % do something with 'outputblock' ... % repeat for each block ... ssr_nfc_hoa out_channels ssr_nfc_hoa clear ssr_nfc_hoa help ``` ssr-0.4.2/mex/ssr_aap.cpp000066400000000000000000000045411236416011200152510ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ // AAP renderer as MEX file for GNU Octave and MATLAB. #include "ssr_mex.h" #include "aaprenderer.h" SsrMex mexobj; void mexFunction(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[]) { mexobj.mexFunction(nlhs, plhs, nrhs, prhs); } // Settings for Vim (http://www.vim.org/), please do not remove: // vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent ssr-0.4.2/mex/ssr_binaural.cpp000066400000000000000000000045601236416011200163060ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ // Binaural renderer as MEX file for GNU Octave and MATLAB. #include "ssr_mex.h" #include "binauralrenderer.h" SsrMex mexobj; void mexFunction(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[]) { mexobj.mexFunction(nlhs, plhs, nrhs, prhs); } // Settings for Vim (http://www.vim.org/), please do not remove: // vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent ssr-0.4.2/mex/ssr_helper.m000066400000000000000000000014471236416011200154430ustar00rootroot00000000000000function out = ssr_helper(in, func) % Run the SSR block-wise on an input signal % 'func' is the SSR function, e.g. @ssr_nfc_hoa % 'func' must be initialized already block_size = func('block_size'); out_channels = func('out_channels'); in_channels = size(in, 2); % calculate (and discard) one block with empty input garbage = func('process', single(zeros(block_size, in_channels))); clear garbage % now all parameters are up-to-date signallength = size(in, 1); blocks = ceil(signallength/block_size); out = zeros(signallength, out_channels); for idx = 1:blocks-1 block_start = (idx-1) * block_size + 1; block_end = block_start + block_size - 1; inputblock = in(block_start:block_end, :); out(block_start:block_end, :) = func('process', inputblock); end % TODO: handle last block! end ssr-0.4.2/mex/ssr_mex.h000066400000000000000000000330431236416011200147450ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// SSR renderers as MEX file for GNU Octave and MATLAB. #ifndef SSR_MEX_H #define SSR_MEX_H #ifdef SSR_MEX_USE_DOUBLE #define APF_MIMOPROCESSOR_SAMPLE_TYPE double #else #define APF_MIMOPROCESSOR_SAMPLE_TYPE float #endif #include // for std::unique_ptr #include "apf/mextools.h" #include "apf/stringtools.h" #include "apf/pointer_policy.h" #include "apf/default_thread_policy.h" #include "../src/source.h" template class SsrMex { public: using sample_type = typename Renderer::sample_type; void mexFunction(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[]) { try { if (nrhs == 0) { _help(nlhs, plhs, nrhs, prhs); return; } std::string command; apf::mex::next_arg(nrhs, prhs, command , "First argument must be a string (e.g. 'help')!"); if (command == "help") { _help(nlhs, plhs, nrhs, prhs); } else if (command == "init") { _init(nlhs, plhs, nrhs, prhs); } else if (command == "block_size") { _error_init(); APF_MEX_ERROR_NO_FURTHER_INPUTS("'block_size'"); APF_MEX_ERROR_ONE_OPTIONAL_OUTPUT("'block_size'"); plhs[0] = mxCreateDoubleScalar(_block_size); } else if (command == "out_channels") { _error_init(); APF_MEX_ERROR_NO_FURTHER_INPUTS("'out_channels'"); APF_MEX_ERROR_ONE_OPTIONAL_OUTPUT("'out_channels'"); plhs[0] = mxCreateDoubleScalar(_out_channels); } // Only "clear" shall be documented, the others are hidden features else if (command == "free" || command == "delete" || command == "clear") { APF_MEX_ERROR_NO_FURTHER_INPUTS("'clear'"); APF_MEX_ERROR_NO_OUTPUT_SUPPORTED("'clear'"); // This is safe even if engine wasn't initialized before: _engine.reset(); } else { _chained_commands(command, nlhs, plhs, nrhs, prhs); } } catch (std::exception& e) { mexErrMsgTxt(e.what()); } catch (...) { mexErrMsgTxt("Unknown exception!"); } } private: void _help(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[]) { APF_MEX_ERROR_NO_OUTPUT_SUPPORTED("'help'"); APF_MEX_ERROR_NO_FURTHER_INPUTS("'help'"); mexPrintf("\n%1$s: SSR as MEX file\n\n" "...\n" "sub-commands: 'init', ...\n" "...\n" "\n" "'init'\n" "\n" "...\n" " sources = 4;\n" " params.sample_rate = 44100;\n" " params.block_size = 128;\n" " %1$s('init', sources, params)\n" "...\n" "TODO: write more help text!\n" "\n" , mexFunctionName()); } void _error_init() { if (!_engine) { mexErrMsgTxt("Not initialized, use 'init' first!"); } } void _init(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[]) { APF_MEX_ERROR_NO_OUTPUT_SUPPORTED("'init'"); apf::mex::next_arg(nrhs, prhs, _in_channels , "First argument to 'init' must be the number of sources!"); std::map options; apf::mex::next_arg(nrhs, prhs, options , "Second argument to 'init' must be a scalar structure!"); // Note: Fields are not checked, the SSR is supposed to do that. APF_MEX_ERROR_NO_FURTHER_INPUTS("'init'"); auto info = std::string("Starting the SSR with following settings:\n"); info += " * number of sources: "; info += apf::str::A2S(_in_channels); info += "\n"; for (auto it: options) { info += " * "; info += it.first; info += ": "; info += it.second; info += "\n"; } mexPrintf(info.c_str()); _engine.reset(new Renderer(apf::parameter_map(std::move(options)))); _block_size = _engine->block_size(); _engine->load_reproduction_setup(); _out_channels = _engine->get_output_list().size(); for (mwSize i = 0; i < _in_channels; ++i) { // TODO: specify ID? _engine->add_source(); } _inputs.resize(_in_channels); _outputs.resize(_out_channels); _engine->activate(); // start parallel processing (if threads > 1) mexPrintf("Initialization of %s completed, %d outputs available.\n" , _engine->name(), _out_channels); } void _chained_commands(const std::string& command , int& nlhs, mxArray**& plhs, int& nrhs, const mxArray**& prhs) { _error_init(); if (command == "source_position") { _source_position(nrhs, prhs); } else if (command == "source_orientation") { _source_orientation(nrhs, prhs); } else if (command == "source_mute") { _source_mute(nrhs, prhs); } else if (command == "source_model") { _source_model(nrhs, prhs); } else if (command == "reference_position") { _reference_position(nrhs, prhs); } else if (command == "reference_orientation") { _reference_orientation(nrhs, prhs); } else if (command == "process") { _process(nlhs, plhs, nrhs, prhs); } else { mexPrintf("Command: '%s'\n", command.c_str()); mexErrMsgTxt("Unknown command!"); } if (nrhs > 0) { std::string command; apf::mex::next_arg(nrhs, prhs, command , "Too many arguments (or missing command string)!"); _chained_commands(command, nlhs, plhs, nrhs, prhs); } if (nlhs != 0) { mexErrMsgTxt("Output argument(s) available but not needed!"); } } void _process(int& nlhs, mxArray**& plhs, int& nrhs, const mxArray**& prhs) { APF_MEX_ERROR_EXACTLY_ONE_OUTPUT("'process'!\n" "And 'process' can only be used once in a chained comand"); APF_MEX_ERROR_FURTHER_INPUT_NEEDED("'process'"); APF_MEX_ERROR_SAME_NUMBER_OF_ROWS(_block_size, "as block size"); APF_MEX_ERROR_SAME_NUMBER_OF_COLUMNS(_in_channels , "as number of sources"); APF_MEX_ERROR_REAL_INPUT("Argument to 'process'"); #ifdef SSR_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()); --nlhs; ++plhs; --nrhs; ++prhs; } void _source_position(int& nrhs, const mxArray**& prhs) { APF_MEX_ERROR_FURTHER_INPUT_NEEDED("'source_position'"); APF_MEX_ERROR_REAL_INPUT("Source positions"); APF_MEX_ERROR_SAME_NUMBER_OF_COLUMNS(_in_channels , "as number of sources"); if (mxGetM(prhs[0]) == 3) { mexErrMsgTxt("Three-dimensional positions are not supported (yet)!"); } if (mxGetM(prhs[0]) != 2) { mexErrMsgTxt("Number of rows must be 2 (x and y coordinates)!"); } double* coordinates = mxGetPr(prhs[0]); --nrhs; ++prhs; for (mwSize i = 0; i < _in_channels; ++i) { // TODO: handle 3D coordinates auto* source = _engine->get_source(i + 1); // TODO: check if source == nullptr source->position = Position(coordinates[i*2], coordinates[i*2+1]); } } void _source_orientation(int& nrhs, const mxArray**& prhs) { APF_MEX_ERROR_FURTHER_INPUT_NEEDED("'source_orientation'"); APF_MEX_ERROR_REAL_INPUT("Source orientations"); APF_MEX_ERROR_SAME_NUMBER_OF_COLUMNS(_in_channels , "as number of sources!"); if (mxGetM(prhs[0]) != 1) { mexErrMsgTxt("Last argument must be a row vector of angles!"); } double* angles = mxGetPr(prhs[0]); --nrhs; ++prhs; for (mwSize i = 0; i < _in_channels; ++i) { auto* source = _engine->get_source(i + 1); // TODO: check if source == nullptr source->orientation = Orientation(angles[i]); // degree } } void _source_mute(int& nrhs, const mxArray**& prhs) { APF_MEX_ERROR_FURTHER_INPUT_NEEDED("'source_mute'"); APF_MEX_ERROR_SAME_NUMBER_OF_COLUMNS(_in_channels , "as number of sources!"); if (!mxIsLogical(prhs[0])) { mexErrMsgTxt("Argument after 'source_mute' must be of logical type!"); } if (mxGetM(prhs[0]) != 1) { mexErrMsgTxt("Argument after 'source_mute' must be a row vector!"); } mxLogical* mute = mxGetLogicals(prhs[0]); --nrhs; ++prhs; for (mwSize i = 0; i < _in_channels; ++i) { auto* source = _engine->get_source(i + 1); source->mute = mute[i]; // logical } } void _source_model(int& nrhs, const mxArray**& prhs) { if (nrhs < _in_channels) { mexErrMsgTxt("Specify as many model strings as there are sources!"); } for (int i = 0; i < _in_channels; ++i) { std::string model_str; apf::mex::next_arg(nrhs, prhs, model_str, "All further arguments to " "'source_model' must be a valid source model strings!"); Source::model_t model = Source::unknown; if (!apf::str::S2A(model_str, model)) { mexPrintf("Model string '%s':", model_str.c_str()); mexErrMsgTxt("Couldn't convert source model string!"); } _engine->get_source(i + 1)->model = model; } } void _reference_position(int& nrhs, const mxArray**& prhs) { APF_MEX_ERROR_FURTHER_INPUT_NEEDED("'reference_position'"); APF_MEX_ERROR_REAL_INPUT("Reference position"); if (mxGetN(prhs[0]) != 1) { mexErrMsgTxt("Number of columns must be 1"); } if (mxGetM(prhs[0]) == 3) { mexErrMsgTxt("Three-dimensional positions are not supported (yet)!"); } if (mxGetM(prhs[0]) != 2) { mexErrMsgTxt("Number of rows must be 2 (x and y coordinates)!"); } double* coordinates = mxGetPr(prhs[0]); --nrhs; ++prhs; _engine->state.reference_position = Position(coordinates[0], coordinates[1]); } void _reference_orientation(int& nrhs, const mxArray**& prhs) { APF_MEX_ERROR_FURTHER_INPUT_NEEDED("'reference_orientation'"); APF_MEX_ERROR_REAL_INPUT("Reference orientation"); if (mxGetN(prhs[0]) != 1 || mxGetM(prhs[0]) != 1) { mexErrMsgTxt("Last argument must be a scalar"); } double* angle = mxGetPr(prhs[0]); --nrhs; ++prhs; _engine->state.reference_orientation = Orientation(*angle); } std::unique_ptr _engine; mwSize _in_channels, _out_channels, _block_size; std::vector _inputs, _outputs; }; #endif // Settings for Vim (http://www.vim.org/), please do not remove: // vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent ssr-0.4.2/mex/ssr_nfc_hoa.cpp000066400000000000000000000045531236416011200161100ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ // NFC-HOA renderer as MEX file for GNU Octave and MATLAB. #include "ssr_mex.h" #include "nfchoarenderer.h" SsrMex mexobj; void mexFunction(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[]) { mexobj.mexFunction(nlhs, plhs, nrhs, prhs); } // Settings for Vim (http://www.vim.org/), please do not remove: // vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent ssr-0.4.2/mex/ssr_vbap.cpp000066400000000000000000000045441236416011200154430ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ // VBAP renderer as MEX file for GNU Octave and MATLAB. #include "ssr_mex.h" #include "vbaprenderer.h" SsrMex mexobj; void mexFunction(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[]) { mexobj.mexFunction(nlhs, plhs, nrhs, prhs); } // Settings for Vim (http://www.vim.org/), please do not remove: // vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent ssr-0.4.2/mex/ssr_wfs.cpp000066400000000000000000000045411236416011200153070ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ // WFS renderer as MEX file for GNU Octave and MATLAB. #include "ssr_mex.h" #include "wfsrenderer.h" SsrMex mexobj; void mexFunction(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[]) { mexobj.mexFunction(nlhs, plhs, nrhs, prhs); } // Settings for Vim (http://www.vim.org/), please do not remove: // vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent ssr-0.4.2/mex/test_ssr.m000066400000000000000000000025431236416011200151410ustar00rootroot00000000000000% Script for testing the SSR MEX file infilename = 'input.wav'; outfilename = 'output.wav'; positions = [0; 2]; % one column for each input channel orientations = -90; % row vector of angles in degree mutes = false; % row vector of logicals models = { 'plane' }; % cell array of model strings reference_position = [-1; 0]; % one column vector reference_orientation = 90; % one angle in degree params.block_size = 1024; params.threads = 2; % only for loudspeaker renderers: params.reproduction_setup = '../data/reproduction_setups/circle.asd'; % only for binaural renderer: params.hrir_file = '../data/impulse_responses/hrirs/hrirs_fabian.wav'; % only for WFS renderer: params.prefilter_file = ... '../data/impulse_responses/wfs_prefilters/wfs_prefilter_120_1500_44100.wav'; if ~exist('ssr', 'var') ssr = @ssr_binaural; end [sig, params.sample_rate] = wavread(infilename); sig = single(sig); sources = size(sig, 2); ssr('init', sources, params) ssr('source_position', positions) ssr('source_orientation', orientations) ssr('source_mute', mutes) ssr('source_model', models{:}) ssr('reference_position', reference_position) ssr('reference_orientation', reference_orientation) out = ssr_helper(sig, ssr); assert(ssr('out_channels') == size(out, 2)) assert(ssr('block_size') == params.block_size) ssr('clear') wavwrite(out, params.sample_rate, outfilename); ssr-0.4.2/release.sh000077500000000000000000000023371236416011200143040ustar00rootroot00000000000000#!/bin/sh # This script automates the necessary steps for releasing the SSR. run_command() { # show message; run command; if unsuccessful, show error message and exit echo $0: Running \"$@\" ... "$@" || { status=$?; echo $0: Error in \"$@\"!; exit $status; } } # change to the directory where the script is located # (in case it was started from somewhere else) cd $(dirname $0) if [ "$(git symbolic-ref --short HEAD)" != master ] then echo \"$0\" should be called on the master branch!; exit 42; fi # first of all, make "tabula rasa" run_command ./cleanse.sh # prepare the build system run_command ./autogen.sh # create the user manual (cd doc/manual && run_command latexmk) || exit # prepare Makefiles run_command ./configure # enable parallel make if CONCURRENCY_LEVEL is defined if test ! -z $CONCURRENCY_LEVEL; then MAKE_OPTIONS=-j$CONCURRENCY_LEVEL fi # this must be run first, because "make distcheck" needs the executables to # generate the man pages with help2man: run_command make $MAKE_OPTIONS # create tarball and run some basic tests run_command make distcheck $MAKE_OPTIONS echo $0: Done! # Settings for Vim (http://www.vim.org/), please do not remove: # vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80 ssr-0.4.2/src/000077500000000000000000000000001236416011200131075ustar00rootroot00000000000000ssr-0.4.2/src/Doxyfile000066400000000000000000001646461236416011200146360ustar00rootroot00000000000000# Doxyfile 1.5.6 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for the SoundScape Renderer # # All text after a hash (#) is considered a comment and will be ignored # The format is: # TAG = value [value, ...] # For lists items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (" ") #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config file # that follow. The default is UTF-8 which is also the encoding used for all # text before the first occurrence of this tag. Doxygen uses libiconv (or the # iconv built into libc) for the transcoding. See # http://www.gnu.org/software/libiconv for the list of possible encodings. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded # by quotes) that should identify the project. PROJECT_NAME = "SoundScape Renderer (SSR)" # The PROJECT_NUMBER tag can be used to enter a project or revision number. # This could be handy for archiving the generated documentation or # if some version control system is used. PROJECT_NUMBER = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) # base path where the generated documentation will be put. # If a relative path is entered, it will be relative to the location # where doxygen was started. If left blank the current directory will be used. OUTPUT_DIRECTORY = ../doc/doxygen # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create # 4096 sub-directories (in 2 levels) under the output directory of each output # format and will distribute the generated files over these directories. # Enabling this option can be useful when feeding doxygen a huge amount of # source files, where putting all generated files in the same directory would # otherwise cause performance problems for the file system. CREATE_SUBDIRS = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # The default language is English, other supported languages are: # Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, # Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek, # Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages), # Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish, # Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, Swedish, # and Ukrainian. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will # include brief member descriptions after the members that are listed in # the file and class documentation (similar to JavaDoc). # Set to NO to disable this. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend # the brief description of a member or function before the detailed description. # Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator # that is used to form the text in various listings. Each string # in this list, if found as the leading text of the brief description, will be # stripped from the text and the result after processing the whole list, is # used as the annotated text. Otherwise, the brief description is used as-is. # If left blank, the following values are used ("$name" is automatically # replaced with the name of the entity): "The $name class" "The $name widget" # "The $name file" "is" "provides" "specifies" "contains" # "represents" "a" "an" "the" ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # Doxygen will generate a detailed section even if there is only a brief # description. ALWAYS_DETAILED_SEC = YES # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full # path before files name in the file list and in the header files. If set # to NO the shortest path that makes the file name unique will be used. FULL_PATH_NAMES = YES # If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag # can be used to strip a user-defined part of the path. Stripping is # only done if one of the specified strings matches the left-hand part of # the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the # path to strip. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of # the path mentioned in the documentation of a class, which tells # the reader which header file to include in order to use a class. # If left blank only the name of the header file containing the class # definition is used. Otherwise one should specify the include paths that # are normally passed to the compiler using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter # (but less readable) file names. This can be useful is your file systems # doesn't support long names like on DOS, Mac, or CD-ROM. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen # will interpret the first line (until the first dot) of a JavaDoc-style # comment as the brief description. If set to NO, the JavaDoc # comments will behave just like regular Qt-style comments # (thus requiring an explicit @brief command for a brief description.) JAVADOC_AUTOBRIEF = YES # If the QT_AUTOBRIEF tag is set to YES then Doxygen will # interpret the first line (until the first dot) of a Qt-style # comment as the brief description. If set to NO, the comments # will behave just like regular Qt-style comments (thus requiring # an explicit \brief command for a brief description.) QT_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen # treat a multi-line C++ special comment block (i.e. a block of //! or /// # comments) as a brief description. This used to be the default behaviour. # The new default is to treat a multi-line C++ comment block as a detailed # description. Set this tag to YES if you prefer the old behaviour instead. MULTILINE_CPP_IS_BRIEF = NO # If the DETAILS_AT_TOP tag is set to YES then Doxygen # will output the detailed description near the top, like JavaDoc. # If set to NO, the detailed description appears after the member # documentation. DETAILS_AT_TOP = NO # If the INHERIT_DOCS tag is set to YES (the default) then an undocumented # member inherits the documentation from any documented member that it # re-implements. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce # a new page for each member. If set to NO, the documentation of a member will # be part of the file/class/namespace that contains it. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. # Doxygen uses this value to replace tabs by spaces in code fragments. TAB_SIZE = 2 # This tag can be used to specify a number of aliases that acts # as commands in the documentation. An alias has the form "name=value". # For example adding "sideeffect=\par Side Effects:\n" will allow you to # put the command \sideeffect (or @sideeffect) in the documentation, which # will result in a user-defined paragraph with heading "Side Effects:". # You can put \n's in the value part of an alias to insert newlines. ALIASES = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C # sources only. Doxygen will then generate output that is more tailored for C. # For instance, some of the names that are used will be different. The list # of all members will be omitted, etc. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java # sources only. Doxygen will then generate output that is more tailored for # Java. For instance, namespaces will be presented as packages, qualified # scopes will look different, etc. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources only. Doxygen will then generate output that is more tailored for # Fortran. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for # VHDL. OPTIMIZE_OUTPUT_VHDL = NO # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should # set this tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); v.s. # func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. BUILTIN_STL_SUPPORT = YES # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. # Doxygen will parse them like normal C++ but will assume all classes use public # instead of private inheritance when no explicit protection keyword is present. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate getter # and setter methods for a property. Setting this option to YES (the default) # will make doxygen to replace the get and set methods by a property in the # documentation. This will only work if the methods are indeed getting or # setting a simple type. If this is not the case, or you want to show the # methods anyway, you should set this option to NO. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES, then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. DISTRIBUTE_GROUP_DOC = YES # Set the SUBGROUPING tag to YES (the default) to allow class member groups of # the same type (for instance a group of public functions) to be put as a # subgroup of that type (e.g. under the Public Functions section). Set it to # NO to prevent subgrouping. Alternatively, this can be done per class using # the \nosubgrouping command. SUBGROUPING = YES # When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum # is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically # be useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. TYPEDEF_HIDES_STRUCT = NO #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in # documentation are documented, even if no documentation was available. # Private class members and static file members will be hidden unless # the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES EXTRACT_ALL = NO # If the EXTRACT_PRIVATE tag is set to YES all private members of a class # will be included in the documentation. EXTRACT_PRIVATE = YES # If the EXTRACT_STATIC tag is set to YES all static members of a file # will be included in the documentation. EXTRACT_STATIC = YES # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) # defined locally in source files will be included in the documentation. # If set to NO only classes defined in header files are included. EXTRACT_LOCAL_CLASSES = YES # This flag is only useful for Objective-C code. When set to YES local # methods, which are defined in the implementation section but not in # the interface are included in the documentation. # If set to NO (the default) only methods in the interface are included. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base # name of the file that contains the anonymous namespace. By default # anonymous namespace are hidden. EXTRACT_ANON_NSPACES = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members of documented classes, files or namespaces. # If set to NO (the default) these members will be included in the # various overviews, but no documentation section is generated. # This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. # If set to NO (the default) these classes will be included in the various # overviews. This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all # friend (class|struct|union) declarations. # If set to NO (the default) these declarations will be included in the # documentation. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any # documentation blocks found inside the body of a function. # If set to NO (the default) these blocks will be appended to the # function's detailed documentation block. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation # that is typed after a \internal command is included. If the tag is set # to NO (the default) then the documentation will be excluded. # Set it to YES to include the internal documentation. INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate # file names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen # will show members with their full class and namespace scopes in the # documentation. If set to YES the scope will be hidden. HIDE_SCOPE_NAMES = NO # If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen # will put a list of the files that are included by a file in the documentation # of that file. SHOW_INCLUDE_FILES = YES # If the INLINE_INFO tag is set to YES (the default) then a tag [inline] # is inserted in the documentation for inline members. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen # will sort the (detailed) documentation of file and class members # alphabetically by member name. If set to NO the members will appear in # declaration order. SORT_MEMBER_DOCS = NO # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the # brief documentation of file, namespace and class members alphabetically # by member name. If set to NO (the default) the members will appear in # declaration order. SORT_BRIEF_DOCS = NO # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the # hierarchy of group names into alphabetical order. If set to NO (the default) # the group names will appear in their defined order. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be # sorted by fully-qualified names, including namespaces. If set to # NO (the default), the class list will be sorted only by class name, # not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the # alphabetical list. SORT_BY_SCOPE_NAME = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or # disable (NO) the todo list. This list is created by putting \todo # commands in the documentation. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable (YES) or # disable (NO) the test list. This list is created by putting \test # commands in the documentation. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or # disable (NO) the bug list. This list is created by putting \bug # commands in the documentation. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or # disable (NO) the deprecated list. This list is created by putting # \deprecated commands in the documentation. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional # documentation sections, marked by \if sectionname ... \endif. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines # the initial value of a variable or define consists of for it to appear in # the documentation. If the initializer consists of more lines than specified # here it will be hidden. Use a value of 0 to hide initializers completely. # The appearance of the initializer of individual variables and defines in the # documentation can be controlled using \showinitializer or \hideinitializer # command in the documentation regardless of this setting. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated # at the bottom of the documentation of classes and structs. If set to YES the # list will mention the files that were used to generate the documentation. SHOW_USED_FILES = YES # If the sources in your project are distributed over multiple directories # then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy # in the documentation. The default is NO. SHOW_DIRECTORIES = YES # Set the SHOW_FILES tag to NO to disable the generation of the Files page. # This will remove the Files entry from the Quick Index and from the # Folder Tree View (if specified). The default is YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the # Namespaces page. This will remove the Namespaces entry from the Quick Index # and from the Folder Tree View (if specified). The default is YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command , where is the value of # the FILE_VERSION_FILTER tag, and is the name of an input file # provided by doxygen. Whatever the program writes to standard output # is used as the file version. See the manual for examples. FILE_VERSION_FILTER = #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated # by doxygen. Possible values are YES and NO. If left blank NO is used. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are # generated by doxygen. Possible values are YES and NO. If left blank # NO is used. WARNINGS = YES # If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings # for undocumented members. If EXTRACT_ALL is set to YES then this flag will # automatically be disabled. WARN_IF_UNDOCUMENTED = YES # If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some # parameters in a documented function, or documenting parameters that # don't exist or using markup commands wrongly. WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be abled to get warnings for # functions that are documented, but have no documentation for their parameters # or return value. If set to NO (the default) doxygen will only warn about # wrong or incomplete parameter documentation, but not about the absence of # documentation. WARN_NO_PARAMDOC = YES # The WARN_FORMAT tag determines the format of the warning messages that # doxygen can produce. The string should contain the $file, $line, and $text # tags, which will be replaced by the file and line number from which the # warning originated and the warning text. Optionally the format may contain # $version, which will be replaced by the version of the file (if it could # be obtained via FILE_VERSION_FILTER) WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning # and error messages should be written. If left blank the output is written # to stderr. WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag can be used to specify the files and/or directories that contain # documented source files. You may enter file names like "myfile.cpp" or # directories like "/usr/src/myproject". Separate the files or directories # with spaces. INPUT = main.cpp . # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is # also the default input encoding. Doxygen uses libiconv (or the iconv built # into libc) for the transcoding. See http://www.gnu.org/software/libiconv for # the list of possible encodings. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank the following patterns are tested: # *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx # *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 FILE_PATTERNS = # The RECURSIVE tag can be used to turn specify whether or not subdirectories # should be searched for input files as well. Possible values are YES and NO. # If left blank NO is used. RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. EXCLUDE = netgui \ tests \ blockdelayline/test \ boost_test \ spare \ examples \ jack_status jack_transport \ vim_help bash_completion \ _template.cpp _template.h \ config.h # The EXCLUDE_SYMLINKS tag can be used select whether or not files or # directories that are symbolic links (a Unix filesystem feature) are excluded # from the input. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. Note that the wildcards are matched # against the file with absolute path, so to exclude all test directories # for example use the pattern */test/* EXCLUDE_PATTERNS = # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or # directories that contain example code fragments that are included (see # the \include command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank all files are included. EXAMPLE_PATTERNS = # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude # commands irrespective of the value of the RECURSIVE tag. # Possible values are YES and NO. If left blank NO is used. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or # directories that contain image that are included in the documentation (see # the \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command , where # is the value of the INPUT_FILTER tag, and is the name of an # input file. Doxygen will then use the output that the filter program writes # to standard output. If FILTER_PATTERNS is specified, this tag will be # ignored. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the # filter if there is a match. The filters are a list of the form: # pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further # info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER # is applied to all files. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will be used to filter the input files when producing source # files to browse (i.e. when SOURCE_BROWSER is set to YES). FILTER_SOURCE_FILES = NO #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will # be generated. Documented entities will be cross-referenced with these sources. # Note: To get rid of all source code in the generated output, make sure also # VERBATIM_HEADERS is set to NO. SOURCE_BROWSER = YES # Setting the INLINE_SOURCES tag to YES will include the body # of functions and classes directly in the documentation. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct # doxygen to hide any special comment blocks from generated source code # fragments. Normal C and C++ comments will always remain visible. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES (the default) # then for each documented function all documented # functions referencing it will be listed. REFERENCED_BY_RELATION = YES # If the REFERENCES_RELATION tag is set to YES (the default) # then for each documented function all documented entities # called/used by that function will be listed. REFERENCES_RELATION = YES # If the REFERENCES_LINK_SOURCE tag is set to YES (the default) # and SOURCE_BROWSER tag is set to YES, then the hyperlinks from # functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will # link to the source code. Otherwise they will link to the documentstion. REFERENCES_LINK_SOURCE = YES # If the USE_HTAGS tag is set to YES then the references to source code # will point to the HTML generated by the htags(1) tool instead of doxygen # built-in source browser. The htags tool is part of GNU's global source # tagging system (see http://www.gnu.org/software/global/global.html). You # will need version 4.8.6 or higher. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen # will generate a verbatim copy of the header file for each class for # which an include is specified. Set to NO to disable this. VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index # of all compounds will be generated. Enable this if the project # contains a lot of classes, structs, unions or interfaces. ALPHABETICAL_INDEX = NO # If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then # the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns # in which this list will be split (can be a number in the range [1..20]) COLS_IN_ALPHA_INDEX = 5 # In case all classes in a project start with a common prefix, all # classes will be put under the same header in the alphabetical index. # The IGNORE_PREFIX tag can be used to specify one or more prefixes that # should be ignored while generating the index headers. IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES (the default) Doxygen will # generate HTML output. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `html' will be used as the default path. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for # each generated HTML page (for example: .htm,.php,.asp). If it is left blank # doxygen will generate files with .html extension. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a personal HTML header for # each generated HTML page. If it is left blank doxygen will generate a # standard header. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a personal HTML footer for # each generated HTML page. If it is left blank doxygen will generate a # standard footer. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading # style sheet that is used by each HTML page. It can be used to # fine-tune the look of the HTML output. If the tag is left blank doxygen # will generate a default style sheet. Note that doxygen will try to copy # the style sheet file to the HTML output directory, so don't put your own # stylesheet in the HTML output directory as well, or it will be erased! HTML_STYLESHEET = # If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, # files or namespaces will be aligned in HTML using tables. If set to # NO a bullet list will be used. HTML_ALIGN_MEMBERS = YES # If the GENERATE_HTMLHELP tag is set to YES, additional index files # will be generated that can be used as input for tools like the # Microsoft HTML help workshop to generate a compiled HTML help file (.chm) # of the generated HTML documentation. GENERATE_HTMLHELP = NO # If the GENERATE_DOCSET tag is set to YES, additional index files # will be generated that can be used as input for Apple's Xcode 3 # integrated development environment, introduced with OSX 10.5 (Leopard). # To create a documentation set, doxygen will generate a Makefile in the # HTML output directory. Running make will produce the docset in that # directory and running "make install" will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find # it at startup. GENERATE_DOCSET = NO # When GENERATE_DOCSET tag is set to YES, this tag determines the name of the # feed. A documentation feed provides an umbrella under which multiple # documentation sets from a single provider (such as a company or product suite) # can be grouped. DOCSET_FEEDNAME = "Doxygen generated docs" # When GENERATE_DOCSET tag is set to YES, this tag specifies a string that # should uniquely identify the documentation set bundle. This should be a # reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen # will append .docset to the name. DOCSET_BUNDLE_ID = org.doxygen.Project # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. For this to work a browser that supports # JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox # Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). HTML_DYNAMIC_SECTIONS = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can # be used to specify the file name of the resulting .chm file. You # can add a path in front of the file if the result should not be # written to the html output directory. CHM_FILE = # If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can # be used to specify the location (absolute path including file name) of # the HTML help compiler (hhc.exe). If non-empty doxygen will try to run # the HTML help compiler on the generated index.hhp. HHC_LOCATION = # If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag # controls if a separate .chi index file is generated (YES) or that # it should be included in the master .chm file (NO). GENERATE_CHI = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING # is used to encode HtmlHelp index (hhk), content (hhc) and project file # content. CHM_INDEX_ENCODING = # If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag # controls whether a binary table of contents is generated (YES) or a # normal table of contents (NO) in the .chm file. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members # to the contents of the HTML help documentation and to the tree view. TOC_EXPAND = NO # The DISABLE_INDEX tag can be used to turn on/off the condensed index at # top of each HTML page. The value NO (the default) enables the index and # the value YES disables it. DISABLE_INDEX = NO # This tag can be used to set the number of enum values (range [1..20]) # that doxygen will group on one line in the generated HTML documentation. ENUM_VALUES_PER_LINE = 4 # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. # If the tag value is set to FRAME, a side panel will be generated # containing a tree-like index structure (just like the one that # is generated for HTML Help). For this to work a browser that supports # JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, # Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are # probably better off using the HTML help feature. Other possible values # for this tag are: HIERARCHIES, which will generate the Groups, Directories, # and Class Hiererachy pages using a tree view instead of an ordered list; # ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which # disables this behavior completely. For backwards compatibility with previous # releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE # respectively. GENERATE_TREEVIEW = NONE # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be # used to set the initial width (in pixels) of the frame in which the tree # is shown. TREEVIEW_WIDTH = 250 # Use this tag to change the font size of Latex formulas included # as images in the HTML documentation. The default is 10. Note that # when you change the font size after a successful doxygen run you need # to manually remove any form_*.png images from the HTML output directory # to force them to be regenerated. FORMULA_FONTSIZE = 10 #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- # If the GENERATE_LATEX tag is set to YES (the default) Doxygen will # generate Latex output. GENERATE_LATEX = NO # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `latex' will be used as the default path. LATEX_OUTPUT = latex # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be # invoked. If left blank `latex' will be used as the default command name. LATEX_CMD_NAME = latex # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to # generate index for LaTeX. If left blank `makeindex' will be used as the # default command name. MAKEINDEX_CMD_NAME = makeindex # If the COMPACT_LATEX tag is set to YES Doxygen generates more compact # LaTeX documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_LATEX = NO # The PAPER_TYPE tag can be used to set the paper type that is used # by the printer. Possible values are: a4, a4wide, letter, legal and # executive. If left blank a4wide will be used. PAPER_TYPE = a4wide # The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX # packages that should be included in the LaTeX output. EXTRA_PACKAGES = # The LATEX_HEADER tag can be used to specify a personal LaTeX header for # the generated latex document. The header should contain everything until # the first chapter. If it is left blank doxygen will generate a # standard header. Notice: only use this tag if you know what you are doing! LATEX_HEADER = # If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated # is prepared for conversion to pdf (using ps2pdf). The pdf file will # contain links (just like the HTML output) instead of page references # This makes the output suitable for online browsing using a pdf viewer. PDF_HYPERLINKS = NO # If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of # plain latex in the generated Makefile. Set this option to YES to get a # higher quality PDF documentation. USE_PDFLATEX = YES # If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. # command to the generated LaTeX files. This will instruct LaTeX to keep # running if errors occur, instead of asking the user for help. # This option is also used when generating formulas in HTML. LATEX_BATCHMODE = NO # If LATEX_HIDE_INDICES is set to YES then doxygen will not # include the index chapters (such as File Index, Compound Index, etc.) # in the output. LATEX_HIDE_INDICES = NO #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- # If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output # The RTF output is optimized for Word 97 and may not look very pretty with # other RTF readers or editors. GENERATE_RTF = NO # The RTF_OUTPUT tag is used to specify where the RTF docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `rtf' will be used as the default path. RTF_OUTPUT = rtf # If the COMPACT_RTF tag is set to YES Doxygen generates more compact # RTF documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_RTF = NO # If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated # will contain hyperlink fields. The RTF file will # contain links (just like the HTML output) instead of page references. # This makes the output suitable for online browsing using WORD or other # programs which support those fields. # Note: wordpad (write) and others do not support links. RTF_HYPERLINKS = NO # Load stylesheet definitions from file. Syntax is similar to doxygen's # config file, i.e. a series of assignments. You only have to provide # replacements, missing definitions are set to their default value. RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an rtf document. # Syntax is similar to doxygen's config file. RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- # If the GENERATE_MAN tag is set to YES (the default) Doxygen will # generate man pages GENERATE_MAN = NO # The MAN_OUTPUT tag is used to specify where the man pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `man' will be used as the default path. MAN_OUTPUT = man # The MAN_EXTENSION tag determines the extension that is added to # the generated man pages (default is the subroutine's section .3) MAN_EXTENSION = .3 # If the MAN_LINKS tag is set to YES and Doxygen generates man output, # then it will generate one additional man file for each entity # documented in the real man page(s). These additional files # only source the real man page, but without them the man command # would be unable to find the correct page. The default is NO. MAN_LINKS = NO #--------------------------------------------------------------------------- # configuration options related to the XML output #--------------------------------------------------------------------------- # If the GENERATE_XML tag is set to YES Doxygen will # generate an XML file that captures the structure of # the code including all documentation. GENERATE_XML = NO # The XML_OUTPUT tag is used to specify where the XML pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `xml' will be used as the default path. XML_OUTPUT = xml # The XML_SCHEMA tag can be used to specify an XML schema, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_SCHEMA = # The XML_DTD tag can be used to specify an XML DTD, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_DTD = # If the XML_PROGRAMLISTING tag is set to YES Doxygen will # dump the program listings (including syntax highlighting # and cross-referencing information) to the XML output. Note that # enabling this will significantly increase the size of the XML output. XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will # generate an AutoGen Definitions (see autogen.sf.net) file # that captures the structure of the code including all # documentation. Note that this feature is still experimental # and incomplete at the moment. GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # configuration options related to the Perl module output #--------------------------------------------------------------------------- # If the GENERATE_PERLMOD tag is set to YES Doxygen will # generate a Perl module file that captures the structure of # the code including all documentation. Note that this # feature is still experimental and incomplete at the # moment. GENERATE_PERLMOD = NO # If the PERLMOD_LATEX tag is set to YES Doxygen will generate # the necessary Makefile rules, Perl scripts and LaTeX code to be able # to generate PDF and DVI output from the Perl module output. PERLMOD_LATEX = NO # If the PERLMOD_PRETTY tag is set to YES the Perl module output will be # nicely formatted so it can be parsed by a human reader. This is useful # if you want to understand what is going on. On the other hand, if this # tag is set to NO the size of the Perl module output will be much smaller # and Perl will parse it just the same. PERLMOD_PRETTY = YES # The names of the make variables in the generated doxyrules.make file # are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. # This is useful so different doxyrules.make files included by the same # Makefile don't overwrite each other's variables. PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- # If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will # evaluate all C-preprocessor directives found in the sources and include # files. ENABLE_PREPROCESSING = YES # If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro # names in the source code. If set to NO (the default) only conditional # compilation will be performed. Macro expansion can be done in a controlled # way by setting EXPAND_ONLY_PREDEF to YES. MACRO_EXPANSION = YES # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES # then the macro expansion is limited to the macros specified with the # PREDEFINED and EXPAND_AS_DEFINED tags. EXPAND_ONLY_PREDEF = NO # If the SEARCH_INCLUDES tag is set to YES (the default) the includes files # in the INCLUDE_PATH (see below) will be search if a #include is found. SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by # the preprocessor. INCLUDE_PATH = # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the # directories. If left blank, the patterns specified with FILE_PATTERNS will # be used. INCLUDE_FILE_PATTERNS = # The PREDEFINED tag can be used to specify one or more macro names that # are defined before the preprocessor is started (similar to the -D option of # gcc). The argument of the tag is a list of macros of the form: name # or name=definition (no spaces). If the definition and the = are # omitted =1 is assumed. To prevent a macro definition from being # undefined via #undef or recursively expanded use the := operator # instead of the = operator. PREDEFINED = # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then # this tag can be used to specify a list of macro names that should be expanded. # The macro definition that is found in the sources will be used. # Use the PREDEFINED tag if you want to use a different macro definition. EXPAND_AS_DEFINED = # If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then # doxygen's preprocessor will remove all function-like macros that are alone # on a line, have an all uppercase name, and do not end with a semicolon. Such # function macros are typically used for boiler-plate code, and will confuse # the parser if not removed. SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- # Configuration::additions related to external references #--------------------------------------------------------------------------- # The TAGFILES option can be used to specify one or more tagfiles. # Optionally an initial location of the external documentation # can be added for each tagfile. The format of a tag file without # this location is as follows: # TAGFILES = file1 file2 ... # Adding location for the tag files is done as follows: # TAGFILES = file1=loc1 "file2 = loc2" ... # where "loc1" and "loc2" can be relative or absolute paths or # URLs. If a location is present for each tag, the installdox tool # does not have to be run to correct the links. # Note that each tag file must have a unique name # (where the name does NOT include the path) # If a tag file is not located in the directory in which doxygen # is run, you must also specify the path to the tagfile here. TAGFILES = # When a file name is specified after GENERATE_TAGFILE, doxygen will create # a tag file that is based on the input files it reads. GENERATE_TAGFILE = # If the ALLEXTERNALS tag is set to YES all external classes will be listed # in the class index. If set to NO only the inherited external classes # will be listed. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed # in the modules index. If set to NO, only the current project's groups will # be listed. EXTERNAL_GROUPS = YES # The PERL_PATH should be the absolute path and name of the perl script # interpreter (i.e. the result of `which perl'). PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- # If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will # generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base # or super classes. Setting the tag to NO turns the diagrams off. Note that # this option is superseded by the HAVE_DOT option below. This is only a # fallback. It is recommended to install and use dot, since it yields more # powerful graphs. CLASS_DIAGRAMS = YES # You can define message sequence charts within doxygen comments using the \msc # command. Doxygen will then run the mscgen tool (see # http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the # documentation. The MSCGEN_PATH tag allows you to specify the directory where # the mscgen tool resides. If left empty the tool is assumed to be found in the # default search path. MSCGEN_PATH = # If set to YES, the inheritance and collaboration graphs will hide # inheritance and usage relations if the target is undocumented # or is not a class. HIDE_UNDOC_RELATIONS = YES # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz, a graph visualization # toolkit from AT&T and Lucent Bell Labs. The other options in this section # have no effect if this option is set to NO (the default) HAVE_DOT = YES # By default doxygen will write a font called FreeSans.ttf to the output # directory and reference it in all dot files that doxygen generates. This # font does not include all possible unicode characters however, so when you need # these (or just want a differently looking font) you can specify the font name # using DOT_FONTNAME. You need need to make sure dot is able to find the font, # which can be done by putting it in a standard location or by setting the # DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory # containing the font. DOT_FONTNAME = FreeSans # By default doxygen will tell dot to use the output directory to look for the # FreeSans.ttf font (which doxygen will put there itself). If you specify a # different font using DOT_FONTNAME you can set the path where dot # can find it using this tag. DOT_FONTPATH = # If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect inheritance relations. Setting this tag to YES will force the # the CLASS_DIAGRAMS tag to NO. CLASS_GRAPH = YES # If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect implementation dependencies (inheritance, containment, and # class references variables) of the class with other documented classes. COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen # will generate a graph for groups, showing the direct groups dependencies GROUP_GRAPHS = YES # If the UML_LOOK tag is set to YES doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. UML_LOOK = NO # If set to YES, the inheritance and collaboration graphs will show the # relations between templates and their instances. TEMPLATE_RELATIONS = YES # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT # tags are set to YES then doxygen will generate a graph for each documented # file showing the direct and indirect include dependencies of the file with # other documented files. INCLUDE_GRAPH = YES # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and # HAVE_DOT tags are set to YES then doxygen will generate a graph for each # documented header file showing the documented files that directly or # indirectly include this file. INCLUDED_BY_GRAPH = YES # If the CALL_GRAPH and HAVE_DOT options are set to YES then # doxygen will generate a call dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable call graphs # for selected functions only using the \callgraph command. CALL_GRAPH = NO # If the CALLER_GRAPH and HAVE_DOT tags are set to YES then # doxygen will generate a caller dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable caller # graphs for selected functions only using the \callergraph command. CALLER_GRAPH = NO # If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen # will graphical hierarchy of all classes instead of a textual one. GRAPHICAL_HIERARCHY = YES # If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES # then doxygen will show the dependencies a directory has on other directories # in a graphical way. The dependency relations are determined by the #include # relations between the files in the directories. DIRECTORY_GRAPH = YES # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. Possible values are png, jpg, or gif # If left blank png will be used. DOT_IMAGE_FORMAT = png # The tag DOT_PATH can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found in the path. DOT_PATH = # The DOTFILE_DIRS tag can be used to specify one or more directories that # contain dot files that are included in the documentation (see the # \dotfile command). DOTFILE_DIRS = # The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of # nodes that will be shown in the graph. If the number of nodes in a graph # becomes larger than this value, doxygen will truncate the graph, which is # visualized by representing a node as a red box. Note that doxygen if the # number of direct children of the root node in a graph is already larger than # DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note # that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. DOT_GRAPH_MAX_NODES = 50 # The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the # graphs generated by dot. A depth value of 3 means that only nodes reachable # from the root by following a path via at most 3 edges will be shown. Nodes # that lay further from the root node will be omitted. Note that setting this # option to 1 or 2 may greatly reduce the computation time needed for large # code bases. Also note that the size of a graph can be further restricted by # DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. MAX_DOT_GRAPH_DEPTH = 0 # Set the DOT_TRANSPARENT tag to YES to generate images with a transparent # background. This is enabled by default, which results in a transparent # background. Warning: Depending on the platform used, enabling this option # may lead to badly anti-aliased labels on the edges of a graph (i.e. they # become hard to read). DOT_TRANSPARENT = YES # Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) # support this, this feature is disabled by default. DOT_MULTI_TARGETS = YES # If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will # generate a legend page explaining the meaning of the various boxes and # arrows in the dot generated graphs. GENERATE_LEGEND = YES # If the DOT_CLEANUP tag is set to YES (the default) Doxygen will # remove the intermediate dot files that are used to generate # the various graphs. DOT_CLEANUP = YES #--------------------------------------------------------------------------- # Configuration::additions related to the search engine #--------------------------------------------------------------------------- # The SEARCHENGINE tag specifies whether or not a search engine should be # used. If set to NO the values of all tags below this one will be ignored. SEARCHENGINE = NO ssr-0.4.2/src/Makefile.am000066400000000000000000000145401236416011200151470ustar00rootroot00000000000000## This file will be processed by automake (which is called by autogen.sh) to ## generate Makefile.in, which in turn will be processed by configure to ## generate Makefile. ## comments starting with a single # are copied to Makefile.in (and afterwards ## to Makefile), comments with ## are dropped. ## See configure.ac bin_PROGRAMS = $(SSR_executables) ## All possible optional programs must be listed here EXTRA_PROGRAMS = ssr-binaural ssr-wfs ssr-generic ssr-brs ssr-nfc-hoa ssr-vbap ssr-aap ## CPPFLAGS: preprocessor flags, e.g. -I and -D ## -I., -I$(srcdir), and a -I pointing to the directory holding config.h ## are separately provided by Automake (disable with "nostdinc") ## for CFLAGS vs. AM_CFLAGS vs. mumble_CFLAGS read this (highly recommended!): ## http://www.gnu.org/software/automake/manual/html_node/Flag-Variables-Ordering.html AM_CPPFLAGS = ## Add Audio Processing Framework (APF) path AM_CPPFLAGS += -I$(srcdir)/../apf if ENABLE_APP_BUNDLE AM_CPPFLAGS += -DSSR_DATA_DIR=\"SoundScapeRenderer.app/Contents/Resources\" else AM_CPPFLAGS += -DSSR_DATA_DIR=\"$(pkgdatadir)\" endif ## this is needed for multi-threaded programs AM_CPPFLAGS += -D_REENTRANT ## this is somehow needed for Solaris (or not?) AM_CPPFLAGS += -D_POSIX_PTHREAD_SEMANTICS ## C++ compiler flags which are used for everything AM_CXXFLAGS = $(WARNING_FLAGS) $(PKG_FLAGS) $(OPT_FLAGS) $(DEBUGGING_FLAGS) # This is also passed to doxygen DOXYGEN_DOC_DIR = ../doc/doxygen # files which should be distributed but not installed dist_noinst_DATA = Doxyfile coding_style.txt ssr_binaural_SOURCES = ssr_binaural.cpp binauralrenderer.h \ $(SSRSOURCES) nodist_ssr_binaural_SOURCES = $(SSRMOCFILES) ssr_wfs_SOURCES = ssr_wfs.cpp wfsrenderer.h \ $(LOUDSPEAKERSOURCES) \ $(SSRSOURCES) nodist_ssr_wfs_SOURCES = $(SSRMOCFILES) ssr_generic_SOURCES = ssr_generic.cpp genericrenderer.h \ $(LOUDSPEAKERSOURCES) \ $(SSRSOURCES) nodist_ssr_generic_SOURCES = $(SSRMOCFILES) ssr_vbap_SOURCES = ssr_vbap.cpp vbaprenderer.h \ $(LOUDSPEAKERSOURCES) \ $(SSRSOURCES) nodist_ssr_vbap_SOURCES = $(SSRMOCFILES) ssr_aap_SOURCES = ssr_aap.cpp aaprenderer.h \ $(LOUDSPEAKERSOURCES) \ $(SSRSOURCES) nodist_ssr_aap_SOURCES = $(SSRMOCFILES) ssr_brs_SOURCES = ssr_brs.cpp brsrenderer.h \ $(SSRSOURCES) nodist_ssr_brs_SOURCES = $(SSRMOCFILES) ssr_nfc_hoa_SOURCES = ssr_nfc_hoa.cpp nfchoarenderer.h \ hoacoefficients.h laplace_coeffs_double.h laplace_coeffs_float.h \ ../apf/apf/biquad.h \ ../apf/apf/denormalprevention.h \ $(LOUDSPEAKERSOURCES) \ $(SSRSOURCES) nodist_ssr_nfc_hoa_SOURCES = $(SSRMOCFILES) LOUDSPEAKERSOURCES = \ loudspeakerrenderer.h \ loudspeaker.h SSRSOURCES = \ ../apf/apf/misc.h \ ../apf/apf/jackclient.h \ ../apf/apf/lockfreefifo.h \ ../apf/apf/math.h \ ../apf/apf/parameter_map.h \ ../apf/apf/stringtools.h \ ../apf/apf/posix_thread_policy.h \ ../apf/apf/jack_policy.h \ ../apf/apf/pointer_policy.h \ ../apf/apf/iterator.h \ ../apf/apf/mimoprocessor.h \ ../apf/apf/commandqueue.h \ ../apf/apf/rtlist.h \ ../apf/apf/shareddata.h \ ../apf/apf/container.h \ ../apf/apf/convolver.h \ ../apf/apf/blockdelayline.h \ ../apf/apf/fftwtools.h \ ../apf/apf/sndfiletools.h \ ../apf/apf/combine_channels.h \ configuration.cpp \ configuration.h \ controller.h \ directionalpoint.cpp \ directionalpoint.h \ maptools.h \ orientation.cpp \ orientation.h \ position.cpp \ position.h \ posixpathtools.h \ publisher.h \ rendererbase.h \ scene.cpp \ scene.h \ source.h \ ssr_global.cpp \ ssr_global.h \ subscriber.h \ timetools.h \ tracker.h \ xmlparser.cpp \ xmlparser.h \ rendersubscriber.h if ENABLE_INTERSENSE SSRSOURCES += trackerintersense.cpp trackerintersense.h endif if ENABLE_POLHEMUS SSRSOURCES += trackerpolhemus.cpp trackerpolhemus.h endif if ENABLE_RAZOR SSRSOURCES += trackerrazor.cpp trackerrazor.h SSRSOURCES += razor-ahrs/RazorAHRS.cpp razor-ahrs/RazorAHRS.h dist_noinst_DATA += \ razor-ahrs/README.txt \ razor-ahrs/GPL.txt \ razor-ahrs/Example.cpp endif if ENABLE_VRPN SSRSOURCES += trackervrpn.cpp trackervrpn.h endif if ENABLE_ECASOUND SSRSOURCES += \ audioplayer.cpp \ audioplayer.h \ audiorecorder.cpp \ audiorecorder.h endif if ENABLE_IP_INTERFACE AM_CPPFLAGS += -I$(srcdir)/boostnetwork SSRSOURCES += \ boostnetwork/commandparser.cpp \ boostnetwork/commandparser.h \ boostnetwork/connection.cpp \ boostnetwork/connection.h \ boostnetwork/networksubscriber.cpp \ boostnetwork/networksubscriber.h \ boostnetwork/server.cpp \ boostnetwork/server.h endif if ENABLE_GUI AM_CPPFLAGS += -I$(srcdir)/gui SSRSOURCES += \ gui/qclicktextlabel.cpp \ gui/qclicktextlabel.h \ gui/qcpulabel.cpp \ gui/qcpulabel.h \ gui/qfilemenulabel.cpp \ gui/qfilemenulabel.h \ gui/qgui.cpp \ gui/qguiframe.cpp \ gui/qguiframe.h \ gui/qgui.h \ gui/qopenglplotter.cpp \ gui/qopenglplotter.h \ gui/qscenebutton.cpp \ gui/qscenebutton.h \ gui/qsourceproperties.cpp \ gui/qsourceproperties.h \ gui/qssrtimeline.cpp \ gui/qssrtimeline.h \ gui/qtimeedit.cpp \ gui/qtimeedit.h \ gui/quserinterface.cpp \ gui/quserinterface.h \ gui/qvolumeslider.cpp \ gui/qvolumeslider.h \ gui/qzoomlabel.cpp \ gui/qzoomlabel.h ## moc-files should not go to the tarball because of possible version ## incompatibilities of Qt. SSRMOCFILES = \ gui/qclicktextlabel_moc.cpp \ gui/qcpulabel_moc.cpp \ gui/qfilemenulabel_moc.cpp \ gui/qgui_moc.cpp \ gui/qguiframe_moc.cpp \ gui/qopenglplotter_moc.cpp \ gui/qscenebutton_moc.cpp \ gui/qsourceproperties_moc.cpp \ gui/qssrtimeline_moc.cpp \ gui/qtimeedit_moc.cpp \ gui/quserinterface_moc.cpp \ gui/qvolumeslider_moc.cpp \ gui/qzoomlabel_moc.cpp SUFFIXES = _moc.cpp .h_moc.cpp: @test -d gui || $(MKDIR_P) gui @QTMOC@ @MOCFLAGS@ -I$(srcdir)/gui -o $@ $< endif doc: @echo "Running doxygen..." @test -f Doxyfile || (echo \"Doxyfile\" not found! && false) @( cat Doxyfile \ ; echo PROJECT_NUMBER = '"Version @PACKAGE_VERSION@"'\ ; echo OUTPUT_DIRECTORY = $(DOXYGEN_DOC_DIR)\ ) \ | doxygen - > /dev/null # discard stdout, only display errors .PHONY: doc ## these links won't work on VPATH builds, but we don't care all-local: cd ../data && for prog in $(bin_PROGRAMS) ; do \ $(RM) $$prog ; $(LN_S) local_ssr.sh $$prog ; done clean-local: $(RM) gui/*_moc.cpp $(RM) -r $(DOXYGEN_DOC_DIR) cd ../data && for prog in $(bin_PROGRAMS) ; do $(RM) $$prog ; done ## Settings for Vim (http://www.vim.org/), please do not remove: ## vim:textwidth=80:comments+=bO\:## ssr-0.4.2/src/aaprenderer.h000066400000000000000000000213431236416011200155530ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Ambisonics Amplitude Panning renderer. #ifndef SSR_AAPRENDERER_H #define SSR_AAPRENDERER_H #include "ssr_global.h" #include "loudspeakerrenderer.h" #include "apf/combine_channels.h" namespace ssr { class AapRenderer : public SourceToOutput { private: using _base = SourceToOutput; public: static const char* name() { return "AAP-Renderer"; } class Source; class SourceChannel; class Output; class RenderFunction; explicit AapRenderer(const apf::parameter_map& params) : _base(params) , _ambisonics_order(params.get("ambisonics_order", 0)) , _in_phase_rendering(params.get("in_phase", true)) { VERBOSE((_in_phase_rendering ? "U" : "Not u") << "sing in-phase rendering."); } APF_PROCESS(AapRenderer, _base) { _process_list(_source_list); } void load_reproduction_setup(); private: int _ambisonics_order; bool _in_phase_rendering; }; class AapRenderer::Source : public _base::Source { public: Source(const Params& p) : _base::Source(p, p.parent->get_output_list().size(), this) {} bool get_output_levels(sample_type* first, sample_type* last) const; }; class AapRenderer::SourceChannel { public: explicit SourceChannel(const Source* s) : source(*s) {} const Source& source; using iterator = decltype(source.begin()); iterator begin() const { return source.begin(); } iterator end() const { return source.end(); } apf::BlockParameter stored_weight; }; bool AapRenderer::Source::get_output_levels(sample_type* first , sample_type* last) const { assert( static_cast(std::distance(first, last)) == this->sourcechannels.size()); (void)last; for (const auto& channel: this->sourcechannels) { *first = channel.stored_weight; ++first; } return true; } class AapRenderer::RenderFunction { public: RenderFunction(const Output& out) : _out(out) {} apf::CombineChannelsResult::type select(SourceChannel& in); sample_type operator()(sample_type in) { return in * _weight; } sample_type operator()(sample_type in, sample_type index) { return in * _interpolator(index); } private: sample_type _weight; apf::math::linear_interpolator _interpolator; const Output& _out; }; class AapRenderer::Output : public _base::Output { public: Output(const Params& p) : _base::Output(p) , _combiner(this->sourcechannels, this->buffer) { // TODO: add delay line (in some base class?) // TODO: amplitude correction for misplaced loudspeakers? //_weight = loudspeaker_distance / farthest_loudspeaker_distance; } APF_PROCESS(Output, _base::Output) { _combiner.process(RenderFunction(*this)); } private: apf::CombineChannelsInterpolation, buffer_type> _combiner; }; void AapRenderer::load_reproduction_setup() { // TODO: find a way to avoid overwriting load_reproduction_setup() _base::load_reproduction_setup(); // TODO: avoid code duplication between VBAP and AAP renderers! // TODO: move some stuff to base class? // TODO: check somehow if loudspeaker setup is reasonable? // TODO: get loudspeaker delays from setup? // delay_samples = size_t(delay * sample_rate + 0.5f) int normal_loudspeakers = 0; for (const auto& out: rtlist_proxy(this->get_output_list())) { if (out.model == Loudspeaker::subwoofer) { // TODO: something } else // loudspeaker type == normal { ++normal_loudspeakers; } } if (normal_loudspeakers < 1) { throw std::logic_error("No loudspeakers found!"); } if (!_ambisonics_order) { _ambisonics_order = (normal_loudspeakers - 1) / 2; } assert(_ambisonics_order > 0); VERBOSE("Using Ambisonics order " << _ambisonics_order << "."); // TODO: more things? } apf::CombineChannelsResult::type AapRenderer::RenderFunction::select(SourceChannel& in) { // TODO: take loudspeaker weight into account (for misplaced loudspeakers)? using apf::math::deg2rad; float two_times_order = 2 * _out.parent._ambisonics_order; auto weighting_factor = sample_type(); if (_out.model == Loudspeaker::normal) { // WARNING: The reference offset is currently broken! float alpha_0 = deg2rad((_out.position).orientation().azimuth); float theta_pw = deg2rad(((in.source.position - _out.parent.state.reference_position).orientation() - _out.parent.state.reference_orientation).azimuth); // TODO: wrap angles? if (_out.parent._in_phase_rendering) { weighting_factor = std::pow(std::cos((alpha_0 - theta_pw) / 2) , two_times_order); } else { // check numerical stability if (std::abs(std::sin((alpha_0 - theta_pw) / 2)) < 0.0001f) { weighting_factor = 1; } else { weighting_factor = std::sin((two_times_order + 1) * (alpha_0 - theta_pw) / 2) / ((two_times_order + 1) * std::sin((alpha_0 - theta_pw) / 2)); } } } else { // TODO: subwoofer gets weighting factor 1.0? weighting_factor = 1; } // TODO: centralize distance attenuation // no distance attenuation for plane waves if (in.source.model == ::Source::plane) { auto ampl_ref = _out.parent.state.amplitude_reference_distance; weighting_factor *= 0.5f / ampl_ref; // 1/r //weighting_factor *= 0.25f / sqrt(ampl_ref); // 1/sqrt(r) } else { auto source_distance = (in.source.position - _out.parent.state.reference_position).length(); // no volume increase for sources closer than 0.5m to reference position source_distance = std::max(source_distance, 0.5f); weighting_factor *= 0.5f / source_distance; // 1/r //weighting_factor *= 0.25f / sqrt(source_distance); // 1/sqrt(r) } // Apply source volume, mute, ... weighting_factor *= in.source.weighting_factor; in.stored_weight = weighting_factor; auto old_weight = in.stored_weight.old(); using namespace apf::CombineChannelsResult; if (old_weight == 0 && weighting_factor == 0) { return nothing; } else if (old_weight == weighting_factor) { _weight = weighting_factor; return constant; } else { _interpolator.set(old_weight, weighting_factor, _out.parent.block_size()); return change; } } } // namespace ssr #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'\:'=' ssr-0.4.2/src/audioplayer.cpp000066400000000000000000000263261236416011200161420ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Audio player using ecasound (implementation). #include // for jack_client_name_size() #include #include "audioplayer.h" #include "maptools.h" #include "ssr_global.h" #include "apf/stringtools.h" #include "posixpathtools.h" using maptools::get_item; /// delete the file map. AudioPlayer::~AudioPlayer() { if (!_file_map.empty()) { maptools::purge(_file_map); VERBOSE2("AudioPlayer dtor: file map deleted."); } else { VERBOSE2("AudioPlayer dtor."); } } /** _. * If the file is already opened, an existing instance of ecasound is used. * @param audio_file_name name of the audio file, what did you think? * @param channel select a channel of a multichannel file (starting with 1) * @param loop temporary solution for a loop mode * @return Name of the JACK port. * @warning If @a audio_file_name uses symbolic links or such things it can * happen that one file is opened several times. **/ std::string AudioPlayer::get_port_name(const std::string& audio_file_name, int channel, bool loop) { assert(channel >= 0); auto registered_file = get_item(_file_map, audio_file_name); if (registered_file != nullptr) { VERBOSE2("AudioPlayer: Input file '" + audio_file_name + "' already registered."); if (channel > registered_file->get_channels()) { ERROR("AudioPlayer: Channel " << channel << " doesn't exist in '" + audio_file_name + "'!"); return ""; } } else // file not yet registered { auto temp = Soundfile::create(audio_file_name, loop); if (!temp) { WARNING("AudioPlayer: Initialization of soundfile '" + audio_file_name + "' failed!"); return ""; } if (channel > temp->get_channels()) { ERROR("AudioPlayer: Channel " << channel << " doesn't exist in '" + audio_file_name + "'!"); // if wrong channel is requested, audiofile is not registered. return ""; } registered_file = temp.get(); _file_map[audio_file_name] = temp.release(); } return registered_file->get_client_name() + ":" + registered_file->output_prefix + "_" + apf::str::A2S(channel); } /** _. * @param audio_file_name the audio file you want to know the length of. * @warning If the file wasn't loaded before, 0 is returned! **/ long int AudioPlayer::get_file_length(const std::string& audio_file_name) const { const Soundfile* const file = get_item(_file_map, audio_file_name); return file ? file->get_length() : 0; } std::string AudioPlayer::Soundfile::get_format(const std::string& filename , size_t& channels, size_t& sample_rate) { ECA_CONTROL_INTERFACE eca; eca.command("cs-add dummy_chainsetup"); eca.command("c-add dummy_chain"); eca.command("ai-add sndfile," + posixpathtools::get_escaped_filename(filename)); eca.command("ao-add null"); eca.command("cs-connect"); if (eca.error()) { throw soundfile_error("get_format(): " + eca.last_error()); } eca.command("ai-index-select 1"); eca.command("ai-get-format"); std::string str = eca.last_string(); eca.command("cs-disconnect"); eca.command("c-remove"); eca.command("cs-remove"); std::replace(str.begin(), str.end(), ',', ' '); std::istringstream iss(str); std::string format; iss >> format >> channels >> sample_rate; if (iss.fail()) { throw soundfile_error("Couldn't convert format string!"); } assert(sample_rate >= 1); return format; } size_t AudioPlayer::Soundfile::_get_jack_sample_rate() { ECA_CONTROL_INTERFACE eca; eca.command("cs-add dummy_chainsetup"); eca.command("c-add dummy_chain"); eca.command("ai-add jack"); eca.command("ao-add null"); eca.command("cs-connect"); if (eca.error()) { throw soundfile_error("_get_jack_sample_rate(): " + eca.last_error()); } eca.command("ai-get-format"); std::string str = eca.last_string(); eca.command("cs-disconnect"); eca.command("c-remove"); eca.command("cs-remove"); std::replace(str.begin(), str.end(), ',', ' '); std::istringstream iss(str); std::string format; size_t channels, sample_rate; iss >> format >> channels >> sample_rate; if (iss.fail()) { throw soundfile_error("Couldn't convert string for getting sample rate!"); } assert(sample_rate >= 1); return sample_rate; } /** ctor. * @param filename name of soundfile * @param loop enable loop mode * @param prefix prefix used for channel names * @throw soundfile_error * @attention The soundfile must have the same sampling rate as the JACK server. * @warning If the name of the soundfile (including path) is longer than the * maximum allowed JACK client-name, it gets truncated. But if another file is * truncated to the same client-name, ecasound probably wants to add "_2" to * distinguish them but this will then be longer than the allowed length. * \par * UPDATE: This is already taken care of, but only up to "_99". So if you have * more than 99 equal client-names, you will get a problem. Hopefully, you * don't have that many ... **/ AudioPlayer::Soundfile::Soundfile(const std::string& filename, bool loop, const std::string& prefix) throw (soundfile_error) : output_prefix(prefix), _filename(filename), _client_name(""), _channels(0) { _sample_format = get_format(_filename, _channels, _sample_rate); size_t jack_sample_rate = _get_jack_sample_rate(); _eca.command("cs-add real_chainsetup"); _eca.command("c-add real_chain"); _eca.command("cs-set-audio-format ," + apf::str::A2S(_channels) + "," + apf::str::A2S(jack_sample_rate)); std::string ai_add = "ai-add "; if (loop) { ai_add += "audioloop,"; } if (_sample_rate != jack_sample_rate) { WARNING("'" + _filename + "' has a different sample rate than JACK! (" + apf::str::A2S(_sample_rate) + " vs. " + apf::str::A2S(jack_sample_rate) + ")"); ai_add += "resample-hq,auto,"; } ai_add += "sndfile,"; ai_add += posixpathtools::get_escaped_filename(filename); _eca.command(ai_add); _eca.command("ao-add jack_generic," + this->output_prefix); // check if filename is too long for a JACK portname. // if yes, truncate filename (keep the end) int max_size = jack_client_name_size(); // #include #ifdef __APPLE__ // TODO this is a workaround, and might not work on every system or with every version of jack. // on OS X jack_client_name_size() returns 64, but 52 seems to be the max supported size. //_client_name = "a123456789a123456789a123456789a123456789a123456789a"; // length = 51 + \0 = 52 max_size = 52; #endif max_size--; // max_size includes the terminating \0 character! max_size -= 3; // to allow ecasound to append a number up to "_99" max_size--; // we will add a special character at the beginning (maybe '['?) _client_name = _filename; assert(max_size >= 0); if (_filename.size() > static_cast(max_size)) { _client_name = _filename.substr(_filename.size() - max_size); // to visualize the truncation _client_name[0] = '<'; } // to group the inputs in an alphabetic list of clients (e.g. in qjackctl) _client_name.insert(0,"["); // see max_size-- above! std::replace(_client_name.begin(), _client_name.end(), '/', '_'); // set the name of the JACK client (using the truncated audio-filename) // do not send, only receive transport msg. // this must be done AFTER adding a chain _eca.command("-G:jack," + _client_name + ",recv"); _eca.command("cs-connect"); if (_eca.error()) { throw soundfile_error("AudioPlayer::Soundfile: " + _eca.last_error()); } // after cs-connect, get information about the input file: //_eca.command("ai-get-length"); //_length = _eca.last_float(); _eca.command("ai-get-length-samples"); _length_samples = _eca.last_long_integer(); _eca.command("engine-launch"); if (_eca.error()) { _eca.command("cs-disconnect"); throw soundfile_error("AudioPlayer::Soundfile: " + _eca.last_error()); } // It takes a little time until the client is available // This is a little ugly, but I don't know a better way to do it. // If you know one, tell me, please! usleep(ssr::usleeptime); VERBOSE2("Added '" + _filename + "', format: '" + apf::str::A2S(_sample_format) + "', channels: " + apf::str::A2S(_channels) + ", sample rate: " + apf::str::A2S(_sample_rate) + "."); } /// disconnects from ecasound. AudioPlayer::Soundfile::~Soundfile() { // TODO: check if ecasound is really running. _eca.command("cs-disconnect"); // implies "stop" and "engine-halt" VERBOSE2("AudioPlayer::Soundfile: '" + _filename + "' disconnected."); } AudioPlayer::Soundfile::ptr_t AudioPlayer::Soundfile::create( const std::string& filename, bool loop) { ptr_t temp; // temp = NULL try { temp.reset(new Soundfile(filename, loop)); } catch(soundfile_error& e) { ERROR(e.what()); } return temp; } int AudioPlayer::Soundfile::get_channels() const { return _channels; } std::string AudioPlayer::Soundfile::get_client_name() const { return _client_name; } long int AudioPlayer::Soundfile::get_length() const { return _length_samples; } // 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'\:'=' ssr-0.4.2/src/audioplayer.h000066400000000000000000000130571236416011200156040ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Audio player using ecasound (definition). #ifndef SSR_AUDIOPLAYER_H #define SSR_AUDIOPLAYER_H #include #include #include #include // for std::runtime_error #include #include "apf/misc.h" // for NonCopyable /** Loads audiofiles for playback using ecasound (with JACK transport). * For each (multichannel) soundfile an ecasound instance is opened. **/ class AudioPlayer : apf::NonCopyable { public: using ptr_t = std::unique_ptr; ///< unique_ptr to AudioPlayer class Soundfile; // nested class // using default ctor. virtual ~AudioPlayer(); /// Open audio file with ecasound and return corresponding JACK port. std::string get_port_name(const std::string& audio_file_name, int channel, bool loop); /// get length (in samples) of given audio file. long int get_file_length(const std::string& audio_file_name) const; private: /// map of Soundfiles, indexed by strings. using soundfile_map_t = std::map; /// map to associate an ecasound instance with a filename soundfile_map_t _file_map; }; /** Plays a single (possibly multichannel) audio file. * Playback can be started using JACK transport. **/ class AudioPlayer::Soundfile : apf::NonCopyable { public: using ptr_t = std::unique_ptr; ///< unique_ptr to Soundfile /// exception to be thrown by ctor. struct soundfile_error : public std::runtime_error { soundfile_error(const std::string& s): std::runtime_error(s) {} }; Soundfile(const std::string& filename, bool loop, const std::string& prefix = "channel") throw (soundfile_error); /// "named constructor" for %Soundfile objects static ptr_t create(const std::string& filename, bool loop = false); ~Soundfile(); int get_channels() const; ///< get number of channels std::string get_client_name() const; ///< get name of JACK client long int get_length() const; ///< get length (in samples) static std::string get_format(const std::string& filename , size_t& channels, size_t& sample_rate); static std::string get_format(const std::string& filename, size_t& channels) { size_t dummy; return get_format(filename, channels, dummy); } static std::string get_format(const std::string& filename) { size_t dummy; return get_format(filename, dummy); } /// output prefix used for %Soundfile channels const std::string output_prefix; private: static size_t _get_jack_sample_rate(); ECA_CONTROL_INTERFACE _eca; ///< interface to the ecasound library const std::string _filename; ///< name of input sound file const std::string _escaped_filename; ///< name of input sound file with escaped white spaces std::string _client_name; ///< name of JACK client used by ecasound std::string _sample_format; ///< format of input sound file (e.g. s16_le) size_t _channels; ///< number of channels in input sound file size_t _sample_rate; ///< sample rate of input sound file long int _length_samples; ///< length of soundfile (in samples) std::string _get_escaped_filename(const std::string& filename); }; #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'\:'=' ssr-0.4.2/src/audiorecorder.cpp000066400000000000000000000134641236416011200164520ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Audio recorder using ecasound (implementation). #include // for usleep() #include "audiorecorder.h" #include "ssr_global.h" #include "posixpathtools.h" /** _. * @param audio_file_name name of audio file which will store the recording. * @param format_string ecasound formatstring consisting of * "sample_format,channels,sample_rate", for example "16,4,48000" (16 bit, 4 * channels, 48000 Hz sampling rate). * @param record_source JACK client whose output will be recorded. All channels * will be connected up to the given number of channels. Further channels in the * output client will be ignored. * If no @a record_source is given or if it is an empty string, no connections * are made. * @warning Connections are made in the order of port-creation, numeric * portnames like out_1, out_2, etc. are not relevant. If you don't like this * behaviour, omit the optional parameter @a record_source and make your * connections manually. * @warning @a record_source must already be connected to JACK @b and its * outputs must already be registered in order to be able to make automatic * connections. * @param input_prefix_ The input prefix is the thing between the colon (:) and * the underscore (_) followed by a number, e.g. alsa_pcm:playback_1, where * "playback" is the input prefix. * @throw audiorecorder_error **/ AudioRecorder::AudioRecorder(const std::string& audio_file_name, const std::string& format_string, const std::string& record_source, const std::string& client_name_, const std::string& input_prefix_) throw (audiorecorder_error) : client_name(client_name_), input_prefix(input_prefix_) { _eca.command("cs-add audiorecorder_chainsetup"); _eca.command("cs-set-audio-format " + format_string); _eca.command("c-add recorder_chain"); // TODO: check if audiofile already exists. If it exists, audio data is added // TODO: to the end. This is not wanted! _eca.command("ao-add " + posixpathtools::get_escaped_filename(audio_file_name)); if (record_source == "") { // do not make any connections _eca.command("ai-add jack_generic," + this->input_prefix); } else { // make connections with record_source automatically _eca.command("ai-add jack_auto," + record_source); } // use JACK transport (only receive), and set client name _eca.command("-G:jack," + this->client_name + ",recv"); VERBOSE_NOLF("AudioRecorder ('" + this->client_name + "'): Trying to activate ... "); _eca.command("cs-connect"); if (_eca.error()) { VERBOSE("failed!"); ERROR("File must be writable and needs an extension recognized by " "ecasound, e.g. \".wav\"."); throw audiorecorder_error("ecasound: " + _eca.last_error()); } VERBOSE("done."); if (!this->enable()) { throw audiorecorder_error("Couldn't enable AudioRecorder \"" + this->client_name + "\"!"); } // It takes a little time until the client is available // This is a little ugly, but I don't know a better way to do it. // If you know one, tell me, please! usleep(ssr::usleeptime); } /// disconnects from ecasound AudioRecorder::~AudioRecorder() { _eca.command("cs-disconnect"); // implies "stop" and "engine-halt" VERBOSE2("AudioRecorder dtor ('" + client_name + "')."); } bool AudioRecorder::enable() { _eca.command("engine-launch"); if (_eca.error()) { ERROR("ecasound: " + _eca.last_error()); return false; } return true; } bool AudioRecorder::disable() { _eca.command("engine-halt"); if (_eca.error()) { ERROR("ecasound: " + _eca.last_error()); return false; } return true; } // 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'\:'=' ssr-0.4.2/src/audiorecorder.h000066400000000000000000000067311236416011200161160ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Audio recorder using ecasound (definition). #ifndef SSR_AUDIORECORDER_H #define SSR_AUDIORECORDER_H #include #include #include #include // for std::runtime_error /** * Writes a (multichannel) soundfile using ecasound and JACK transport. **/ class AudioRecorder { public: using ptr_t = std::unique_ptr; ///< auto_ptr to AudioRecorder /// exception to be thrown by ctor. struct audiorecorder_error : public std::runtime_error { audiorecorder_error(const std::string& s): std::runtime_error(s) {} }; ~AudioRecorder(); AudioRecorder(const std::string& audio_file_name, const std::string& format_string, const std::string& record_source, const std::string& client_name = "recorder", const std::string& input_prefix = "channel") throw(audiorecorder_error); bool enable(); ///< enable recording as soon as transport is started bool disable(); ///< disable recording const std::string client_name; ///< name of JACK client used by ecasound const std::string input_prefix; ///< prefix used for channels private: ECA_CONTROL_INTERFACE _eca; ///< interface to ecasound }; #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'\:'=' ssr-0.4.2/src/binauralrenderer.h000066400000000000000000000273461236416011200166200ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Binaural renderer. #ifndef SSR_BINAURALRENDERER_H #define SSR_BINAURALRENDERER_H #include "rendererbase.h" #include "apf/iterator.h" // for apf::cast_proxy, apf::make_cast_proxy() #include "apf/convolver.h" // for apf::conv::* #include "apf/container.h" // for apf::fixed_matrix #include "apf/sndfiletools.h" // for apf::load_sndfile #include "apf/combine_channels.h" // for apf::raised_cosine_fade, ... namespace ssr { // TODO: derive from HeadphoneRenderer? class BinauralRenderer : public SourceToOutput { private: using _base = SourceToOutput; public: static const char* name() { return "BinauralRenderer"; } class SourceChannel; class Source; class Output; class RenderFunction; BinauralRenderer(const apf::parameter_map& params) : _base(params) , _fade(this->block_size()) , _partitions(0) {} void load_reproduction_setup(); APF_PROCESS(BinauralRenderer, _base) { this->_process_list(_source_list); } private: using hrtf_set_t = apf::fixed_vector; void _load_hrtfs(const std::string& filename, size_t size); static bool _cmp_abs(sample_type left, sample_type right) { return std::abs(left) < std::abs(right); } apf::raised_cosine_fade _fade; size_t _partitions; size_t _angles; // Number of angles in HRIR file std::unique_ptr _hrtfs; std::unique_ptr _neutral_filter; }; class BinauralRenderer::SourceChannel : public apf::conv::Output , public apf::has_begin_and_end { public: SourceChannel(const apf::conv::Input& input) : apf::conv::Output(input) , temporary_hrtf(input.block_size(), input.partitions()) , _block_size(input.block_size()) {} void convolve_and_more(sample_type weight) { _begin = this->convolve(weight); _end = _begin + _block_size; } void update() { this->convolve_and_more(this->weight); } apf::conv::Filter temporary_hrtf; sample_type weight; apf::CombineChannelsResult::type crossfade_mode; private: const size_t _block_size; }; void BinauralRenderer::_load_hrtfs(const std::string& filename, size_t size) { auto hrir_file = apf::load_sndfile(filename, this->sample_rate(), 0); const size_t no_of_channels = hrir_file.channels(); if (no_of_channels % 2 != 0) { throw std::logic_error("Number of channels must be a multiple of 2!"); } _angles = no_of_channels / 2; // TODO: handle size > hrir_file.frames() if (size == 0) size = hrir_file.frames(); // Deinterleave channels and transform to FFT domain auto transpose = apf::fixed_matrix(size, no_of_channels); size = hrir_file.readf(transpose.data(), size); _partitions = apf::conv::min_partitions(this->block_size(), size); auto temp = apf::conv::Transform(this->block_size()); _hrtfs.reset(new hrtf_set_t(no_of_channels, this->block_size(), _partitions)); auto target = _hrtfs->begin(); for (const auto& slice: transpose.slices) { temp.prepare_filter(slice.begin(), slice.end(), *target++); } // prepare neutral filter (dirac impulse) for interpolation around the head // get index of absolute maximum in first channel (frontal direcion, left) apf::fixed_matrix::slice_iterator maximum = std::max_element(transpose.slices.begin()->begin() , transpose.slices.begin()->end(), _cmp_abs); int index = std::distance(transpose.slices.begin()->begin(), maximum); auto impulse = apf::fixed_vector(index + 1); impulse.back() = 1; _neutral_filter.reset(new apf::conv::Filter(this->block_size() , impulse.begin(), impulse.end())); // Number of partitions may be different from _hrtfs! } class BinauralRenderer::RenderFunction { public: RenderFunction() : _in(0) {} apf::CombineChannelsResult::type select(SourceChannel& in) { _in = ∈ return in.crossfade_mode; } void update() { assert(_in); _in->update(); } private: SourceChannel* _in; }; class BinauralRenderer::Output : public _base::Output { public: Output(const Params& p) : _base::Output(p) , _combiner(this->sourcechannels, this->buffer, this->parent._fade) {} APF_PROCESS(Output, _base::Output) { _combiner.process(RenderFunction()); } private: apf::CombineChannelsCrossfadeCopy, buffer_type , apf::raised_cosine_fade> _combiner; }; void BinauralRenderer::load_reproduction_setup() { // TODO: read settings from proper reproduction system try { _load_hrtfs(this->params["hrir_file"], this->params.get("hrir_size", 0)); } catch (const std::logic_error& e) { throw std::logic_error("Error loading HRIR file: " + std::string(e.what())); } auto params = Output::Params(); const std::string prefix = this->params.get("system_output_prefix", ""); if (prefix != "") { // TODO: read target from proper reproduction file params.set("connect_to", prefix + "1"); } this->add(params); if (prefix != "") { params.set("connect_to", prefix + "2"); } this->add(params); } class BinauralRenderer::Source : public apf::conv::Input, public _base::Source { private: void _process(); public: Source(const Params& p) // TODO: assert that p.parent != 0? : apf::conv::Input(p.parent->block_size(), p.parent->_partitions) , _base::Source(p, 2, *this) , _hrtf_index(size_t(-1)) , _interp_factor(-1.0f) , _weight(0.0f) {} APF_PROCESS(Source, _base::Source) { _process(); } private: apf::BlockParameter _hrtf_index; apf::BlockParameter _interp_factor; apf::BlockParameter _weight; }; void BinauralRenderer::Source::_process() { float interp_factor = 0.0f; float weight = 0.0f; this->add_block(_input.begin()); auto ref_pos = _input.parent.state.reference_position + _input.parent.state.reference_offset_position; auto ref_ori = _input.parent.state.reference_orientation + _input.parent.state.reference_offset_orientation; if (this->weighting_factor != 0) { weight = 1; if (this->model == ::Source::plane) { // no distance attenuation for plane waves // 1/r: weight *= 0.5f / _input.parent.state.amplitude_reference_distance; // 1/sqrt(r): //weight *= 0.25f / sqrt( // _input.parent.state.amplitude_reference_distance); } else { float source_distance = (this->position - ref_pos).length(); if (source_distance < 0.5f) { interp_factor = 1.0f - 2 * source_distance; } // no volume increase for sources closer than 0.5m source_distance = std::max(source_distance, 0.5f); weight *= 0.5f / source_distance; // 1/r // weight *= 0.25f / sqrt(source_distance); // 1/sqrt(r) } weight *= this->weighting_factor; } _interp_factor = interp_factor; // Assign (once!) to BlockParameter _weight = weight; // ... same here float angles = _input.parent._angles; // calculate relative orientation of sound source auto rel_ori = -ref_ori; if (this->model == ::Source::plane) { // plane wave orientation points into direction of propagation // +180 degree has to be applied to select the hrtf correctly rel_ori += this->orientation + Orientation(180); } else { rel_ori += (this->position - ref_pos).orientation(); } _hrtf_index = size_t(apf::math::wrap( rel_ori.azimuth * angles / 360.0f + 0.5f, angles)); using namespace apf::CombineChannelsResult; auto crossfade_mode = apf::CombineChannelsResult::type(); // Check on one channel only, filters are always changed in parallel bool queues_empty = this->sourcechannels[0].queues_empty(); bool hrtf_changed = _hrtf_index.changed() || _interp_factor.changed(); if (_weight.both() == 0) { crossfade_mode = nothing; } else if (queues_empty && !_weight.changed() && !hrtf_changed) { crossfade_mode = constant; } else if (_weight == 0) { crossfade_mode = fade_out; } else if (_weight.old() == 0) { crossfade_mode = fade_in; } else { crossfade_mode = change; } for (size_t i = 0; i < 2; ++i) { auto& channel = this->sourcechannels[i]; if (crossfade_mode == nothing || crossfade_mode == fade_in) { // No need to convolve } else { channel.convolve_and_more(_weight.old()); } if (!queues_empty) channel.rotate_queues(); if (hrtf_changed) { // left and right channels are interleaved auto& hrtf = (*_input.parent._hrtfs)[2 * _hrtf_index + i]; if (_interp_factor == 0) { channel.set_filter(hrtf); } else { // Interpolate between selected HRTF and neutral filter (Dirac) apf::conv::transform_nested(hrtf , *_input.parent._neutral_filter, channel.temporary_hrtf , [this] (sample_type one, sample_type two) { return (1.0f - _interp_factor) * one + _interp_factor * two; }); this->sourcechannels[i].set_filter(channel.temporary_hrtf); } } channel.crossfade_mode = crossfade_mode; channel.weight = _weight; } assert(_hrtf_index.exactly_one_assignment()); assert(_interp_factor.exactly_one_assignment()); assert(_weight.exactly_one_assignment()); } } // namespace ssr #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'\:'=' ssr-0.4.2/src/boostnetwork/000077500000000000000000000000001236416011200156475ustar00rootroot00000000000000ssr-0.4.2/src/boostnetwork/commandparser.cpp000066400000000000000000000535721236416011200212220ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// CommandParser class (implementation). #include #include "ssr_global.h" // for ERROR() #include "commandparser.h" #include "publisher.h" #include "apf/stringtools.h" #include "apf/math.h" // for dB2linear() using namespace apf::str; /** ctor. * @param controller **/ ssr::CommandParser::CommandParser(Publisher& controller) : _controller(controller) {} /// dtor. ssr::CommandParser::~CommandParser() {} /** Parse a XML string and map to Controller. * @param cmd XML string. **/ void ssr::CommandParser::parse_cmd(std::string& cmd) { XMLParser xp; XMLParser::doc_t doc1 = xp.load_string(cmd); if (!doc1) { ERROR("Unable to load string! (\"" << cmd << "\")"); return; } XMLParser::xpath_t result1 = doc1->eval_xpath("/request"); if (!result1) { ERROR("XPath: no result!"); return; } XMLParser::Node node1 = result1->node(); // get first (and only?) node. for (XMLParser::Node i = node1.child(); !!i; ++i) { if (i == "source") { bool new_source = false; if (!S2A (i.get_attribute("new"), new_source)) { new_source = false; } id_t id = 0; // ugly ... if (!new_source) { if (!S2A(i.get_attribute("id"),id)) { ERROR("No source ID specified!"); return; } } Position position; Orientation orientation; bool position_fixed = false, orientation_fixed = false; for (XMLParser::Node inner_loop = i.child(); !!inner_loop; ++inner_loop) { if (inner_loop == "position") { if (S2A(inner_loop.get_attribute("x"), position.x) && S2A(inner_loop.get_attribute("y"), position.y)) { if (!new_source) { _controller.set_source_position(id, position); VERBOSE2("set source position: id = " << id << ", " << position); } else { // position is used later for _controller.new_source(...) } } else { position = Position(); } if (S2A(inner_loop.get_attribute("fixed"), position_fixed)) { if (!new_source) { _controller.set_source_position_fixed(id, position_fixed); VERBOSE2("set source position fixed: id = " << id << ", fixed = " << A2S(position_fixed)); } } else { position_fixed = false; } } else if (inner_loop == "orientation") { if (S2A(inner_loop.get_attribute("azimuth"), orientation.azimuth)) { if (!new_source) { _controller.set_source_orientation(id, orientation); VERBOSE2("set source orientation: id = " << id << ", " << orientation); } else { // orientation is used later for _controller.new_source(...) } } else { orientation = Orientation(); } // source orientation_fixed is not yet implemented! /* if (S2A(inner_loop.get_attribute("fixed"), orientation_fixed)) { if (!new_source) { _controller.set_source_orientation_fix(id, orientation_fixed); VERBOSE2("set source orientation fixed"); } } else { orientation_fixed = false; } */ } }//for (XMLParser:... float volume; if (S2A(i.get_attribute("volume"),volume)) { // volume is given in dB in the network messages volume = apf::math::dB2linear(volume); if (!new_source) { _controller.set_source_gain(id, volume); VERBOSE2("set source volume: id = " << id << ", volume (linear) = " << volume); } } else { volume = 1.0; // linear } bool muted; if (S2A(i.get_attribute("mute"), muted)) { if (!new_source) { _controller.set_source_mute(id, muted); VERBOSE2("set source mute mode: id = " << id << ", mute = " << A2S(muted)); } } else { muted = false; } std::string name = i.get_attribute("name"); if (!name.empty() && !new_source) { _controller.set_source_name(id,name); VERBOSE2("set source name: id = " << id << ", name = " << name); } std::string properties_file = i.get_attribute("properties_file"); if (!properties_file.empty() && !new_source) { _controller.set_source_properties_file(id,properties_file); VERBOSE2("set source properties file name: id = " << id << ", file = " << properties_file); } Source::model_t model = Source::model_t(); if (S2A(i.get_attribute("model"),model)) { if (!new_source) { _controller.set_source_model(id,model); VERBOSE2("set source model: id = " << id << ", model = " << model); } } else { model = Source::point; } std::string port_name = i.get_attribute("port"); if (!port_name.empty() && !new_source) { _controller.set_source_port_name(id,port_name); VERBOSE2("set source port name: id = " << id << ", port = " << port_name); } std::string file_or_port_name = port_name; std::string file_name = i.get_attribute("file"); if (!file_name.empty() && !new_source) { ERROR("Cannot set file name! This works only for new sources."); } if (!file_name.empty() && !port_name.empty()) { ERROR("Either file name or port name can be set, not both!"); } if (new_source && file_name.empty() && port_name.empty()) { ERROR("Either file name or port name must be specified!"); } int channel = 0; if (S2A(i.get_attribute("channel"), channel)) { assert(channel >= 0); if (file_name.empty()) { ERROR("'channel' is only allowed if a file name is also specified!"); channel = 0; } else { file_or_port_name = file_name; } } else if (!file_name.empty()) { file_or_port_name = file_name; channel = 1; } if (new_source) { if (file_or_port_name == "") { ERROR("Either file name or port name must be specified!"); } else { VERBOSE2("Creating source with following properties:" "\nname: " << name << "\nmodel: " << model << "\nfile_or_port_name: " << file_or_port_name << "\nchannel: " << channel << "\nposition: " << position << "\nposition_fixed: " << A2S(position_fixed) << "\norientation: " << orientation << "\norientation_fixed: " << A2S(orientation_fixed) << "\nvolume (linear): " << volume << "\nmuted: " << A2S(muted) << "\nproperties_file: " << properties_file << "\n"); _controller.new_source(name, model, file_or_port_name, channel, position, position_fixed, orientation, orientation_fixed, volume, muted, properties_file); } } } // if (source) else if (i == "reference") { for (XMLParser::Node inner_loop = i.child(); !!inner_loop; ++inner_loop) { if (inner_loop == "position") { float x, y; if (S2A(inner_loop.get_attribute("x"), x) && S2A(inner_loop.get_attribute("y"), y)) { _controller.set_reference_position(Position(x,y)); VERBOSE2("set reference position: " << Position(x,y)); } else ERROR("Invalid reference position!"); } else if (inner_loop == "orientation") { float azimuth; if (S2A(inner_loop.get_attribute("azimuth"), azimuth)) { _controller.set_reference_orientation(Orientation(azimuth)); VERBOSE2("set reference orientation: " << Orientation(azimuth)); } else ERROR("Invalid reference orientation!"); } } } // if (reference) else if (i == "reference_offset") { for (XMLParser::Node inner_loop = i.child(); !!inner_loop; ++inner_loop) { if (inner_loop == "position") { float x, y; if (S2A(inner_loop.get_attribute("x"), x) && S2A(inner_loop.get_attribute("y"), y)) { _controller.set_reference_offset_position(Position(x,y)); VERBOSE2("set reference offset position: " << Position(x,y)); } else ERROR("Invalid reference offset position!"); } else if (inner_loop == "orientation") { float azimuth; if (S2A(inner_loop.get_attribute("azimuth"), azimuth)) { _controller.set_reference_offset_orientation(Orientation(azimuth)); VERBOSE2("set reference offset orientation: " << Orientation(azimuth)); } else ERROR("Invalid reference offset orientation!"); } } } // if (reference_offset) else if (i == "delete") { for (XMLParser::Node inner_loop = i.child(); !!inner_loop; ++inner_loop) { if (inner_loop == "source") { id_t id; if (S2A(inner_loop.get_attribute("id"),id)) { // if (id == 0) {} //_controller.delete_all_sources(); // else {}//_controller.delete_source(id); _controller.delete_source(id); } else ERROR("Cannot read source ID!"); } else {} }// for () }// else if (delete) else if (i== "scene") { // if both save and load are requested, first save, then load. // TODO: or make save and load exclusive? That's maybe better ... // save scene std::string save_scene = i.get_attribute("save"); if (save_scene != "") { _controller.save_scene_as_XML(save_scene); } // load scene std::string load_scene = i.get_attribute("load"); if (load_scene != "") { _controller.load_scene(load_scene); } std::string volume_str = i.get_attribute("volume"); float volume; if (volume_str == "") { // do nothing } else if (S2A(volume_str, volume)) { // volume is given in dB in the network messages _controller.set_master_volume(apf::math::dB2linear(volume)); VERBOSE2("set master volume: " << volume << " dB"); } else ERROR("Invalid Volume Setting! (\"" << volume_str << "\")"); bool clear_scene; if (S2A(i.get_attribute("clear"), clear_scene) && clear_scene) { _controller.delete_all_sources(); } } else if (i == "state") { // start/stop audio processing std::string processing= i.get_attribute("processing"); if (processing == "start") { _controller.start_processing(); } else if (processing == "stop") { _controller.stop_processing(); } else if (processing != "") { ERROR("Invalid value for \"processing\": " << processing); } // start/stop/rewind transport std::string transport = i.get_attribute("transport"); if (transport == "start") { _controller.transport_start(); } else if (transport == "stop") { _controller.transport_stop(); } else if (transport == "rewind") { _controller.transport_locate(0); } else if (transport != "") { ERROR("Invalid value for \"transport\": " << transport); } // jump to a certain time std::string seek = i.get_attribute("seek"); // seek can be in format h:mm:ss.x or "xx.x h|min|s|ms" just in seconds // decimals are optional // multiple whitespace is allowed before and after. float time; if (string2time(seek, time)) { _controller.transport_locate(time); } else if (seek != "") { ERROR("Couldn't get the time out of the \"seek\" attribute (\"" << seek << "\")."); } // reset tracker std::string tracker = i.get_attribute("tracker"); if (tracker == "reset") { _controller.calibrate_client(); } else if (tracker != "") { ERROR("Invalid value for \"tracker\": " << tracker); } } } } #if 0 ssr::CommandParser::parse_string2(std::string cmd) { XMLParser xp; XMLParser::doc_t doc1 = xp.load_string(data); if (!doc1) { std::cerr << "client_parser_thread: Unable to load string!" << std::endl; return; } XMLParser::xpath_t result1 = doc1->eval_xpath("/update"); if (!result1) { std::cerr << "XPath: no result!" << std::endl; continue ; } XMLParser::Node node1 = result1->node(); // get first (and only?) node. // all loudspeaker information is written to this temporary container and // transmitted in the end of the function Loudspeaker::container_t loudspeakers; for (XMLParser::Node i = node1.child(); !!i; ++i) { if (i == "source") { id_t id; if (!S2A (i.get_attribute("id"),id)) { ERROR("No source ID given!"); return; } _scene.new_source(id); for (XMLParser::Node inner_loop = i.child(); !!inner_loop; ++inner_loop) { if (inner_loop == "position") { float x, y; if (S2A(inner_loop.get_attribute("x"), x) && S2A(inner_loop.get_attribute("y"), y)) { _scene.set_source_position(id, Position(x,y)); VERBOSE2("set source position"); } else { std::cerr << "Invalid position!" << std::endl; } } else if (inner_loop == "orientation") { float azimuth; if (S2A(inner_loop.get_attribute("azimuth"), azimuth)) { _scene.set_source_orientation(id,Orientation(azimuth)); VERBOSE2("set source orientation"); } else { std::cerr << "Invalid source orientation!" << std::endl; } } }//eof for float volume; if (S2A(i.get_attribute("volume"),volume)) { // volume is given in dB in the network messages _scene.set_source_gain(id, apf::math::dB2linear(volume)); VERBOSE2("set source volume"); } bool muted; if (S2A(i.get_attribute("mute"), muted)) { _scene.set_source_mute(id, muted); VERBOSE2("set source mute mode"); } std::string name = i.get_attribute("name"); if (name.empty() == false) { _scene.set_source_name(id,name); VERBOSE2("set source name"); } std::string properties_file = i.get_attribute("brirs"); if (properties_file.empty() == false) { _scene.set_source_properties_file(id,properties_file); VERBOSE2("set source BRIR file name"); } Source::model_t model; if (S2A(i.get_attribute("model"),model)) { _scene.set_source_model(id,model); VERBOSE2("set source model"); } std::string port_name = i.get_attribute("port_name"); if (!port_name.empty()) { _scene.set_source_port_name(id,port_name); VERBOSE2("Set source port name"); } float level; if (S2A(i.get_attribute("level"),level)) { _scene.set_source_signal_level(id,level); } }//eof if (source) else if (i == "reference") { for (XMLParser::Node inner_loop = i.child(); !!inner_loop; ++inner_loop) { if (inner_loop == "position") { float x, y; if (S2A(inner_loop.get_attribute("x"), x) && S2A(inner_loop.get_attribute("y"), y)) { _scene.set_reference_position(Position(x,y)); VERBOSE2("Set Reference Position"); } else { std::cerr << "Invalid reference position!" << std::endl; } } else if (inner_loop == "orientation") { float azimuth; if (S2A(inner_loop.get_attribute("azimuth"), azimuth)) { _scene.set_reference_orientation(Orientation(azimuth)); VERBOSE2("Set Reference Orientation"); } else std::cerr << "Invalid reference orientation!" << std::endl; } }// for }// if (reference) else if (i == "loudspeaker") { Loudspeaker loudspeaker; for (XMLParser::Node inner_loop = i.child(); !!inner_loop; ++inner_loop) { if (inner_loop == "position") { float x, y; if (S2A(inner_loop.get_attribute("x"), x) && S2A(inner_loop.get_attribute("y"), y)) loudspeaker.position = Position(x,y); else std::cerr << "Invalid loudspeaker position!" << std::endl; } else if (inner_loop == "orientation") { float azimuth; if (S2A(inner_loop.get_attribute("azimuth"), azimuth)) loudspeaker.orientation = Orientation(azimuth); else std::cerr << "Invalid loudspeaker orientation!" << std::endl; } }//for Loudspeaker::model_t model; if (S2A(i.get_attribute("model"), model)) { loudspeaker.model = model; } bool mute = false; if (S2A(i.get_attribute("mute"), mute)) loudspeaker.mute = mute; loudspeakers.push_back(loudspeaker); } else if (i == "volume") { float volume; if (S2A(get_content(i),volume)) { // volume is given in dB in the network messages _scene.set_master_volume(apf::math::dB2linear(volume)); VERBOSE2("Set Master Volume"); } else std::cerr << "Invalid Volume Setting!" << std::endl; } //if (volume) else if (i == "active") { Loudspeaker::container_t::size_type loudspeaker_id; if (S2A(i.get_attribute("loudspeaker"),loudspeaker_id)) { std::set active_source_ids; id_t temp_id; std::istringstream iss (get_content(i)); while (iss >> temp_id) { active_source_ids.insert(temp_id); } if (!active_source_ids.empty()) _scene.set_active_sources(loudspeaker_id, active_source_ids); } } else if (i == "delete") { for (XMLParser::Node inner_loop = i.child(); !!inner_loop; ++inner_loop) { if (inner_loop == "source") { id_t id; if (S2A(inner_loop.get_attribute("id"),id)) { if (id == 0) { _scene.delete_all_sources(); VERBOSE2("delete all sources"); } else { _scene.delete_source(id); VERBOSE2("delete source with id " << A2S(id)); } } else std::cerr << "Cannot read source ID!" << std::endl; } else {} } }//eof if (delete) else if (i == "cpu") { float load; if (S2A(i.get_attribute("load"),load)) _scene.set_cpu_load(load); } //cpu else if (i == "master") { float level; if (S2A(i.get_attribute("level"),level)) _scene.set_master_signal_level(level); } } // if there were any loudspeakers given, save them if (!loudspeakers.empty()) { _scene.set_loudspeakers(loudspeakers); } } #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'\:'=' ssr-0.4.2/src/boostnetwork/commandparser.h000066400000000000000000000057601236416011200206630ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// CommandParser class (definition). #ifndef SSR_COMMANDPARSER_H #define SSR_COMMANDPARSER_H #include #include #include #include "ssr_global.h" #include "xmlparser.h" namespace ssr { struct Publisher; /** Parses a XML string and maps to Controller. * This class is the bridge between the network interface and the Controller. * Incoming XML messages (in ASDF-format) are parsed and the appropriate * functions of Controller called. **/ class CommandParser { public: CommandParser(Publisher& controller); ~CommandParser(); void parse_cmd(std::string &cmd); //void parse_cmd_old(std::string &cmd) private: Publisher& _controller; //Subscriber& _scene; }; } // namespace ssr #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'\:'=' ssr-0.4.2/src/boostnetwork/connection.cpp000066400000000000000000000145211236416011200205150ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Connection class (implementation). #include #include "connection.h" #include "publisher.h" /// ctor ssr::Connection::Connection(boost::asio::io_service &io_service , Publisher &controller) : _socket(io_service) , _timer(io_service) , _controller(controller) , _subscriber(*this) , _commandparser(controller) , _is_subscribed(false) {} /// dtor ssr::Connection::~Connection() { if (_is_subscribed) _controller.unsubscribe(&_subscriber); _is_subscribed = false; } /** Get an instance of Connection. * @param io_service * @param controller used to (un)subscribe and get the actual Scene * @return ptr to Connection **/ ssr::Connection::pointer ssr::Connection::create(boost::asio::io_service &io_service , Publisher& controller) { return pointer(new Connection(io_service, controller)); } /** Start the connection. * - Subscribe this instance of Connection to the Controller. * - Send the actual scene over the network. * - Start reading incoming messages. * - Initialize the timer. **/ void ssr::Connection::start() { // ok... this Connection object is activated. // now we can connect the NetworkSubscriber. _controller.subscribe(&_subscriber); _is_subscribed = true; // this stuff should perhaps get refactored. // need to think about this. not sure if i like this mixed into // the Connection code. std::string whole_scene = _controller.get_scene_as_XML(); this->write(whole_scene); // And we can also start_read ing. start_read(); // intialize the timer _timer.expires_from_now(boost::posix_time::milliseconds(100)); _timer.async_wait(boost::bind(&Connection::timeout_handler, shared_from_this() , boost::asio::placeholders::error)); } /** Send levels on timeout. * - Send level. * - Reset timer. * - Wait async. * @param e self explanatory **/ void ssr::Connection::timeout_handler(const boost::system::error_code &e) { if (e) return; _subscriber.send_levels(); // Set timer again. _timer.expires_from_now(boost::posix_time::milliseconds(100)); _timer.async_wait(boost::bind(&Connection::timeout_handler, shared_from_this() , boost::asio::placeholders::error)); } /// Start reading from socket. void ssr::Connection::start_read() { async_read_until(_socket, _streambuf, '\0' , boost::bind(&Connection::read_handler, shared_from_this() , boost::asio::placeholders::error , boost::asio::placeholders::bytes_transferred)); } /// Forward string from socket to CommandParser. void ssr::Connection::read_handler(const boost::system::error_code &error , size_t size) { if (!error) { std::istream input_stream(&_streambuf); std::string packet_string; getline(input_stream, packet_string, '\0'); (void) size; //cout << "size= " << size << endl; //cout << "line: " << packet_string << endl; _commandparser.parse_cmd(packet_string); this->start_read(); } else { _timer.cancel(); } } /** Write to socket. * @param writestring: String to be send over the network. **/ void ssr::Connection::write(std::string &writestring) { // Create a Copy of this string. // Put into shared_ptr bound to the callback // handler. This is sufficient to make it // be destroyed on exit. boost::shared_ptr str_ptr(new std::string(writestring + '\0')); boost::asio::async_write(_socket, boost::asio::buffer(*str_ptr) , boost::bind(&Connection::write_handler, shared_from_this(), str_ptr , boost::asio::placeholders::error , boost::asio::placeholders::bytes_transferred)); } /** Empty callback handler. * * @param str_ptr String passed to Connection::write * @param error error code * @param bytes_transferred self explanatory * * @todo Check if we can delete this function. **/ void ssr::Connection::write_handler(boost::shared_ptr str_ptr , const boost::system::error_code &error, size_t bytes_transferred) { (void) str_ptr; (void) error; (void) bytes_transferred; // Dont do nothing. we could check for error and stuff. // the shared_ptr was just used to keep the string alive and // destroy it now. } // 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'\:'=' ssr-0.4.2/src/boostnetwork/connection.h000066400000000000000000000075731236416011200201730ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Connection class (definition). #ifndef SSR_CONNECTION_H #define SSR_CONNECTION_H #ifdef HAVE_CONFIG_H #include // for ENABLE_* #endif #include #include #include #include "networksubscriber.h" #include "commandparser.h" namespace ssr { struct Publisher; /// Connection class. class Connection : public boost::enable_shared_from_this { public: /// Ptr to Connection typedef boost::shared_ptr pointer; typedef boost::asio::ip::tcp::socket socket_t; static pointer create(boost::asio::io_service &io_service , Publisher &controller); void start(); void write(std::string &writestring); /// @return Reference to socket socket_t& socket() { return _socket; } ~Connection(); private: Connection(boost::asio::io_service &io_service, Publisher &controller); void start_read(); void read_handler(const boost::system::error_code &error, size_t size); void write_handler(boost::shared_ptr str_ptr , const boost::system::error_code &error, size_t bytes_transferred); void timeout_handler(const boost::system::error_code &e); /// TCP/IP socket socket_t _socket; /// Buffer for incoming messages. boost::asio::streambuf _streambuf; /// @see Connection::timeout_handler boost::asio::deadline_timer _timer; /// Reference to Controller Publisher &_controller; /// Subscriber obj NetworkSubscriber _subscriber; /// Commandparser obj CommandParser _commandparser; bool _is_subscribed; }; } // namespace ssr #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'\:'=' ssr-0.4.2/src/boostnetwork/networksubscriber.cpp000066400000000000000000000232741236416011200221400ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// NetworkSubscriber class (implementation). #include "networksubscriber.h" #include "apf/stringtools.h" #include "apf/math.h" // for linear2dB() #include "connection.h" using apf::str::A2S; // temporary hack: //static bool previous_state = false; ssr::NetworkSubscriber::NetworkSubscriber(Connection &connection) : _connection(connection) , _master_level(0.0) {} ssr::NetworkSubscriber::~NetworkSubscriber() {} void ssr::NetworkSubscriber::update_all_clients(std::string str) { _connection.write(str); } void ssr::NetworkSubscriber::send_levels() { source_level_map_t::iterator i; std::string ms = ""; for (i=_source_levels.begin(); i!=_source_levels.end(); i++) { ms += (""); } ms += ""; update_all_clients(ms); } // Subscriber interface void ssr::NetworkSubscriber::set_loudspeakers( const Loudspeaker::container_t& loudspeakers) { (void) loudspeakers; //not_implemented("NetworkSubscriber::set_loudspeakers()"); } void ssr::NetworkSubscriber::new_source(id_t id) { std::string ms = ""; update_all_clients(ms); } void ssr::NetworkSubscriber::delete_source(id_t id) { _source_levels.erase(id); std::string ms = "" + + ""; update_all_clients(ms); } void ssr::NetworkSubscriber::delete_all_sources() { _source_levels.clear(); std::string ms = ""; update_all_clients(ms); } bool ssr::NetworkSubscriber::set_source_position(id_t id, const Position& position) { std::string ms = ""; update_all_clients(ms); return true; } bool ssr::NetworkSubscriber::set_source_position_fixed(id_t id, const bool& fixed) { std::string ms = ""; update_all_clients(ms); return true; } bool ssr::NetworkSubscriber::set_source_orientation(id_t id , const Orientation& orientation) { std::string ms = ""; update_all_clients(ms); return true; } bool ssr::NetworkSubscriber::set_source_gain(id_t id, const float& gain) { std::string ms = ""; update_all_clients(ms); return true; } bool ssr::NetworkSubscriber::set_source_mute(id_t id, const bool& mute) { std::string ms = ""; update_all_clients(ms); return true; } bool ssr::NetworkSubscriber::set_source_name(id_t id, const std::string& name) { (void) id; (void) name; #if 0 std::string ms = ""; update_all_clients(ms); #endif return true; } bool ssr::NetworkSubscriber::set_source_properties_file(id_t id, const std::string& name) { (void) id; (void) name; #if 0 std::string ms = ""; update_all_clients(ms); #endif return true; } void ssr::NetworkSubscriber::set_amplitude_reference_distance(float distance) { (void) distance; //not_implemented("NetworkSubscriber::set_amplitude_reference_distance()"); return; } bool ssr::NetworkSubscriber::set_source_model(id_t id, const Source::model_t& model) { std::string tmp; tmp = A2S(model); if (tmp == "") return false; std::string ms = ""; update_all_clients(ms); return true; } bool ssr::NetworkSubscriber::set_source_port_name(id_t id, const std::string& port_name) { (void) id; (void) port_name; #if 0 std::string ms = ""; update_all_clients(ms); #endif return true; } bool ssr::NetworkSubscriber::set_source_file_name(id_t id, const std::string& file_name) { (void) id; (void) file_name; return 1; } bool ssr::NetworkSubscriber::set_source_file_channel(id_t id , const int& file_channel) { (void) id; (void) file_channel; return 1; } bool ssr::NetworkSubscriber::set_source_file_length(id_t id, const long int& length) { std::string ms = ""; update_all_clients(ms); return true; } void ssr::NetworkSubscriber::set_reference_position(const Position& position) { std::string ms = ""; update_all_clients(ms); } void ssr::NetworkSubscriber::set_reference_orientation(const Orientation& orientation) { std::string ms = ""; update_all_clients(ms); } void ssr::NetworkSubscriber::set_reference_offset_position(const Position& position) { std::string ms = ""; update_all_clients(ms); } void ssr::NetworkSubscriber::set_reference_offset_orientation(const Orientation& orientation) { std::string ms = ""; update_all_clients(ms); } void ssr::NetworkSubscriber::set_master_volume(float volume) { std::string ms = ""; update_all_clients(ms); } void ssr::NetworkSubscriber::set_source_output_levels(id_t id, float* first , float* last) { std::string ms = ""; update_all_clients(ms); } void ssr::NetworkSubscriber::set_processing_state(bool state) { (void) state; } void ssr::NetworkSubscriber::set_transport_state( const std::pair& state) { // temporary hack: only start/stop is forwarded, the "time" in samples is // ignored static bool previous_state = false; if (previous_state != state.first) { std::string ms = ""; update_all_clients(ms); previous_state = state.first; } } void ssr::NetworkSubscriber::set_cpu_load(float load) { (void)load; // temporarily disabled: /* std::string ms = ""; update_all_clients(ms); */ } void ssr::NetworkSubscriber::set_sample_rate(int sr) { (void)sr; } void ssr::NetworkSubscriber::set_master_signal_level(float level) { _master_level = level; //std::string ms = ""; //update_all_clients(ms); } bool ssr::NetworkSubscriber::set_source_signal_level(const id_t id, const float& level) { _source_levels[id] = level; std::string ms = ""; //update_all_clients(ms); return true; } // 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'\:'=' ssr-0.4.2/src/boostnetwork/networksubscriber.h000066400000000000000000000123501236416011200215760ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// NetworkSubscriber (definition). #ifndef SSR_NETWORKSUBSCRIBER_H #define SSR_NETWORKSUBSCRIBER_H #include "subscriber.h" #include namespace ssr { class Connection; /** NetworkSubscriber. * This Subscriber turns function calls to the Subscriber interface into * strings (XML-messages in ASDF format) and sends it over a Connection to * connected clients. * * @todo There will be a set of flags, which can filter certain * events. But this will be done by deriving. **/ class NetworkSubscriber : public Subscriber { public: NetworkSubscriber(Connection &connection); ~NetworkSubscriber(); // XXX: This is just to make the old code work. // only sends string to one connection. void update_all_clients(std::string str); void send_levels(); // Subscriber Interface virtual void set_loudspeakers(const Loudspeaker::container_t& loudspeakers); virtual void new_source(id_t id); virtual void delete_source(id_t id); virtual void delete_all_sources(); virtual bool set_source_position(id_t id, const Position& position); virtual bool set_source_position_fixed(id_t id, const bool& fix); virtual bool set_source_orientation(id_t id, const Orientation& orientation); virtual bool set_source_gain(id_t id, const float& gain); virtual bool set_source_mute(id_t id, const bool& mute); virtual bool set_source_name(id_t id, const std::string& name); virtual bool set_source_properties_file(id_t id, const std::string& name); virtual bool set_source_model(id_t id, const Source::model_t& model); virtual bool set_source_port_name(id_t id, const std::string& port_name); virtual bool set_source_file_name(id_t id, const std::string& file_name); virtual bool set_source_file_channel(id_t id, const int& file_channel); virtual bool set_source_file_length(id_t id, const long int& length); virtual void set_reference_position(const Position& position); virtual void set_reference_orientation(const Orientation& orientation); virtual void set_reference_offset_position(const Position& position); virtual void set_reference_offset_orientation(const Orientation& orientation); virtual void set_master_volume(float volume); virtual void set_source_output_levels(id_t id, float* first, float* last); virtual void set_processing_state(bool state); //virtual void set_transport_state(JackClient::State state); virtual void set_transport_state( const std::pair& state); virtual void set_amplitude_reference_distance(float distance); virtual void set_master_signal_level(float level); virtual void set_cpu_load(float load); virtual void set_sample_rate(int sample_rate); virtual bool set_source_signal_level(const id_t id, const float& level); private: Connection &_connection; typedef std::map source_level_map_t; source_level_map_t _source_levels; float _master_level; }; } // namespace ssr #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'\:'=' ssr-0.4.2/src/boostnetwork/server.cpp000066400000000000000000000066541236416011200176740ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Server class (implementation). #include "server.h" #include ssr::Server::Server(Publisher& controller, int port) : _controller(controller) , _io_service() , _acceptor(_io_service , boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)) , _network_thread(0) {} ssr::Server::~Server() { this->stop(); } void ssr::Server::start_accept() { Connection::pointer new_connection = Connection::create(_io_service , _controller); _acceptor.async_accept(new_connection->socket() , boost::bind(&Server::handle_accept, this, new_connection , boost::asio::placeholders::error)); } void ssr::Server::handle_accept(Connection::pointer new_connection , const boost::system::error_code &error) { if (!error) { new_connection->start(); start_accept(); } } void ssr::Server::start() { _network_thread = new boost::thread(boost::bind(&Server::run, this)); } void ssr::Server::stop() { VERBOSE2("Stopping network thread ..."); if (_network_thread) { _io_service.stop(); _network_thread->join(); } VERBOSE2("Network thread stopped."); } void ssr::Server::run() { start_accept(); _io_service.run(); } // 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'\:'=' ssr-0.4.2/src/boostnetwork/server.h000066400000000000000000000060451236416011200173330ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Server class (definition). #ifndef SSR_SERVER_H #define SSR_SERVER_H #ifdef HAVE_CONFIG_H #include // for ENABLE_* #endif #include #include #include #include #include "connection.h" namespace ssr { struct Publisher; /// Server class. class Server { public: Server(Publisher& controller, int port); ~Server(); void start(); void stop(); private: void run(); void start_accept(); void handle_accept(Connection::pointer new_connection , const boost::system::error_code &error); Publisher& _controller; boost::asio::io_service _io_service; boost::asio::ip::tcp::acceptor _acceptor; boost::thread *_network_thread; }; } // namespace ssr #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'\:'=' ssr-0.4.2/src/brsrenderer.h000066400000000000000000000220241236416011200155750ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Binaural Room Synthesis renderer. #ifndef SSR_BRSRENDERER_H #define SSR_BRSRENDERER_H #include "rendererbase.h" #include "apf/convolver.h" // for apf::conv::* #include "apf/sndfiletools.h" // for apf::load_sndfile #include "apf/combine_channels.h" // for apf::raised_cosine_fade, ... namespace ssr { class BrsRenderer : public SourceToOutput { private: using _base = SourceToOutput; public: static const char* name() { return "BrsRenderer"; } using Input = _base::DefaultInput; class Source; struct SourceChannel; class Output; class RenderFunction; BrsRenderer(const apf::parameter_map& params) : _base(params) , _fade(this->block_size()) {} void load_reproduction_setup(); APF_PROCESS(BrsRenderer, _base) { this->_process_list(_source_list); } private: apf::raised_cosine_fade _fade; }; struct BrsRenderer::SourceChannel : apf::has_begin_and_end , apf::conv::Output { explicit SourceChannel(const apf::conv::Input& in) : apf::conv::Output(in) {} // out-of-class definition because of cyclic dependencies with Source void update(); void convolve_and_more(sample_type weight); apf::CombineChannelsResult::type crossfade_mode; sample_type new_weighting_factor; }; class BrsRenderer::Source : public _base::Source { public: Source(const Params& p) : _base::Source(p) , _weighting_factor(-1.0f) , _brtf_index(size_t(-1)) { SndfileHandle ir_file = apf::load_sndfile(p.get("properties_file") , this->parent.sample_rate(), 0); size_t no_of_channels = ir_file.channels(); if (no_of_channels % 2 != 0) { throw std::logic_error( "Number of channels in BRIR file must be a multiple of 2!"); } _angles = no_of_channels / 2; size_t size = ir_file.frames(); using matrix_t = apf::fixed_matrix; auto ir_data = matrix_t(size, no_of_channels); // TODO: check return value? ir_file.readf(ir_data.data(), size); size_t block_size = this->parent.block_size(); auto temp = apf::conv::Transform(block_size); size_t partitions = apf::conv::min_partitions(block_size, size); _brtf_set.reset(new brtf_set_t(no_of_channels, block_size, partitions)); auto target = _brtf_set->begin(); for (const auto& slice: ir_data.slices) { temp.prepare_filter(slice.begin(), slice.end(), *target++); } assert(target == _brtf_set->end()); _convolver_input.reset(new apf::conv::Input(block_size, partitions)); this->sourcechannels.reserve(2); this->sourcechannels.emplace_back(*_convolver_input); this->sourcechannels.emplace_back(*_convolver_input); } APF_PROCESS(Source, _base::Source) { _convolver_input->add_block(_input.begin()); _weighting_factor = this->weighting_factor; float azi = this->parent.state.reference_orientation.get().azimuth; // TODO: get reference offset! // get BRTF index from listener orientation // (source positions are NOT considered!) // 90 degree is in the middle of index 0 _brtf_index = size_t(apf::math::wrap( (azi - 90.0f) * float(_angles) / 360.0f + 0.5f, float(_angles))); using namespace apf::CombineChannelsResult; auto crossfade_mode = apf::CombineChannelsResult::type(); // Check on one channel only, filters are always changed in parallel bool queues_empty = this->sourcechannels[0].queues_empty(); if (_weighting_factor.both() == 0) { crossfade_mode = nothing; } else if (queues_empty && !_weighting_factor.changed() && !_brtf_index.changed()) { crossfade_mode = constant; } else if (_weighting_factor.old() == 0) { crossfade_mode = fade_in; } else if (_weighting_factor == 0) { crossfade_mode = fade_out; } else { crossfade_mode = change; } for (size_t i = 0; i < 2; ++i) { if (crossfade_mode == nothing || crossfade_mode == fade_in) { // No need to convolve with old values } else { this->sourcechannels[i].convolve_and_more(_weighting_factor.old()); } if (!queues_empty) this->sourcechannels[i].rotate_queues(); if (_brtf_index.changed()) { // left and right channels are interleaved this->sourcechannels[i].set_filter((*_brtf_set)[2 * _brtf_index + i]); } this->sourcechannels[i].crossfade_mode = crossfade_mode; this->sourcechannels[i].new_weighting_factor = _weighting_factor; } assert(_brtf_index.exactly_one_assignment()); assert(_weighting_factor.exactly_one_assignment()); } private: using brtf_set_t = apf::fixed_vector; std::unique_ptr _brtf_set; apf::BlockParameter _weighting_factor; apf::BlockParameter _brtf_index; std::unique_ptr _convolver_input; size_t _angles; // Number of angles in BRIR file }; void BrsRenderer::SourceChannel::update() { this->convolve_and_more(this->new_weighting_factor); } void BrsRenderer::SourceChannel::convolve_and_more(sample_type weight) { _begin = this->convolve(weight); _end = _begin + this->block_size(); } class BrsRenderer::RenderFunction { public: RenderFunction() : _in(0) {} apf::CombineChannelsResult::type select(SourceChannel& in) { _in = ∈ return in.crossfade_mode; } void update() { assert(_in); _in->update(); } private: SourceChannel* _in; }; class BrsRenderer::Output : public _base::Output { public: Output(const Params& p) : _base::Output(p) , _combiner(this->sourcechannels, this->buffer, this->parent._fade) {} APF_PROCESS(Output, _base::Output) { _combiner.process(RenderFunction()); } private: apf::CombineChannelsCrossfadeCopy, buffer_type , apf::raised_cosine_fade> _combiner; }; void BrsRenderer::load_reproduction_setup() { // TODO: generalize this for all headphone-based renderers! // TODO: read settings from proper reproduction system auto params = Output::Params(); const std::string prefix = this->params.get("system_output_prefix", ""); if (prefix != "") { // TODO: read target from proper reproduction file params.set("connect_to", prefix + "1"); } this->add(params); if (prefix != "") { params.set("connect_to", prefix + "2"); } this->add(params); } } // namespace ssr #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'\:'=' ssr-0.4.2/src/coding_style.txt000066400000000000000000000162661236416011200163460ustar00rootroot00000000000000// CODING STYLE // following rules should be obeyed when writing new SSR code and overhauling // old code. // there are always exceptional cases where these rules don't apply, but they // should always be taken into consideration. // maximum line length 80 characters (using 2 spaces for each indentation). // no TAB characters are used! // typenames get a '_t' postfixed if appropriate typedef int my_counter_t; // variable names in lowercase letters, words separated by underscore (_) my_counter_t my_counter = 3; // in the most cases, full words should be used. no abbreviations. sr_it = fr_it + bl_sz; // bad. second_read_iterator = first_read_iterator + block_size; // better. // opening brace on new line. Always on same indentation level as closing brace. // exception: empty braces: {} if (something) { a = c; do_something(); } else // comment for the "else" part { a = b; do_something_else(); // no empty line before closing brace } // if (and only if) it can be written in one line, braces can be omitted: if (something) a = c; // PARENTHESES, SPACES // // no space between function name and opening parenthesis, // nor after ( and [ // nor before , ) ] and ; // in most cases, no space before [ // no space before comma but 1 space afterwards. my_function ( arg1, arg2 ) ; // bad my_function(arg1, arg2); // better // but: spaces after if, for, while, switch, ... // spaces around operators are generally recommended, but sometimes it looks // nicer without ... result = (a + b) * factor; // never use more than one empty line. // * and & are next to the types, not next to the variables float *data; // bad, old-school C-style float* data; // better // CLASSES // // traditional class names start with a capital letter per word (CamelCase), // no underscores (_), no hideous prefix like in 'CMyClass'. // "special" classes (traits, iterators, policies, ...) can be in lowercase and // may use underscores. class MyClass : public MyBaseClass { public: // no empty line between public/protected/private and following line // but empty line before it, except if there is an opening brace ({) MyClass(); // similar functions grouped together without newlines void my_function1(int my_argument); bool my_function2(const std::string my_string); // also add "virtual" in inherited functions (once virtual - always virtual) virtual my_base_function(int base_argument); int public_val1; private: // private (and protected) member variables and member functions are // "uglified" with '_' int _priv_val1; int _priv_val2; } // constructor initiator lists // // first line starts with : all others with , // each member variable gets its own line. MyClass::MyClass() : public_val1(99) , _priv_val1(129) , _priv_val2(0) {} // within the class implementation, non-uglified functions/variables are used // with a prefixed "this->". // This is redundant, but it helps to show where a function/variable belongs to. my_function1(2); // unclear where this function comes from (is it global?) this->my_function1(3); // better. _priv_val1 = 42; // clear: it has to be a class member (because uglified = private/protected) // NAMESPACES // // namespaces follow the same naming conventions as classes, but names can be in // lowercase letters. // there is no extra indentation level for a namespace! namespace mynamespace { int my_namespace_member(int arg); } // if a namespace member is implemented in a .cpp file, the namespace is // prepended to the function name mynamespace::my_namespace_member(int arg) { return 42; } // TYPE CASTING // // use C++ casting instead of C-style casting float value; int step; step = (int)value; // not good, this should only be used in C step = static_cast(value); // better // LINE BREAKS // // as mentioned before, maximum line length should be 80 characters. // if a function definition doesn't fit on a line, consider breaking after the // return type. // if lines are broken at operators or punctuation marks, those should end up at // the beginning of the second line. int my_very_special_and_long_function(my_rather_special_data_t input1 , my_rather_special_data_t input2) { my_very_long_result = 27 * my_other_very_special_and_long_function(input1) + my_other_very_special_and_long_function(input2); return my_very_long_result * my_very_long_result; } // if the declaration of a for-loop doesn't fit on one line, put each semicolon // on a new line for (MyContainerClass::iterator i = my_container.begin() ; i != my_container.end() ; ++i) { do_something(*i); } // template definitions always go on their own line, return types, especially if // they are nested types, may also use their own line template T::my_inner_type some_function(const T& input) { // ... return something; } // COMPILER WARNINGS // // compiler warnings should be taken seriously. // // if a function has to be defined but is not implemented, one can use following // workaround to avoid the warning about an unused variable: void my_function(int unused_arg) { (void)unused_arg; // no "unused variable" warning is shown. } // this is another possibility: void my_function(int /* unused_arg */) {} // at least following warnings should be used: // -W -Wall -pedantic -Wpointer-arith -Wcast-align -Wwrite-strings // -Wredundant-decls -Wno-long-long -Wconversion // // shadowing should be avoided and can be checked by -Wshadow // // suggestions from Meyers' "Effective C++" can be activated by -Weffc++ // COMMENTS // // one-line comments should always use "//". // if a comment is on the same line as actual source code, it's separated by two // spaces. // multi-line comments should always have an empty line before them. // // C-style comments (/* ... */) should only be used for // function/class/file/...-headers (mainly with Doxygen) and not within // functions. // Remember: C-style comments cannot be nested! #if 0 Code can also be deactivated by surrounding it with #if 0 ... #endif #endif // DOXYGEN COMMENTS // // everything should be documented, doxygen should run without warnings. // // the abovementioned rules for C and C++-style comments apply. // especially, there should be an empty line before multi-line comments. // C-style comments should only be used for class and function documentation and // should have more or less this form: /** Brief description (ending with a dot!). * Afterwards, there can be a longer description. * This kind of comments should only be used if there are at least three lines * of text. * @tparam T Template parameter * @param x Input parameter * @param[out] y Parameter used as output * @param[in,out] z Parameter used as input and output * @return Documentation of the return value * @throw std::runtime_error Documentation of the exception **/ template T example_function(const T& x, T& y, T& z) { y = x + z++; if (y == z) throw std::runtime_error("y and z are equal!"); return x * y; } // MODELINES // // all source files should include a modeline for Vim as shown in the last two // lines of this file. // Only necessary for this textfile: vim:syntax=cpp.doxygen // Settings for Vim (http://www.vim.org/), please do not remove: // vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent ssr-0.4.2/src/configuration.cpp000066400000000000000000000606721236416011200164750ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Command line parsing, and config-file related stuff (implementation). #ifdef HAVE_CONFIG_H #include // for ENABLE_*, HAVE_*, WITH_* #endif #ifdef ENABLE_ISATTY #include #endif #include // for assert() #include // for getopt_long() #include // for getenv(), ... #include #include #include "configuration.h" #include "posixpathtools.h" #include "apf/stringtools.h" #include "ssr_global.h" // for ssr::verbose, WARNING(), ... #include "xmlparser.h" // TODO: move this somewhere else using posixpathtools::make_path_relative_to_current_dir; using apf::str::S2A; // only needed in this file enum CONFIG_ERRORS {CONFIG_SUCCESS, CONFIG_NOVALUE, CONFIG_EMPTY, CONFIG_NOQUOTE, CONFIG_QUOTE}; const int BUF_SIZE = 4096; namespace // anonymous { /// show version and compiled-in features void print_version_details() { #ifdef ENABLE_ISATTY if (isatty(1)) #endif { std::cout << "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" "It's ... " << std::flush; sleep(3); std::cout << "the "; } std::cout << PACKAGE_STRING "\n" SSR_COPYRIGHT << std::endl; #ifdef ENABLE_ISATTY if (isatty(1)) { std::cout << "\nFollowing compile-time features are activated: |" #ifndef NDEBUG "debug mode|" #endif #ifdef ENABLE_GUI "GUI|" #endif #ifdef ENABLE_IP_INTERFACE "IP interface|" #endif #ifdef ENABLE_INTERSENSE "InterSense " #ifdef HAVE_INTERSENSE_404 "(>= v4.04)|" #else "(< v4.04)|" #endif #endif // ENABLE_INTERSENSE #ifdef ENABLE_POLHEMUS "Polhemus|" #endif #ifdef ENABLE_VRPN "VRPN|" #endif #ifdef ENABLE_RAZOR "Razor AHRS|" #endif #ifdef ENABLE_ECASOUND "Ecasound|" #endif "\n"; } #endif std::cout << "\n" SSR_AUTHORS "\n\n"; } } /** parse command line options and configuration file(s) * @param argc number of command line arguments. * @param argv the arguments themselves. * @param conf structure with the configuration options * @return @b true if main program shall continue (It shall exit if e.g. only * version information or the help screen was shown). **/ ssr::conf_struct ssr::configuration(int& argc, char* argv[]) { // TODO: Move this somewhere else? Now it's used for reproduction setup and // scene, mabe make two separate instances? XMLParser::Init(); conf_struct conf; #ifndef NDEBUG // Because of this warning, "make check" fails for debug builds (on purpose). WARNING(argv[0] << " was compiled for debugging!"); #endif // hard coded default values: #ifdef ENABLE_GUI conf.gui = true; #else conf.gui = false; #endif #ifdef ENABLE_IP_INTERFACE conf.ip_server = true; #else conf.ip_server = false; #endif conf.server_port = 4711; conf.freewheeling = false; conf.scene_file_name = ""; conf.renderer_params.set("reproduction_setup" , SSR_DATA_DIR"/default_setup.asd"); conf.xml_schema = SSR_DATA_DIR"/asdf.xsd"; conf.audio_recorder_file_name = ""; // default: no recording conf.input_port_prefix = "system:capture_"; conf.output_port_prefix = "system:playback_"; conf.path_to_gui_images = SSR_DATA_DIR"/images"; conf.path_to_scene_menu = "./scene_menu.conf"; conf.renderer_params.set("amplitude_reference_distance", 3); // meters // for WFS renderer conf.renderer_params.set("prefilter_file" , SSR_DATA_DIR"/default_wfs_prefilter.wav"); conf.renderer_params.set("delayline_size", 100000); // in samples conf.renderer_params.set("initial_delay", 1000); // in samples // for binaural renderer conf.renderer_params.set("hrir_size", 0); // "0" means use all that are there conf.renderer_params.set("hrir_file", SSR_DATA_DIR"/default_hrirs.wav"); // for AAP renderer conf.renderer_params.set("ambisonics_order", 0); // "0" means use maximum that makes sense conf.renderer_params.set("in_phase", false); conf.tracker = ""; // USB ports have to be checked first! conf.tracker_ports = "/dev/ttyUSB0 /dev/ttyUSB1 /dev/ttyS0 /dev/tty.usbserial-00001004 /dev/tty.usbserial-00002006"; conf.loop = false; // temporary solution! // load system-wide config file (Mac) load_config_file("/Library/SoundScapeRenderer/ssr.conf",conf); // load system-wide config file (Linux et al) load_config_file("/etc/ssr.conf",conf); // load user config file (Mac) std::string filename = getenv("HOME"); filename += "/Library/SoundScapeRenderer/ssr.conf"; load_config_file(filename.c_str(),conf); // load user config file (Linux et al.) filename = getenv("HOME"); filename += "/.ssr/ssr.conf"; load_config_file(filename.c_str(),conf); const std::string usage_string = "Usage: " + std::string(argv[0]) + " [OPTIONS] \n"; const std::string help_string = "\nThe SoundScape Renderer (SSR) is a tool for real-time " "spatial audio reproduction\n" "providing a variety of rendering algorithms.\n" "\n" "Options:\n\n" "Renderer-specific options:\n" " --hrirs=FILE Load HRIRs for binaural renderer from FILE\n" " --hrir-size=N Truncate HRIRs to length N\n" " --prefilter=FILE\n" " Load WFS prefilter from FILE\n" " -o, --ambisonics-order=VALUE\n" " Ambisonics order to use for AAP (default: maximum)\n" " --in-phase-rendering\n" " Use in-phase rendering for AAP renderer\n" "\n" "JACK options:\n" " -n, --name=NAME Set JACK client name to NAME\n" " --input-prefix=PREFIX\n" " Input port prefix (default: \"system:capture_\")\n" " --output-prefix=PREFIX\n" " Output port prefix (default: \"system:playback_\")\n" " -f, --freewheel Use JACK in freewheeling mode\n" "\n" "General options:\n" " -c, --config=FILE Read configuration from FILE\n" " -s, --setup=FILE Load reproduction setup from FILE\n" " --threads=N Number of audio threads (default: auto)\n" " -r, --record=FILE Record the audio output of the renderer to FILE\n" #ifndef ENABLE_ECASOUND " (disabled at compile time!)\n" #endif // TODO: --loop is a temporary option, should rather be done in scene file " --loop Loop all audio files\n" " --master-volume-correction=VALUE\n" " Correction of the master volume in dB " "(default: 0 dB)\n" #ifdef ENABLE_IP_INTERFACE " -i, --ip-server[=PORT]\n" " Start IP server (default on),\n" " a port number can be specified (default 4711)\n" " -I, --no-ip-server Don't start IP server\n" #else " -i, --ip-server Start IP server (not enabled at compile time!)\n" " -I, --no-ip-server Don't start IP server (default)\n" #endif #ifdef ENABLE_GUI " -g, --gui Start GUI (default)\n" " -G, --no-gui Don't start GUI\n" #else " -g, --gui Start GUI (not enabled at compile time!)\n" " -G, --no-gui Don't start GUI (default)\n" #endif #if defined(ENABLE_INTERSENSE) || defined(ENABLE_POLHEMUS) || defined(ENABLE_VRPN) || defined(ENABLE_RAZOR) " -t, --tracker=TYPE Select head tracker, possible value(s):\n" " " #if defined(ENABLE_POLHEMUS) " polhemus" #endif #if defined(ENABLE_VRPN) " vrpn" #endif #if defined(ENABLE_INTERSENSE) " intersense" #endif #if defined(ENABLE_RAZOR) " razor" #endif "\n" " --tracker-port=PORT\n" " Port name/number of head tracker, e.g. /dev/ttyS1\n" #else " -t, --tracker Select tracker (not enabled at compile time!)\n" #endif " -T, --no-tracker Don't use a head tracker (default)\n" "\n" " -h, --help Show help and exit\n" " -v, --verbose Increase verbosity level (up to -vvv)\n" " -V, --version Show version information and exit\n" #ifdef ENABLE_GUI //"\n" //" -- All arguments after \"--\" are passed on to the GUI.\n" #else //"\n" //" -- All arguments after \"--\" are ignored.\n" #endif "\n" "Report bugs to: <" PACKAGE_BUGREPORT ">\n" "SSR home page: <" PACKAGE_URL ">\n" ; // the special argument "--" forces the end of option scanning const struct option longopts[] = { {"hrirs", required_argument, nullptr, 0 }, {"hrir-size", required_argument, nullptr, 0 }, {"prefilter", required_argument, nullptr, 0 }, {"ambisonics-order",required_argument,nullptr,'o'}, {"in-phase-rendering", no_argument, nullptr, 0 }, {"name", required_argument, nullptr, 'n'}, {"input-prefix", required_argument, nullptr, 0 }, {"output-prefix",required_argument, nullptr, 0 }, {"freewheel", no_argument, nullptr, 'f'}, {"config", required_argument, nullptr, 'c'}, {"setup", required_argument, nullptr, 's'}, {"threads", required_argument, nullptr, 0 }, {"record", required_argument, nullptr, 'r'}, {"loop", no_argument, nullptr, 0 }, {"master-volume-correction", required_argument, nullptr, 0}, {"ip-server", optional_argument, nullptr, 'i'}, {"no-ip-server", no_argument, nullptr, 'I'}, {"gui", no_argument, nullptr, 'g'}, {"no-gui", no_argument, nullptr, 'G'}, {"tracker", required_argument, nullptr, 't'}, {"no-tracker", no_argument, nullptr, 'T'}, {"tracker-port", required_argument, nullptr, 0 }, {"help", no_argument, nullptr, 'h'}, {"verbose", no_argument, nullptr, 'v'}, {"version", no_argument, nullptr, 'V'}, // for some obscure reasons, the last element has to be filled with zeroes {0, 0, 0, 0} }; // one colon: required argument; two colons: optional argument // if first character is '-', non-option arguments return 1 (see case 1 below) const char *optstring = "-c:fgGhi::In:o:r:s:t:TvV?"; int opt; int longindex = 0; int non_option_parameters = 0; while ((opt = getopt_long(argc, argv, optstring, longopts, &longindex)) != -1) { switch (opt) { case 0: // long option without a short arg if (strcmp("hrirs", longopts[longindex].name) == 0) { conf.renderer_params.set("hrir_file", optarg); } else if (strcmp("hrir-size", longopts[longindex].name) == 0) { conf.renderer_params.set("hrir_size", optarg); assert(conf.renderer_params.get("hrir_size", 0) >= 1); } else if (strcmp("prefilter", longopts[longindex].name) == 0) { conf.renderer_params.set("prefilter_file", optarg); } else if (strcmp("in-phase-rendering", longopts[longindex].name) == 0) { conf.renderer_params.set("in_phase", true); } else if (strcmp("input-prefix", longopts[longindex].name) == 0) { conf.input_port_prefix = optarg; } else if (strcmp("output-prefix", longopts[longindex].name) == 0) { conf.output_port_prefix = optarg; } else if (strcmp("threads", longopts[longindex].name) == 0) { conf.renderer_params.set("threads", optarg); } else if (strcmp("loop", longopts[longindex].name) == 0) { conf.loop = true; } else if (strcmp("master-volume-correction",longopts[longindex].name)==0) { conf.renderer_params.set("master_volume_correction", optarg); } else if (strcmp("tracker-port", longopts[longindex].name) == 0) { conf.tracker_ports = optarg; } break; case 1: // if first character of optstring is '-', // non-option parameters (e.g. filenames) arrive here. non_option_parameters++; if (non_option_parameters == 1) { conf.scene_file_name = optarg; } else { ERROR("For now, only one non-option parameter " "(= scene file) is allowed!"); WARNING("Ignoring '" << optarg << "'."); } break; case 'c': if (load_config_file(optarg, conf) == EXIT_FAILURE) { throw std::logic_error("Couldn't load config file \"" + std::string(optarg) + "\"!"); } break; case 'f': conf.freewheeling = true; break; case 'g': // enable GUI conf.gui = true; break; case 'G': conf.gui = false; break; case 'h': // show help message std::cout << usage_string << help_string; exit(EXIT_SUCCESS); break; case 'i': conf.ip_server = true; #ifdef ENABLE_IP_INTERFACE if (optarg) { if (!S2A(optarg, conf.server_port)) { ERROR("Invalid server port specified!"); } } #endif break; case 'I': conf.ip_server = false; break; case 'n': conf.renderer_params.set("name", optarg); break; case 'o': conf.renderer_params.set("ambisonics_order", atoi(optarg)); break; case 'r': conf.audio_recorder_file_name = optarg; break; case 's': conf.renderer_params.set("reproduction_setup", optarg); break; case 't': conf.tracker = optarg; break; case 'T': conf.tracker = ""; break; case 'v': // increase verbosity ssr::verbose++; break; case 'V': print_version_details(); exit(EXIT_SUCCESS); break; case '?': // here we deal with unknown/invalid options // getopt() already prints an error message for unknown options std::cout << usage_string; std::cout << "Type '" << argv[0] << " --help' " "for more information.\n\n"; exit(EXIT_FAILURE); break; default: // should not happen? throw std::logic_error("BUG: parsing input arguments."); } } // remove arguments up to (and including) '--', but leave argv[0]. // "optind" is set to the index of the next argument by getopt_long() for (int i = optind; i < argc; ++i) { argv[1 + i - optind] = argv[i]; } argc = 1 + argc - optind; conf.renderer_params.set("xml_schema", conf.xml_schema); if (!conf.freewheeling) { conf.renderer_params.set("system_output_prefix", conf.output_port_prefix); } VERBOSE2("Requested renderer settings:"); for (const auto& entry: conf.renderer_params) { VERBOSE2(entry.first << " = " << entry.second); } return conf; } /******************************************************************************/ /* this function determines whether a line is empty or a comment */ static int is_comment_or_empty(const char *line){ while (*line && isspace(*line)) line++; return (*line == '#') || (!*line); } /******************************************************************************/ /* This function takes a line from the configuration file and splits * it into a key- and value-pair. If a problem occures, the function * returns a non-zero value and the user will be scolded. * NOTE: no buffer overflows in the while-loops because key and * value are as big as line is. */ static int parse(const char *line, char *key, char *value) { /* is the value-string quoted? */ int quoted = 0; /* skip leading whitespaces */ while (*line && isspace(*line)) line++; /* read the key which is a single word, so stop when * whitespaces or the = occurs */ while (*line && !isspace(*line) && *line != '=') *(key++) = *(line++); /* null-terminate the key-string */ *key = '\0'; /* skip whitespaces between key and = */ while (*line && isspace(*line)) line++; /* huh, no value? */ if (!*line || *line != '=') return CONFIG_NOVALUE; /* skip whitespaces and the = */ while (*line && (isspace(*line) || *line == '=')) line++; /* empty value but no quotes -> warning */ if (!*line) return CONFIG_EMPTY; /* if the value-string is quoted, skip the " */ if (*line && *line == '"') { quoted = 1; line++; } /* read the value until end of line, newline, carriage return * or a terminating " */ while (*line && *line != '"' && *line != '\r' && *line != '\n') *(value++) = *(line++); /* null-terminate the value-string */ *value = '\0'; /* quoted value but no end of quote? */ if (quoted && *line != '"') return CONFIG_NOQUOTE; /* an ending quote without beginning? */ if (!quoted && *line == '"') return CONFIG_QUOTE; return CONFIG_SUCCESS; } /******************************************************************************/ /* Read the .conf file with Server Settings */ int ssr::load_config_file(const char *filename, conf_struct& conf){ FILE *file; char line[BUF_SIZE], key[BUF_SIZE], value[BUF_SIZE]; char line_number=0; if ((file = fopen(filename, "r")) == nullptr) { if (verbose) fprintf(stderr,"Cannot open %s !\n",filename); return (EXIT_FAILURE); //need to create one? } /* we can read the file */ while (fgets(line,sizeof(line),file)){ line_number++; /* skip comments and empty lines */ if (is_comment_or_empty(line))continue; /* parse line and test for errors */ switch(parse(line, key, value)) { case CONFIG_SUCCESS: break; /* no value specified: error */ case CONFIG_NOVALUE: fprintf(stderr, "%s:%u: no value found\n",filename, line_number); return EXIT_FAILURE; /* quoted string not finished: error */ case CONFIG_NOQUOTE: fprintf(stderr, "%s:%u: no end of quote found\n",filename, line_number); return EXIT_FAILURE; /* ending quote without starting a quoted string: error */ case CONFIG_QUOTE: fprintf(stderr, "%s:%u: end of quote found, but no start\n", filename, line_number); return EXIT_FAILURE; /* empty value not quoted: warning */ case CONFIG_EMPTY: fprintf(stderr, "%s:%u: suggest quotes around empty values\n", filename, line_number); break; }//switch VERBOSE2(key << " = " << value); if (!strcmp(key, "NAME")) { conf.renderer_params.set("name", value); } else if (!strcmp(key, "NUMBER_OF_THREADS") || !strcmp(key, "THREADS")) { conf.renderer_params.set("threads", value); } else if (!strcmp(key, "MASTER_VOLUME_CORRECTION")) { conf.renderer_params.set("master_volume_correction", value); } else if (!strcmp(key, "STANDARD_AMPLITUDE_REFERENCE_DISTANCE")) { conf.renderer_params.set("amplitude_reference_distance", value); } else if (!strcmp(key, "SCENE_FILE_NAME")) { conf.scene_file_name = make_path_relative_to_current_dir(value, filename); } else if (!strcmp(key, "SCENE_MENU")) { conf.path_to_scene_menu = make_path_relative_to_current_dir(value, filename); } else if (!strcmp(key, "PLAYBACK_SETUP_FILE_NAME")) { conf.renderer_params.set("reproduction_setup" , make_path_relative_to_current_dir(value, filename)); } else if (!strcmp(key, "XML_SCHEMA_FILE_NAME")) { conf.xml_schema = make_path_relative_to_current_dir(value, filename); } else if (!strcmp(key, "AUDIO_RECORDER_FILE_NAME")) { conf.audio_recorder_file_name = make_path_relative_to_current_dir(value, filename); } else if (!strcmp(key, "RENDERER_TYPE")) { WARNING("\"RENDERER_TYPE\" is deprecated, don't use it anymore!"); } else if (!strcmp(key, "WFS_PREFILTER")) { conf.renderer_params.set("prefilter_file" , make_path_relative_to_current_dir(value, filename)); } else if (!strcmp(key, "DELAYLINE_SIZE")) { conf.renderer_params.set("delayline_size", value); assert(conf.renderer_params.get("delayline_size") >= 0); } else if (!strcmp(key, "INITIAL_DELAY")) { conf.renderer_params.set("initial_delay", value); assert(conf.renderer_params.get("initial_delay") >= 0); } else if (!strcmp(key, "HRIR_FILE_NAME")) { conf.renderer_params.set("hrir_file" , make_path_relative_to_current_dir(value, filename)); } else if (!strcmp(key, "HRIR_SIZE")) { conf.renderer_params.set("hrir_size", value); assert(conf.renderer_params.get("hrir_size", 0) >= 1); } else if (!strcmp(key, "AMBISONICS_ORDER")) { conf.renderer_params.set("ambisonics_order", atoi(value)); } else if (!strcmp(key, "IN_PHASE_RENDERING")) { if (!strcasecmp(value,"TRUE")) conf.renderer_params.set("in_phase", true); else if (!strcasecmp(value,"true")) conf.renderer_params.set("in_phase", true); else if (!strcasecmp(value,"FALSE")) conf.renderer_params.set("in_phase", false); else if (!strcasecmp(value,"false")) conf.renderer_params.set("in_phase", false); else ERROR("I don't understand the option '" << value << "' for in-phase rendering."); } else if (!strcmp(key, "INPUT_PREFIX")) { conf.input_port_prefix = value; } else if (!strcmp(key, "OUTPUT_PREFIX")) { conf.output_port_prefix = value; } else if (!strcmp(key, "PATH_TO_GUI_IMAGES")) { conf.path_to_gui_images = make_path_relative_to_current_dir(value, filename); } else if (!strcmp(key, "FREEWHEEL")) { if (!strcasecmp(value, "yes")) conf.freewheeling = true; else conf.freewheeling = false; } else if (!strcmp(key, "GUI")) { if (!strcasecmp(value, "on")) conf.gui = true; else conf.gui= false; } else if (!strcmp(key, "NETWORK_INTERFACE")) { #ifdef ENABLE_IP_INTERFACE if (!strcasecmp(value, "on")) conf.ip_server= true; else conf.ip_server= false; #endif } else if (!strcmp(key, "SERVER_PORT")) { #ifdef ENABLE_IP_INTERFACE conf.server_port = atoi(value); #endif } else if (!strcmp(key, "VERBOSE")) { ssr::verbose = atoi(value); } else if (!strcmp(key, "TRACKER")) { conf.tracker = value; } else if (!strcmp(key, "TRACKER_PORTS")) { conf.tracker_ports = value; } else if (!strcmp(key, "LOOP")) { if (!strcasecmp(value, "yes")) conf.loop = true; else conf.loop = false; } else { printf("%s:%u unknown option \"%s\"\n",filename, line_number, key); return EXIT_FAILURE; } }//while return EXIT_SUCCESS; } // 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'\:'=' ssr-0.4.2/src/configuration.h000066400000000000000000000104521236416011200161310ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Command line parsing, and config-file related stuff (definition). #ifndef SSR_CONFIGURATION_H #define SSR_CONFIGURATION_H #include #include "apf/parameter_map.h" namespace ssr { /// program settings from config-file and command line struct conf_struct { apf::parameter_map renderer_params; std::string exec_name; ///< name of executable (without path) float stand_ampl_ref_dist; ///< distance where plane sources are ///< as loud as the other source types bool gui; ///< start GUI? bool ip_server; ///< start IP server? std::string tracker; ///< type of head tracker (or "") std::string tracker_ports; ///< space-separated serial ports bool freewheeling; ///< use JACK's freewheeling mode? std::string scene_file_name; ///< scene file to load //std::string playback_setup_file_name; ///< reproduction setup to load std::string xml_schema; ///< schema file to validate XML files std::string audio_recorder_file_name; ///< output file for audio recorder std::string input_port_prefix; ///< e.g. "alsa_pcm:capture" std::string output_port_prefix; ///< e.g. "alsa_pcm:playback" std::string path_to_gui_images; ///< dto. std::string path_to_scene_menu; ///< path to scene_menu.conf int server_port; ///< listening port /// size of delay line (in samples) int wfs_delayline_size; /// maximum negative delay (in samples, wfs_initial_delay >= 0) int wfs_initial_delay; //int hrir_size; //std::string hrir_file_name; int ambisonics_order; bool in_phase_rendering; bool loop; ///< temporary solution for looping sound files }; conf_struct configuration(int& argc, char* argv[]); // static int parse(const char *line, char *key, char *value); // static int is_comment_or_empty(const char *line); int load_config_file(const char *filename, conf_struct& conf); } // namespace ssr #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'\:'=' ssr-0.4.2/src/controller.h000066400000000000000000001477741236416011200154670ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// %Controller. #ifndef SSR_CONTROLLER_H #define SSR_CONTROLLER_H #ifdef HAVE_CONFIG_H #include // for ENABLE_*, HAVE_*, WITH_* #endif // TODO: move these includes to a more suitable location? #include "apf/jack_policy.h" #include "apf/posix_thread_policy.h" #define SSR_QUERY_POLICY apf::enable_queries #include // temporary hack! #include "ssr_global.h" #include "publisher.h" #ifdef ENABLE_ECASOUND #include "audioplayer.h" #include "audiorecorder.h" #endif #include "xmlparser.h" #include "configuration.h" #ifdef ENABLE_GUI #include "qgui.h" #endif #ifdef ENABLE_IP_INTERFACE #include "server.h" #endif #include "tracker.h" #ifdef ENABLE_INTERSENSE #include "trackerintersense.h" #endif #ifdef ENABLE_POLHEMUS #include "trackerpolhemus.h" #endif #ifdef ENABLE_VRPN #include "trackervrpn.h" #endif #ifdef ENABLE_RAZOR #include "trackerrazor.h" #endif #include "scene.h" // for Scene #include "rendersubscriber.h" #include "posixpathtools.h" #include "apf/math.h" #include "apf/stringtools.h" using Node = XMLParser::Node; ///< a node of the DOM tree namespace ssr { namespace internal { inline void print_about_message() { const std::string about_string = " ___ \n" " / ___ \n" " ___/ / ___ \n" " ___/ / " PACKAGE_STRING "\n" " / \n" " \n" "Website: <" PACKAGE_URL ">\n" "Contact: <" PACKAGE_BUGREPORT ">\n" "\n" SSR_COPYRIGHT ; std::cout << about_string << std::endl; } } // namespace internal /** %Controller class. * Has a list of objects which receive messages on events like position change * etc. With this list a kind of Publisher/Subscriber pattern is realized. **/ template class Controller : public Publisher { public: using loudspeaker_id_t = Loudspeaker::container_t::size_type; /// ctor Controller(int argc, char *argv[]); virtual ~Controller(); ///< dtor bool run(); void set_source_output_levels(id_t id, float* first, float* last); virtual bool load_scene(const std::string& scene_file_name); virtual bool save_scene_as_XML(const std::string& filename) const; virtual void start_processing(); virtual void stop_processing(); virtual void new_source(const std::string& name, Source::model_t model , const std::string& file_name_or_port_number, int channel = 0 , const Position& position = Position(), const bool pos_fix = false , const Orientation& orientation = Orientation() , const bool or_fix = false , const float gain = 1.0f, const bool muted = false , const std::string& properties_file = ""); virtual void delete_source(id_t id); /// delete all sources in all subscribers virtual void delete_all_sources(); virtual void set_source_position(id_t id, const Position& position); virtual void set_source_orientation(id_t id , const Orientation& orientation); virtual void set_source_gain(id_t id, float gain); virtual void set_source_signal_level(const id_t id , const float level); virtual void set_source_mute(id_t id, bool mute); virtual void set_source_name(id_t id, const std::string& name); virtual void set_source_properties_file(id_t id, const std::string& name); virtual void set_source_model(id_t id, Source::model_t model); virtual void set_source_port_name(id_t id, const std::string& port_name); virtual void set_source_file_name(id_t id, const std::string& file_name); virtual void set_source_file_channel(id_t id, const int& channel); virtual void set_source_position_fixed(id_t id, const bool fixed); virtual void set_reference_position(const Position& position); virtual void set_reference_orientation(const Orientation& orientation); virtual void set_reference_offset_position(const Position& position); virtual void set_reference_offset_orientation(const Orientation& orientation); virtual void set_master_volume(float volume); virtual void set_amplitude_reference_distance(const float dist); virtual void set_master_signal_level(float level); virtual void set_cpu_load(const float load); virtual void publish_sample_rate(const int sample_rate); virtual std::string get_renderer_name() const; virtual bool show_head() const; virtual void transport_start(); virtual void transport_stop(); virtual bool transport_locate(float time); virtual void calibrate_client(); /// update JACK transport state void set_transport_state(const std::pair& state); /// send processing state of the renderer to all subscribers. virtual void set_processing_state(bool state); virtual std::string get_scene_as_XML() const; virtual void subscribe(Subscriber* subscriber); virtual void unsubscribe(Subscriber* subscriber); void set_loop_mode(bool loop) { _loop = loop; } ///< temporary solution! // temporary solution! void deactivate() { _renderer.deactivate(); } private: using output_list_t = typename Renderer::template rtlist_proxy; class query_state; #ifdef ENABLE_ECASOUND /// load the audio recorder and set it to "record enable" mode. void _load_audio_recorder(const std::string& audio_file_name , const std::string& sample_format = "16" , const std::string& client_name = "recorder" , const std::string& input_prefix = "channel"); #endif void _start_tracker(const std::string& type, const std::string& ports = ""); #ifdef ENABLE_GUI int _start_gui(const std::string& path_to_gui_images , const std::string& path_to_scene_menu); #endif int _argc; char** _argv; conf_struct _conf; Scene _scene; /// a list of subscribers using subscriber_list_t = std::vector; /// list of objects that will be notified on all events subscriber_list_t _subscribers; #ifdef ENABLE_GUI std::unique_ptr _gui; #endif Renderer _renderer; query_state _query_state; #ifdef ENABLE_ECASOUND AudioRecorder::ptr_t _audio_recorder; ///< pointer to audio recorder AudioPlayer::ptr_t _audio_player; ///< pointer to audio player #endif std::string _schema_file_name; ///< XML Schema std::string _input_port_prefix; ///< e.g. alsa_pcm:capture #ifdef ENABLE_IP_INTERFACE std::unique_ptr _network_interface; #endif std::unique_ptr _tracker; /// check if audio player is running and start it if necessary bool _audio_player_is_running(); /// Publishing function. /// The first argument is a pointer to a member function of the Subscriber /// class, the rest are arguments to said member function. template inline void _publish(R (Subscriber::*f)(FuncArgs...), Args&&... args) { ScopedLock guard(_subscribers_lock); for (auto& subscriber: _subscribers) { (subscriber->*f)(std::forward(args)...); // ignore return value } } /// helper struct for a source including its source id struct SourceCopy : public Source { typedef std::vector container_t; ///< list of SourceCopys /// type conversion constructor SourceCopy(const std::pair& other) : Source(other.second) // copy ctor of base class , id(other.first) {} id_t id; ///< unique ID, see Scene::source_map_t }; void _add_master_volume(Node& node) const; void _add_transport_state(Node& node) const; void _add_reference(Node& node) const; void _add_position(Node& node , const Position& position, const bool fixed = false) const; void _add_orientation(Node& node, const Orientation& orientation) const; void _add_loudspeakers(Node& node) const; void _add_sources(Node& node, const std::string& filename = "") const; void _add_audio_file_name(Node& node, const std::string& name , int channel) const; bool _create_spontaneous_scene(const std::string& audio_file_name); bool _loop; ///< part of a quick-hack. should be removed some time. std::unique_ptr> _query_thread; typename Renderer::Lock _subscribers_lock; using ScopedLock = typename Renderer::ScopedLock; }; template Controller::Controller(int argc, char* argv[]) : _argc(argc) , _argv(argv) , _conf(configuration(_argc, _argv)) , _renderer(_conf.renderer_params) , _query_state(query_state(*this, _renderer)) , _tracker(nullptr) , _loop(false) { // TODO: signal handling? internal::print_about_message(); _renderer.load_reproduction_setup(); #ifndef ENABLE_IP_INTERFACE if (_conf.ip_server) { throw std::logic_error(_conf.exec_name + " was compiled without IP-server support!\n" "Use the --no-ip-server option to disable the IP-server.\n" "Type '" + _conf.exec_name + " --help' for more information."); } #endif #ifndef ENABLE_GUI if (_conf.gui) { throw std::logic_error(_conf.exec_name + " was compiled without GUI support!\n" "Use the --no-gui option to disable GUI.\n" "Type '" + _conf.exec_name + " --help' for more information."); } #endif if (_conf.ip_server && _conf.freewheeling) { WARNING("Freewheel mode cannot be used together with IP-server. Ignored.\n" "Type '" + _conf.exec_name + " --help' for more information."); _conf.freewheeling = false; } if (_conf.freewheeling && _conf.gui) { WARNING("In 'freewheeling' mode the GUI cannot be used! Disabled.\n" "Type '" + _conf.exec_name + " --help' for more information."); _conf.gui = false; } // temporary solution: this->set_loop_mode(_conf.loop); this->subscribe(&_scene); this->publish_sample_rate(_renderer.sample_rate()); std::vector loudspeakers; _renderer.get_loudspeakers(loudspeakers); _publish(&Subscriber::set_loudspeakers, loudspeakers); // TODO: memory leak, subscriber is never deleted! auto subscriber = new RenderSubscriber(_renderer); this->subscribe(subscriber); #ifdef ENABLE_ECASOUND _load_audio_recorder(_conf.audio_recorder_file_name); #endif if (!this->load_scene(_conf.scene_file_name)) { throw std::runtime_error("Couldn't load scene!"); } if (_conf.freewheeling) { if (!_renderer.set_freewheel(1)) { throw std::runtime_error("Unable to switch to freewheeling mode!"); } } #ifdef ENABLE_IP_INTERFACE if (_conf.ip_server) { VERBOSE("Starting IP Server with port " << _conf.server_port); _network_interface.reset(new Server(*this, _conf.server_port)); _network_interface->start(); } #endif // ENABLE_IP_INTERFACE } template class Controller::query_state { public: query_state(Controller& controller, Renderer& renderer) : _controller(controller) , _renderer(renderer) {} void query() { _state = _renderer.get_transport_state(); _cpu_load = _renderer.get_cpu_load(); auto output_list = output_list_t(_renderer.get_output_list()); _master_level = typename Renderer::sample_type(); for (const auto& out: output_list) { _master_level = std::max(_master_level, out.get_level()); } using source_list_t = typename Renderer::template rtlist_proxy; auto source_list = source_list_t(_renderer.get_source_list()); if (_source_levels.size() == source_list.size()) { auto levels = _source_levels.begin(); for (const auto& source: source_list) { levels->source_id = source.id; levels->source_level = source.get_level(); levels->outputs_available = source.get_output_levels( &*levels->outputs.begin(), &*levels->outputs.end()); ++levels; } _discard_source_levels = false; } else { _discard_source_levels = true; _new_size = source_list.size(); } } void update() { _controller._publish(&Subscriber::set_transport_state, _state); _controller.set_cpu_load(_cpu_load); _controller.set_master_signal_level(_master_level); if (!_discard_source_levels) { for (auto& item: _source_levels) { _controller.set_source_signal_level(item.source_id , item.source_level); // TODO: make this a compile-time decision: if (item.outputs_available) { _controller.set_source_output_levels(item.source_id , &*item.outputs.begin(), &*item.outputs.end()); } } } else { _source_levels.resize(_new_size , SourceLevel(_renderer.get_output_list().size())); } } private: struct SourceLevel { explicit SourceLevel(size_t n) : outputs_available(false) , outputs(n) {} typename Renderer::sample_type source_level; int source_id; bool outputs_available; // this may never be resized: std::vector outputs; }; using source_levels_t = std::vector; Controller& _controller; Renderer& _renderer; std::pair _state; float _cpu_load; typename Renderer::sample_type _master_level; source_levels_t _source_levels; bool _discard_source_levels = true; size_t _new_size = 0; }; template bool Controller::run() { // TODO: make sleep time customizable _query_thread.reset(Renderer::new_scoped_thread( typename Renderer::QueryThread(_renderer._query_fifo), 10 * 1000)); _start_tracker(_conf.tracker, _conf.tracker_ports); if (!_renderer.activate()) { return false; } // CAUTION: this must be called after activate()! // If not, an infinite recursion happens! _renderer.new_query(_query_state); if (_conf.gui) { #ifdef ENABLE_GUI // TEMPORARY!!! this->start_processing(); this->transport_locate(0.0f); //this->transport_start(); if (!_start_gui(_conf.path_to_gui_images, _conf.path_to_scene_menu)) { return false; } #else // this condition has already been checked above! assert(false); #endif // #ifdef ENABLE_GUI } else // without GUI { // TODO: check if IP-server is running // TODO: wait for shutdown command (run forever) // TEMPORARY!!! this->start_processing(); this->transport_locate(0.0f); this->transport_start(); bool keep_running = true; while (keep_running) { switch(fgetc(stdin)) { case 'c': std::cout << "Calibrating client.\n"; this->calibrate_client(); break; case 'p': this->transport_start(); break; case 'q': keep_running = false; break; case 'r': this->transport_locate(0.0f); break; case 's': this->transport_stop(); break; } } } return true; } template Controller::~Controller() { //recorder->disable(); this->stop_processing(); // TODO: check if transport needs to be stopped this->transport_stop(); if (!this->save_scene_as_XML("ssr_scene_autosave.asd")) { ERROR("Couldn't write XML scene! (It's an ugly hack anyway ..."); } { ScopedLock guard(_subscribers_lock); _subscribers.clear(); } // unlock this->deactivate(); } namespace internal { struct PositionPlusBool : Position { PositionPlusBool() : fixed(false) {} explicit PositionPlusBool(Position pos, const bool fixed = false) : Position(pos), fixed(fixed) {} PositionPlusBool(const float x, const float y, const bool fixed = false) : Position(x,y), fixed(fixed) {} bool fixed; }; /// find position in child nodes. /// Traverse through all child nodes of @a node and check for a @b position /// element. /// @param node parent node /// @return std::unique_ptr to the obtained position, empty unique_ptr if no /// position element was found. /// @warning If you want to extract e.g. position and orientation it would be /// more effective to do both (or even more) in one loop. But anyway, this /// seems easier to use. inline std::unique_ptr get_position(const Node& node) { std::unique_ptr temp; // temp = NULL if (!node) return temp; // return NULL for (Node i = node.child(); !!i; ++i) { if (i == "position") { float x, y; bool fixed; // if read operation successful if (apf::str::S2A(i.get_attribute("x"), x) && apf::str::S2A(i.get_attribute("y"), y)) { // "fixed" indicated if (apf::str::S2A(i.get_attribute("fixed"), fixed)) { temp.reset(new PositionPlusBool(x, y, fixed)); } else // "fixed" not indicated { temp.reset(new PositionPlusBool(x, y)); } return temp; // return sucessfully } else { ERROR("Invalid position!"); return temp; // return NULL } // if read operation successful } // if (i == "position") } return temp; // return NULL } /// find orientation in child nodes. /// @param node parent node /// @see get_position inline std::unique_ptr get_orientation(const Node& node) { std::unique_ptr temp; // temp = NULL if (!node) return temp; // return NULL for (Node i = node.child(); !!i; ++i) { if (i == "orientation") { float azimuth; if (apf::str::S2A(i.get_attribute("azimuth"), azimuth)) { temp.reset(new Orientation(azimuth)); return temp; // return sucessfully } else { ERROR("Invalid orientation!"); return temp; // return NULL } } } return temp; // return NULL } /** get attribute of a node. * @param node the node you want to have the attribute of. * @param attribute name of attribute * @param default_value default return value if something goes wrong * @return value default_value on error. **/ template T get_attribute_of_node(const Node& node, const std::string attribute , const T default_value) { if (!node) return default_value; return apf::str::S2RV(node.get_attribute(attribute), default_value); } /** check for file/port * @param node parent node * @param file_name_or_port_number a string where the obtained file name or * port number is stored. * @return channel number, 0 if port was given. * @note on error, @p file_name_or_port_number is set to the empty string "" * and 0 is returned. **/ inline int get_file_name_or_port_number(const Node& node , std::string& file_name_or_port_number) { for (Node i = node.child(); !!i; ++i) { if (i == "file") { file_name_or_port_number = get_content(i); int channel = apf::str::S2RV(i.get_attribute("channel"), 1); // TODO: raise error if channel is negative? assert(channel >= 0); return channel; } else if (i == "port") { file_name_or_port_number = get_content(i); return 0; } } // nothing found: file_name_or_port_number = ""; return 0; } } // end of anonymous namespace template void Controller::subscribe(Subscriber* const subscriber) { ScopedLock guard(_subscribers_lock); _subscribers.push_back(subscriber); } template void Controller::unsubscribe(Subscriber* subscriber) { ScopedLock guard(_subscribers_lock); auto s = std::find(_subscribers.begin(), _subscribers.end(), subscriber); if (s != _subscribers.end()) { _subscribers.erase(s); } else { WARNING("unsubscribe(): given subscriber not found!"); } } template bool Controller::load_scene(const std::string& scene_file_name) { this->stop_processing(); // TODO: get state. // remove all existing sources (if any) this->delete_all_sources(); if (scene_file_name == "") { VERBOSE("No scene file specified. Opening empty scene ..."); return true; } std::string file_extension = posixpathtools::get_file_extension(scene_file_name); if (file_extension == "") { ERROR("File name '" << scene_file_name << "' does not have an extension."); return false; } else if (file_extension == "asd") { XMLParser xp; // load XML parser auto scene_file = xp.load_file(scene_file_name); if (!scene_file) { ERROR("Unable to load scene setup file '" << scene_file_name << "'!"); return false; } if (_conf.xml_schema == "") { ERROR("No schema file specified!"); // TODO: return true and continue anyway? return false; } else if (scene_file->validate(_conf.xml_schema)) { VERBOSE("Valid scene setup (" << scene_file_name << ")."); } else { ERROR("Error validating '" << scene_file_name << "' with schema '" << _conf.xml_schema << "'!"); return false; } XMLParser::xpath_t xpath_result; // GET MASTER VOLUME float master_volume = 0.0f; // dB xpath_result = scene_file->eval_xpath("//scene_setup/volume"); if (xpath_result && !apf::str::S2A(get_content(xpath_result->node()), master_volume)) { WARNING("Invalid master volume specified in scene!"); master_volume = 0.0f; } VERBOSE("Setting master volume to " << master_volume << " dB."); this->set_master_volume(apf::math::dB2linear(master_volume)); // GET AMPLITUDE REFERENCE DISTANCE auto ref_dist = _conf.renderer_params.get( "amplitude_reference_distance"); // throws on error! xpath_result = scene_file->eval_xpath("//scene_setup/amplitude_reference_distance"); if (xpath_result) { if (!apf::str::S2A(get_content(xpath_result->node()), ref_dist)) { WARNING("Invalid amplitude reference distance!"); } } // always use default value when nothing is specified VERBOSE("Setting amplitude reference distance to " << ref_dist << " meters."); this->set_amplitude_reference_distance(ref_dist); // LOAD REFERENCE std::unique_ptr pos_ptr; std::unique_ptr dir_ptr; xpath_result = scene_file->eval_xpath("//scene_setup/reference"); if (xpath_result) { // there should be only one result: if (xpath_result->size() != 1) { ERROR("More than one reference found in scene setup! Aborting."); return false; } pos_ptr = internal::get_position (xpath_result->node()); dir_ptr = internal::get_orientation(xpath_result->node()); } else { VERBOSE("No reference point given in XML file. " "Using standard (= origin)."); } if (!pos_ptr) pos_ptr.reset(new internal::PositionPlusBool()); if (!dir_ptr) dir_ptr.reset(new Orientation(90)); this->set_reference_position(*pos_ptr); this->set_reference_orientation(*dir_ptr); this->set_reference_offset_position(Position()); this->set_reference_offset_orientation(Orientation()); // LOAD SOURCES xpath_result = scene_file->eval_xpath("//scene_setup/source"); if (xpath_result) { for (Node node; (node = xpath_result->node()); ++(*xpath_result)) { std::string name = node.get_attribute("name"); std::string id = node.get_attribute("id"); std::string properties_file = node.get_attribute("properties_file"); properties_file = posixpathtools::make_path_relative_to_current_dir( properties_file, scene_file_name); pos_ptr.reset(); dir_ptr.reset(); pos_ptr = internal::get_position (node); dir_ptr = internal::get_orientation(node); // just for the error message: std::string id_str; if (id != "") id_str = " id: \"" + id + "\""; std::string name_str; if (name != "") name_str = " name: \"" + name + "\""; Source::model_t model = internal::get_attribute_of_node(node, "model", Source::unknown); if (model == Source::unknown) { VERBOSE("Source model not defined!" << id_str << name_str << " Using default (= point source)."); model = Source::point; } if ((model == Source::point) && !dir_ptr) { // orientation is optional for point sources, required for plane waves dir_ptr.reset(new Orientation); } if (!pos_ptr || !dir_ptr) { ERROR("Both position and orientation have to be specified for source" << id_str << name_str << "! Not loaded"); continue; // next source } std::string file_name_or_port_number; int channel = internal::get_file_name_or_port_number(node , file_name_or_port_number); if (channel != 0) // --> soundfile { file_name_or_port_number = posixpathtools::make_path_relative_to_current_dir( file_name_or_port_number, scene_file_name); } float gain_dB = internal::get_attribute_of_node(node, "volume", 0.0f); bool muted = internal::get_attribute_of_node(node, "mute", false); // bool doppler = internal::get_attribute_of_node(node, "doppler_effect", false); this->new_source(name, model, file_name_or_port_number, channel , *pos_ptr, pos_ptr->fixed, *dir_ptr, false , apf::math::dB2linear(gain_dB), muted, properties_file); } } else { WARNING("No sources found in \"" << scene_file_name << "\"!"); } } else // file_extension != "asd" -> try to open file as audio file { WARNING("Trying to open specified file as audio file."); if (!_create_spontaneous_scene(scene_file_name)) { ERROR("\"" << scene_file_name << "\" could not be loaded as audio file!"); return false; } } this->transport_locate(0); // go to beginning of audio files // TODO: only start processing if it was on before this->start_processing(); return true; } template bool Controller::_create_spontaneous_scene(const std::string& audio_file_name) { #ifndef ENABLE_ECASOUND ERROR("Couldn't create scene from file \"" << audio_file_name << "\"! Ecasound was disabled at compile time."); return false; #else size_t no_of_audio_channels; AudioPlayer::Soundfile::get_format(audio_file_name, no_of_audio_channels); if (no_of_audio_channels == 0) { WARNING("No audio channels found in file \"" << audio_file_name << "\"!"); return false; } WARNING("Creating spontaneous scene from the audio file \"" << audio_file_name << "\"."); if (_renderer.params.get("name", "") == "brs") { WARNING("I don't have information on the BRIRs. I'll use default HRIRs. " "Everything will sound in front."); } // extract pure file name const std::string source_name = audio_file_name.substr(audio_file_name.rfind('/') + 1); // set master volume this->set_master_volume(apf::math::dB2linear(-6.0f)); this->set_amplitude_reference_distance(_conf.renderer_params.get( "amplitude_reference_distance")); // throws on error! // set reference this->set_reference_position(Position()); this->set_reference_orientation(Orientation(90.0f)); this->set_reference_offset_position(Position()); this->set_reference_offset_orientation(Orientation()); const float default_source_distance = 2.5f; // for mono and stereo files switch (no_of_audio_channels) { case 1: // mono file this->new_source(source_name, Source::point, audio_file_name, 1 , Position(0.0f, default_source_distance), false, Orientation() , false, apf::math::dB2linear(0.0f), false, ""); VERBOSE("Creating point source at x = " << apf::str::A2S(0.0f) << " mtrs, y = " << apf::str::A2S(default_source_distance) << " mtrs."); break; case 2: // stereo file { #undef PI const float PI = 3.14159265358979323846; const float pos_x = default_source_distance * cos(PI/3.0f); const float pos_y = default_source_distance * sin(PI/3.0f); // create source this->new_source(source_name + " left", Source::plane, audio_file_name , 1, Position(-pos_x, pos_y), false, Orientation(-60), false , apf::math::dB2linear(0.0f), false, ""); VERBOSE("Creating point source at x = " << apf::str::A2S(-pos_x) << " mtrs, y = " << apf::str::A2S(pos_y) << " mtrs."); // create source this->new_source(source_name + " right", Source::plane, audio_file_name , 2u, Position(pos_x, pos_y), false, Orientation(-120), false , apf::math::dB2linear(0.0f), false, ""); VERBOSE("Creating point source at x = " << apf::str::A2S(pos_x) << " mtrs, y = " << apf::str::A2S(pos_y) << " mtrs."); break; } default: // init random position generator srand((unsigned int)time(0)); int N = 7; // create one source for each audio channel for (size_t n = 0; n < no_of_audio_channels; n++) { // random positions between -N mrts and N mtrs const float pos_x = (static_cast(rand()%(10*(N+1))) - (5.0f*(N+1))) / 10.0f; const float pos_y = 2.0f + (static_cast(rand()%(10*(N+1))) - (5.0f*(N+1))) / 10.0f; VERBOSE("Creating point source at x = " << apf::str::A2S(pos_x) << " mtrs, y = " << apf::str::A2S(pos_y) << " mtrs."); // create sources this->new_source(source_name + " " + apf::str::A2S(n+1), Source::point , audio_file_name, n+1u, Position(pos_x, pos_y), false , Orientation(), false, apf::math::dB2linear(0.0f), false, ""); } // for each audio channel } // switch return true; #endif // ENABLE_ECASOUND } #ifdef ENABLE_GUI template int Controller::_start_gui(const std::string& path_to_gui_images , const std::string& path_to_scene_menu) { // Check whether the system supports OpenGL and set default OpenGL format // which will be applied to QGLWidget. // Should be equivalent to glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH | GLUT_MULTISAMPLE); QGLFormat gl_format = QGLFormat::defaultFormat(); gl_format.setSampleBuffers(true); gl_format.setRgba(true); gl_format.setDoubleBuffer(true); gl_format.setDepth(true); QGLFormat::setDefaultFormat(gl_format); _gui.reset(new QGUI(*this, _scene, _argc, _argv, path_to_gui_images, path_to_scene_menu)); // check if anti-aliasing is possible if (!_gui->format().sampleBuffers()) { WARNING("This system does not provide sample buffer support.\n" "I can not enable anti-aliasing for OpenGl stuff."); } return _gui->run(); } #endif // ENABLE_GUI template void Controller::_start_tracker(const std::string& type, const std::string& ports) { if (type == "") { return; } else if (type == "intersense") { #if defined(ENABLE_INTERSENSE) _tracker = TrackerInterSense::create(*this, ports); #else ERROR("The SSR was compiled without InterSense tracker support!"); (void)ports; // avoid "unused parameter" warning return; #endif } else if (type == "polhemus") { #if defined(ENABLE_POLHEMUS) _tracker = TrackerPolhemus::create(*this, ports); #else ERROR("The SSR was compiled without Polhemus tracker support!"); (void)ports; // avoid "unused parameter" warning return; #endif } else if (type == "vrpn") { #if defined(ENABLE_VRPN) _tracker = TrackerVrpn::create(*this, ports); #else ERROR("The SSR was compiled without VRPN tracker support!"); (void)ports; // avoid "unused parameter" warning return; #endif } else if (type == "razor") { #if defined(ENABLE_RAZOR) _tracker = TrackerRazor::create(*this, ports); #else ERROR("The SSR was compiled without Razor AHRS tracker support!"); (void)ports; // avoid "unused parameter" warning return; #endif } else { ERROR("Unknown tracker type \"" << type << "\"!"); return; } if (!_tracker) { WARNING("Cannot find tracker. " "Make sure that you have the appropriate access rights " "to read from the port. I continue without tracker."); } } /// This is temporary!!!! template void Controller::calibrate_client() { #if defined(ENABLE_INTERSENSE) || defined(ENABLE_POLHEMUS) || defined(ENABLE_VRPN) || defined(ENABLE_RAZOR) if (_tracker) { _tracker->calibrate(); } else { WARNING("No tracker there to calibrate."); } #endif } #ifdef ENABLE_ECASOUND /** * The recorder is started as soon the JACK transport is running. * @param audio_file_name quite obviously, the file which will be recorded * to. It will have the given @a sample_format, the same number of channels as * the renderer has output channels and it will have the same sample rate as the * JACK audio server. * @param sample_format for more information see the ecasound(1) manpage and * especially the documentation of the -f option. * @warning This may only be used before activate() is called! **/ template void Controller::_load_audio_recorder(const std::string& audio_file_name , const std::string& sample_format, const std::string& client_name , const std::string& input_prefix) { if (audio_file_name == "") return; output_list_t output_list = _renderer.get_output_list(); size_t channels = output_list.size(); int sample_rate = _renderer.sample_rate(); _audio_recorder.reset(new AudioRecorder(audio_file_name , sample_format + "," + apf::str::A2S(channels) + "," + apf::str::A2S(sample_rate), "", client_name, input_prefix)); size_t channel = 1; for (const auto& out: output_list) { _renderer.connect_ports(out.port_name() , client_name + ":" + input_prefix + "_" + apf::str::A2S(channel++)); } } #endif /** start audio processing. * This sets the Scene's processing state to "processing". The * process callback function has to check for this variable and act accordingly. **/ template void Controller::start_processing() { if (!_scene.get_processing_state()) { this->set_processing_state(true); } else { WARNING("Renderer is already processing."); } } /** Stop audio processing. * This sets the Scene's processing state to "ready". The * process callback function has to check for this variable and act * accordingly. **/ template void Controller::stop_processing() { if (_scene.get_processing_state()) { this->set_processing_state(false); } else { WARNING("Renderer was already stopped."); } } /** _. * @param state processing state. * @warning States are not checked for validity. To start and stop rendering, * use preferably start_processing() and stop_processing(). This function should * only be used by the respective renderer to set the state "ready" and by * anyone who wants to set the state "exiting". **/ template void Controller::set_processing_state(bool state) { _publish(&Subscriber::set_processing_state, state); } // non-const because audioplayer could be started template void Controller::transport_start() { _renderer.transport_start(); } // non-const because audioplayer could be started template void Controller::transport_stop() { _renderer.transport_stop(); } /** Skips the scene to a specified instant of time * @ param frame instant of time in sec to locate **/ template bool Controller::transport_locate(float time) { // convert time to samples (cut decimal part) return _renderer.transport_locate( static_cast(time * _renderer.sample_rate())); } /** Create a new source. * @param name Source name * @param model Source model * @param file_name_or_port_number File name or port number (as string) * @param channel Channel of soundfile. If 0, a JACK portname is expected. * @param position initial position of the source. * @param orientation initial orientation of the source. * @param gain gain (=volume) of the source. * @return ID of the created source. If 0, no source was created. **/ template void Controller::new_source(const std::string& name , const Source::model_t model , const std::string& file_name_or_port_number, int channel , const Position& position, const bool pos_fixed , const Orientation& orientation, const bool or_fixed, const float gain , const bool muted, const std::string& properties_file) { (void) or_fixed; assert(channel >= 0); std::string port_name; long int file_length = 0; if (channel > 0) // we're dealing with a soundfile { #ifdef ENABLE_ECASOUND // if not already running, start AudioPlayer if (!_audio_player) { _audio_player = AudioPlayer::ptr_t(new AudioPlayer); } port_name = _audio_player->get_port_name(file_name_or_port_number, channel // the thing with _loop is a temporary hack, should be removed some time: , _loop); file_length = _audio_player->get_file_length(file_name_or_port_number); #else ERROR("Couldn't open audio file \"" << file_name_or_port_number << "\"! Ecasound was disabled at compile time."); return; #endif } else // no audio file { if (file_name_or_port_number != "") { port_name = _conf.input_port_prefix + file_name_or_port_number; } } if (port_name == "") { VERBOSE("No audio file or port specified for source '" << name << "'."); } apf::parameter_map p; p.set("connect_to", port_name); p.set("properties_file", properties_file); id_t id; try { auto guard = _renderer.get_scoped_lock(); id = _renderer.add_source(p); } catch (std::exception& e) { ERROR(e.what()); return; } _publish(&Subscriber::new_source, id); // mute while transmitting data _publish(&Subscriber::set_source_mute, id, true); _publish(&Subscriber::set_source_gain, id, gain); _publish(&Subscriber::set_source_position, id, position); _publish(&Subscriber::set_source_position_fixed, id, pos_fixed); _publish(&Subscriber::set_source_orientation, id, orientation); // _publish(&Subscriber::set_source_orientation_fix, id, or_fix); _publish(&Subscriber::set_source_name, id, name); _publish(&Subscriber::set_source_model, id, model); _publish(&Subscriber::set_source_port_name, id, port_name); if (file_name_or_port_number != "") { _publish(&Subscriber::set_source_file_name, id, file_name_or_port_number); _publish(&Subscriber::set_source_file_channel, id, channel); } _publish(&Subscriber::set_source_file_length, id, file_length); _publish(&Subscriber::set_source_properties_file, id, properties_file); // finally, unmute if requested _publish(&Subscriber::set_source_mute, id, muted); } template void Controller::delete_all_sources() { _publish(&Subscriber::delete_all_sources); #ifdef ENABLE_ECASOUND _audio_player.reset(); // shut down audio player #endif // Wait until InternalInput objects are destroyed _renderer.wait_for_rt_thread(); } template void Controller::delete_source(id_t id) { _publish(&Subscriber::delete_source, id); // TODO: stop AudioPlayer if not needed anymore? } template void Controller::set_source_position(const id_t id, const Position& position) { // TODO: check if the client who sent the request is actually allowed to // change the position. (same TODO as above) // TODO: check if position is inside of room boundaries. // if not: change position (e.g. single dimensions) to an allowed position // check if source may be moved if (!_scene.get_source_position_fixed(id)) { _publish(&Subscriber::set_source_position, id, position); } else { WARNING("Source \'" << _scene.get_source_name(id) << "\' can not be moved."); } } template void Controller::set_source_orientation(const id_t id , const Orientation& orientation) { // TODO: validate orientation? _publish(&Subscriber::set_source_orientation, id, orientation); } template void Controller::set_source_gain(const id_t id, const float gain) { _publish(&Subscriber::set_source_gain, id, gain); } template void Controller::set_source_signal_level(const id_t id, const float level) { _publish(&Subscriber::set_source_signal_level, id, level); } template void Controller::set_source_mute(const id_t id, const bool mute) { _publish(&Subscriber::set_source_mute, id, mute); } template void Controller::set_source_name(const id_t id, const std::string& name) { _publish(&Subscriber::set_source_name, id, name); } template void Controller::set_source_properties_file(const id_t id, const std::string& name) { _publish(&Subscriber::set_source_properties_file, id, name); } template void Controller::set_source_model(const id_t id, const Source::model_t model) { _publish(&Subscriber::set_source_model, id, model); } template void Controller::set_source_port_name(const id_t id, const std::string& port_name) { _publish(&Subscriber::set_source_port_name, id, port_name); } template void Controller::set_source_file_name(const id_t id, const std::string& file_name) { _publish(&Subscriber::set_source_file_name, id, file_name); } template void Controller::set_source_file_channel(const id_t id, const int& channel) { _publish(&Subscriber::set_source_file_channel, id, channel); } template void Controller::set_source_position_fixed(const id_t id, const bool fixed) { _publish(&Subscriber::set_source_position_fixed, id, fixed); } template void Controller::set_reference_position(const Position& position) { _publish(&Subscriber::set_reference_position, position); // TODO: update orientations of plane waves } template void Controller::set_reference_orientation(const Orientation& orientation) { _publish(&Subscriber::set_reference_orientation, orientation); } template void Controller::set_reference_offset_position(const Position& position) { _publish(&Subscriber::set_reference_offset_position, position); } template void Controller::set_reference_offset_orientation(const Orientation& orientation) { _publish(&Subscriber::set_reference_offset_orientation, orientation); } // linear volume! template void Controller::set_master_volume(const float volume) { // TODO: validate volume? _publish(&Subscriber::set_master_volume, volume); } template void Controller::set_amplitude_reference_distance(const float dist) { if (dist > 1.0f) { _publish(&Subscriber::set_amplitude_reference_distance, dist); } else { ERROR("Amplitude reference distance can not be smaller than 1."); } } // linear scale template void Controller::set_master_signal_level(float level) { _publish(&Subscriber::set_master_signal_level, level); } template void Controller::set_cpu_load(const float load) { _publish(&Subscriber::set_cpu_load, load); } template void Controller::publish_sample_rate(const int sample_rate) { _publish(&Subscriber::set_sample_rate, sample_rate); } template std::string Controller::get_renderer_name() const { return _renderer.params.get("name", ""); } template bool Controller::show_head() const { return _renderer.show_head(); } template void Controller::set_source_output_levels(id_t id, float* first , float* last) { _publish(&Subscriber::set_source_output_levels, id, first, last); } template std::string Controller::get_scene_as_XML() const { XMLParser xp; // load XML parser Node node = xp.new_node("update"); // add master volume _add_master_volume(node); // quick hack: add transport state (play/stop) _add_transport_state(node); // TODO: add other scene information? _add_reference(node); _add_loudspeakers(node); _add_sources(node); // TODO: memory of node is never freed! return node.to_string(); } template bool Controller::save_scene_as_XML(const std::string& filename) const { // ATTENTION: this is an ugly work-around/quick-hack! // TODO: the following should be included into the XMLParser wrapper! XMLParser xp; // load XML parser Node root = xp.new_node("asdf"); Node node = root.new_child("scene_setup"); // add master volume _add_master_volume(node); // TODO: add other scene information? _add_reference(node); _add_sources(node, filename); // ugly quick hack! // TODO: memory of node is never freed! xmlDocPtr doc = xmlNewDoc(nullptr); //xmlDocPtr doc = xmlNewDoc(BAD_CAST "1.0"); if (!doc) return false; xmlDocSetRootElement(doc, root.get()); //xmlNewNs(my_node, BAD_CAST "http://www.w3.org/1999/xhtml", nullptr); //xmlSaveCtxtPtr ctxt = xmlSaveToFilename(filename.c_str(), nullptr, 0); xmlSaveCtxtPtr ctxt = xmlSaveToFilename(filename.c_str(), nullptr , XML_SAVE_FORMAT); if (!ctxt) return false; xmlSaveDoc(ctxt, doc); int ret = xmlSaveClose(ctxt); if (ret < 1) return false; else return true; // Memory for "doc" is never freed! } template void Controller::_add_master_volume(Node& node) const { float volume = _scene.get_master_volume(); node.new_child("volume", apf::str::A2S(apf::math::linear2dB(volume))); } template void Controller::_add_transport_state(Node& node) const { node.new_child("transport" , _renderer.get_transport_state().first ? "start" : "stop"); } template void Controller::_add_reference(Node& node) const { DirectionalPoint reference = _scene.get_reference(); Node reference_node = node.new_child("reference"); _add_position(reference_node, reference.position); _add_orientation(reference_node, reference.orientation); } template void Controller::_add_position(Node& node , const Position& position, const bool fixed) const { Node position_node = node.new_child("position"); position_node.new_attribute("x", apf::str::A2S(position.x)); position_node.new_attribute("y", apf::str::A2S(position.y)); if (fixed) { position_node.new_attribute("fixed", apf::str::A2S(fixed)); } } template void Controller::_add_orientation(Node& node, const Orientation& orientation) const { Node orientation_node = node.new_child("orientation"); orientation_node.new_attribute("azimuth", apf::str::A2S(orientation.azimuth)); } template void Controller::_add_loudspeakers(Node& node) const { Loudspeaker::container_t loudspeakers; _scene.get_loudspeakers(loudspeakers, false); // get relative positions for (const auto& ls: loudspeakers) { Node loudspeaker_node = node.new_child("loudspeaker"); loudspeaker_node.new_attribute("model", apf::str::A2S(ls.model)); _add_position(loudspeaker_node, ls.position); _add_orientation(loudspeaker_node, ls.orientation); } } template void Controller::_add_sources(Node& node , const std::string& scene_file_name) const { typename SourceCopy::container_t sources; _scene.get_sources(sources); for (const auto& source: sources) { Node source_node = node.new_child("source"); if (scene_file_name != "") { // ignore "id" -> ugly quick hack! } else { source_node.new_attribute("id", apf::str::A2S(source.id)); } source_node.new_attribute("name", apf::str::A2S(source.name)); source_node.new_attribute("model", apf::str::A2S(source.model)); if (scene_file_name == "" || source.audio_file_channel > 0) { if (source.audio_file_name != "") { _add_audio_file_name(source_node , posixpathtools::make_path_relative_to_file(source.audio_file_name , scene_file_name), source.audio_file_channel); } } if (scene_file_name == "") { if (source.port_name != "") { source_node.new_child("port", source.port_name); } } else if (source.audio_file_channel == 0 && source.audio_file_name != "") { source_node.new_child("port", source.audio_file_name); } _add_position(source_node, source.position, source.fixed_position); _add_orientation(source_node, source.orientation); if (scene_file_name != "") // ugly quick hack { // don't add port name! } else { source_node.new_attribute("length", apf::str::A2S(source.file_length)); } source_node.new_attribute("mute", apf::str::A2S(source.mute)); // save volume in dB! source_node.new_attribute("volume", apf::str::A2S(apf::math::linear2dB(source.gain))); // TODO: information about mirror sources if (source.properties_file != "") { source_node.new_attribute("properties_file" , posixpathtools::make_path_relative_to_file(source.properties_file , scene_file_name)); } // TODO: save doppler effect setting (source.doppler_effect) } } template void Controller::_add_audio_file_name(Node& node, const std::string& name , int channel) const { Node file_node = node.new_child("file", name); if (channel != 1) { file_node.new_attribute("channel", apf::str::A2S(channel)); } } } // namespace ssr #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'\:'=' ssr-0.4.2/src/directionalpoint.cpp000066400000000000000000000160161236416011200171660ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Geometry information of a point including an orientation (implementation). #include #include // for cos() #include "directionalpoint.h" /** ctor. * @param position position * @param orientation orientation **/ DirectionalPoint::DirectionalPoint(const Position& position, const Orientation& orientation) : position(position), orientation(orientation) {} /** += operator * @param other addend * @return reference to itself after adding @a other **/ DirectionalPoint& DirectionalPoint::operator+=(const DirectionalPoint& other) { position += other.position; orientation += other.orientation; return *this; } /** -= operator * @param other minuend * @return reference to itself after subtracting @a other **/ DirectionalPoint& DirectionalPoint::operator-=(const DirectionalPoint& other) { position -= other.position; orientation -= other.orientation; return *this; } /** Distance between plane and point. * \par Algorithm * The plane is defined by the DirectionalPoint (Position and Orientation) * properties of *this (=the current object) where its Position is the point * \f$\displaystyle\mathbf{p}={p_x \choose p_y}\f$ somewhere on * the plane and its Orientation is its normal vector \f$\mathbf{n}\f$. * The point we want to get the distance to is given by * \f$\displaystyle\mathbf{r}={r_x \choose r_y}\f$. * The distance \f$l\f$ is calculated as the inner product of the vector from * \f$\mathbf{p}\f$ to \f$\mathbf{r}\f$ with the normal vector \f$\mathbf{n}\f$. * \par * \f$ l = (\mathbf{r} - \mathbf{p}) \cdot \mathbf{n} \f$ * \par * Note that the distance \f$l\f$ can be negative, if the point \f$\mathbf{r}\f$ * is in the half-space opposite to the normal vector. * \par * Because we have the normal vector only as an angle (its length is irrelevant * to us), we calculate the inner * product as the product of the absolute values and the angle between the two * vectors. Therefore, the distance is given as * \f$ l = |\mathbf{r} - \mathbf{p}| \cos\phi \f$, where \f$\phi\f$ is the angle * between \f$ (\mathbf{r} - \mathbf{p}) \f$ and \f$ \mathbf{n} \f$. * * \par finally, the equation: * \f$ \displaystyle l = \sqrt{(r_x-p_x)^2+(r_y-p_y)^2} * \cos\left(\mathop{\mathrm{atan}}\left(\frac{r_y-p_y}{r_x-p_x}\right) - * \phi_\mathbf{n}\right) \f$ * \par * The order of the subtrahends doesn't matter because the cosine function is * symmetric. * * \par Alternative * The distance could also be calculated with the use of the "sine rule" * (de:Sinussatz). I don't know which one is computationally more efficient. * * @param point The distance to this point is returned * @return The distance between a plane (represented by the current object) and * @a point (in meters). * @warning This is only a 2D implementation! The z-value, elevation and tilt * are ignored. **/ float DirectionalPoint::plane_to_point_distance(const Position& point) const { Position temp = point - this->position; // the order of the arguments to angle() doesn't matter because cos() // is symmetric. return temp.length() * cos(angle(this->orientation,temp.orientation())); } /** ._ * @param angle angle in degrees. * @return the resulting point * @warning only possible for 2D! For a more general application use the * overloaded member function rotate(const Orientation&). **/ DirectionalPoint& DirectionalPoint::rotate(float angle) { this->position.rotate(angle); this->orientation.rotate(angle); return *this; } DirectionalPoint& DirectionalPoint::rotate(const Orientation& rotation) { this->position.rotate(rotation); this->orientation.rotate(rotation); return *this; } DirectionalPoint& DirectionalPoint::transform(const DirectionalPoint& t) { this->position.rotate(t.orientation); this->position += t.position; this->orientation.rotate(t.orientation); return *this; } /** _. * @param a * @param b * @return Angle in radians between the two Orientations. **/ float angle(const DirectionalPoint& a, const DirectionalPoint& b) { return angle(a.orientation, b.orientation); } /** plus (+) operator. * @param a one addend. * @param b the other one. * @return sum **/ DirectionalPoint operator+(const DirectionalPoint& a, const DirectionalPoint& b) { DirectionalPoint temp(a); return temp += b; } /** minus (-) operator. * @param a subtrahend. * @param b minuend. * @return difference **/ DirectionalPoint operator-(const DirectionalPoint& a, const DirectionalPoint& b) { DirectionalPoint temp(a); return temp -= b; } /** output stream operator (<<). * @param stream the stream * @param point the point you want to throw into the stream * @return a reference to the stream **/ std::ostream& operator<<(std::ostream& stream, const DirectionalPoint& point) { stream << point.position << ", " << point.orientation; return stream; } // 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'\:'=' ssr-0.4.2/src/directionalpoint.h000066400000000000000000000100101236416011200166170ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Geometry information of a point including an orientation (definition). #ifndef SSR_DIRECTIONALPOINT_H #define SSR_DIRECTIONALPOINT_H #include #include "position.h" #include "orientation.h" /** Class which combines Position and Orientation. * Anything which has a position and orientation can be derived from it. **/ struct DirectionalPoint { DirectionalPoint() {} ///< standard ctor DirectionalPoint(const Position& position, const Orientation& orientation); Position position; ///< position Orientation orientation; ///< orientation /// Distance between a plane (*this) and a point float plane_to_point_distance(const Position& point) const; DirectionalPoint& rotate(float angle); ///< rotate around the origin /// rotate around the origin DirectionalPoint& rotate(const Orientation& rotation); /// move and rotate DirectionalPoint& transform(const DirectionalPoint& t); DirectionalPoint& operator+=(const DirectionalPoint& other); DirectionalPoint& operator-=(const DirectionalPoint& other); friend DirectionalPoint operator+(const DirectionalPoint& a, const DirectionalPoint& b); friend DirectionalPoint operator-(const DirectionalPoint& a, const DirectionalPoint& b); friend std::ostream& operator<<(std::ostream& stream, const DirectionalPoint& point); /** division (/) operator. * @param a dividend, a DirectionalPoint. * @param b divisor, any numeric Type.. * @return quotient. **/ template friend DirectionalPoint operator/(const DirectionalPoint& a, const T& b) { return DirectionalPoint(a.position / b, a.orientation / b); } }; /// Angle between the Orientations of two DirectionalPoints. float angle(const DirectionalPoint& a, const DirectionalPoint& b); #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'\:'=' ssr-0.4.2/src/genericrenderer.h000066400000000000000000000150231236416011200164240ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Generic renderer. #ifndef SSR_GENERICRENDERER_H #define SSR_GENERICRENDERER_H #include "loudspeakerrenderer.h" #include "apf/convolver.h" // for apf::conv::* #include "apf/sndfiletools.h" // for apf::load_sndfile #include "apf/combine_channels.h" // for apf::raised_cosine_fade, ... namespace ssr { class GenericRenderer : public SourceToOutput { private: using _base = SourceToOutput; public: static const char* name() { return "GenericRenderer"; } using Input = _base::DefaultInput; class Source; struct SourceChannel; class Output; class RenderFunction; GenericRenderer(const apf::parameter_map& params) : _base(params) , _fade(this->block_size()) {} APF_PROCESS(GenericRenderer, _base) { this->_process_list(_source_list); } private: apf::raised_cosine_fade _fade; }; struct GenericRenderer::SourceChannel : apf::has_begin_and_end { template explicit SourceChannel(const Source& s, In first, In last); // out-of-class definition because of cyclic dependencies with Source void update(); void convolve(sample_type weight); const Source& source; apf::conv::StaticOutput convolver; }; class GenericRenderer::Source : public _base::Source { public: explicit Source(const Params& p) : _base::Source(p) , _weighting_factor() { using matrix_t = apf::fixed_matrix; size_t outputs = this->parent.get_output_list().size(); auto ir_file = apf::load_sndfile( p.get("properties_file"), this->parent.sample_rate() , outputs); size_t size = ir_file.frames(); auto ir_data = matrix_t(size, outputs); // TODO: check return value? ir_file.readf(ir_data.data(), size); size_t block_size = this->parent.block_size(); _convolver.reset(new apf::conv::Input(block_size , apf::conv::min_partitions(block_size, size))); this->sourcechannels.reserve(outputs); for (matrix_t::slices_iterator slice = ir_data.slices.begin() ; slice != ir_data.slices.end() ; slice++) { this->sourcechannels.emplace_back(*this, slice->begin(), slice->end()); } } APF_PROCESS(Source, _base::Source) { _weighting_factor = this->weighting_factor; _convolver->add_block(_input.begin()); assert(_weighting_factor.exactly_one_assignment()); } apf::BlockParameter _weighting_factor; std::unique_ptr _convolver; }; template GenericRenderer::SourceChannel::SourceChannel(const Source& s , In first, In last) : source(s) // TODO: assert s._convolver != 0? , convolver(*s._convolver, first, last) {} void GenericRenderer::SourceChannel::update() { this->convolve(this->source._weighting_factor); } void GenericRenderer::SourceChannel::convolve(sample_type weight) { // TODO: check if convolver == 0? _begin = this->convolver.convolve(weight); _end = _begin + this->convolver.block_size(); } class GenericRenderer::RenderFunction { public: RenderFunction() : _in(0) {} apf::CombineChannelsResult::type select(SourceChannel& in) { _in = & in; const auto& factor = in.source._weighting_factor; using namespace apf::CombineChannelsResult; if (factor.both() == 0) return nothing; if (factor.old() == 0) return fade_in; in.convolve(factor.old()); if (factor == 0) return fade_out; if (!factor.changed()) return constant; return change; } void update() { assert(_in); _in->update(); } private: SourceChannel* _in; }; class GenericRenderer::Output : public _base::Output { public: Output(const Params& p) : _base::Output(p) , _combiner(this->sourcechannels, this->buffer, this->parent._fade) {} APF_PROCESS(Output, _base::Output) { _combiner.process(RenderFunction()); } private: apf::CombineChannelsCrossfadeCopy, buffer_type , apf::raised_cosine_fade> _combiner; }; } // namespace ssr #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'\:'=' ssr-0.4.2/src/gui/000077500000000000000000000000001236416011200136735ustar00rootroot00000000000000ssr-0.4.2/src/gui/qclicktextlabel.cpp000066400000000000000000000056211236416011200175560ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// TODO: add description #include "qclicktextlabel.h" QClickTextLabel::QClickTextLabel(QWidget* parent, int ID) : QLabel(parent), ID(ID) { setTextFormat(Qt::RichText); setAlignment(Qt::AlignTop); } QClickTextLabel::QClickTextLabel( const QString& text, QWidget * parent) : QLabel(text, parent), ID(0) {} void QClickTextLabel::mousePressEvent(QMouseEvent *event) { event->accept(); if (ID) emit clicked(ID); else emit clicked(); if (event->button() == Qt::RightButton) emit signal_right_clicked(event); } void QClickTextLabel::mouseDoubleClickEvent(QMouseEvent *event) { emit signal_double_clicked(); event->ignore(); } // 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'\:'=' ssr-0.4.2/src/gui/qclicktextlabel.h000066400000000000000000000055241236416011200172250ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// QClickTextLabel #ifndef SSR_QCLICKTEXTLABEL_H #define SSR_QCLICKTEXTLABEL_H #include #include /// QClickTextLabel class QClickTextLabel : public QLabel { Q_OBJECT public: QClickTextLabel( QWidget* parent = 0, int ID = 0); QClickTextLabel( const QString& text, QWidget * parent = 0); protected: int ID; void mousePressEvent(QMouseEvent *event); void mouseDoubleClickEvent(QMouseEvent *event); signals: void clicked(); void clicked(int); void signal_double_clicked(); void signal_right_clicked(QMouseEvent *); }; #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'\:'=' ssr-0.4.2/src/gui/qcpulabel.cpp000066400000000000000000000077121236416011200163560ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// TODO: add description #include #include #include #include "qcpulabel.h" QCPULabel::QCPULabel(QWidget* parent, unsigned int update_interval) : QLabel(parent), load(0.0f) { setAlignment(Qt::AlignCenter); // update widget every update_interval msec QTimer *timer = new QTimer(this); connect(timer, SIGNAL(timeout()), this, SLOT(update())); timer->start(update_interval); } void QCPULabel::set_load(float l) { load = l; // limit possible values load = std::min(load, 100.0f); load = std::max(load, 0.0f); // update(); } void QCPULabel::paintEvent(QPaintEvent * event) { event->accept(); // draw QLabel stuff QLabel::paintEvent(event); QPainter painter(this); // frame painter.setPen(QPen(QColor(237,237,230),1)); painter.drawLine(QLine(0,0,width(),0)); painter.drawLine(QLine(0,height()-1,width(),height()-1)); painter.drawLine(QLine(0,0,0,height())); painter.drawLine(QLine(width()-1,0,width()-1,height()-1)); // choose colors if (load <= 60.0f){ painter.setPen(QPen(QColor(58,239,58))); painter.setBrush(QBrush(QColor(58,239,58))); // green } else if (load > 80.0f){ painter.setPen(QPen(QColor(255,0,0))); painter.setBrush(QBrush(QColor(255,0,0))); // red // TODO: rgb } else { painter.setPen(QPen(QColor(255,255,0))); painter.setBrush(QBrush(QColor(255,255,0))); // yellow // TODO: rgb } // draw load bar painter.drawRect(1, 1, (int)(width()*load/100.0f)-1, height()-3); // set text //setText(QString().setNum((int)(load + 0.5f))); // quick-hack painter.setPen(QPen(QColor(0, 0, 0))); painter.drawText(QRect(0, 1, width(), height()), Qt::AlignCenter, QString().setNum((int)(load + 0.5f))); } // 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'\:'=' ssr-0.4.2/src/gui/qcpulabel.h000066400000000000000000000051521236416011200160170ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// QCPULabel #ifndef SSR_QCPULABEL_H #define SSR_QCPULABEL_H #include #include /// QCPULabel class QCPULabel : public QLabel { Q_OBJECT public: QCPULabel(QWidget* parent = 0, unsigned int update_interval = 500); void set_load(float load); protected: float load; void paintEvent( QPaintEvent * event); }; #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'\:'=' ssr-0.4.2/src/gui/qfilemenulabel.cpp000066400000000000000000000061501236416011200173660ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// TODO: add description #include #include "qfilemenulabel.h" QFileMenuLabel::QFileMenuLabel(QWidget* parent) : QClickTextLabel(parent) {} void QFileMenuLabel::paintEvent( QPaintEvent * event) { // draw QLabel stuff QLabel::paintEvent(event); // draw custom stuff QPainter painter(this); // frame painter.setPen(QPen(QColor(237,237,230),1)); painter.drawLine(QLine(0,0,width(),0)); painter.drawLine(QLine(0,height()-1,width(),height()-1)); painter.drawLine(QLine(0,0,0,height())); painter.drawLine(QLine(width()-1,0,width()-1,height())); // enable anti-aliasing painter.setRenderHint(QPainter::Antialiasing); painter.setPen(QPen(QColor(0,0,0),2)); QLineF line_down(110.0, 13.0, 115.0, 18.0); QLineF line_up (115.0, 18.0, 120.0, 13.0); // draw down-arrow painter.drawLine(line_down); painter.drawLine(line_up); } // 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'\:'=' ssr-0.4.2/src/gui/qfilemenulabel.h000066400000000000000000000051211236416011200170300ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// QFileMenuLabel #ifndef SSR_QFILEMENULABEL_H #define SSR_QFILEMENULABEL_H #include "qclicktextlabel.h" #include /// QFileMenuLabel class QFileMenuLabel : public QClickTextLabel { Q_OBJECT public: QFileMenuLabel( QWidget* parent = 0); protected: virtual void paintEvent( QPaintEvent * event); }; #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'\:'=' ssr-0.4.2/src/gui/qgui.cpp000066400000000000000000000317401236416011200153510ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// GUI wrapper class (implementation). #include "qgui.h" // Qt stylesheet QString qt_style_sheet = #ifdef __APPLE__ "QWidget { \n" " color: black; \n" " font: normal 13pt; \n" " } \n" #else "QWidget { \n" " color: black; \n" " font: normal 9pt \"Monospace\"; \n" " } \n" #endif "QLabel { \n" " background-color: rgb(244, 244, 244); \n" " color: black; \n" " } \n" " \n" "QLabel#notifier { \n" " background-color: rgb(244, 0, 0, 255); \n" /* " color: black; \n"*/ " } \n" " \n" // define style of processing button "QPushButton#_processing_button { \n" " border-image: url(images/processing_button.png) 0; \n" " } \n" " \n" "QPushButton#_processing_button:pressed { \n" " border-image: url(images/processing_button_pressed.png) 0; \n" " } \n" " \n" "QPushButton#_processing_button:checked { \n" " border-image: url(images/processing_button_pressed.png)0; \n" " } \n" " \n" // define style of stop button "QPushButton#_skip_back_button { \n" " border-image: url(images/skip_back_button.png) 0; \n" " } \n" " \n" "QPushButton#_skip_back_button:pressed { \n" " border-image: url(images/skip_back_button_pressed.png) 0; \n" " } \n" " \n" // define style of pause button "QPushButton#_pause_button { \n" " border-image: url(images/pause_button.png)0; \n" " } \n" " \n" "QPushButton#_pause_button:checked { \n" " border-image: url(images/pause_button_pressed.png)0; \n" " } \n" " \n" "QPushButton#_pause_button:pressed { \n" " border-image: url(images/pause_button_pressed.png)0; \n" " } \n" " \n" // define style of play button "QPushButton#_play_button { \n" " border-image: url(images/play_button.png)0; \n" " } \n" " \n" "QPushButton#_play_button:checked { \n" " border-image: url(images/play_button_pressed.png)0; \n" " } \n" " \n" "QPushButton#_play_button:pressed { \n" " border-image: url(images/play_button_pressed.png)0; \n" " } \n" " \n" // define style of other labels "QSceneButton { \n" " border-image: url(images/scene_menu_item.png)0; \n" " } \n" " \n" "QSceneButton:pressed { \n" " border-image: url(images/scene_menu_item_selected.png)0; \n" " } \n" " \n" "QSceneButton:checked { \n" " border-image: url(images/scene_menu_item_selected.png)0; \n" " } \n" " \n" "QVolumeSlider { \n" " margin: 4px; \n" " background-color: rgb(244, 244, 244); \n" //" border-width: 1px; \n" //" border-color: red; \n" //" border-style: solid; \n" " border-radius: 0; \n" " } \n" " \n" "QTimeLine { \n" " background-color: rgb(244, 244, 244); \n" " } \n" " \n" "QCPULabel { \n" " background-color: white; \n" " } \n" " \n" " \n" "QZoomLabel { \n" " background-color: white; \n" " } \n" " \n" "QFileMenuLabel { \n" " background-color: white; \n" " font: bold 11pt; \n" //" border-width: 1px; \n" //" border-color: red; \n" //" border-style: solid; \n" " border-radius: 0; \n" " } \n" " \n" // define style of popup menu "QMenu { \n" " background-color: rgba(255, 255, 255, 92%); \n" " border-width: 1px; \n" " border-color: black; \n" " border-style: solid; \n" " border-radius: 0; \n" " } \n" " \n" "QMenu::item:hover { \n" " selection-background-color: rgba(208, 245, 247, 92%);\n" " selection-color: black; \n" " } \n" "QSourceProperties { \n" " background-color: rgba(255, 255, 255, 92%); \n" " border-width: 1px; \n" " border-color: black; \n" " border-style: solid; \n" " border-radius: 0; \n" " } \n" " \n" "QLabel#item { \n" " background-color: rgb(255, 255, 255); \n" " color: black; \n" " } \n" " \n" "QClicktextLabel#item:hover { \n" " background-color: rgba(208, 245, 247, 92%); \n" " color: red; \n" " } \n" " \n" "QRadioButton { \n" " background-color: rgb(255, 255, 255); \n" " color: black; \n" " } \n" " \n" "QCheckBox { \n" " background-color: rgb(255, 255, 255); \n" " color: black; \n" " } \n" " \n" ; // don't forget this semi-colon /** ctor. * @param controller this can be either the Controller or, if the IP interface * is used, an IPClient. * @param scene reference to the MasterScene or to a client-side copy of the * Scene. * @param argc number of command line arguments passed to the GUI. * @param argv the arguments themselves. **/ ssr::QGUI::QGUI(Publisher& controller, const Scene& scene, int &argc, char *argv[] , const std::string& path_to_gui_images , const std::string& path_to_scene_menu) : _qt_app(argc, argv), _gui(controller, scene , path_to_gui_images, path_to_scene_menu) { // this is a quick hack to allow dynamic specification of path qt_style_sheet.replace(QString("images"), QString( path_to_gui_images.c_str() )); // set stylesheet _qt_app.setStyleSheet(qt_style_sheet); } ssr::QGUI::~QGUI() {} /** * This function is used to verify if setting the sample buffer (enabling * anti-aliasing) has succeeded. * @return current QGLFormat */ QGLFormat ssr::QGUI::format() const { return _gui.format(); } /** * This function starts the GUI. * @return application exit code */ int ssr::QGUI::run() { // TODO: check return values _gui.show(); #ifdef __APPLE__ // bring window to front _gui.raise(); _gui.activateWindow(); #endif // TODO: check if _qt_app is valid _qt_app.connect(&_qt_app, SIGNAL(lastWindowClosed()), &_qt_app, SLOT(quit())); return _qt_app.exec(); } // 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'\:'=' ssr-0.4.2/src/gui/qgui.h000066400000000000000000000061011236416011200150070ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file qgui.h /// GUI wrapper class (definition). #ifndef SSR_QGUI_H #define SSR_QGUI_H #include #include #include #include "quserinterface.h" #include "publisher.h" #include "scene.h" namespace ssr { /** GUI wrapper class. * This class allows for comfortable creation, running, and stopping of the * SSR graphical user interface. **/ class QGUI : public QObject { Q_OBJECT public: QGUI(Publisher& controller, const Scene& scene, int &argc , char *argv[], const std::string& path_to_gui_images , const std::string& path_to_scene_menu); ~QGUI(); QGLFormat format () const; int run(); ///< start the GUI. private: QApplication _qt_app; ///< every Qt application has this QUserInterface _gui; ///< the main GUI class }; } // namespace ssr #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'\:'=' ssr-0.4.2/src/gui/qguiframe.cpp000066400000000000000000000076011236416011200163630ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// TODO: add description #include "qguiframe.h" QGUIFrame::QGUIFrame(QWidget* parent, int top, int bottom, int left, int right) : QWidget(parent) { // build up frame around OpenGL window _top = new QLabel(parent); //_bottom = new QClickTextLabel(parent); _bottom = new QLabel(parent); _left = new QLabel(parent); _right = new QLabel(parent); resize(top, bottom, left, right); //connect(_bottom, SIGNAL(clicked()), this, SLOT(_clear_text())); _clear_text_timer = new QTimer(this); _clear_text_timer->setSingleShot(true); connect(_clear_text_timer, SIGNAL(timeout()), this, SLOT(_clear_text())); } QGUIFrame::~QGUIFrame() { delete _top; delete _bottom; delete _left; delete _right; } void QGUIFrame::mousePressEvent(QMouseEvent *event) { event->accept(); } void QGUIFrame::mouseMoveEvent(QMouseEvent *event) { event->accept(); } void QGUIFrame::resize(const int top, const int bottom, const int left, const int right) { _top->setGeometry( 0, 0, parentWidget()->width(), top ); _bottom->setGeometry( 0, parentWidget()->height()-bottom, parentWidget()->width(), bottom ); _left->setGeometry( 0, top, left, parentWidget()->height()-top-bottom); _right->setGeometry( parentWidget()->width()-right, top, right, parentWidget()->height()-top-bottom ); _bottom->setIndent(left + 5); } void QGUIFrame::set_text(const QString text) { _bottom->setText("" + text + ""); if (_clear_text_timer->isActive()) { _clear_text_timer->stop(); } _clear_text_timer->start(5000); // 5 seconds } void QGUIFrame::_clear_text() { _bottom->clear(); } // 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'\:'=' ssr-0.4.2/src/gui/qguiframe.h000066400000000000000000000061171236416011200160310ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// QGUIFrame #ifndef SSR_QGUIFRAME_H #define SSR_QGUIFRAME_H #include #include #include #include //#include "qclicktextlabel.h" /// QGUIFrame class QGUIFrame : public QWidget { Q_OBJECT public: QGUIFrame( QWidget* parent = 0, int top = 0, int bottom = 0, int left = 0, int right = 0); ~QGUIFrame(); void resize(const int top, const int bottom, const int left, const int right); void set_text(const QString text); private: // Components of frame QLabel* _top; //QClickTextLabel* _bottom; QLabel* _bottom; QLabel* _left; QLabel* _right; QTimer* _clear_text_timer; private slots: void _clear_text(); protected: virtual void mousePressEvent(QMouseEvent *event); virtual void mouseMoveEvent(QMouseEvent *event); }; #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'\:'=' ssr-0.4.2/src/gui/qopenglplotter.cpp000066400000000000000000001024331236416011200174610ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// TODO: add description #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "qopenglplotter.h" #include "qclicktextlabel.h" //#include "mathtools.h" #define BACKGROUNDCOLOR 0.9294f,0.9294f,0.9020f // Define how detailedly circles are plotted #define LEVELOFDETAIL 40 //////////////////////////////////////////////////////////////////////////////// // Implementation of the nested class QGUI::SourceCopy //////////////////////////////////////////////////////////////////////////////// /** Type conversion constructor. * @param other reference to an element of the source map **/ ssr::QOpenGLPlotter::SourceCopy::SourceCopy( const Scene::source_map_t::value_type& other) : //const std::pair& other) : DirectionalPoint(other.second), id(other.first), model(other.second.model), mute(other.second.mute), gain(other.second.gain), signal_level(other.second.signal_level), output_levels(other.second.output_levels), name(other.second.name), fixed_position(other.second.fixed_position) {} //////////////////////////////////////////////////////////////////////////////// ssr::QOpenGLPlotter::QOpenGLPlotter(Publisher& controller, const Scene& scene , const std::string& path_to_gui_images , QWidget *parent) : QGLWidget(parent), _controller(controller), _scene(scene), _active_source(-1), _path_to_gui_images(path_to_gui_images), _id_of_last_clicked_source(0), _zoom_factor(STDZOOMFACTOR), _previous_mouse_event(QMouseEvent(QEvent::MouseButtonPress, QPoint(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)), // dummy event _ctrl_pressed(false), _alt_pressed(false), _rubber_band_starting_point(Position()), _rubber_band_ending_point(Position()), _window_x_offset(0.0f), _window_y_offset(STDWINDOWYOFFSET), _window_z_offset(0.0f), _x_offset(0.0f), _y_offset(0.0f), _z_offset(0.0f), _rotation_x(0.0f), _rotation_y(0.0f), _rotation_z(0.0f), _reference_selected(false), _volume_slider_selected(false), _direction_handle_selected(false), _allow_displaying_text(true), _glu_quadric(gluNewQuadric()), _plot_listener(false) { _set_zoom(100); // 100% _soloed_sources.clear(); // define possible colors for sources _color_vector.push_back(QColor(163, 95, 35)); _color_vector.push_back(QColor( 43,174,247)); _color_vector.push_back(QColor( 75,135, 35)); _color_vector.push_back(QColor( 97, 31,160)); _color_vector.push_back(QColor(173, 54, 35)); //_color_vector.push_back(QColor(242,226, 22)); // yellow is too hard to read } ssr::QOpenGLPlotter::~QOpenGLPlotter() { // delete source_properties; } void ssr::QOpenGLPlotter::_load_background_textures() { QImage image_buffer; image_buffer = QImage(); // load SSR logo QString path_to_image( _path_to_gui_images.c_str() ); path_to_image.append("/ssr_logo.png"); image_buffer.load(path_to_image, "PNG"); if (!image_buffer.isNull()) _ssr_logo_texture = bindTexture(image_buffer); else ERROR("Texture \"" << path_to_image.toAscii().data() << "\" not loaded."); image_buffer = QImage(); // load source shadow texture path_to_image = QString( _path_to_gui_images.c_str() ).append("/source_shadow.png"); image_buffer.load(path_to_image, "PNG"); if (!image_buffer.isNull()) { _source_shadow_texture = bindTexture(image_buffer); } else ERROR("Texture \"" << path_to_image.toAscii().data() << "\" not loaded."); if (_controller.show_head()) { _plot_listener = true; // load listener texture image_buffer = QImage(); path_to_image = QString( _path_to_gui_images.c_str() ).append("/listener.png"); image_buffer.load(path_to_image, "PNG"); if (!image_buffer.isNull()) _listener_texture = bindTexture(image_buffer); else ERROR("Texture \"" << path_to_image.toAscii().data() << "\" not loaded."); // load listener shadow texture image_buffer = QImage(); path_to_image = QString( _path_to_gui_images.c_str() ).append("/listener_shadow.png"); image_buffer.load(path_to_image, "PNG"); if (!image_buffer.isNull()) { _listener_shadow_texture = bindTexture(image_buffer); } else ERROR("Texture \"" << path_to_image.toAscii().data() << "\" not loaded."); // load listener background texture image_buffer = QImage(); path_to_image = QString( _path_to_gui_images.c_str() ).append("/listener_background.png"); image_buffer.load(path_to_image, "PNG"); if (!image_buffer.isNull()) { _listener_background_texture = bindTexture(image_buffer); } else ERROR("Texture \"" << path_to_image.toAscii().data() << "\" not loaded."); } } void ssr::QOpenGLPlotter::initializeGL() { glClearColor(1.0,1.0,1.0,1.0); // done using Qt now: // glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH | GLUT_MULTISAMPLE); glEnable(GL_CULL_FACE); glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_FASTEST); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); // for blending //glColor4f(.0f,.0f,.0f,1.0f); //glBlendFunc(GL_SRC_ALPHA,GL_ONE); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); //glPolygonOffset(-0.2, -1.0); // TODO: necessary ??? glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); // load TLabs logo and SSR logo _load_background_textures(); update(); } void ssr::QOpenGLPlotter::resizeGL(int width, int height) { glViewport(0, 0, width, height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(-(float)width/_zoom_factor, (float)width/_zoom_factor, -(float)height/_zoom_factor, (float)height/_zoom_factor, 1.0, 15.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); _glu_quadric = gluNewQuadric(); } void ssr::QOpenGLPlotter::paintGL() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt(0.0f, 0.0f, 10.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f); // translate to center of plot glTranslatef(_window_x_offset, _window_y_offset, _window_z_offset); _draw_background(); // rotate plot (not implemented yet) glRotatef(_rotation_x, 0.0f, 0.0f, 1.0f); _draw_reference(); _draw_objects(); _draw_rubber_band(); } void ssr::QOpenGLPlotter::_draw_background() { // background color glColor3f(BACKGROUNDCOLOR); // ID of background glLoadName(BACKGROUNDINDEX); // plot background glBegin(GL_QUADS); glVertex3f( (-(float)width() /_zoom_factor - _window_x_offset), (-(float)height()/_zoom_factor - _window_y_offset), 0.0f); glVertex3f( ( (float)width() /_zoom_factor - _window_x_offset), (-(float)height()/_zoom_factor - _window_y_offset), 0.0f); glVertex3f( ( (float)width() /_zoom_factor - _window_x_offset), ( (float)height()/_zoom_factor - _window_y_offset), 0.0f); glVertex3f( (-(float)width() /_zoom_factor - _window_x_offset), ( (float)height()/_zoom_factor - _window_y_offset), 0.0f); glEnd(); // Draw textured stuff glColor3f(1.0f,1.0f,1.0f); glEnable(GL_TEXTURE_2D); // draw SSR logo glPushMatrix(); // TODO: Incorporate the following line more elegantly glTranslatef(-_window_x_offset, -_window_y_offset, -_window_z_offset); // translate to respective position // TODO: Include it in verteces // fixed position // glTranslatef( -4.1f*STDZOOMFACTOR/_zoom_factor, -3.3f*STDZOOMFACTOR/_zoom_factor, 0.0f); // relative position glTranslatef( (-(float)width() /_zoom_factor + 0.4f*STDZOOMFACTOR/_zoom_factor), (-(float)height()/_zoom_factor + 0.3f*STDZOOMFACTOR/_zoom_factor), 0.0f); glBindTexture(GL_TEXTURE_2D, _ssr_logo_texture); glLoadName(SSRLOGOINDEX); // ID of logo glColor3f(1.0f,1.0f,1.0f); glBegin(GL_QUADS); glTexCoord2f(0.0f, 0.0f); glVertex3f(0.0f, 0.0f, 0.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f(0.5f*STDZOOMFACTOR/_zoom_factor, 0.0f, 0.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f(0.5f*STDZOOMFACTOR/_zoom_factor, 0.5f*STDZOOMFACTOR/_zoom_factor, 0.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f(0.0f, 0.5f*STDZOOMFACTOR/_zoom_factor, 0.0f); glEnd(); glDisable(GL_TEXTURE_2D); glPopMatrix(); } void ssr::QOpenGLPlotter::_draw_reference() { glEnable(GL_MULTISAMPLE); glEnable(GL_POLYGON_OFFSET_LINE); glPushMatrix(); // translate according to reference position glTranslatef(_scene.get_reference().position.x, _scene.get_reference().position.y, 0.0f); glPushMatrix(); glLoadName(REFERENCEINDEX1); // ID of handle to rotate listener float scale = 1.0f; if (_plot_listener) { glEnable(GL_TEXTURE_2D); glEnable(GL_BLEND); glBindTexture(GL_TEXTURE_2D, _listener_background_texture); gluQuadricTexture(_glu_quadric, GL_TRUE ); gluDisk(_glu_quadric, 0.0f, 0.7f, LEVELOFDETAIL,1); glTranslatef(0.03f, -0.03f, 0.0f); // rotate according to reference position glRotatef(_scene.get_reference().orientation.azimuth, 0.0f, 0.0f, 1.0f); glBindTexture(GL_TEXTURE_2D, _listener_shadow_texture); glBegin(GL_QUADS); glTexCoord2f(0.0f, 0.0f); glVertex3f(-0.35f, -0.35f, 0.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f( 0.35f, -0.35f, 0.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f( 0.35f, 0.35f, 0.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f(-0.35f, 0.35f, 0.0f); glEnd(); glPopMatrix(); // rotate according to reference position glRotatef(_scene.get_reference().orientation.azimuth, 0.0f, 0.0f, 1.0f); glBindTexture(GL_TEXTURE_2D, _listener_texture); glBegin(GL_QUADS); glTexCoord2f(0.0f, 0.0f); glVertex3f(-0.3f, -0.3f, 0.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f( 0.3f, -0.3f, 0.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f( 0.3f, 0.3f, 0.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f(-0.3f, 0.3f, 0.0f); glEnd(); glDisable(GL_BLEND); glDisable(GL_TEXTURE_2D); } else { // rotate according to reference position glRotatef(_scene.get_reference().orientation.azimuth, 0.0f, 0.0f, 1.0f); // background color glColor3f(BACKGROUNDCOLOR); // define handle to interact with reference glBegin(GL_QUADS); glVertex3f( 0.15f * scale, -0.05f * scale, 0.0f); glVertex3f( 0.15f * scale, 0.05f * scale, 0.0f); glVertex3f(-0.05f * scale, 0.05f * scale, 0.0f); glVertex3f(-0.05f * scale, -0.05f * scale, 0.0f); glEnd(); glColor3f(0.4f, 0.4f, 0.4f); // draw rhomb glBegin(GL_LINES); glVertex3f( 0.0f, -0.05f * scale, 0.0f); glVertex3f( 0.15f * scale, 0.0f, 0.0f); glVertex3f( 0.15f * scale, 0.0f, 0.0f); glVertex3f( 0.0f , 0.05f * scale, 0.0f); glVertex3f( 0.0f , 0.05f * scale, 0.0f); glVertex3f(-0.05f * scale, 0.0f, 0.0f); glVertex3f(-0.05f * scale, 0.0f, 0.0f); glVertex3f( 0.0f, -0.05f * scale, 0.0f); glEnd(); // somehow this 90 degree rotation is needed glRotatef(-90, 0.0f, 0.0f, 1.0f); // rotate/translate according to reference offset glTranslatef(_scene.get_reference_offset().position.x , _scene.get_reference_offset().position.y, 0.0f); glRotatef(_scene.get_reference_offset().orientation.azimuth , 0.0f, 0.0f, 1.0f); // draw cross (showing the reference offset) glBegin(GL_LINES); glVertex3f( 0.02f * scale, 0.0f, 0.0f); glVertex3f(-0.02f * scale, 0.0f, 0.0f); glVertex3f( 0.0f, -0.02f * scale, 0.0f); glVertex3f( 0.0f, 0.03f * scale, 0.0f); glEnd(); } glPopMatrix(); glPopMatrix(); // mark origin of coordinate system glColor3f(0.4f, 0.4f, 0.4f); // draw origin of coordinate system glBegin(GL_LINES); glVertex3f( 0.02f, 0.0f, 0.0f); glVertex3f(-0.02f, 0.0f, 0.0f); glVertex3f( 0.0f, -0.02f, 0.0f); glVertex3f( 0.0f, 0.02f, 0.0f); glEnd(); glDisable(GL_POLYGON_OFFSET_LINE); glDisable(GL_MULTISAMPLE); } void ssr::QOpenGLPlotter::_draw_objects() { // enable anti-aliasing glEnable(GL_MULTISAMPLE); // get source info source_buffer_list_t source_buffer_list; _scene.get_sources(source_buffer_list); // plot loudspeakers //////////////////////////////////////////////////////// std::vector output_levels; if (_id_of_last_clicked_source > 0) { source_buffer_list_t::const_iterator temp = source_buffer_list.begin(); std::advance(temp, _id_of_last_clicked_source - 1); output_levels = temp->output_levels; } _loudspeakers.clear(); _scene.get_loudspeakers(_loudspeakers); glLoadName(NAMESTACKOFFSET + source_buffer_list.size() * NAMESTACKSTEP + 4); for (Loudspeaker::container_t::size_type i = 0; i < _loudspeakers.size(); ++i) { glPushMatrix(); // TODO: z position??? glTranslatef(_loudspeakers[i].position.x, _loudspeakers[i].position.y, 0.0f); glRotatef(_loudspeakers[i].orientation.azimuth, 0.0f, 0.0f, 1.0f); // TODO: display actual level instead of on/off value bool active = false; if (output_levels.size() > i) active = output_levels[i]; _draw_loudspeaker(_loudspeakers[i].mute, active); glPopMatrix(); } // for // end plot loudspeakers //////////////////////////////////////////////////// // plot sound sources /////////////////////////////////////////////////////// int n = 1; for (source_buffer_list_t::const_iterator i = source_buffer_list.begin(); i != source_buffer_list.end(); i++) { glPushMatrix(); // go to source position glTranslatef(i->position.x,i->position.y,0.0); // draw it _draw_source(i, n); glPopMatrix(); // increment counter n++; } // for // end plot sources ///////////////////////////////////////////////////////// // disable anti-aliasing glDisable(GL_MULTISAMPLE); } void ssr::QOpenGLPlotter::_draw_source(source_buffer_list_t::const_iterator& source, unsigned int index) { float scale = 1.0f; bool selected = false; bool soloed = false; if (_selected_sources_map.find(index) != _selected_sources_map.end()) { selected = true; } if (_soloed_sources.find(source->id) != _soloed_sources.end()) { soloed = true; } // check if source is selected if (selected) scale = 2.0f; // dummy id to prevend mouse action glLoadName(DUMMYINDEX); // draw shadow texture glEnable(GL_TEXTURE_2D); //glEnable(GL_ALPHA_TEST); glEnable(GL_BLEND); glBindTexture(GL_TEXTURE_2D, _source_shadow_texture); glPushMatrix(); glTranslatef(0.015f, -0.015f, 0.01f); gluQuadricTexture(_glu_quadric, GL_TRUE ); gluDisk(_glu_quadric, 0.0f, 0.163f * scale,LEVELOFDETAIL,1); glPopMatrix(); glDisable(GL_TEXTURE_2D); //glDisable(GL_ALPHA_TEST); glDisable(GL_BLEND); // make sure that source is drawn on top //if (selected) glTranslatef(0.0f, 0.0f, 0.2f); //else glTranslatef(0.0f, 0.0f, 0.1f); // TODO: make it more elegant // source id glLoadName(NAMESTACKOFFSET + index * NAMESTACKSTEP + 1); // draw source glColor3f(1.0f, 1.0f, 1.0f); // white gluDisk(_glu_quadric, 0.0f, 0.15f * scale, LEVELOFDETAIL, 1); // fill source qglColor(_color_vector[source->id%_color_vector.size()]); gluPartialDisk(_glu_quadric, 0.0f, 0.125f * scale, LEVELOFDETAIL, 1, 0.0f, 360.0f); // draw solo indication if (soloed) { gluPartialDisk(_glu_quadric, 0.2f * scale, 0.22f * scale, LEVELOFDETAIL, 1, 55.0f, 70.0f); gluPartialDisk(_glu_quadric, 0.2f * scale, 0.22f * scale, LEVELOFDETAIL, 1, 235.0f, 70.0f); } // choose color if (source->mute) glColor3f(0.5f, 0.5f, 0.5f); else qglColor(_color_vector[source->id%_color_vector.size()]); // draw ring around source gluDisk(_glu_quadric, 0.14f * scale, 0.15f * scale, LEVELOFDETAIL, 1); // volume slider id; It is accessible only when source selected if (selected) glLoadName(NAMESTACKOFFSET + index * NAMESTACKSTEP + 2); // plot level meter frame in black glBegin(GL_TRIANGLE_FAN); glVertex3f( -0.1f * scale, -0.2f * scale, 0.0f); glVertex3f( -0.1f * scale, -0.25f * scale, 0.0f); glVertex3f( 0.1f * scale, -0.25f * scale, 0.0f); glVertex3f( 0.1f * scale, -0.2f * scale, 0.0f); glEnd(); if (selected) { // plot volume slider glBegin(GL_TRIANGLES); glVertex3f((-0.09f * scale + (20.0f*log10(source->gain)+50.0f)/62.0f * 0.18f * scale ) - 0.015f * scale, -0.29f * scale, 0.0f); glVertex3f((-0.09f * scale + (20.0f*log10(source->gain)+50.0f)/62.0f * 0.18f * scale ) + 0.015f * scale, -0.29f * scale, 0.0f); glVertex3f((-0.09f * scale + (20.0f*log10(source->gain)+50.0f)/62.0f * 0.18f * scale ), -0.26f * scale, 0.0f); glEnd(); } if (source->fixed_position) { // draw cross glBegin(GL_TRIANGLES); glVertex3f( -0.02f * scale, 0.005f * scale, 0.0f); glVertex3f( -0.02f * scale, -0.005f * scale, 0.0f); glVertex3f( 0.02f * scale, 0.005f * scale, 0.0f); glVertex3f( 0.02f * scale, 0.005f * scale, 0.0f); glVertex3f( -0.02f * scale, -0.005f * scale, 0.0f); glVertex3f( 0.02f * scale, -0.005f * scale, 0.0f); glVertex3f( -0.005f * scale, -0.02f * scale, 0.0f); glVertex3f( 0.005f * scale, -0.02f * scale, 0.0f); glVertex3f( 0.005f * scale, 0.02f * scale, 0.0f); glVertex3f( 0.005f * scale, 0.02f * scale, 0.0f); glVertex3f( -0.005f * scale, 0.02f * scale, 0.0f); glVertex3f( -0.005f * scale, -0.02f * scale, 0.0f); glEnd(); } // plot level meter background white glColor3f(1.0f, 1.0f, 1.0f); glBegin(GL_TRIANGLE_FAN); glVertex3f( -0.09f * scale, -0.21f * scale, 0.0f); glVertex3f( -0.09f * scale, -0.24f * scale, 0.0f); glVertex3f( 0.09f * scale, -0.24f * scale, 0.0f); glVertex3f( 0.09f * scale, -0.21f * scale, 0.0f); glEnd(); // plot audio level in green glColor3f(0.2275f, 0.9373f, 0.2275f); // signal_level in dB float signal_level = 20.0f * log10(source->signal_level + 0.00001f); // min -100 dB // only values up to 0 dB can be shown signal_level = std::min(signal_level, 0.0f); // TODO: color glBegin(GL_TRIANGLE_FAN); glVertex3f( -0.09f * scale, -0.21f * scale, 0.0f); glVertex3f( -0.09f * scale, -0.24f * scale, 0.0f); glVertex3f( -0.09f * scale + (signal_level + 50.0f)/50.0f * 0.18f * scale, -0.24f * scale, 0.0f); glVertex3f( -0.09f * scale + (signal_level + 50.0f)/50.0f * 0.18f * scale, -0.21f * scale, 0.0f); glEnd(); // display source name if (_allow_displaying_text) { qglColor(_color_vector[source->id%_color_vector.size()]); QFont f = font(); f.setPointSize(static_cast(_zoom_factor/STDZOOMFACTOR*font().pointSize() + 0.5f)); renderText(0.18f * scale, 0.13f * scale, 0.0f, source->name.c_str(), f); } // dummy id to prevend mouse action glLoadName(DUMMYINDEX); // plot orientation of plane wave if (source->model == Source::plane) { // rotate glRotatef(static_cast(source->orientation.azimuth), 0.0f, 0.0f, 1.0f); if (source->mute) glColor3f(0.5f, 0.5f, 0.5f); else qglColor(_color_vector[source->id%_color_vector.size()]); // plot ring segments gluPartialDisk(_glu_quadric, 0.18f * scale, 0.19f * scale, LEVELOFDETAIL, 1, 105.0f, 75.0f); gluPartialDisk(_glu_quadric, 0.18f * scale, 0.19f * scale, LEVELOFDETAIL, 1, 0.0f, 90.0f); // plot bars glBegin(GL_TRIANGLE_FAN); glVertex3f(-0.005f * scale, -0.36f * scale, 0.0f); glVertex3f( 0.005f * scale, -0.36f * scale, 0.0f); glVertex3f( 0.005f * scale, -0.18f * scale, 0.0f); glVertex3f(-0.005f * scale, -0.18f * scale, 0.0f); glEnd(); glBegin(GL_TRIANGLE_FAN); glVertex3f( 0.005f * scale, 0.36f * scale, 0.0f); glVertex3f(-0.005f * scale, 0.36f * scale, 0.0f); glVertex3f(-0.005f * scale, 0.18f * scale, 0.0f); glVertex3f( 0.005f * scale, 0.18f * scale, 0.0f); glEnd(); // plot arrow glBegin(GL_TRIANGLE_FAN); // lower branch glVertex3f( 0.277f * scale, 0.0f, 0.0f); glVertex3f( 0.25f * scale, -0.028f * scale, 0.0f); glVertex3f( 0.25f * scale, -0.04f * scale, 0.0f); glVertex3f( 0.29f * scale, 0.0f, 0.0f); glEnd(); glBegin(GL_TRIANGLE_FAN); // upper branch glVertex3f( 0.29f * scale, 0.0f, 0.0f); glVertex3f( 0.25f * scale, 0.04f * scale, 0.0f); glVertex3f( 0.25f * scale, 0.028f * scale, 0.0f); glVertex3f( 0.277f * scale, 0.0f, 0.0f); glEnd(); glBegin(GL_TRIANGLE_FAN); // root glVertex3f( 0.18f * scale, 0.005f * scale, 0.0f); glVertex3f( 0.18f * scale, -0.005f * scale, 0.0f); glVertex3f( 0.285f * scale, -0.005f * scale, 0.0f); glVertex3f( 0.285f * scale, 0.005f * scale, 0.0f); glEnd(); } else if (source->model == Source::directional) { // rotate glRotatef((GLfloat)(source->orientation.azimuth), 0.0f, 0.0f, 1.0f); qglColor(_color_vector[source->id%_color_vector.size()]); // plot ring gluPartialDisk(_glu_quadric, 0.18f * scale, 0.19f * scale, LEVELOFDETAIL, 1, 105.0f, 346.0f); // id of direction handle glLoadName(NAMESTACKOFFSET + index * NAMESTACKSTEP + 3); // plot arrow glBegin(GL_TRIANGLE_FAN); // lower branch glVertex3f( 0.277f * scale, 0.0f, 0.0f); glVertex3f( 0.25f * scale, -0.028f * scale, 0.0f); glVertex3f( 0.25f * scale, -0.04f * scale, 0.0f); glVertex3f( 0.29f * scale, 0.0f, 0.0f); glEnd(); glBegin(GL_TRIANGLE_FAN); // upper branch glVertex3f( 0.29f * scale, 0.0f, 0.0f); glVertex3f( 0.25f * scale, 0.04f * scale, 0.0f); glVertex3f( 0.25f * scale, 0.028f * scale, 0.0f); glVertex3f( 0.277f * scale, 0.0f, 0.0f); glEnd(); glBegin(GL_TRIANGLE_FAN); // root glVertex3f( 0.18f * scale, 0.005f * scale, 0.0f); glVertex3f( 0.18f * scale, -0.005f * scale, 0.0f); glVertex3f( 0.285f * scale, -0.005f * scale, 0.0f); glVertex3f( 0.285f * scale, 0.005f * scale, 0.0f); glEnd(); } } void ssr::QOpenGLPlotter::_draw_loudspeaker(bool muted, bool active) { glColor3f(0.4f, 0.4f, 0.4f); if (muted) { // plot muted loudspeaker // TODO: Make lines wider? glBegin(GL_LINE_STRIP); glVertex3f( -0.02f, 0.017f, 0.0f); glVertex3f( -0.02f, -0.017f, 0.0f); glVertex3f( 0.002f, -0.017f, 0.0f); glVertex3f( 0.024f, -0.040f, 0.0f); glVertex3f( 0.024f, 0.040f, 0.0f); glVertex3f( 0.002f, 0.017f, 0.0f); glVertex3f( -0.02f, 0.017f, 0.0f); glEnd(); } else { // plot loudspeaker glBegin(GL_TRIANGLE_FAN); // magnet glVertex3f( -0.02f, 0.017f, 0.0f); glVertex3f( -0.02f, -0.017f, 0.0f); glVertex3f( -0.002f, -0.017f, 0.0f); glVertex3f( -0.002f, 0.017f, 0.0f); glEnd(); glBegin(GL_TRIANGLE_FAN); // cone glVertex3f( 0.002f, 0.017f, 0.0f); glVertex3f( 0.002f, -0.017f, 0.0f); glVertex3f( 0.024f, -0.040f, 0.0f); glVertex3f( 0.024f, 0.040f, 0.0f); glEnd(); glColor3f(1.0f, 1.0f, 1.0f); glBegin(GL_TRIANGLE_FAN); // ring glVertex3f( -0.002f, 0.017f, 0.0f); glVertex3f( -0.002f, -0.017f, 0.0f); glVertex3f( 0.002f, -0.017f, 0.0f); glVertex3f( 0.002f, 0.017f, 0.0f); glEnd(); if (active) { // plot sound waves // TODO: more elegant if (_active_source == -1) _active_source = 0; qglColor(_color_vector[_id_of_last_clicked_source%_color_vector.size()]); gluPartialDisk(_glu_quadric, 0.05f ,0.055f, LEVELOFDETAIL, 1, 50.0f, 80.0f); gluPartialDisk(_glu_quadric, 0.1f , 0.105f, LEVELOFDETAIL, 1, 50.0f, 80.0f); gluPartialDisk(_glu_quadric, 0.15f, 0.155f, LEVELOFDETAIL, 1, 50.0f, 80.0f); } } } void ssr::QOpenGLPlotter::_draw_rubber_band() { // check if rubber band has to be drawn if (_rubber_band_starting_point == _rubber_band_ending_point) { return; } glEnable(GL_COLOR_LOGIC_OP); glLogicOp(GL_XOR); glColor4f(0.4f, 0.4f, 0.4f, 0.5f); // draw rubber band glBegin(GL_LINES); glVertex3f(_rubber_band_starting_point.x, _rubber_band_starting_point.y, 0.0f); glVertex3f(_rubber_band_starting_point.x, _rubber_band_ending_point.y, 0.0f); glVertex3f(_rubber_band_starting_point.x, _rubber_band_ending_point.y, 0.0f); glVertex3f(_rubber_band_ending_point.x, _rubber_band_ending_point.y, 0.0f); glVertex3f(_rubber_band_ending_point.x, _rubber_band_ending_point.y, 0.0f); glVertex3f(_rubber_band_ending_point.x, _rubber_band_starting_point.y,0.0f); glVertex3f(_rubber_band_ending_point.x, _rubber_band_starting_point.y, 0.0f); glVertex3f(_rubber_band_starting_point.x, _rubber_band_starting_point.y, 0.0f); glEnd(); glDisable(GL_COLOR_LOGIC_OP); // determine limits of rubber band const float upper_x = std::max(_rubber_band_starting_point.x, _rubber_band_ending_point.x); const float lower_x = std::min(_rubber_band_starting_point.x, _rubber_band_ending_point.x); const float upper_y = std::max(_rubber_band_starting_point.y, _rubber_band_ending_point.y); const float lower_y = std::min(_rubber_band_starting_point.y, _rubber_band_ending_point.y); source_buffer_list_t source_buffer_list; _scene.get_sources(source_buffer_list); int n = 1; // determine which sources are under the rubber band for (source_buffer_list_t::const_iterator i = source_buffer_list.begin(); i != source_buffer_list.end(); i++) { // de-/select sources underneath the rubber band if (i->position.x > lower_x && i->position.x < upper_x && i->position.y > lower_y && i->position.y < upper_y ) { if (_ctrl_pressed && _alt_pressed) _deselect_source(n); else _select_source(n, true); } else if (!_ctrl_pressed) _deselect_source(n); n++; } } int ssr::QOpenGLPlotter::_find_selected_object(const QPoint &pos) { const int MaxSize = 512; GLuint buffer[MaxSize]; GLint viewport[4]; // avoid error message from X server _allow_displaying_text = false; glGetIntegerv(GL_VIEWPORT, viewport); glSelectBuffer(MaxSize, buffer); glRenderMode(GL_SELECT); glInitNames(); glPushName(DUMMYINDEX); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); gluPickMatrix((GLdouble)pos.x(),(GLdouble)(viewport[3] - pos.y()),5.0, 5.0, viewport); glOrtho(-(float)width()/_zoom_factor, (float)width()/_zoom_factor, -(float)height()/_zoom_factor, (float)height()/_zoom_factor, 1.0f, 15.0f); // redraw stuff in projection mode paintGL(); glMatrixMode(GL_PROJECTION); glPopMatrix(); // glPushName,glPopName ? // danger of error message from X server is over _allow_displaying_text = true; GLint no_of_hits = glRenderMode(GL_RENDER); if (no_of_hits > 0){ // TODO: Make sure that closest object gets selected. return buffer[(no_of_hits-1)*4+3]; } else return 0; } void ssr::QOpenGLPlotter::_get_openGL_pos(int x, int y, GLdouble* pos_x, GLdouble* pos_y, GLdouble* pos_z) { GLint viewport[4]; GLdouble modelview[16]; GLdouble projection[16]; GLfloat win_x, win_y, win_z; glGetDoublev(GL_MODELVIEW_MATRIX, modelview); glGetDoublev(GL_PROJECTION_MATRIX, projection); glGetIntegerv(GL_VIEWPORT, viewport ); win_x = static_cast(x); win_y = static_cast(viewport[3]) - static_cast(y); glReadPixels(x, static_cast(win_y), 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &win_z); gluUnProject(win_x, win_y, win_z, modelview, projection, viewport, pos_x, pos_y, pos_z); } void ssr::QOpenGLPlotter::_get_pixel_pos(GLdouble pos_x, GLdouble pos_y, GLdouble pos_z, int* x, int* y) { GLint viewport[4]; GLdouble modelview[16]; GLdouble projection[16]; GLdouble win_x, win_y, win_z; glGetDoublev(GL_MODELVIEW_MATRIX, modelview); glGetDoublev(GL_PROJECTION_MATRIX, projection); glGetIntegerv(GL_VIEWPORT, viewport); gluProject(pos_x, pos_y, pos_z, modelview, projection, viewport, &win_x, &win_y, &win_z); *x = static_cast(win_x + 0.5); *y = static_cast(viewport[3] - win_y + 0.5); } void ssr::QOpenGLPlotter::_set_zoom(int zoom) { // limit possible zoom range zoom = std::max( 30, zoom); zoom = std::min(300, zoom); // update zoom factor _zoom_factor = STDZOOMFACTOR*zoom/100.0f; // update projection matrix glMatrixMode(GL_PROJECTION); glLoadIdentity(); // TODO: Define clipping planes such that nothing is rendered behind overlays glOrtho(-(float)width()/_zoom_factor, (float)width()/_zoom_factor, -(float)height()/_zoom_factor, (float)height()/_zoom_factor, 1.0f, 15.0f); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); emit signal_zoom_set(zoom); } void ssr::QOpenGLPlotter::_select_source(int source, bool add_to_selection) { if (!add_to_selection) _deselect_all_sources(); // check if listener is selected if (source == 0) _reference_selected = true; else _reference_selected = false; // if listener or background selected clear map if (source <= 0) _selected_sources_map.clear(); // if source is not yet selected if (source > 0 && _selected_sources_map.find(source) == _selected_sources_map.end()) { source_buffer_list_t source_buffer_list; // get selected sources ID _scene.get_sources(source_buffer_list); // if source does not exist if (source > static_cast(source_buffer_list.size())) return; source_buffer_list_t::iterator i = source_buffer_list.begin(); // iterate to source for (int n = 1; n < source; n++) i++; // store source and its id _selected_sources_map[source] = i->id; // make its id directly available _id_of_last_clicked_source = i->id; } else if (!_alt_pressed) { // make valid id available _id_of_last_clicked_source = _selected_sources_map.find(source)->second; } // if source is already selected then deselect it else if (_alt_pressed) _deselect_source(source); } void ssr::QOpenGLPlotter::_select_all_sources() { // clear variables; _selected_sources_map.clear(); _id_of_last_clicked_source = 0; int n = 1; source_buffer_list_t source_buffer_list; // get selected sources ID _scene.get_sources(source_buffer_list); for (source_buffer_list_t::const_iterator i = source_buffer_list.begin(); i != source_buffer_list.end(); i++) { // store source and its id _selected_sources_map[n] = i->id; // make valid id available _id_of_last_clicked_source = i->id; n++; } } void ssr::QOpenGLPlotter::_deselect_source(int source) { // make sure that at least one sources stays selected // and that id_of_last_clicked_source has a valid value if (_selected_sources_map.size() > 1) { _selected_sources_map.erase(source); _id_of_last_clicked_source = _selected_sources_map.begin()->second; } } void ssr::QOpenGLPlotter::_deselect_all_sources() { _selected_sources_map.clear(); _id_of_last_clicked_source = 0; } // 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'\:'=' ssr-0.4.2/src/gui/qopenglplotter.h000066400000000000000000000170151236416011200171270ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// TODO: add description #ifndef SSR_QOPENGLPLOTTER_H #define SSR_QOPENGLPLOTTER_H #ifdef __APPLE__ #include #else #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "publisher.h" #include "scene.h" #include "qsourceproperties.h" #define STDZOOMFACTOR 280.0f #define STDWINDOWYOFFSET -1.0f #define VOLUMEACCELERATION 0.3f #define DUMMYINDEX 0 #define BACKGROUNDINDEX 1 #define REFERENCEINDEX1 2 #define REFERENCEINDEX2 3 #define SSRLOGOINDEX 4 #define NAMESTACKOFFSET 6 #define NAMESTACKSTEP 5 /* Info about name stack: * * dummy (to prevent mouse action): 0 * background: 1 * reference handle for translation: 2 * reference handle for rotation: 3 * SSR logo: 4 * * source: NAMESTACKOFFSET + index_of_source * NAMESTACKSTEP + 1 * source volume slider: NAMESTACKOFFSET + index_of_source * NAMESTACKSTEP + 2 * source direction handle: NAMESTACKOFFSET + index_of_source * NAMESTACKSTEP + 3 * loudspeaker: NAMESTACKOFFSET + index_of_loudspeaker * NAMESTACKSTEP + 4 * */ namespace ssr { /// open GL plotter class QOpenGLPlotter : public QGLWidget { Q_OBJECT // TODO: Discriminate between GLfloat and float etc. protected: struct SourceCopy; // nested class, defined later //////////////////////////////////////////////////////////////////////////////// // Declaration of the nested class SourceCopy //////////////////////////////////////////////////////////////////////////////// /** Temporary buffer for source information. * This class is used to extract certain information for each source from * the Scene. There is no use in copying data which are not used afterwards. **/ struct SourceCopy : DirectionalPoint { /// SourceCopies want to be stored in such a list typedef std::list list_t; /// type conversion constructor SourceCopy(const Scene::source_map_t::value_type& other); ssr::id_t id; ///< identifier Source::model_t model; ///< source model bool mute; ///< mute state float gain; ///< source gain float signal_level; ///< level of audio stream (linear, between 0 and 1) std::vector output_levels; std::string name; /// color_vector_t; protected: Publisher& _controller; const Scene& _scene; int _active_source; const std::string _path_to_gui_images; ssr::id_t _id_of_last_clicked_source; typedef std::map selected_sources_map_t; selected_sources_map_t _selected_sources_map; float _zoom_factor; std::set _soloed_sources; // void mousePressEvent(QMouseEvent *event); QMouseEvent _previous_mouse_event; bool _ctrl_pressed; bool _alt_pressed; int _find_selected_object(const QPoint &pos); void _get_openGL_pos(int x, int y, GLdouble* posX, GLdouble* posY, GLdouble* posZ); void _get_pixel_pos(GLdouble pos_x, GLdouble pos_y, GLdouble pos_z, int* x, int* y); Position _rubber_band_starting_point; Position _rubber_band_ending_point; GLfloat _window_x_offset, _window_y_offset, _window_z_offset; GLfloat _x_offset, _y_offset, _z_offset; GLfloat _angle_offset; //Orientation orientation_offset; GLfloat _rotation_x; GLfloat _rotation_y; GLfloat _rotation_z; bool _reference_selected; bool _volume_slider_selected; bool _direction_handle_selected; void _select_source(int source, bool add_to_selection = false); void _select_all_sources(); void _deselect_source(int source); void _deselect_all_sources(); private: GLuint _ssr_logo_texture; GLuint _source_shadow_texture; GLuint _listener_texture; GLuint _listener_shadow_texture; GLuint _listener_background_texture; Loudspeaker::container_t _loudspeakers; color_vector_t _color_vector; bool _allow_displaying_text; /// Quadric necessary to plot spheres and disks GLUquadricObj *_glu_quadric; bool _plot_listener; // OpenGL functions void initializeGL(); void resizeGL(int width, int height); void paintGL(); // drawing functions void _draw_background(); void _draw_objects(); void _draw_source(source_buffer_list_t::const_iterator& source, unsigned int index); void _draw_loudspeaker(bool muted = false, bool active = false); void _draw_rubber_band(); void _draw_reference(); void _load_background_textures(); protected slots: void _set_zoom(int zoom); signals: void signal_zoom_set(int zoom); }; } // namespace ssr #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'\:'=' ssr-0.4.2/src/gui/qscenebutton.cpp000066400000000000000000000076251236416011200171230ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// TODO: add description #include #include "qscenebutton.h" #define MAXNUMBEROFCHARACTERS 9 QSceneButton::QSceneButton(QWidget* parent, const QString& text_in, const QString& path) : QPushButton(parent), path(path) { // limit number of characters if (text_in.length() > MAXNUMBEROFCHARACTERS){ text = text_in.left(MAXNUMBEROFCHARACTERS); text.append("..."); } else text = text_in; } void QSceneButton::mousePressEvent(QMouseEvent *event) { if ( !isChecked() ) QPushButton::mousePressEvent(event); else return; emit signal_open_scene(path); } void QSceneButton::mouseMoveEvent(QMouseEvent *event) { // prevend action of QPushButton event->accept(); } void QSceneButton::paintEvent( QPaintEvent * event) { // draw QLabel stuff QPushButton::paintEvent(event); // draw custom stuff QPainter painter(this); // enable anti-aliasing painter.setRenderHint(QPainter::Antialiasing); // black painter.setPen(QPen(QColor(0,0,0),1)); // plot arrow if ( isChecked() ){ // draw down-arrow painter.drawLine(QLineF( 80.0f, 7.0f, 80.0f, 16.0f)); // root painter.drawLine(QLineF( 76.0f, 13.0f, 80.0f, 16.0f)); // left branch painter.drawLine(QLineF( 84.0f, 13.0f, 80.0f, 16.0f)); // right branch } else { // draw right-arrow painter.drawLine(QLineF( 77.0f, 10.0f, 86.0f, 10.0f)); // root painter.drawLine(QLineF( 83.0f, 14.0f, 86.0f, 10.0f)); // lower branch painter.drawLine(QLineF( 83.0f, 6.0f, 86.0f, 10.0f)); // upper branch } // enable text anti-aliasing painter.setRenderHint(QPainter::TextAntialiasing); // display text painter.drawText(QPointF(10.0f, 13.0f), text); } // 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'\:'=' ssr-0.4.2/src/gui/qscenebutton.h000066400000000000000000000055531236416011200165660ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// QSceneButton #ifndef SSR_QSCENEBUTTON_H #define SSR_QSCENEBUTTON_H #include #include /// QSceneButton class QSceneButton : public QPushButton { Q_OBJECT public: QSceneButton( QWidget* parent, const QString& text, const QString& path); protected: QString text; const QString path; virtual void mousePressEvent(QMouseEvent *event); virtual void mouseMoveEvent(QMouseEvent *event); // virtual void mouseReleaseEvent(QMouseEvent *event); virtual void paintEvent( QPaintEvent * event); signals: void signal_open_scene(const QString&); }; #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'\:'=' ssr-0.4.2/src/gui/qsourceproperties.cpp000066400000000000000000000323251236416011200202020ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// TODO: add description #include #include #include "qsourceproperties.h" #include "ssr_global.h" #include "apf/math.h" using namespace apf::math; //#define COLLAPSEDHEIGHT 200 //#define EXPANDEDHEIGHT 300 //#define WIDTH 200 //#define ROWHEIGHT 40 QSourceProperties::QSourceProperties(QWidget* parent) #ifdef __APPLE__ : QFrame(parent, Qt::Window | Qt::CustomizeWindowHint), #else : QFrame(parent), #endif _create_new_source(false) { this->setAutoFillBackground(true); this->setPalette(QPalette(QColor(255,255,255))); this->setFrameStyle( QFrame::Panel | QFrame::Raised ); this->setLineWidth(2); QFont widget_font = this->font(); widget_font.setPointSize(16); this->setFont(widget_font); // create grid to organize layout // TODO: better use QFormLayout? _grid = new QGridLayout(this); unsigned int current_row = 0; // name _grid->addWidget(_create_text_label("name"), 0, 0); _name_display = new QLineEdit(); _name_display->setObjectName("item"); _name_display->setAlignment(Qt::AlignRight); _name_display->setFrame(false); _name_display->setReadOnly(true); _name_display->setFocusPolicy(Qt::NoFocus); _grid->addWidget(_name_display, current_row, 1, 1, 1); current_row++; // coordinates _grid->addWidget(_create_text_label("x, y"), current_row, 0); _coordinates_display = new QLabel(); _coordinates_display->setObjectName("item"); _coordinates_display->setAlignment(Qt::AlignRight); _grid->addWidget(_coordinates_display, current_row, 1); _grid->addWidget(_create_text_label(" mtrs"), current_row, 2); current_row++; // distance _grid->addWidget(_create_text_label("distance"), current_row, 0); _distance_display = new QLabel(); _distance_display->setObjectName("item"); _distance_display->setAlignment(Qt::AlignRight); _grid->addWidget(_distance_display, current_row, 1); _grid->addWidget(_create_text_label(" mtrs"), current_row, 2); current_row++; // azimuth _grid->addWidget(_create_text_label("azimuth"), current_row, 0); _azimuth_display = new QLabel(); _azimuth_display->setObjectName("item"); _azimuth_display->setAlignment(Qt::AlignRight); _grid->addWidget(_azimuth_display, current_row, 1); _grid->addWidget(_create_text_label(" degs"), current_row, 2); current_row++; // position fix _grid->addWidget(_create_text_label("fixed position"), current_row, 0); _position_fix_box = new QCheckBox(""); _position_fix_box->setFocusPolicy(Qt::NoFocus); connect(_position_fix_box, SIGNAL( toggled(bool) ), this, SLOT( _set_source_position_fixed(bool) )); _grid->addWidget(_position_fix_box, current_row, 2, Qt::AlignLeft); current_row++; // doppler effect enabled //_grid->addWidget(_create_text_label("Doppler effect"), current_row, 0); //_doppler_box = new QCheckBox(""); //_doppler_box->setFocusPolicy(Qt::NoFocus); //connect(_doppler_box, SIGNAL( toggled(bool) ), this, SLOT( _set_doppler(bool) )); //_grid->addWidget(_doppler_box, current_row, 2, Qt::AlignLeft); //current_row++; // volume _grid->addWidget(_create_text_label("volume"), current_row, 0); _volume_display = new QLabel(); _volume_display->setObjectName("item"); _volume_display->setAlignment(Qt::AlignRight); _grid->addWidget(_volume_display, current_row, 1); _grid->addWidget(_create_text_label(" dB"), current_row, 2); current_row++; // mute state _grid->addWidget(_create_text_label("muted"), current_row, 0); _muted_check_box = new QCheckBox(""); _muted_check_box->setFocusPolicy(Qt::NoFocus); connect(_muted_check_box, SIGNAL( toggled(bool) ), this, SLOT( _set_source_mute(bool) )); _grid->addWidget(_muted_check_box, current_row, 2, Qt::AlignLeft); current_row++; // solo state //_grid->addWidget(_create_text_label("soloed"), current_row, 0); //_soloed_check_box = new QCheckBox(""); //_soloed_check_box->setFocusPolicy(Qt::NoFocus); //connect(_soloed_check_box, SIGNAL( toggled(bool) ), this, SLOT( _set_source_solo(bool) )); //_grid->addWidget(_soloed_check_box, current_row, 2, Qt::AlignLeft); //current_row++; // source model _source_model_label = _create_text_label("model"); _grid->addWidget(_source_model_label, current_row, 0); _source_model_display = new QComboBox(); _source_model_display->setObjectName("item"); _source_model_display->setFrame(false); _source_model_display->insertItem(0, "plane wave"); _source_model_display->insertItem(1, "point source"); //_source_model_display->insertItem(2, "directional source"); _grid->addWidget(_source_model_display, current_row, 1, 1, 2); connect(_source_model_display, SIGNAL(activated(int)), this, SLOT( _set_source_model(int))); current_row++; // audio source _audio_source_label = _create_text_label("audio source"); _grid->addWidget(_audio_source_label, current_row, 0); _audio_source_display = new QLineEdit(); _audio_source_display->setObjectName("item"); _audio_source_display->setFrame(false); _audio_source_display->setAlignment(Qt::AlignRight); _audio_source_display->setReadOnly(true); _grid->addWidget(_audio_source_display, current_row, 1, 1, 2); current_row++; _port_radio_button = new QRadioButton("port"); _port_radio_button->setFocusPolicy(Qt::NoFocus); _file_radio_button = new QRadioButton("file"); _file_radio_button->setFocusPolicy(Qt::NoFocus); //_grid->addWidget(_port_radio_button, current_row, 1, 1, 1); //_grid->addWidget(_file_radio_button, current_row, 2, 1, 1); //current_row++; // properties file _properties_label = _create_text_label("properties"); _grid->addWidget(_properties_label, current_row, 0); _properties_display = new QLineEdit(); _properties_display->setObjectName("item"); _properties_display->setFrame(false); _properties_display->setAlignment(Qt::AlignRight); _properties_display->setReadOnly(true); _grid->addWidget(_properties_display, current_row, 1, 1, 2); current_row++; // close button _close_button = new QClickTextLabel("Close"); _close_button->setObjectName("item"); connect(_close_button, SIGNAL( clicked() ), this, SLOT( hide() )); _grid->addWidget(_close_button, current_row, 0); // create source button _create_source_button = new QClickTextLabel("Create source"); _create_source_button->setObjectName("item"); //connect(_less_button, SIGNAL( clicked() ), this, SLOT( _collapse() )); //_grid->addWidget(_create_source_button, current_row, 1, 1, 2); current_row++; setLayout(_grid); // size _grid->setColumnMinimumWidth (0, 80); _grid->setColumnMinimumWidth (1, 80); _grid->setColumnMinimumWidth (2, 40); for (int n = 0; n < _grid->rowCount(); n++) { _grid->setRowMinimumHeight (n, 20); } _grid->setSpacing(3); _grid->setSizeConstraint(QLayout::SetNoConstraint); // _collapse(); _expand(); } QSourceProperties::~QSourceProperties() { // TODO: Delete, delete, delete } QLabel* QSourceProperties::_create_text_label(const QString& text) { QLabel* buffer = new QLabel(text); buffer->setObjectName("item"); buffer->setTextFormat(Qt::RichText); buffer->setAlignment(Qt::AlignLeft); return buffer; } void QSourceProperties::update_displays(const Source& source, const DirectionalPoint& reference) { // do not update display if dialog is used to create a new sound source if ( _create_new_source ) return; if (!_name_display->hasFocus()) _name_display->setText( QString::fromStdString( source.name.c_str() ) ); if (!_audio_source_display->hasFocus()) _audio_source_display->setText( QString::fromStdString( source.port_name.c_str() ) ); _coordinates_display->setText(QString().setNum(source.position.x,'f',2) + ", " + QString().setNum(source.position.y,'f',2)); // calculate distance between reference point and source const float dist = sqrt(square(source.position.x - reference.position.x) + square(source.position.y - reference.position.y)); _distance_display->setText(QString().setNum(dist,'f',2)); // calculate angle from which the source is seen float ang = rad2deg(angle(source.position, reference.orientation)); // confine angle to interval ]-180, 180] TODO: Make it more elegant ang = std::fmod(ang, 360.0f); if (ang > 180.0f) ang -= 360.0f; else if (ang <= -180.0f) ang += 360.0f; _azimuth_display->setText(QString().setNum(ang,'f',2)); _position_fix_box->setChecked(source.fixed_position); // set source model switch(source.model){ case Source::point: _source_model_display->setCurrentIndex(1); break; case Source::plane: _source_model_display->setCurrentIndex(0); break; default: break; } //_doppler_box->setChecked(source.doppler_effect); _volume_display->setText(QString().setNum(linear2dB(source.gain),'f',1)); _muted_check_box->setChecked(source.mute); if (source.properties_file.empty()) { _properties_display->setText("No file specified."); } else { _properties_display->setText(source.properties_file.c_str()); } } void QSourceProperties::_expand() { _audio_source_label->show(); _audio_source_display->show(); _source_model_label->show(); _source_model_display->show(); _properties_label->show(); _properties_display->show(); _close_button->show(); //_create_source_button->show(); _grid->setSizeConstraint(QLayout::SetMinimumSize); } void QSourceProperties::_collapse() { // _more_button->show(); _audio_source_label->hide(); _audio_source_display->hide(); _source_model_label->hide(); _source_model_display->hide(); _properties_label->hide(); _properties_display->hide(); // _less_button->hide(); _grid->setSizeConstraint(QLayout::SetMinimumSize); } void QSourceProperties::mousePressEvent(QMouseEvent *event) { event->accept(); } void QSourceProperties::show(const bool create_new_source) { _create_new_source = create_new_source; QFrame::show(); } void QSourceProperties::_set_source_mute(bool flag) { emit signal_set_source_mute(flag); } void QSourceProperties::_set_source_solo(bool flag) { emit signal_set_source_solo(flag); } void QSourceProperties::_set_source_position_fixed(bool flag) { emit signal_set_source_position_fixed(flag); } void QSourceProperties::_set_source_model(int index) { emit signal_set_source_model(index); } void QSourceProperties::_set_doppler(bool flag) { (void)flag; //emit signal_set_source_property( ); } bool QSourceProperties::event(QEvent *e) { // catch mouse events if (e->type() == QEvent::MouseButtonDblClick || e->type() == QEvent::MouseButtonPress || e->type() == QEvent::MouseButtonRelease || e->type() == QEvent::MouseMove) { e->accept(); return true; } else return false; } // 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'\:'=' ssr-0.4.2/src/gui/qsourceproperties.h000066400000000000000000000103751236416011200176500ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// QSourceProperties #ifndef SSR_QSOURCEPROPERTIES_H #define SSR_QSOURCEPROPERTIES_H #include #include #include #include #include #include #include #include "qclicktextlabel.h" #include "source.h" /// QSourceProperties class QSourceProperties : public QFrame { Q_OBJECT public: QSourceProperties(QWidget* parent = 0); ~QSourceProperties(); void update_displays(const Source& source, const DirectionalPoint& reference); private: QGridLayout* _grid; QLineEdit* _name_display; QLabel* _coordinates_display; QLabel* _distance_display; QLabel* _azimuth_display; QCheckBox* _position_fix_box; QCheckBox* _doppler_box; QLabel* _volume_display; QCheckBox* _muted_check_box; QCheckBox* _soloed_check_box; QRadioButton* _port_radio_button; QRadioButton* _file_radio_button; QLabel* _source_model_label; QComboBox* _source_model_display; QLabel* _audio_source_label; QLineEdit* _audio_source_display; QLabel* _properties_label; QLineEdit* _properties_display; QClickTextLabel* _create_source_button; QClickTextLabel* _close_button; QLabel* _create_text_label(const QString& text = QString()); bool _create_new_source; virtual void mousePressEvent(QMouseEvent *event); virtual bool event(QEvent *e); private slots: void _set_source_mute(bool flag); void _set_source_solo(bool flag); void _set_source_position_fixed(bool flag); void _set_source_model(int index); void _set_doppler(bool flag); void _expand(); void _collapse(); public slots: void show(const bool create_new_source = false); signals: void signal_set_source_mute(bool); void signal_set_source_position_fixed(bool); void signal_set_source_model(int); void signal_set_source_solo(bool); //void signal_set_source_property( ); }; #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'\:'=' ssr-0.4.2/src/gui/qssrtimeline.cpp000066400000000000000000000215611236416011200171230ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// TODO: add description #include #include #include #include #include "qssrtimeline.h" #include "ssr_global.h" #define ACCELERATION 0.2f #define TIMESPAN 120 // margin in pixels #define TOPMARGIN 5 #define BOTTOMMARGIN 12 #define LEFTMARGIN 20 #define RIGHTMARGIN 20 QSSRTimeLine::QSSRTimeLine(QWidget* parent, unsigned int update_interval) : QLabel(parent), _progress(0.0f), _lower_time_boundary(0u), _mouse_pos_at_click(0), _progress_at_mouse_click(0) //, _previous_time("") { (void)update_interval; _time_edit = new QTimeEdit(this); connect(_time_edit, SIGNAL(returnPressed()), this, SLOT(_interpret_time_edit())); _time_edit->setVisible(false); // update widget every update_interval msec //QTimer *timer = new QTimer(this); //connect(timer, SIGNAL(timeout()), this, SLOT(update())); //timer->start(update_interval); } void QSSRTimeLine::set_progress(float prog) { _progress = prog; // limit possible values _progress = std::max(_progress, 0.0f); _lower_time_boundary = static_cast(_progress/TIMESPAN) * TIMESPAN; update(); } void QSSRTimeLine::mousePressEvent(QMouseEvent *event) { event->accept(); _time_edit->setVisible(false); _mouse_pos_at_click = event->x(); _progress_at_mouse_click = _progress; if (event->buttons() == Qt::LeftButton && event->x()-LEFTMARGIN-1 <= 0) { //emit signal_transport_locate(0.0f); emit signal_transport_locate(_progress - 5.0f); } else if (event->buttons() == Qt::LeftButton && event->x() <= width()-RIGHTMARGIN) { emit signal_transport_locate(static_cast(event->x()-LEFTMARGIN-1)/ static_cast(width()-(LEFTMARGIN+RIGHTMARGIN))* TIMESPAN + _lower_time_boundary); } else if (event->buttons() == Qt::LeftButton && event->x() > width()-RIGHTMARGIN) { emit signal_transport_locate(_progress + 5.0f); } else if (event->buttons() == Qt::RightButton) { show_time_edit(); } } void QSSRTimeLine::mouseMoveEvent(QMouseEvent *event) { event->accept(); float current_progress = static_cast(event->x() - _mouse_pos_at_click)/ static_cast(width()-(LEFTMARGIN+RIGHTMARGIN))* TIMESPAN + _progress_at_mouse_click; if (current_progress > 0) { emit signal_transport_locate(current_progress); } else emit signal_transport_locate(0.0f); } void QSSRTimeLine::mouseDoubleClickEvent(QMouseEvent *event) { event->accept(); if (event->x()-LEFTMARGIN-1 > 0 && event->x() < width()-RIGHTMARGIN) { emit signal_transport_locate(0.0f); } } void QSSRTimeLine::mouseReleaseEvent(QMouseEvent *event) { event->accept(); } void QSSRTimeLine::paintEvent( QPaintEvent * event) { event->accept(); QPainter painter(this); // draw white background for level meter painter.setPen(QPen(QColor(255,255,255))); painter.setBrush(QBrush(QColor(255,255,255))); painter.drawRect(LEFTMARGIN, TOPMARGIN+1, width() - (LEFTMARGIN+RIGHTMARGIN), height()-(BOTTOMMARGIN+TOPMARGIN+2)); // draw frame painter.setPen(QPen(QColor(237,237,230),1)); painter.drawLine(QLine(LEFTMARGIN,TOPMARGIN, width()-RIGHTMARGIN,TOPMARGIN)); painter.drawLine(QLine(LEFTMARGIN,height()-BOTTOMMARGIN, width()-RIGHTMARGIN,height()-BOTTOMMARGIN)); painter.drawLine(QLine(LEFTMARGIN,TOPMARGIN,LEFTMARGIN, height()-BOTTOMMARGIN)); painter.drawLine(QLine(width()-RIGHTMARGIN,TOPMARGIN, width()-RIGHTMARGIN,height()-BOTTOMMARGIN)); // draw progress bar /* QLinearGradient gradient(0, 0, (int)(width()*0.7f), 0); gradient.setColorAt(0.7, QColor(43,174,247)); gradient.setColorAt(0, Qt::blue); painter.setBrush(gradient); */ // enable anti-aliasing painter.setRenderHint(QPainter::Antialiasing); // painter.setPen(QPen(QColor(43,174,247))); painter.setBrush(QBrush(QColor(43,174,247))); const float handle_position = std::min(_progress - _lower_time_boundary, static_cast(TIMESPAN))/TIMESPAN * (width()-(LEFTMARGIN+RIGHTMARGIN)) + LEFTMARGIN + 1; // draw progress bar painter.drawRect(LEFTMARGIN+1, TOPMARGIN+1, static_cast(handle_position - LEFTMARGIN)-1, height()-(TOPMARGIN+BOTTOMMARGIN+1)); painter.setPen(QPen(QColor(0,0,0))); // black painter.setBrush(QBrush(QColor(0,0,0))); // black // define vertical bar of handle const QPointF points_quad[4] = { QPointF(handle_position - 0.5f, TOPMARGIN), QPointF(handle_position - 0.5f, static_cast(height()-(BOTTOMMARGIN+1))), QPointF(handle_position + 0.5f, static_cast(height()-(BOTTOMMARGIN+1))), QPointF(handle_position + 0.5f, TOPMARGIN) }; painter.drawPolygon(points_quad, 4); // define triangle for handle const QPointF points_tri[3] = { QPointF(handle_position - 3.0f, 0.0f), QPointF(handle_position + 3.0f, 0.0f), QPointF(handle_position, TOPMARGIN + 1.0f) }; painter.drawPolygon(points_tri, 3); int hours = static_cast(_progress/3600); int min = static_cast((_progress - hours*3600)/60); int sec = static_cast(fmod(_progress,60)); // set seconds QString time = QString().setNum(sec); if (sec < 10) time.prepend("0"); // set minutes time.prepend(QString().setNum(min) + ":"); if (hours) { if (min < 10) time.prepend("0"); // set hours time.prepend(QString().setNum(hours) + ":"); } // enable anti-aliasing painter.setRenderHint(QPainter::TextAntialiasing); painter.drawText(QPointF(handle_position - 7.0f, 35.0f), time); } void QSSRTimeLine::_interpret_time_edit() { float new_time = 0.0f; bool conversion_ok = false; QString text = _time_edit->text(); if ((text.size() > 3) && (QString(text.at(text.size() - 3)) == ":")) { // seconds new_time = text.section(':',-1).toFloat(&conversion_ok); if (conversion_ok) { // minutes new_time += text.section(':',-2,-2).toFloat(&conversion_ok) * 60; } if (conversion_ok && (text.size() > 6) && (QString(text.at(text.size() - 6)) == ":")) { // hours new_time += text.section(':',0,-3).toFloat(&conversion_ok) * 3600; } } else { // interpret all as seconds new_time = text.toFloat(&conversion_ok); } if (conversion_ok) signal_transport_locate(new_time); hide_time_edit(); } void QSSRTimeLine::show_time_edit() { _time_edit->setGeometry(width()/2 - 20, 0, 40, height()); _time_edit->clear(); _time_edit->setVisible(true); _time_edit->setFocus(); } void QSSRTimeLine::hide_time_edit() { _time_edit->clearFocus(); _time_edit->setVisible(false); } // 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'\:'=' ssr-0.4.2/src/gui/qssrtimeline.h000066400000000000000000000064361236416011200165740ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// QTimeLine #ifndef SSR_QTIMELINE_H #define SSR_QTIMELINE_H #include #include #include #include #include #include "qtimeedit.h" /// Qtimeline class QSSRTimeLine : public QLabel { Q_OBJECT public: QSSRTimeLine( QWidget* parent = 0, unsigned int update_interval = 500u); void set_progress(float progress); void show_time_edit(); void hide_time_edit(); private: float _progress; unsigned int _lower_time_boundary; int _mouse_pos_at_click; float _progress_at_mouse_click; QString _previous_time; QTimeEdit* _time_edit; protected: virtual void mousePressEvent(QMouseEvent *event); virtual void mouseMoveEvent(QMouseEvent *event); virtual void mouseDoubleClickEvent(QMouseEvent *event); virtual void mouseReleaseEvent(QMouseEvent *event); virtual void paintEvent( QPaintEvent * event); signals: void signal_transport_locate(float); private slots: void _interpret_time_edit(); }; #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'\:'=' ssr-0.4.2/src/gui/qtimeedit.cpp000066400000000000000000000055111236416011200163660ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// TODO: add description #include "qtimeedit.h" //#include "ssr_global.h" QTimeEdit::QTimeEdit(QWidget* parent) : QLineEdit(parent) { QString qt_style_sheet = "* { background-color: white; \n" "border-radius: 0; \n" "border-width: 1px; \n" "border-color: rgb(237,237,230); }"; this->setStyleSheet(qt_style_sheet); } void QTimeEdit::keyPressEvent(QKeyEvent *event) { QLineEdit::keyPressEvent(event); // to avoid propagating of RETURN to main widget event->accept(); } // 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'\:'=' ssr-0.4.2/src/gui/qtimeedit.h000066400000000000000000000050531236416011200160340ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// QTimeEdit #ifndef SSR_QTIMEEDIT_H #define SSR_QTIMEEDIT_H #include #include /// for QSSRTimeLine class QTimeEdit : public QLineEdit { Q_OBJECT public: QTimeEdit( QWidget* parent = 0 ); protected: virtual void keyPressEvent(QKeyEvent *event); }; #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'\:'=' ssr-0.4.2/src/gui/quserinterface.cpp000066400000000000000000001343011236416011200174210ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// TODO: add description #include #include #include #include #include #include #include #include #include #include #include #include #include #include "quserinterface.h" #include "apf/math.h" using apf::math::dB2linear; using apf::math::linear2dB; #include "apf/stringtools.h" #include "posixpathtools.h" #define FILEMENUWIDTH 128 #define BETWEENBUTTONSPACE 6 #define BETWEENLABELSPACE 20 #define SCENEBUTTONWIDTH 102 #define BETWEENSCENEBUTTONSPACE 18 // define frame geometry in pixels #define DEFAULTFRAMETOP 90 #define DEFAULTFRAMEBOTTOM 30 #define DEFAULTFRAMELEFT 30 #define DEFAULTFRAMERIGHT 30 // define button size in pixels #define BUTTONWIDTH 55 #define BUTTONHEIGHT 31 #define UPDATEINTERVALFORQTWIDGETS 300u // in dB #define MAXVOLUME 12.0f // given by qvolumeslider #define MINVOLUME -50.0f /** ctor. * @param controller this can be either the Controller or, if the IP interface * is used, an IPClient. * @param scene reference to the MasterScene or to a client-side copy of the * Scene. * @param parent parent Qt widget. If left as \a NULL (default) then the window * is startet as an independent main window. **/ ssr::QUserInterface::QUserInterface(Publisher& controller, const Scene& scene , const std::string& path_to_gui_images , const std::string& path_to_scene_menu , unsigned int update_frequency , QWidget *parent) : QOpenGLPlotter(controller, scene, path_to_gui_images, parent), _active_scene(0), _ignore_mouse_events(false), _controlsParent(this) { // set window title std::string type = _controller.get_renderer_name(); if (type == "wfs") setWindowTitle("SSR - WFS"); else if (type == "binaural") setWindowTitle("SSR - Binaural"); else if (type == "brs") setWindowTitle("SSR - BRS"); else if (type == "vbap") setWindowTitle("SSR - VBAP"); else if (type == "aap") setWindowTitle("SSR - AAP"); else if (type == "generic") setWindowTitle("SSR - Generic Renderer"); else if (type == "ambisonics") setWindowTitle("SSR - Ambisonics"); else if (type == "") setWindowTitle("SSR"); else setWindowTitle(type.c_str()); setWindowIcon(QIcon("images/ssr_logo.png")); // make sure that buttons look good setMinimumSize(580, 215); // setWindowState( Qt::WindowFullScreen ); // setWindowState( Qt::WindowMaximized ); // set default size setGeometry(200, 100, 900, 800); #ifdef ENABLE_FLOATING_CONTROL_PANEL // TODO: use screen size for initial window positions //QRect screenSize = QApplication::desktop()->screenGeometry(); setGeometry(200, 70, 900, 700); _controlsParent = new QLabel(this, Qt::Window | Qt::CustomizeWindowHint | Qt::WindowTitleHint); _controlsParent->setFixedSize(900, 75); _controlsParent->move(200, 780); _controlsParent->setWindowTitle("Controls"); _controlsParent->installEventFilter(this); _controlsParent->show(); #else // build up frame around OpenGL window _frame = new QGUIFrame(this); #endif _source_properties = new QSourceProperties(this); connect(_source_properties, SIGNAL(signal_set_source_mute(bool)), this, SLOT(_set_source_mute(bool))); connect(_source_properties, SIGNAL(signal_set_source_position_fixed(bool)), this, SLOT(_set_source_position_fixed(bool))); connect(_source_properties, SIGNAL(signal_set_source_model(int)), this, SLOT(_set_source_model(int))); _source_properties->hide(); // set window icon QString path_to_image( _path_to_gui_images.c_str() ) ; setWindowIcon(QIcon(QPixmap(path_to_image.append("/ssr_logo_large.png")))); // build up buttons etc. _file_menu_label = new QFileMenuLabel(_controlsParent); // _file_menu_label->setStyleSheet("border-width: 1px; border-color: black; border-style: solid"); _file_menu_label->setIndent(5); _file_menu_label->setText("File"); _file_menu_label->show(); connect(_file_menu_label, SIGNAL(clicked()), this, SLOT(_show_file_menu())); _processing_button = new QPushButton(_controlsParent); _processing_button->setObjectName("_processing_button"); _processing_button->setText("on/off"); _processing_button->setCheckable(true); _processing_button->setFocusPolicy(Qt::NoFocus); _processing_button->show(); connect(_processing_button, SIGNAL(clicked()), this, SLOT(_processing_button_pressed())); _skip_back_button = new QPushButton(_controlsParent); _skip_back_button->setObjectName("_skip_back_button"); _skip_back_button->setFocusPolicy(Qt::NoFocus); _skip_back_button->show(); connect(_skip_back_button, SIGNAL(clicked()), this, SLOT(_skip_back())); _pause_button = new QPushButton(_controlsParent); _pause_button->setObjectName("_pause_button"); _pause_button->setCheckable(true); _pause_button->setFocusPolicy(Qt::NoFocus); _pause_button->show(); connect(_pause_button, SIGNAL(clicked()), this, SLOT(_pause_button_pressed())); _play_button = new QPushButton(_controlsParent); _play_button->setObjectName("_play_button"); _play_button->setCheckable(true); _play_button->setFocusPolicy(Qt::NoFocus); _play_button->show(); connect(_play_button, SIGNAL(clicked()), this, SLOT(_play_button_pressed())); _time_line = new QSSRTimeLine(_controlsParent, UPDATEINTERVALFORQTWIDGETS); _time_line->show(); connect(_time_line, SIGNAL(signal_transport_locate(float)), this, SLOT(_transport_locate(float))); _cpu_label = new QCPULabel(_controlsParent, UPDATEINTERVALFORQTWIDGETS); _cpu_label->show(); _cpu_label_text_tag = new QLabel(_controlsParent); _cpu_label_text_tag->setAlignment(Qt::AlignCenter); _cpu_label_text_tag->setText("cpu"); _zoom_label = new QZoomLabel(_controlsParent); _zoom_label->show(); connect(_zoom_label, SIGNAL(signal_zoom_changed(int)), this, SLOT(_set_zoom(int))); connect(this, SIGNAL(signal_zoom_set(int)), _zoom_label, SLOT(update_display(int))); _zoom_label_text_tag = new QLabel(_controlsParent); _zoom_label_text_tag->setAlignment(Qt::AlignCenter); _zoom_label_text_tag->setText("zoom"); _zoom_label_text_tag->show(); _volume_slider = new QVolumeSlider(_controlsParent); _volume_slider->show(); connect(_volume_slider, SIGNAL(signal_volume_changed(float)), this, SLOT(_set_master_volume(float))); _volume_slider_text_tag = new QLabel(_controlsParent); _volume_slider_text_tag->setAlignment(Qt::AlignCenter); _volume_slider_text_tag->setText("level"); _volume_slider_text_tag->show(); // open_file_label = new QClickTextLabel( this ); // open_file_label->setGeometry( QRect( XREF, YREF, 270, 39 ) ); // open_file_label->setSizePolicy( QSizePolicy( (QSizePolicy::SizeType)1, (QSizePolicy::SizeType)1, 0, 0, open_file_label->sizePolicy().hasHeightForWidth() ) ); // put labels into correct position _file_menu_label->setGeometry(DEFAULTFRAMELEFT, 24, FILEMENUWIDTH, 24); _processing_button->setGeometry(DEFAULTFRAMELEFT+FILEMENUWIDTH+ BETWEENBUTTONSPACE, 20, BUTTONWIDTH, BUTTONHEIGHT); _skip_back_button->setGeometry(DEFAULTFRAMELEFT+FILEMENUWIDTH+ BETWEENBUTTONSPACE+BUTTONWIDTH+ BETWEENBUTTONSPACE, 20, BUTTONWIDTH, BUTTONHEIGHT); _pause_button->setGeometry(DEFAULTFRAMELEFT+FILEMENUWIDTH+ 2*(BETWEENBUTTONSPACE+BUTTONWIDTH)+ BETWEENBUTTONSPACE, 20, BUTTONWIDTH, BUTTONHEIGHT); _play_button->setGeometry(DEFAULTFRAMELEFT+FILEMENUWIDTH+ 3*(BETWEENBUTTONSPACE+BUTTONWIDTH)+ BETWEENBUTTONSPACE, 20, BUTTONWIDTH, BUTTONHEIGHT); // functional inits _deselect_all_sources(); // no source selected #ifdef ENABLE_FLOATING_CONTROL_PANEL // scene menu is not shown if floating control panel is used VERBOSE("Floating control panel is used, scene menu will not be shown."); (void)path_to_scene_menu; #else // create the scene menu if an according config file is present _create_scene_menu(path_to_scene_menu); #endif // update screen with update_frequency QTimer *timer = new QTimer(this); connect(timer, SIGNAL(timeout()), this, SLOT(_update_screen())); timer->start(static_cast(1.0f/static_cast(update_frequency) * 1000.0f)); #ifdef ENABLE_FLOATING_CONTROL_PANEL _resizeControls(_controlsParent->width()); #endif } /// Dtor. ssr::QUserInterface::~QUserInterface() { // TODO: DELETE, DELETE, DELETE Widgets // clear memory if scene button list has been in use if (!_scene_button_list.empty()) { for (scene_button_list_t::iterator i = _scene_button_list.begin(); i != _scene_button_list.end(); i++) { delete *i; } } _scene_button_list.clear(); } /// Skips back to the beginning of the scene void ssr::QUserInterface::_skip_back() { _controller.transport_locate(0); } /** Skips the scene to a specified instant of time * @ param time instant of time in sec to locate */ void ssr::QUserInterface::_transport_locate(float time) { if (time >= 0.0f) { _controller.transport_locate(time); } else { _skip_back(); } // update time line _time_line->set_progress(static_cast(_scene.get_transport_position())/ static_cast(_scene.get_sample_rate())); } void ssr::QUserInterface::_solo_selected_sources() { _soloed_sources.clear(); for (selected_sources_map_t::iterator i = _selected_sources_map.begin(); i != _selected_sources_map.end(); i++) { _soloed_sources.insert(i->second); // make sure it's not muted _controller.set_source_mute(i->second, false); } // for // get sources source_buffer_list_t source_buffer_list; _scene.get_sources(source_buffer_list); // mute other sources for (source_buffer_list_t::const_iterator i = source_buffer_list.begin(); i != source_buffer_list.end(); i++) { // if source is not soloed if (_soloed_sources.find(i->id) == _soloed_sources.end()) { // then mute it _controller.set_source_mute(i->id, true); } } } /// this function is not so useful void ssr::QUserInterface::_unsolo_selected_sources() { for (selected_sources_map_t::iterator i = _selected_sources_map.begin(); i != _selected_sources_map.end(); i++) { _soloed_sources.erase(i->second); } // for // if no source is soloed anymore if (_soloed_sources.empty()) { _unsolo_all_sources(); } // if other sources are soloed else { for (selected_sources_map_t::iterator i = _selected_sources_map.begin(); i != _selected_sources_map.end(); i++) { // then mute the unsoloed sources _controller.set_source_mute(i->second, true); } // for } } void ssr::QUserInterface::_toggle_solo_state_of_selected_sources() { for (selected_sources_map_t::iterator i = _selected_sources_map.begin(); i != _selected_sources_map.end(); i++) { if (_soloed_sources.find(i->second) == _soloed_sources.end()) { // solo source _soloed_sources.insert(i->second); } else { // unsolo source _soloed_sources.erase(i->second); } } // for // get sources source_buffer_list_t source_buffer_list; _scene.get_sources(source_buffer_list); for (source_buffer_list_t::const_iterator i = source_buffer_list.begin(); i != source_buffer_list.end(); i++) { if (_soloed_sources.find(i->id) == _soloed_sources.end()) { // mute _controller.set_source_mute(i->id, true); } else { // unmute _controller.set_source_mute(i->id, false); } } } void ssr::QUserInterface::_unsolo_all_sources() { _soloed_sources.clear(); // get sources source_buffer_list_t source_buffer_list; _scene.get_sources(source_buffer_list); for (source_buffer_list_t::const_iterator i = source_buffer_list.begin(); i != source_buffer_list.end(); i++) { _controller.set_source_mute(i->id, false); } } /// This slot is called when the \a processing \a button was clicked by the user. void ssr::QUserInterface::_processing_button_pressed() { if(_scene.get_processing_state()) { _controller.stop_processing(); } else { _controller.start_processing(); } } /// This slot is called when the \a pause \a button was clicked by the user. void ssr::QUserInterface::_pause_button_pressed() { _controller.transport_stop(); } /// This slot is called when the \a play \a button was clicked by the user. void ssr::QUserInterface::_play_button_pressed() { _controller.transport_start(); } /** This function is called whenever the fiel menu actions (open/close etc.) * are demanded. */ void ssr::QUserInterface::_show_file_menu() { QMenu file_menu(this); file_menu.setStyleSheet("min-width: 87px;"); // "background-color: rgba(255, 0, 0, 5%)"); QAction open_act("open",this); QAction save_as_act("save scene as...",this); // QAction new_act("new scene",this); QAction close_act("quit",this); file_menu.addAction(&open_act); file_menu.addAction(&save_as_act); // file_menu.addAction(&new_act); file_menu.addAction(&close_act); // connect actions to the respective functions connect(&open_act, SIGNAL( triggered() ), this, SLOT( _open_file() )); connect(&save_as_act, SIGNAL( triggered() ), this, SLOT( _save_file_as() )); // connect(&new_act, SIGNAL( triggered() ), this, SLOT( _new_scene() )); connect(&close_act, SIGNAL( triggered() ), this, SLOT( close() )); // file_menu.exec(_file_menu_label->mapToGlobal(QPoint(1,50))); file_menu.exec(_file_menu_label->mapToGlobal(QPoint(1,_file_menu_label->height()))); } void ssr::QUserInterface::_new_scene() { WARNING("Not implemented yet."); } /** This function reads the file \a _scene_menu.conf and creates the accoding * tabs that give fast access to a number of desired _scenes. */ void ssr::QUserInterface::_create_scene_menu(const std::string& path_to_scene_menu) { // clear memory if list has already been in use if (!_scene_button_list.empty()) { for (scene_button_list_t::iterator i = _scene_button_list.begin(); i != _scene_button_list.end(); i++) { delete *i; } } _scene_button_list.clear(); //std::string file_name = "scene_menu.conf"; // open file std::ifstream config_file(path_to_scene_menu.c_str()); if (!config_file.is_open()) { WARNING("Cannot open scene config file '" << path_to_scene_menu << "'."); return; } VERBOSE("Creating scene menu from file '" << path_to_scene_menu << "'."); std::string line; std::string::size_type index; unsigned int no_of_scenes = 0u; QSceneButton* button_buffer; // parse file and create a button for each scene while (!config_file.eof()) { // read line from config file getline(config_file, line); // check if line contains a comment index = line.find_first_of("#",0); if (index != line.npos) line.erase(index); // process the data if(!line.empty()) { index = line.find_first_of(" ",0); // TODO: Is this good??? button_buffer = new QSceneButton(_controlsParent, line.substr(index+1).c_str(), line.substr(0, index).c_str()); button_buffer->setGeometry(DEFAULTFRAMELEFT + (SCENEBUTTONWIDTH+BETWEENSCENEBUTTONSPACE) * no_of_scenes, 67, SCENEBUTTONWIDTH, 23); button_buffer->setCheckable(true); button_buffer->setFocusPolicy(Qt::NoFocus); button_buffer->show(); connect(button_buffer, SIGNAL(signal_open_scene(const QString&)), this, SLOT(_load_scene(const QString&))); // Add button to list _scene_button_list.push_back(button_buffer); // increment _scene counter no_of_scenes++; } // if } // while config_file.close(); } /// This function opens a file dialog. void ssr::QUserInterface::_open_file() { QString path = QFileDialog::getOpenFileName(this, "Load audio scene", ".", "scene descriptions and audio files (*.asd *.wav);; all files (*)"); // if a file is selected if (!path.isNull()) _load_scene(path); // adjust _time_line // audiofileiotools::get_file_duration(); } /** Sets master volume. * @param volume new master volume in dB */ void ssr::QUserInterface::_set_master_volume(float volume) { // limit range volume = std::min(volume, MAXVOLUME); volume = std::max(volume, MINVOLUME); // convert to linear scale _controller.set_master_volume(apf::math::dB2linear(volume)); } /** Changes selected sources' volume. * @param d_volume volume change in dB */ void ssr::QUserInterface::_change_volume_of_selected_sources(float d_volume) { for (selected_sources_map_t::iterator i = _selected_sources_map.begin(); i != _selected_sources_map.end(); i++) { float current_gain = _scene.get_source_gain(i->second)*dB2linear(d_volume); // limit range (linear scale) current_gain = std::min(current_gain, dB2linear(MAXVOLUME)); current_gain = std::max(current_gain, dB2linear(MINVOLUME)); _controller.set_source_gain(i->second, current_gain); } // for } /// Opens a save-file-as dialog. void ssr::QUserInterface::_save_file_as() { QString file_name = QFileDialog::getSaveFileName(this, "Save scene in ASDF", ".", "ASDF files (*.asd)"); // if aborted if ( file_name.isEmpty() ) { VERBOSE("Scene not saved."); } else { // convert to std::string std::string file_name_std = file_name.toStdString(); // check file extension if ( posixpathtools::get_file_extension(file_name_std) != "asd" ) { file_name_std.append(".asd"); } _controller.save_scene_as_XML(file_name_std); VERBOSE("Scene saved in '" << file_name_std << "'."); } } /** Loads a new _scene whose asdf description resides in * @param path_to_scene path to _scene description file (absolute or relative). */ void ssr::QUserInterface::_load_scene(const QString& path_to_scene) { // tell user to wait setCursor(Qt::WaitCursor); // restore standard orientation _rotation_x = 0.0f; _deselect_all_sources(); // Check who sent the signal (if anybody) QSceneButton* sending_object = static_cast(sender()); // Check if function was called by a signal if (sending_object != nullptr) { // Uncheck the other buttons for (scene_button_list_t::iterator i = _scene_button_list.begin(); i != _scene_button_list.end(); i++) { // Set all buttons up that have not been clicked if ( (*i) != sending_object ) (*i)->setChecked(false); } } _controller.load_scene(std::string(path_to_scene.toAscii())); // clear mouse cursor setCursor(Qt::ArrowCursor); } /** Updates all widgets on the screen including OpenGL stuff.*/ void ssr::QUserInterface::_update_screen() { // update time line _time_line->set_progress(static_cast(_scene.get_transport_position())/ static_cast(_scene.get_sample_rate())); // update CPU label _cpu_label->set_load(_scene.get_cpu_load()); // update volume slider (incl. master level) _volume_slider->update_displays(linear2dB(_scene.get_master_signal_level() + 0.00001f), linear2dB(_scene.get_master_volume() + 0.00001f)); // min -100dB // update transport tools if (_scene.is_playing() ){ _pause_button->setChecked(false); _play_button->setChecked(true); } else { _pause_button->setChecked(true); _play_button->setChecked(false); } if(_scene.get_processing_state()) _processing_button->setChecked(true); else _processing_button->setChecked(false); // update source properties dialog if (_source_properties->isVisible()) { // move the dialog to the desired position _update_source_properties_position(); // update displays of source properties dialog _source_properties->update_displays(_scene.get_source(_id_of_last_clicked_source), _scene.get_reference()); } // if // refresh screen update(); } #ifndef ENABLE_FLOATING_CONTROL_PANEL /** Checks if a mouse event occurred outside of the visible OpenGL window. * @param event incoming Qt event. * @return @b true if event occurred outside. */ bool ssr::QUserInterface::_mouse_event_out_of_scope(QMouseEvent *event) { if (event->x() < DEFAULTFRAMELEFT + 2 || event->x() > width() - DEFAULTFRAMERIGHT || event->y() < DEFAULTFRAMETOP + 2 || event->y() > height() - DEFAULTFRAMEBOTTOM) { return true; } return false; } /** Catches mouse events which occurred outside of the visible OpenGL widget. * @param event incoming Qt event. * @return @b true if event was caught. */ bool ssr::QUserInterface::event(QEvent *e) { // check if mouse action starts outside of scope if (e->type() == QEvent::MouseButtonPress || e->type() == QEvent::MouseButtonDblClick) { // close text edit in time line if visible //_time_line->reset_appearence(); QMouseEvent *mouse_event = (QMouseEvent *)e; if (_mouse_event_out_of_scope(mouse_event)) { _ignore_mouse_events = true; return true; } else _ignore_mouse_events = false; } else if (e->type() == QEvent::MouseMove && _ignore_mouse_events) { return true; } return QOpenGLPlotter::event(e); } #endif /** Handles Qt resize events. * @param event Qt resize event. */ void ssr::QUserInterface::resizeEvent(QResizeEvent *event) { QOpenGLPlotter::resizeEvent(event); #ifndef ENABLE_FLOATING_CONTROL_PANEL // resize frame _frame->resize(DEFAULTFRAMETOP, DEFAULTFRAMEBOTTOM, DEFAULTFRAMELEFT, DEFAULTFRAMERIGHT); // horizontal arrangement of buttons in pixels: // frame file menu proc. but. // DEFAULTFRAMELEFT FILEMENUWIDTH BETWEENBUTTONSPACE BUTTONWIDTH BETWEENBUTTONSPACE // skip button pause play // BUTTONWIDTH BETWEENBUTTONSPACE BUTTONWIDTH BETWEENBUTTONSPACE BUTTONWIDTH // // 3*BETWEENBUTTONSPACE _resizeControls(width()); #endif } void ssr::QUserInterface::_resizeControls(int newWidth) { const int _time_line_position_x = DEFAULTFRAMELEFT+FILEMENUWIDTH+ 4*(BETWEENBUTTONSPACE+BUTTONWIDTH)+ BETWEENLABELSPACE - 12; // the 12 is due to LEFTMARGIN in qssrtimeline.cpp const int _zoom_label_position_x = newWidth - DEFAULTFRAMERIGHT + 3 - FILEMENUWIDTH - 2*BUTTONWIDTH - 2*BETWEENLABELSPACE; // if there is space then show the _time_line if (width()-DEFAULTFRAMERIGHT-FILEMENUWIDTH-150 > _time_line_position_x + 30) { _time_line->setGeometry(_time_line_position_x, 23, _zoom_label_position_x - _time_line_position_x - BETWEENBUTTONSPACE, 36); _time_line->show(); } else _time_line->hide(); // if there is space then show the _zoom_label if (_zoom_label_position_x > _time_line_position_x - BETWEENLABELSPACE) { _zoom_label->setGeometry(_zoom_label_position_x, 30, BUTTONWIDTH, 15); _zoom_label_text_tag->setGeometry(_zoom_label_position_x, 15, BUTTONWIDTH, 15); _zoom_label->show(); _zoom_label_text_tag->show(); } else { _zoom_label->hide(); _zoom_label_text_tag->hide(); } // if there is space then show the _cpu_label if (_zoom_label_position_x > _time_line_position_x - 2*BETWEENLABELSPACE - BUTTONWIDTH) { _cpu_label->setGeometry(_zoom_label_position_x+BUTTONWIDTH+BETWEENLABELSPACE, 30, BUTTONWIDTH, 15); _cpu_label_text_tag->setGeometry(_zoom_label_position_x+BUTTONWIDTH+ BETWEENLABELSPACE, 15, BUTTONWIDTH, 15 ); _cpu_label->show(); _cpu_label_text_tag->show(); } else { _cpu_label->hide(); _cpu_label_text_tag->hide(); } _volume_slider->setGeometry(newWidth - DEFAULTFRAMERIGHT - FILEMENUWIDTH + 3, 30, FILEMENUWIDTH, 25); _volume_slider_text_tag->setGeometry(newWidth - DEFAULTFRAMERIGHT - FILEMENUWIDTH + 3, 15, FILEMENUWIDTH, 15 ); int no_of_scenes = 0; // check which scene buttons are visible if (!_scene_button_list.empty()) { for (scene_button_list_t::iterator i = _scene_button_list.begin(); i != _scene_button_list.end(); i++) { if (DEFAULTFRAMELEFT + (SCENEBUTTONWIDTH+BETWEENSCENEBUTTONSPACE) * no_of_scenes + SCENEBUTTONWIDTH < newWidth - DEFAULTFRAMERIGHT) { (*i)->show(); } else (*i)->hide(); no_of_scenes++; } } } /** Handles Qt mouse press events. * @param event Qt mouse event. */ void ssr::QUserInterface::mousePressEvent(QMouseEvent *event) { ssr::id_t _id_of_lastlast_clicked_source = _id_of_last_clicked_source; event->accept(); _volume_slider_selected = false; _reference_selected = false; _direction_handle_selected = false; _previous_mouse_event = *event; uint selected_object = _find_selected_object(event->pos()); // WARNING("Object " << selected_object << " selected."); // get position of mouse event GLdouble pos_x, pos_y, pos_z; _get_openGL_pos(event->x(),event->y(),&pos_x,&pos_y,&pos_z); // initialize rubber band if (event->button() == Qt::RightButton) { _rubber_band_starting_point.x = _rubber_band_ending_point.x = pos_x; _rubber_band_starting_point.y = _rubber_band_ending_point.y = pos_y; } // if click on listener if (selected_object == REFERENCEINDEX1) { if (!_ctrl_pressed) _select_source(0); _x_offset = _scene.get_reference().position.x - pos_x; _y_offset = _scene.get_reference().position.y - pos_y; } // click on SSR icon else if (selected_object == SSRLOGOINDEX) _show_about_window(); // check if no source was clicked if (selected_object <= NAMESTACKOFFSET) { // hide source properties dialog _source_properties->hide(); // no source was clicked if (event->button() == Qt::LeftButton) _deselect_all_sources(); return; } // subtract NAMESTACKOFFSET else // a source was clicked { selected_object -= NAMESTACKOFFSET; } // check if left click on source volume slider if (event->button() == Qt::LeftButton && selected_object % NAMESTACKSTEP == 2) { _volume_slider_selected = true; } // check if click on source direction handle else if (event->button() == Qt::LeftButton && selected_object % NAMESTACKSTEP == 3) { _direction_handle_selected = true; } // check if loudspeaker was clicked if (selected_object % NAMESTACKSTEP == 4 && !_ctrl_pressed) { _source_properties->hide(); // make sure that no sound source is selected _deselect_all_sources(); return; } // if not, select the respective sound source else _select_source(static_cast(selected_object/NAMESTACKSTEP), _ctrl_pressed); ///////////////////////////////////////////////////////////////// // from here on we can be sure that a source has been selected // ///////////////////////////////////////////////////////////////// std::unique_ptr source_position = _scene.get_source_position(_id_of_last_clicked_source); // save mouse pointer offset to source position _x_offset = source_position->x - pos_x; _y_offset = source_position->y - pos_y; // right click on source if (event->button() == Qt::RightButton && selected_object % NAMESTACKSTEP == 1) { if (_source_properties->isVisible() && _id_of_last_clicked_source == _id_of_lastlast_clicked_source) _source_properties->hide(); else { _update_source_properties_position(); _source_properties->show(); } } } /** Handles Qt mouse move events. * @param event Qt mouse event. */ void ssr::QUserInterface::mouseMoveEvent(QMouseEvent *event) { // move source if (event->buttons() == Qt::LeftButton && !_selected_sources_map.empty() && !_volume_slider_selected && !_direction_handle_selected && !_selected_sources_map.empty()) { GLdouble pos_x, pos_y, pos_z; _get_openGL_pos(event->x(), event->y(), &pos_x, &pos_y, &pos_z); Position position(static_cast(pos_x + _x_offset), static_cast(pos_y + _y_offset)); Position d_position = position - *_scene.get_source_position(_id_of_last_clicked_source); // move all selected sources for (selected_sources_map_t::iterator i = _selected_sources_map.begin(); i != _selected_sources_map.end(); i++) { // if source is plane then make sure that it faces the reference if (_scene.get_source_model(i->second) == Source::plane) { _controller.set_source_orientation(i->second,(_scene.get_reference().position - *_scene.get_source_position(i->second)).orientation()); } else if (_scene.get_source_model(i->second) == Source::directional || _scene.get_source_model(i->second) == Source::extended) { // position delta expressed as angle Orientation d_orientation = (*_scene.get_source_position(i->second) + d_position).orientation() - (*_scene.get_source_position(i->second)).orientation(); // set the new orientation _controller.set_source_orientation(i->second, (*_scene.get_source_orientation(i->second)) + d_orientation); } // if // finally set the source's position _controller.set_source_position(i->second, *_scene.get_source_position(i->second) + d_position); } // for } // if // rotate all selected sources else if ((event->buttons() == Qt::LeftButton) && _direction_handle_selected) { GLdouble pos_x,pos_y,pos_z; _get_openGL_pos(event->x(),event->y(),&pos_x,&pos_y,&pos_z); // absolut mouse position in OpenGL coordinates Position mouse_pos(static_cast(pos_x), static_cast(pos_y)); // position relative to source position mouse_pos -= *_scene.get_source_position(_id_of_last_clicked_source); _get_openGL_pos(_previous_mouse_event.x(), _previous_mouse_event.y(), &pos_x,&pos_y,&pos_z); // previous absolute position in OpenGL coordinates Position prev_mouse_pos(static_cast(pos_x), static_cast(pos_y)); // previous position relative to source position prev_mouse_pos -= *_scene.get_source_position(_id_of_last_clicked_source); // rotate all selected sources that can be rotated for (selected_sources_map_t::iterator i = _selected_sources_map.begin(); i != _selected_sources_map.end(); i++) { if (_scene.get_source_model(i->second) == Source::directional || _scene.get_source_model(i->second) == Source::extended) { _controller.set_source_orientation(i->second, (*_scene.get_source_orientation(i->second)) + (mouse_pos.orientation() - prev_mouse_pos.orientation())); } } // for } // change source volume else if ((event->buttons() == Qt::LeftButton) && _volume_slider_selected) { float gain; // change gain of all selected sources for (selected_sources_map_t::iterator i = _selected_sources_map.begin(); i != _selected_sources_map.end(); i++) { // get current source gain gain = _scene.get_source_gain(i->second); // if source is not found, gain = 0. // convert to dB gain = linear2dB(gain); // calculate gain according to mouse position gain += (event->globalX() - _previous_mouse_event.globalX()) * VOLUMEACCELERATION; // change gain if (gain > MINVOLUME && gain <= MAXVOLUME) { _controller.set_source_gain(i->second, dB2linear(gain)); } } // for } // else if // rotate listener else if ((event->buttons() == Qt::LeftButton) && _reference_selected) { GLdouble pos_x,pos_y,pos_z; _get_openGL_pos(event->x(),event->y(),&pos_x,&pos_y,&pos_z); // absolut mouse position in OpenGL coordinates Position mouse_pos(static_cast(pos_x), static_cast(pos_y)); // position relative to reference position mouse_pos -= _scene.get_reference().position; _get_openGL_pos(_previous_mouse_event.x(), _previous_mouse_event.y(), &pos_x,&pos_y,&pos_z); // previous absolut position in OpenGL coordinates Position prev_mouse_pos(static_cast(pos_x), static_cast(pos_y)); // previous position relative to relative position prev_mouse_pos -= _scene.get_reference().position; _controller.set_reference_orientation(_scene.get_reference().orientation + (mouse_pos.orientation() - prev_mouse_pos.orientation())); } // else if // translate listener else if (event->buttons() == Qt::RightButton && _reference_selected) { GLdouble pos_x,pos_y,pos_z; Position position; _get_openGL_pos(event->x(),event->y(),&pos_x,&pos_y,&pos_z); position.x = static_cast(pos_x + _x_offset); position.y = static_cast(pos_y + _y_offset); _controller.set_reference_position(position); } // else if // right click on background else if ((event->buttons() == Qt::RightButton) && !_reference_selected) { GLdouble pos_x, pos_y, pos_z; _get_openGL_pos(event->x(),event->y(),&pos_x,&pos_y,&pos_z); _rubber_band_ending_point.x = pos_x; _rubber_band_ending_point.y = pos_y; } // else if // translate whole plot else if ((event->buttons() == Qt::LeftButton) && !_reference_selected) { _window_x_offset += (event->globalX() - _previous_mouse_event.globalX()) / _zoom_factor*2.0f; _window_y_offset -= (event->globalY() - _previous_mouse_event.globalY()) / _zoom_factor*2.0f; } // else if // store mouse position _previous_mouse_event = *event; } void ssr::QUserInterface::_update_source_properties_position() { if (!_id_of_last_clicked_source) return; std::unique_ptr source_position = _scene.get_source_position(_id_of_last_clicked_source); int x, y; _get_pixel_pos(source_position->x, source_position->y, 0,&x, &y); #ifdef __APPLE__ _source_properties->move(QPoint(this->x() + x + 100, this->y() + y)); #else _source_properties->move(QPoint(x + 100, y)); #endif } /** Handles Qt mouse double click events. * @param event Qt mouse event. */ void ssr::QUserInterface::mouseDoubleClickEvent(QMouseEvent *event) { event->accept(); // double click on background or listener if (_selected_sources_map.empty()) { // hide source properties dialog _source_properties->hide(); // return to reference position _window_x_offset = 0.0f; _window_y_offset = STDWINDOWYOFFSET; // restore zoom _set_zoom(100); } else { // double click on source if (_source_properties->isVisible()) { _source_properties->hide(); } else { ;//_source_properties->show(); } } } /** Handles Qt mouse release events. * @param event Qt mouse event. */ void ssr::QUserInterface::mouseReleaseEvent (QMouseEvent *event) { event->accept(); if (event->button() == Qt::RightButton) { // clear rubber band _rubber_band_starting_point = _rubber_band_ending_point; } } #ifdef ENABLE_FLOATING_CONTROL_PANEL /** Catches Qt key press events for floating control panel. * @param sender Qt object that sent the event. * @param event The event itself. */ bool ssr::QUserInterface::eventFilter(QObject *sender, QEvent *event) { (void)sender; if (event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast(event); this->keyPressEvent(keyEvent); return true; } return false; } #endif /** Handles Qt key press events. * @param event Qt key event. */ void ssr::QUserInterface::keyPressEvent(QKeyEvent *event) { event->accept(); switch ( event->key() ) { // actions directly related to OpenGL window // case Qt::Key_Plus: _set_zoom(static_cast(_zoom_factor/STDZOOMFACTOR * 110.0f + 0.5f)); break; // case Qt::Key_Minus: _set_zoom(static_cast(_zoom_factor/STDZOOMFACTOR * 90.0f + 0.5f)); break; case Qt::Key_Up: _window_y_offset -= 0.1f; update(); break; case Qt::Key_Down: _window_y_offset += 0.1f; update(); break; case Qt::Key_Left: _window_x_offset += 0.1f; update(); break; case Qt::Key_Right: _window_x_offset -= 0.1f; update(); break; case Qt::Key_Space: if (_scene.is_playing()) _controller.transport_stop(); else _controller.transport_start(); break; case Qt::Key_Backspace: _skip_back(); break; // actions related to sources case Qt::Key_A: if (event->modifiers() == Qt::ControlModifier) _select_all_sources(); break; case Qt::Key_F: _toggle_fixation_state_of_selected_sources(); break; case Qt::Key_M: _toggle_mute_state_of_selected_sources(); break; case Qt::Key_P: _toggle_source_models(); break; case Qt::Key_S: if ( event->modifiers() == Qt::ControlModifier ) { _save_file_as(); } else if (_selected_sources_map.empty()) { _unsolo_all_sources(); } else _solo_selected_sources(); break; case Qt::Key_T: if ( event->modifiers() == Qt::ControlModifier ) { _time_line->show_time_edit(); } break; case Qt::Key_0: _deselect_all_sources(); break; case Qt::Key_1: _select_source(1, _ctrl_pressed); break; case Qt::Key_2: _select_source(2, _ctrl_pressed); break; case Qt::Key_3: _select_source(3, _ctrl_pressed); break; case Qt::Key_4: _select_source(4, _ctrl_pressed); break; case Qt::Key_5: _select_source(5, _ctrl_pressed); break; case Qt::Key_6: _select_source(6, _ctrl_pressed); break; case Qt::Key_7: _select_source(7, _ctrl_pressed); break; case Qt::Key_8: _select_source(8, _ctrl_pressed); break; case Qt::Key_9: _select_source(9, _ctrl_pressed); break; // miscellaneous actions case Qt::Key_Plus: if (_selected_sources_map.empty()) { // change master level _set_master_volume(linear2dB(_scene.get_master_volume() + 0.00001f) + 1.0f); // min -100dB } // change selected sources level else _change_volume_of_selected_sources(1.0f); break; case Qt::Key_Minus: if (_selected_sources_map.empty()) { // change master level _set_master_volume(linear2dB(_scene.get_master_volume() + 0.00001f) - 1.0f); // min -100dB } // change selected sources level else _change_volume_of_selected_sources(-1.0f); break; case Qt::Key_Return: {_controller.calibrate_client(); break; } case Qt::Key_Control: {_ctrl_pressed = true; break; } case Qt::Key_Alt: {_alt_pressed = true; break; } case Qt::Key_F11: {if (!isFullScreen()) setWindowState(Qt::WindowFullScreen); else setWindowState( Qt::WindowNoState );} break; case Qt::Key_C: {if (event->modifiers() == Qt::ControlModifier) close(); else break;} case Qt::Key_Escape: close(); } // switch } /** Handles Qt key release events. * @param event Qt key event. */ void ssr::QUserInterface::keyReleaseEvent(QKeyEvent *event) { switch (event->key()){ case Qt::Key_Control: {_ctrl_pressed = false; break; } case Qt::Key_Alt: {_alt_pressed = false; break; } default: ; } } /** Handles Qt mouse wheel events. * @param event Qt mouse wheel event. */ void ssr::QUserInterface::wheelEvent(QWheelEvent *event) { event->accept(); // update zoom _set_zoom(static_cast(_zoom_factor/STDZOOMFACTOR * (100.0f+event->delta()/100.f*5.0f) + 0.5f)); } /// Displays the about window. void ssr::QUserInterface::_show_about_window() { const std::string about_string = "" PACKAGE_NAME "
    " " version " PACKAGE_VERSION "

    " SSR_AUTHORS_QT "

    " "Website: " PACKAGE_URL "
    " "e-Mail:  " PACKAGE_BUGREPORT "
    " "
    " "Copyright © 2012-2014 Institut für Nachrichtentechnik
    " "          " "         " "   Universität Rostock
    " "Copyright © 2006-2012 Quality & Usability Lab
    " "          " "         " "   Telekom Innovation Laboratories
    " "          " "         " "   TU Berlin
    " "
    " "This program comes with ABSOLUTELY NO WARRANTY;
    " "this is free software, and you are welcome
    " "to redistribute it under certain conditions;
    " "for details, see the enclosed file COPYING.
    "; QDialog about_window(this, Qt::Popup); about_window.setAutoFillBackground(true); about_window.setPalette(QPalette(QColor(244, 244, 244))); QClickTextLabel ssr_logo; QClickTextLabel text_label; ssr_logo.setGeometry(20, 30, 310, 211); // the SSR Logo has 310x211 pixels ssr_logo.setScaledContents(true); QString path_to_image( _path_to_gui_images.c_str() ); path_to_image.append("/ssr_logo_large.png"); ssr_logo.setPixmap( QPixmap( path_to_image ) ); // text_label.setGeometry(0, 261, 350, 370); text_label.setText(about_string.c_str()); text_label.setIndent(20); text_label.setAlignment(Qt::AlignTop); text_label.adjustSize(); // arrange widgets in vertical layout QVBoxLayout *layout = new QVBoxLayout; layout->addWidget(&ssr_logo); layout->addWidget(&text_label); about_window.setLayout(layout); //about_window.adjustSize(); connect(&ssr_logo, SIGNAL(clicked()), &about_window, SLOT(close())); connect(&text_label, SIGNAL(clicked()), &about_window, SLOT(close())); about_window.exec(); } /** Sets the mute state of the currently active mouse. * @param flag \a true or \a false */ void ssr::QUserInterface::_set_source_mute(const bool flag) { _controller.set_source_mute(_id_of_last_clicked_source, flag); } /// Toggles the mute state of all selected sound sources void ssr::QUserInterface::_toggle_mute_state_of_selected_sources() { // iterate over selected sources for ( selected_sources_map_t::iterator i = _selected_sources_map.begin(); i != _selected_sources_map.end(); i++) { _controller.set_source_mute(i->second, !_scene.get_source_mute_state(i->second)); } } void ssr::QUserInterface::_toggle_source_models() { // toggle all source types between "plane" and "point" for (selected_sources_map_t::iterator i = _selected_sources_map.begin(); i != _selected_sources_map.end(); i++) { // if source is plane then make sure that it faces the reference if (_scene.get_source_model(i->second) == Source::plane) { _controller.set_source_model(i->second, Source::point); } else if (_scene.get_source_model(i->second) == Source::point) { _controller.set_source_model(i->second, Source::plane); } // if } // for } /** Sets the position fixed state of the currently active mouse. * @param flag \a true or \a false */ void ssr::QUserInterface::_set_source_position_fixed(const bool flag) { _controller.set_source_position_fixed(_id_of_last_clicked_source, flag); } /** Sets the position fixed state of the currently active mouse. * @param index \a 0="plane wave" \a 1="point source" */ void ssr::QUserInterface::_set_source_model(const int index) { Source::model_t model = Source::unknown; switch(index){ case 0: model = Source::plane; break; case 1: model = Source::point; break; } VERBOSE("index: " << index); ssr::id_t id = _id_of_last_clicked_source; _controller.set_source_model(id, model); // make sure that the plane wave is oriented towards the reference point _controller.set_source_orientation(id, (_scene.get_reference().position - *_scene.get_source_position(id)).orientation()); } void ssr::QUserInterface::_toggle_fixation_state_of_selected_sources() { // iterate over selected sources for ( selected_sources_map_t::iterator i = _selected_sources_map.begin(); i != _selected_sources_map.end(); i++) { _controller.set_source_position_fixed(i->second, !_scene.get_source_position_fixed(i->second)); } } // 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'\:'=' ssr-0.4.2/src/gui/quserinterface.h000066400000000000000000000152131236416011200170660ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// QUserInterface class (definition). #ifndef SSR_QUSERINTERFACE_H #define SSR_QUSERINTERFACE_H #ifdef HAVE_CONFIG_H #include // for ENABLE_FLOATING_CONTROL_PANEL #endif #include #include #include #include "qopenglplotter.h" #include "qclicktextlabel.h" #include "qguiframe.h" #include "qfilemenulabel.h" #include "qcpulabel.h" #include "qzoomlabel.h" #include "qvolumeslider.h" #include "qscenebutton.h" #include "qssrtimeline.h" #include "qsourceproperties.h" namespace ssr { /** This class completes the OpenGL window and makes it a proper interface * including handling of mouse and keyboard actions and other interaction tools. */ class QUserInterface : public QOpenGLPlotter { Q_OBJECT public: QUserInterface(Publisher& controller, const Scene& scene , const std::string& path_to_gui_images , const std::string& path_to_scene_menu , unsigned int update_frequency = 30u, QWidget *parent = 0); ~QUserInterface(); // list with scene labels typedef std::list scene_button_list_t; private slots: void _update_screen(); virtual void _create_scene_menu(const std::string& path_to_scene_menu); virtual void _open_file(); virtual void _new_scene(); virtual void _set_master_volume( float volume ); virtual void _change_volume_of_selected_sources(float d_volume); virtual void _save_file_as(); virtual void _load_scene(const QString& scene_name = QString()); virtual void _skip_back(); virtual void _show_file_menu(); virtual void _processing_button_pressed(); virtual void _pause_button_pressed(); virtual void _transport_locate(float time); virtual void _play_button_pressed(); virtual void _set_source_mute(const bool flag); virtual void _set_source_position_fixed(const bool flag); virtual void _set_source_model(const int index); virtual void _resizeControls(int newWidth); protected: std::string scene_description_file; ///< path to current scene descriptinon file (obsolete???) uint _active_scene; ///< number of the quick access tab of the current scene #ifndef ENABLE_FLOATING_CONTROL_PANEL bool _mouse_event_out_of_scope(QMouseEvent *event); virtual bool event(QEvent *event); #else bool eventFilter(QObject *sender, QEvent *event); #endif virtual void resizeEvent(QResizeEvent *event); virtual void keyPressEvent(QKeyEvent *event); virtual void keyReleaseEvent(QKeyEvent *event); virtual void mousePressEvent(QMouseEvent *event); virtual void mouseMoveEvent(QMouseEvent *event); virtual void mouseDoubleClickEvent(QMouseEvent *event); virtual void mouseReleaseEvent (QMouseEvent *event); virtual void wheelEvent(QWheelEvent * event); private: scene_button_list_t _scene_button_list; ///< list which holds all quiack access scene tabs // Fancy stuff QGUIFrame* _frame; ///< frame around the OpenGL window QFileMenuLabel* _file_menu_label; ///< label that holds the file menu QCPULabel* _cpu_label; ///< label that displays the current CPU load QZoomLabel* _zoom_label; ///< zoom action and display label QVolumeSlider* _volume_slider; ///< master volume and master audio level widget QSSRTimeLine* _time_line; // Text labels QLabel* _cpu_label_text_tag; ///< text tag above \a cpu_label QLabel* _zoom_label_text_tag; ///< text tag above \a zoom_label QLabel* _volume_slider_text_tag; ///< text tag above \a volume_slider // Buttons QPushButton* _processing_button; ///< button to enable/disable audio processing QPushButton* _skip_back_button; ///< button to skip to the beginning of a scene QPushButton* _pause_button; ///< button to pause replaying QPushButton* _play_button; ///< button to start replaying bool _ignore_mouse_events; QSourceProperties* _source_properties; ///< source properties dialog void _show_about_window(); void _update_source_properties_position(); void _toggle_mute_state_of_selected_sources(); void _toggle_solo_state_of_selected_sources(); void _toggle_source_models(); void _toggle_fixation_state_of_selected_sources(); void _solo_selected_sources(); void _unsolo_selected_sources(); void _unsolo_all_sources(); QWidget *_controlsParent; ///< parent widget of buttons and labels }; } // namespace ssr #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'\:'=' ssr-0.4.2/src/gui/qvolumeslider.cpp000066400000000000000000000162311236416011200172750ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// TODO: add description #include #include #include "qvolumeslider.h" #define UPPERLIMIT 12.0f #define LOWERLIMIT -50.0f #define ACCELERATION 0.2f // margin in pixels #define SIDEMARGIN 3 #define BOTTOMMARGIN 7 QVolumeSlider::QVolumeSlider(QWidget* parent) : QLabel(parent) { setAlignment(Qt::AlignCenter); // set default values for level and volume update_displays(LOWERLIMIT, LOWERLIMIT); // clear text setText(QString()); } void QVolumeSlider::update_displays(float lev, float vol) { // save values lev = std::min(lev, UPPERLIMIT); lev = std::max(lev, LOWERLIMIT); level = lev; vol = std::min(vol, UPPERLIMIT); vol = std::max(vol, LOWERLIMIT); volume_dB = vol; update(); } void QVolumeSlider::mousePressEvent(QMouseEvent *event) { event->accept(); // display volume as number setText(QString().setNum((int)(volume_dB + 0.5f))); // remember where movement started starting_point = event->globalPos(); } void QVolumeSlider::mouseMoveEvent(QMouseEvent *event) { event->accept(); float vol_tmp = volume_dB + (event->globalX() - starting_point.x())*ACCELERATION; // limit possible values vol_tmp = std::min(vol_tmp, UPPERLIMIT); vol_tmp = std::max(vol_tmp, LOWERLIMIT); // display volume as number setText(QString().setNum((int)(vol_tmp + 0.5f))); emit signal_volume_changed(vol_tmp); // TODO: dB or linear?? // store reference point for mouse motion starting_point = event->globalPos(); } void QVolumeSlider::mouseReleaseEvent(QMouseEvent *event) { event->accept(); // clear text setText(QString()); } void QVolumeSlider::paintEvent(QPaintEvent * event) { event->accept(); // draw QLabel stuff QLabel::paintEvent(event); QPainter painter(this); // draw white background for level meter painter.setPen(QPen(QColor(255,255,255))); painter.setBrush(QBrush(QColor(255,255,255))); painter.drawRect(SIDEMARGIN, 0, width() - 2*SIDEMARGIN, height() - BOTTOMMARGIN); // choose color for level bar if (level > 0.0f) { painter.setPen(QPen(QColor(255,0,0)));// red // TODO: rgb painter.setBrush(QBrush(QColor(255,0,0))); // draw level bar painter.drawRect(SIDEMARGIN+1, 1, static_cast((level-LOWERLIMIT)/(UPPERLIMIT-LOWERLIMIT) * (width()-2*SIDEMARGIN - 1)), height()-BOTTOMMARGIN-2); } else if (level <= -6.0f){ painter.setPen(QPen(QColor(58,239,58))); painter.setBrush(QBrush(QColor(58,239,58))); // green // draw level bar painter.drawRect(SIDEMARGIN+1, 1, static_cast((level-LOWERLIMIT)/(UPPERLIMIT-LOWERLIMIT) * (width()-2*SIDEMARGIN - 1)), height()-BOTTOMMARGIN-2); } else // between -6 and 0 dB { painter.setPen(QPen(QColor(58,239,58))); painter.setBrush(QBrush(QColor(58,239,58))); // green // draw green part of level bar painter.drawRect(SIDEMARGIN+1, 1, static_cast((-6.0f-LOWERLIMIT)/(UPPERLIMIT-LOWERLIMIT) * (width()-2*SIDEMARGIN - 1)), height()-BOTTOMMARGIN-2); painter.setPen(QPen(QColor(255,255,0)));// yellow // TODO: rgb painter.setBrush(QBrush(QColor(255,255,0))); // draw yellow part of level bar painter.drawRect(static_cast((-6.0f-LOWERLIMIT)/(UPPERLIMIT-LOWERLIMIT) * (width()-2*SIDEMARGIN - 1)) + SIDEMARGIN + 2, 1, static_cast((level+6.0f)/(UPPERLIMIT-LOWERLIMIT) * (width()-2*SIDEMARGIN - 1)), height()-BOTTOMMARGIN-2); } // draw volume mark const float vol_position = ((UPPERLIMIT-LOWERLIMIT) - (UPPERLIMIT - volume_dB)) /(UPPERLIMIT-LOWERLIMIT) * (width()-2*SIDEMARGIN) + SIDEMARGIN; // define triangle const QPointF points[3] = { QPointF(vol_position - 4.0f, 29.0f), QPointF(vol_position + 4.0f, 29.0f), QPointF(vol_position , 20.0f) }; // draw frame painter.setPen(QPen(QColor(237,237,230),1)); painter.drawLine(QLine(SIDEMARGIN,0,width()-SIDEMARGIN,0)); painter.drawLine(QLine(SIDEMARGIN,height()-BOTTOMMARGIN,width()-SIDEMARGIN,height()-BOTTOMMARGIN)); painter.drawLine(QLine(SIDEMARGIN,0,SIDEMARGIN,height()-BOTTOMMARGIN)); painter.drawLine(QLine(width()-SIDEMARGIN,0,width()-SIDEMARGIN,height()-BOTTOMMARGIN)); // indicate 0dB with the same color painter.drawLine(QLine(static_cast((2-LOWERLIMIT)/(UPPERLIMIT-LOWERLIMIT) * (width()-2*SIDEMARGIN - 1)), 1, static_cast((2-LOWERLIMIT)/(UPPERLIMIT-LOWERLIMIT) * (width()-2*SIDEMARGIN - 1)), height()-BOTTOMMARGIN)); // enable anti-aliasing painter.setRenderHint(QPainter::Antialiasing); painter.setPen(QPen(QColor(0,0,0))); // black painter.setBrush(QBrush(QColor(0,0,0))); painter.drawPolygon(points, 3); // draw QLabel stuff (i.e. text) on top //QLabel::paintEvent(event); // this is a quick-hack to avoid error messages //this->setText(this->text()); painter.setPen(QPen(QColor(0, 0, 0))); painter.drawText(QRect(0, -3, width(), height()), Qt::AlignCenter, text()); } // 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'\:'=' ssr-0.4.2/src/gui/qvolumeslider.h000066400000000000000000000056611236416011200167470ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// QVolumeSlider #ifndef SSR_QVOLUMESLIDER_H #define SSR_QVOLUMESLIDER_H #include #include #include #include /// QVolumeSlider class QVolumeSlider : public QLabel { Q_OBJECT public: QVolumeSlider( QWidget* parent = 0); void update_displays(float level, float volume_dB); protected: float level; float volume_dB; QPoint starting_point; virtual void mousePressEvent(QMouseEvent *event); virtual void mouseMoveEvent(QMouseEvent *event); virtual void mouseReleaseEvent(QMouseEvent *event); virtual void paintEvent( QPaintEvent * event); signals: void signal_volume_changed(float); }; #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'\:'=' ssr-0.4.2/src/gui/qzoomlabel.cpp000066400000000000000000000115541236416011200165520ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// TODO: add description #include #include #include "qzoomlabel.h" #define ZOOMACCELERATIONDOWNWARD 0.002f #define ZOOMACCELERATIONUPWARD 0.03f QZoomLabel::QZoomLabel(QWidget* parent) : QLabel(parent), zoom(100), zoom_buffer(100) { setAlignment(Qt::AlignCenter); // display number setText(QString().setNum(zoom_buffer)); // notify others // TODO: I think nobody receives it... emit signal_zoom_changed(zoom_buffer); } void QZoomLabel::mousePressEvent(QMouseEvent *event) { event->accept(); // remember where movement started starting_point = event->globalPos(); } void QZoomLabel::mouseMoveEvent(QMouseEvent *event) { event->accept(); float acceleration; if (event->globalY() - starting_point.y() > 0) // mouse moves downward acceleration = ZOOMACCELERATIONDOWNWARD; else // mouse moves upward acceleration = ZOOMACCELERATIONUPWARD; // set zoom buffer according to distance set_zoom_buffer((int)(zoom - zoom * (event->globalY() - starting_point.y()) * acceleration)); } void QZoomLabel::mouseReleaseEvent(QMouseEvent *event) { event->accept(); // set zoom zoom = zoom_buffer; } void QZoomLabel::mouseDoubleClickEvent(QMouseEvent *event) { event->accept(); // Return to 100% // TODO: Make it more elegant set_zoom_buffer(100); zoom = 100; emit signal_zoom_changed(zoom_buffer); } void QZoomLabel::set_zoom_buffer(int z) { // limit values z = std::min(z, 300); z = std::max(z, 10); zoom_buffer = z; // display number setText(QString().setNum(zoom_buffer)); // tell the others emit signal_zoom_changed(zoom_buffer); } void QZoomLabel::update_display(int z) { // avoid infinite loop if (zoom_buffer == z) return; zoom = z; setText(QString().setNum(zoom)); } void QZoomLabel::paintEvent( QPaintEvent * event) { QLabel::paintEvent(event); QPainter painter(this); // frame painter.setPen(QPen(QColor(237,237,230),1)); painter.drawLine(QLine(0,0,width(),0)); painter.drawLine(QLine(0,height()-1,width(),height()-1)); painter.drawLine(QLine(0,0,0,height())); painter.drawLine(QLine(width()-1,0,width()-1,height())); // enable anti-aliasing painter.setRenderHint(QPainter::Antialiasing); painter.setPen(QPen(QColor(0,0,0),1)); // black QLineF line_down_1(40.0f, 9.0f, 44.0f, 12.0f); QLineF line_up_1 (44.0f, 12.0f, 48.0f, 9.0f); QLineF line_down_2(40.0f, 6.0f, 44.0f, 3.0f); QLineF line_up_2 (44.0f, 3.0f, 48.0f, 6.0f); // draw down-arrow painter.drawLine(line_down_1); painter.drawLine(line_up_1); // draw up-arrow painter.drawLine(line_down_2); painter.drawLine(line_up_2); } // 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'\:'=' ssr-0.4.2/src/gui/qzoomlabel.h000066400000000000000000000060001236416011200162050ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// QZoomLabel #ifndef SSR_QZOOMLABEL_H #define SSR_QZOOMLABEL_H #include #include #include #include /// QZoomLabel class QZoomLabel : public QLabel { Q_OBJECT public: QZoomLabel( QWidget* parent = 0); protected: QPoint starting_point; int zoom; int zoom_buffer; void set_zoom_buffer(int z); float distance(QPoint start, QPoint end); void mousePressEvent(QMouseEvent *event); void mouseMoveEvent(QMouseEvent *event); void mouseDoubleClickEvent(QMouseEvent *event); void mouseReleaseEvent(QMouseEvent *event); void paintEvent( QPaintEvent * event); protected slots: void update_display(int z); signals: void signal_zoom_changed(int); }; #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'\:'=' ssr-0.4.2/src/hoacoefficients.h000066400000000000000000000146761236416011200164270ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Coefficients for the IIR filters in NfcHoaRenderer #ifndef SSR_HOACASCADE_H #define SSR_HOACASCADE_H #include // for std::pow() #include "apf/biquad.h" #include "apf/iterator.h" namespace ssr { namespace internal { template struct LaplaceCoeffsBase; // no implementation! template<> struct LaplaceCoeffsBase { static const double laplace_coeffs[][2]; }; template<> struct LaplaceCoeffsBase { static const float laplace_coeffs[][2]; }; const double LaplaceCoeffsBase::laplace_coeffs[][2] = { #include "laplace_coeffs_double.h" }; const float LaplaceCoeffsBase::laplace_coeffs[][2] = { #include "laplace_coeffs_float.h" }; } // namespace internal /// Coefficients for the IIR filters in NfcHoaRenderer template class HoaCoefficients : public std::vector> , private internal::LaplaceCoeffsBase { private: using _base = std::vector>; public: enum source_t { point_source, plane_wave }; /// Constructor. /// @throw std::logic_error if desired order is not supported. HoaCoefficients(size_t order, size_t sample_rate, float array_radius , float speed_of_sound) : _base(order == 0 ? 1 : (order + 1) / 2) // round up // calculate starting index to read Laplace-domain coefficients // for formula see http://oeis.org/A002620 (quarter squares) // plus 1 for all indices except for order 0 , _coeffs_begin(!!order + order * order / 4) , _sample_rate(sample_rate) , _array_radius(array_radius) , _speed_of_sound(speed_of_sound) { if (_coeffs_begin >= sizeof(this->laplace_coeffs) / sizeof(*this->laplace_coeffs)) { throw std::logic_error("HoaCoefficients: Order " + apf::str::A2S(order) + " is not supported!"); } } void reset(float distance, source_t source_type) { auto iter = apf::make_transform_iterator(this->laplace_coeffs + _coeffs_begin , Scaler(distance, source_type , _sample_rate, _array_radius, _speed_of_sound)); std::copy(iter, iter + this->size(), this->begin()); } void swap(HoaCoefficients& other) { // TODO: actually swap this stuff? assert(_coeffs_begin == other._coeffs_begin); assert(_sample_rate == other._sample_rate); assert(_array_radius == other._array_radius); assert(_speed_of_sound == other._speed_of_sound); this->_base::swap(other); } friend std::ostream& operator<<(std::ostream& stream, const HoaCoefficients& c) { std::copy(c.begin(), c.end() , std::ostream_iterator(stream, "\n")); return stream; } private: class Scaler { public: using result_type = apf::SosCoefficients; Scaler(float distance, source_t source_type , size_t sample_rate, float array_radius, float speed_of_sound) : _scale_factor_one(speed_of_sound / distance) , _scale_factor_two(speed_of_sound / array_radius) , _source_type(source_type) , _sample_rate(sample_rate) {} result_type operator()(const T row[2]) const { T two = 2.0; auto c = apf::LaplaceCoefficients(); c.b1 = row[0]; c.b2 = row[1]; if (_source_type == point_source) { c.b1 *= _scale_factor_one; c.b2 *= std::pow(_scale_factor_one, two); } // Note: _scale_factor_two does not depend on the source parameters! // Setting a1 and a2 in the constructor might reduce processing time c.a1 = row[0] * _scale_factor_two; c.a2 = row[1] * std::pow(_scale_factor_two, two); // TODO: make prewarping frequency an (optional) parameter? return apf::bilinear(c, _sample_rate, 1000); } private: const T _scale_factor_one, _scale_factor_two; const source_t _source_type; const size_t _sample_rate; }; const size_t _coeffs_begin; const size_t _sample_rate; const float _array_radius; const float _speed_of_sound; }; } // namespace ssr #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'\:'=' ssr-0.4.2/src/laplace_coeffs_double.h000066400000000000000000000267111236416011200175470ustar00rootroot00000000000000/* This is an automatically generated header file. Made with Matlab-Script 'write_coefficients.m'. */ { 0.000000000000000e+000, 0.000000000000000e+000 }, // 0 { 1.000000000000000e+000, 0.000000000000000e+000 }, // 1 { 3.000000000000001e+000, 3.000000000000002e+000 }, // 2 { 3.677814645373914e+000, 6.459432693483369e+000 }, // 3 { 2.322185354626086e+000, 0.000000000000000e+000 }, { 5.792421205640748e+000, 9.140130890277934e+000 }, // 4 { 4.207578794359250e+000, 1.148780047687117e+001 }, { 6.703912798306966e+000, 1.427248051327957e+001 }, // 5 { 4.649348606363304e+000, 1.815631531345233e+001 }, { 3.646738595329718e+000, 0.000000000000000e+000 }, { 8.496718791726678e+000, 1.880113058957010e+001 }, // 6 { 7.471416712651685e+000, 2.085282317739701e+001 }, { 5.031864495621632e+000, 2.651402534406784e+001 }, { 9.516581056308512e+000, 2.566644475276452e+001 }, // 7 { 8.140278327276295e+000, 2.893654609326553e+001 }, { 5.371353757886604e+000, 3.659678515687718e+001 }, { 4.971786858528596e+000, 0.000000000000000e+000 }, { 1.117577208652944e+001, 3.197722525828925e+001 }, // 8 { 1.040968158126836e+001, 3.393474008516331e+001 }, { 8.736578434407308e+000, 3.856925327510697e+001 }, { 5.677967897794902e+000, 4.843201865263463e+001 }, { 1.225873580858140e+001, 4.058926791013267e+001 }, // 9 { 1.120884363900311e+001, 4.364664575303155e+001 }, { 9.276879774362763e+000, 4.978850265739909e+001 }, { 5.958521596360146e+000, 6.204143762198324e+001 }, { 6.297019181692579e+000, 0.000000000000000e+000 }, { 1.384408981081844e+001, 4.866754856400145e+001 }, // 10 { 1.323058193101615e+001, 5.058236156315873e+001 }, { 1.193505665714332e+001, 5.483915620211695e+001 }, { 9.772439133724468e+000, 6.262558591259445e+001 }, { 6.217832467297624e+000, 7.744270053127073e+001 }, { 1.496845972167442e+001, 5.903122281980341e+001 }, // 11 { 1.411578477527680e+001, 6.198706716144496e+001 }, { 1.260267490974768e+001, 6.754505303635654e+001 }, { 1.023129656781498e+001, 7.710692267760020e+001 }, { 6.459444179841402e+000, 9.465048152263506e+001 }, { 7.622339845644820e+000, 0.000000000000000e+000 }, { 1.650684402245966e+001, 6.887186703115624e+001 }, // 12 { 1.599454119990810e+001, 7.076356524198779e+001 }, { 1.493114248022233e+001, 7.483313621598683e+001 }, { 1.322200850007893e+001, 8.179322136465311e+001 }, { 1.065941718172430e+001, 9.325510262774697e+001 }, { 6.686046615606637e+000, 1.136771906776420e+002 }, { 1.766050416685945e+001, 8.098936204398954e+001 }, // 13 { 1.694118354413458e+001, 8.388826434850337e+001 }, { 1.568876055353315e+001, 8.914831174050255e+001 }, { 1.380074565244657e+001, 9.760915612958530e+001 }, { 1.106136196667262e+001, 1.110896622782437e+002 }, { 6.899734441256984e+000, 1.345334774195627e+002 }, { 8.947709675096704e+000, 0.000000000000000e+000 }, { 1.916634278249178e+001, 9.259009635050425e+001 }, // 14 { 1.872629171116373e+001, 9.446783465251711e+001 }, { 1.782200110737544e+001, 9.842952185833030e+001 }, { 1.639769393999163e+001, 1.049580452568662e+002 }, { 1.434479192457075e+001, 1.150154588136647e+002 }, { 1.144070476764960e+001, 1.306276698351872e+002 }, { 7.102173766756931e+000, 1.572285932736666e+002 }, { 2.034182799917479e+001, 1.064625380634361e+002 }, // 15 { 1.971913445630747e+001, 1.093261380168027e+002 }, { 1.864719863825881e+001, 1.144107825757835e+002 }, { 1.706491810694509e+001, 1.222855511914863e+002 }, { 1.485879398515697e+001, 1.340322720515803e+002 }, { 1.180030342741268e+001, 1.518841715877611e+002 }, { 7.294713724974059e+000, 1.817706601280443e+002 }, { 1.027310966177027e+001, 0.000000000000000e+000 }, { 2.182377224565995e+001, 1.198222006861289e+002 }, // 16 { 2.143797144813365e+001, 1.216908473932981e+002 }, { 2.065023936262388e+001, 1.255842552911985e+002 }, { 1.942465259323567e+001, 1.318542286733379e+002 }, { 1.769593640971744e+001, 1.411519804112610e+002 }, { 1.534648158070612e+001, 1.546776453439713e+002 }, { 1.214248276557533e+001, 1.748725375014955e+002 }, { 7.478463594348010e+000, 2.081668730547978e+002 }, { 2.301615439087554e+001, 1.354502369144839e+002 }, // 17 { 2.246687314774210e+001, 1.382903640236910e+002 }, { 2.152826859187448e+001, 1.432621279468546e+002 }, { 2.016058878849849e+001, 1.507806185786413e+002 }, { 1.829517738289059e+001, 1.615766251814373e+002 }, { 1.581089919014162e+001, 1.769678385424103e+002 }, { 1.246916195600974e+001, 1.996047325270262e+002 }, { 7.654347570269927e+000, 2.364236566191793e+002 }, { 1.159852898169715e+001, 0.000000000000000e+000 }, { 2.447980474629432e+001, 1.505681663837752e+002 }, // 18 { 2.413627092111251e+001, 1.524305430777271e+002 }, { 2.343789782974921e+001, 1.562778482672009e+002 }, { 2.236007820233952e+001, 1.623790857114432e+002 }, { 2.086002578627215e+001, 1.712092820949212e+002 }, { 1.886626447577230e+001, 1.835771205440750e+002 }, { 1.625456788898189e+001, 2.009175734175886e+002 }, { 1.278194556719365e+001, 2.260915322505210e+002 }, { 7.823144582284758e+000, 2.665467873806421e+002 }, { 2.568565148317570e+001, 1.679521095811241e+002 }, // 19 { 2.519412956140811e+001, 1.707759543940568e+002 }, { 2.435846052737699e+001, 1.756703360271071e+002 }, { 2.315120232061048e+001, 1.829536279670487e+002 }, { 2.152707711941880e+001, 1.931581767377006e+002 }, { 1.941220472708710e+001, 2.071696320420125e+002 }, { 1.667960143187698e+001, 2.265402411740961e+002 }, { 1.308219012864361e+001, 2.543426966225476e+002 }, { 7.985517835482826e+000, 2.985414913124624e+002 }, { 1.292396486491939e+001, 0.000000000000000e+000 }, { 2.713485160526972e+001, 1.848279797904152e+002 }, // 20 { 2.682518567822068e+001, 1.866858279885787e+002 }, { 2.619765597806509e+001, 1.905005491001041e+002 }, { 2.523455423174799e+001, 1.964848126464089e+002 }, { 2.390618530856651e+001, 2.050032656738244e+002 }, { 2.216515987675071e+001, 2.166439762890183e+002 }, { 1.993552496656871e+001, 2.323690257199951e+002 }, { 1.708779147619574e+001, 2.538480760882186e+002 }, { 1.337105375513596e+001, 2.843671108527150e+002 }, { 8.142037123478719e+000, 3.324125223357506e+002 }, { 2.835162946276424e+001, 2.039672653822122e+002 }, // 21 { 2.790684047290434e+001, 2.067804402468675e+002 }, { 2.715324553878126e+001, 2.116190321901742e+002 }, { 2.607111291783656e+001, 2.187377514847014e+002 }, { 2.462879953365601e+001, 2.285445057428942e+002 }, { 2.277715330903350e+001, 2.416822056458793e+002 }, { 2.043837048371016e+001, 2.591890030672110e+002 }, { 1.748067118098627e+001, 2.828523001318267e+002 }, { 1.364953385991843e+001, 3.161729020929053e+002 }, { 8.293195949354011e+000, 3.681642262579964e+002 }, { 1.424944729105544e+001, 0.000000000000000e+000 }, { 2.978930863455569e+001, 2.226034532577739e+002 }, // 22 { 2.950695939184219e+001, 2.244517873159250e+002 }, { 2.893765073075149e+001, 2.282514398199607e+002 }, { 2.806597076128432e+001, 2.341432598762231e+002 }, { 2.687234208117540e+001, 2.424468490982422e+002 }, { 2.532219099483218e+001, 2.535920808608197e+002 }, { 2.336551315084579e+001, 2.682875824687695e+002 }, { 2.092257934661064e+001, 2.876421684800918e+002 }, { 1.785956389231566e+001, 3.135632577815193e+002 }, { 1.391849616466763e+001, 3.497675338448404e+002 }, { 8.439424851119011e+000, 4.058005936039216e+002 }, { 3.101259683358534e+001, 2.434571019476970e+002 }, // 23 { 3.061014963528372e+001, 2.463257599927440e+002 }, { 2.992186522922438e+001, 2.510890929472480e+002 }, { 2.894093419663735e+001, 2.581001393469207e+002 }, { 2.764136415069692e+001, 2.676215050587379e+002 }, { 2.598920672472762e+001, 2.801630000487618e+002 }, { 2.393229669654418e+001, 2.964727583816438e+002 }, { 2.138975087079414e+001, 3.177404867708757e+002 }, { 1.822561944475450e+001, 3.459904585713956e+002 }, { 1.417869739158406e+001, 3.851578924716059e+002 }, { 8.581101911729057e+000, 4.453253032543956e+002 }, { 1.557641691443869e+001, 0.000000000000000e+000 }, { 3.244186089284886e+001, 2.638767429511960e+002 }, // 24 { 3.218645841304740e+001, 2.657672864344669e+002 }, { 3.166049206054047e+001, 2.694933950510498e+002 }, { 3.086672599902618e+001, 2.753555284763469e+002 }, { 2.978134350165545e+001, 2.834906916927460e+002 }, { 2.838115627754630e+001, 2.942773799548288e+002 }, { 2.663212904495514e+001, 3.082711838432585e+002 }, { 2.447936792061046e+001, 3.262505205168673e+002 }, { 2.184125340189771e+001, 3.494947052677543e+002 }, { 1.857984831410606e+001, 3.801428042817797e+002 }, { 1.443080305045599e+001, 4.223503401512081e+002 }, { 8.718561123310170e+000, 4.867417597470321e+002 }, { 3.363188907543450e+001, 2.857675164015755e+002 }, // 25 { 3.333314486192653e+001, 2.899124283083404e+002 }, { 3.265614297010323e+001, 2.937570003831303e+002 }, { 3.177933481917367e+001, 3.011817316626902e+002 }, { 3.058863864959372e+001, 3.102632974648937e+002 }, { 2.909489089717475e+001, 3.224580448559494e+002 }, { 2.725290282297287e+001, 3.379197652802300e+002 }, { 2.500826289501575e+001, 3.576346577345449e+002 }, { 2.227832752331273e+001, 3.829148473651700e+002 }, { 1.892313347183865e+001, 4.160284964845858e+002 }, { 1.467540243060401e+001, 4.613507955209723e+002 }, { 8.852099145092456e+000, 5.300531234736003e+002 }, { 1.692583043775725e+001, 0.000000000000000e+000 }, { 3.508354795330700e+001, 3.085156457247867e+002 }, // 26 { 3.488551841170131e+001, 3.109349545608482e+002 }, { 3.434912889512815e+001, 3.139192259557201e+002 }, { 3.365793063451896e+001, 3.203132341521680e+002 }, { 3.264174148675778e+001, 3.280227133237308e+002 }, { 3.137124578393646e+001, 3.386434022971158e+002 }, { 2.978391588621569e+001, 3.521132568191370e+002 }, { 2.785323265036939e+001, 3.691353209715234e+002 }, { 2.552045125672996e+001, 3.906340427584660e+002 }, { 2.270202189284704e+001, 4.180105502386068e+002 }, { 1.925626518946276e+001, 4.536552287277798e+002 }, { 1.491301916886255e+001, 5.021647589019842e+002 }, { 8.981980790163151e+000, 5.752623382069568e+002 }, { 3.590235257401338e+001, 3.340395754528547e+002 }, // 27 { 3.645102941399667e+001, 3.352577557073624e+002 }, { 3.544741132080691e+001, 3.418679971506973e+002 }, { 3.458198501248675e+001, 3.469712866492980e+002 }, { 3.346316628025932e+001, 3.565518720676527e+002 }, { 3.214713331001649e+001, 3.686632605695577e+002 }, { 3.044019291775987e+001, 3.831257454863961e+002 }, { 2.843759907999025e+001, 4.019971770784346e+002 }, { 2.601679426426938e+001, 4.252401993935218e+002 }, { 2.311326674224510e+001, 4.547937877080431e+002 }, { 1.957995680130961e+001, 4.930299037765715e+002 }, { 1.514412006191759e+001, 5.447973778256367e+002 }, { 9.108443573241944e+000, 6.223721533689906e+002 }, { 1.816654864768749e+001, 0.000000000000000e+000 }, { 3.703172702934961e+001, 3.581953299219370e+002 }, // 28 { 3.757508758561276e+001, 3.627466629503100e+002 }, { 3.638072654013736e+001, 3.705879673538182e+002 }, { 3.551880500734364e+001, 3.750888341060493e+002 }, { 3.426202679317626e+001, 3.866484681974314e+002 }, { 3.287949332454511e+001, 3.999476407136868e+002 }, { 3.108899056173056e+001, 4.158553761382784e+002 }, { 2.899953386771190e+001, 4.363495571960974e+002 }, { 2.649974670354486e+001, 4.615123231341487e+002 }, { 2.351286021198728e+001, 4.932638882379442e+002 }, { 1.989481298684565e+001, 5.341601389962585e+002 }, { 1.536912868864135e+001, 5.892534306177754e+002 }, { 9.231701245788324e+000, 6.713851429924438e+002 }, { 3.775535945358558e+001, 3.563157359531782e+002 } ssr-0.4.2/src/laplace_coeffs_float.h000066400000000000000000000275571236416011200174130ustar00rootroot00000000000000/* This is an automatically generated header file. Made with Matlab-Script 'write_coefficients.m'. */ { 0.000000000000000e+000f, 0.000000000000000e+000f }, // 0 { 1.000000000000000e+000f, 0.000000000000000e+000f }, // 1 { 3.000000000000001e+000f, 3.000000000000002e+000f }, // 2 { 3.677814645373914e+000f, 6.459432693483369e+000f }, // 3 { 2.322185354626086e+000f, 0.000000000000000e+000f }, { 5.792421205640748e+000f, 9.140130890277934e+000f }, // 4 { 4.207578794359250e+000f, 1.148780047687117e+001f }, { 6.703912798306966e+000f, 1.427248051327957e+001f }, // 5 { 4.649348606363304e+000f, 1.815631531345233e+001f }, { 3.646738595329718e+000f, 0.000000000000000e+000f }, { 8.496718791726678e+000f, 1.880113058957010e+001f }, // 6 { 7.471416712651685e+000f, 2.085282317739701e+001f }, { 5.031864495621632e+000f, 2.651402534406784e+001f }, { 9.516581056308512e+000f, 2.566644475276452e+001f }, // 7 { 8.140278327276295e+000f, 2.893654609326553e+001f }, { 5.371353757886604e+000f, 3.659678515687718e+001f }, { 4.971786858528596e+000f, 0.000000000000000e+000f }, { 1.117577208652944e+001f, 3.197722525828925e+001f }, // 8 { 1.040968158126836e+001f, 3.393474008516331e+001f }, { 8.736578434407308e+000f, 3.856925327510697e+001f }, { 5.677967897794902e+000f, 4.843201865263463e+001f }, { 1.225873580858140e+001f, 4.058926791013267e+001f }, // 9 { 1.120884363900311e+001f, 4.364664575303155e+001f }, { 9.276879774362763e+000f, 4.978850265739909e+001f }, { 5.958521596360146e+000f, 6.204143762198324e+001f }, { 6.297019181692579e+000f, 0.000000000000000e+000f }, { 1.384408981081844e+001f, 4.866754856400145e+001f }, // 10 { 1.323058193101615e+001f, 5.058236156315873e+001f }, { 1.193505665714332e+001f, 5.483915620211695e+001f }, { 9.772439133724468e+000f, 6.262558591259445e+001f }, { 6.217832467297624e+000f, 7.744270053127073e+001f }, { 1.496845972167442e+001f, 5.903122281980341e+001f }, // 11 { 1.411578477527680e+001f, 6.198706716144496e+001f }, { 1.260267490974768e+001f, 6.754505303635654e+001f }, { 1.023129656781498e+001f, 7.710692267760020e+001f }, { 6.459444179841402e+000f, 9.465048152263506e+001f }, { 7.622339845644820e+000f, 0.000000000000000e+000f }, { 1.650684402245966e+001f, 6.887186703115624e+001f }, // 12 { 1.599454119990810e+001f, 7.076356524198779e+001f }, { 1.493114248022233e+001f, 7.483313621598683e+001f }, { 1.322200850007893e+001f, 8.179322136465311e+001f }, { 1.065941718172430e+001f, 9.325510262774697e+001f }, { 6.686046615606637e+000f, 1.136771906776420e+002f }, { 1.766050416685945e+001f, 8.098936204398954e+001f }, // 13 { 1.694118354413458e+001f, 8.388826434850337e+001f }, { 1.568876055353315e+001f, 8.914831174050255e+001f }, { 1.380074565244657e+001f, 9.760915612958530e+001f }, { 1.106136196667262e+001f, 1.110896622782437e+002f }, { 6.899734441256984e+000f, 1.345334774195627e+002f }, { 8.947709675096704e+000f, 0.000000000000000e+000f }, { 1.916634278249178e+001f, 9.259009635050425e+001f }, // 14 { 1.872629171116373e+001f, 9.446783465251711e+001f }, { 1.782200110737544e+001f, 9.842952185833030e+001f }, { 1.639769393999163e+001f, 1.049580452568662e+002f }, { 1.434479192457075e+001f, 1.150154588136647e+002f }, { 1.144070476764960e+001f, 1.306276698351872e+002f }, { 7.102173766756931e+000f, 1.572285932736666e+002f }, { 2.034182799917479e+001f, 1.064625380634361e+002f }, // 15 { 1.971913445630747e+001f, 1.093261380168027e+002f }, { 1.864719863825881e+001f, 1.144107825757835e+002f }, { 1.706491810694509e+001f, 1.222855511914863e+002f }, { 1.485879398515697e+001f, 1.340322720515803e+002f }, { 1.180030342741268e+001f, 1.518841715877611e+002f }, { 7.294713724974059e+000f, 1.817706601280443e+002f }, { 1.027310966177027e+001f, 0.000000000000000e+000f }, { 2.182377224565995e+001f, 1.198222006861289e+002f }, // 16 { 2.143797144813365e+001f, 1.216908473932981e+002f }, { 2.065023936262388e+001f, 1.255842552911985e+002f }, { 1.942465259323567e+001f, 1.318542286733379e+002f }, { 1.769593640971744e+001f, 1.411519804112610e+002f }, { 1.534648158070612e+001f, 1.546776453439713e+002f }, { 1.214248276557533e+001f, 1.748725375014955e+002f }, { 7.478463594348010e+000f, 2.081668730547978e+002f }, { 2.301615439087554e+001f, 1.354502369144839e+002f }, // 17 { 2.246687314774210e+001f, 1.382903640236910e+002f }, { 2.152826859187448e+001f, 1.432621279468546e+002f }, { 2.016058878849849e+001f, 1.507806185786413e+002f }, { 1.829517738289059e+001f, 1.615766251814373e+002f }, { 1.581089919014162e+001f, 1.769678385424103e+002f }, { 1.246916195600974e+001f, 1.996047325270262e+002f }, { 7.654347570269927e+000f, 2.364236566191793e+002f }, { 1.159852898169715e+001f, 0.000000000000000e+000f }, { 2.447980474629432e+001f, 1.505681663837752e+002f }, // 18 { 2.413627092111251e+001f, 1.524305430777271e+002f }, { 2.343789782974921e+001f, 1.562778482672009e+002f }, { 2.236007820233952e+001f, 1.623790857114432e+002f }, { 2.086002578627215e+001f, 1.712092820949212e+002f }, { 1.886626447577230e+001f, 1.835771205440750e+002f }, { 1.625456788898189e+001f, 2.009175734175886e+002f }, { 1.278194556719365e+001f, 2.260915322505210e+002f }, { 7.823144582284758e+000f, 2.665467873806421e+002f }, { 2.568565148317570e+001f, 1.679521095811241e+002f }, // 19 { 2.519412956140811e+001f, 1.707759543940568e+002f }, { 2.435846052737699e+001f, 1.756703360271071e+002f }, { 2.315120232061048e+001f, 1.829536279670487e+002f }, { 2.152707711941880e+001f, 1.931581767377006e+002f }, { 1.941220472708710e+001f, 2.071696320420125e+002f }, { 1.667960143187698e+001f, 2.265402411740961e+002f }, { 1.308219012864361e+001f, 2.543426966225476e+002f }, { 7.985517835482826e+000f, 2.985414913124624e+002f }, { 1.292396486491939e+001f, 0.000000000000000e+000f }, { 2.713485160526972e+001f, 1.848279797904152e+002f }, // 20 { 2.682518567822068e+001f, 1.866858279885787e+002f }, { 2.619765597806509e+001f, 1.905005491001041e+002f }, { 2.523455423174799e+001f, 1.964848126464089e+002f }, { 2.390618530856651e+001f, 2.050032656738244e+002f }, { 2.216515987675071e+001f, 2.166439762890183e+002f }, { 1.993552496656871e+001f, 2.323690257199951e+002f }, { 1.708779147619574e+001f, 2.538480760882186e+002f }, { 1.337105375513596e+001f, 2.843671108527150e+002f }, { 8.142037123478719e+000f, 3.324125223357506e+002f }, { 2.835162946276424e+001f, 2.039672653822122e+002f }, // 21 { 2.790684047290434e+001f, 2.067804402468675e+002f }, { 2.715324553878126e+001f, 2.116190321901742e+002f }, { 2.607111291783656e+001f, 2.187377514847014e+002f }, { 2.462879953365601e+001f, 2.285445057428942e+002f }, { 2.277715330903350e+001f, 2.416822056458793e+002f }, { 2.043837048371016e+001f, 2.591890030672110e+002f }, { 1.748067118098627e+001f, 2.828523001318267e+002f }, { 1.364953385991843e+001f, 3.161729020929053e+002f }, { 8.293195949354011e+000f, 3.681642262579964e+002f }, { 1.424944729105544e+001f, 0.000000000000000e+000f }, { 2.978930863455569e+001f, 2.226034532577739e+002f }, // 22 { 2.950695939184219e+001f, 2.244517873159250e+002f }, { 2.893765073075149e+001f, 2.282514398199607e+002f }, { 2.806597076128432e+001f, 2.341432598762231e+002f }, { 2.687234208117540e+001f, 2.424468490982422e+002f }, { 2.532219099483218e+001f, 2.535920808608197e+002f }, { 2.336551315084579e+001f, 2.682875824687695e+002f }, { 2.092257934661064e+001f, 2.876421684800918e+002f }, { 1.785956389231566e+001f, 3.135632577815193e+002f }, { 1.391849616466763e+001f, 3.497675338448404e+002f }, { 8.439424851119011e+000f, 4.058005936039216e+002f }, { 3.101259683358534e+001f, 2.434571019476970e+002f }, // 23 { 3.061014963528372e+001f, 2.463257599927440e+002f }, { 2.992186522922438e+001f, 2.510890929472480e+002f }, { 2.894093419663735e+001f, 2.581001393469207e+002f }, { 2.764136415069692e+001f, 2.676215050587379e+002f }, { 2.598920672472762e+001f, 2.801630000487618e+002f }, { 2.393229669654418e+001f, 2.964727583816438e+002f }, { 2.138975087079414e+001f, 3.177404867708757e+002f }, { 1.822561944475450e+001f, 3.459904585713956e+002f }, { 1.417869739158406e+001f, 3.851578924716059e+002f }, { 8.581101911729057e+000f, 4.453253032543956e+002f }, { 1.557641691443869e+001f, 0.000000000000000e+000f }, { 3.244186089284886e+001f, 2.638767429511960e+002f }, // 24 { 3.218645841304740e+001f, 2.657672864344669e+002f }, { 3.166049206054047e+001f, 2.694933950510498e+002f }, { 3.086672599902618e+001f, 2.753555284763469e+002f }, { 2.978134350165545e+001f, 2.834906916927460e+002f }, { 2.838115627754630e+001f, 2.942773799548288e+002f }, { 2.663212904495514e+001f, 3.082711838432585e+002f }, { 2.447936792061046e+001f, 3.262505205168673e+002f }, { 2.184125340189771e+001f, 3.494947052677543e+002f }, { 1.857984831410606e+001f, 3.801428042817797e+002f }, { 1.443080305045599e+001f, 4.223503401512081e+002f }, { 8.718561123310170e+000f, 4.867417597470321e+002f }, { 3.363188907543450e+001f, 2.857675164015755e+002f }, // 25 { 3.333314486192653e+001f, 2.899124283083404e+002f }, { 3.265614297010323e+001f, 2.937570003831303e+002f }, { 3.177933481917367e+001f, 3.011817316626902e+002f }, { 3.058863864959372e+001f, 3.102632974648937e+002f }, { 2.909489089717475e+001f, 3.224580448559494e+002f }, { 2.725290282297287e+001f, 3.379197652802300e+002f }, { 2.500826289501575e+001f, 3.576346577345449e+002f }, { 2.227832752331273e+001f, 3.829148473651700e+002f }, { 1.892313347183865e+001f, 4.160284964845858e+002f }, { 1.467540243060401e+001f, 4.613507955209723e+002f }, { 8.852099145092456e+000f, 5.300531234736003e+002f }, { 1.692583043775725e+001f, 0.000000000000000e+000f }, { 3.508354795330700e+001f, 3.085156457247867e+002f }, // 26 { 3.488551841170131e+001f, 3.109349545608482e+002f }, { 3.434912889512815e+001f, 3.139192259557201e+002f }, { 3.365793063451896e+001f, 3.203132341521680e+002f }, { 3.264174148675778e+001f, 3.280227133237308e+002f }, { 3.137124578393646e+001f, 3.386434022971158e+002f }, { 2.978391588621569e+001f, 3.521132568191370e+002f }, { 2.785323265036939e+001f, 3.691353209715234e+002f }, { 2.552045125672996e+001f, 3.906340427584660e+002f }, { 2.270202189284704e+001f, 4.180105502386068e+002f }, { 1.925626518946276e+001f, 4.536552287277798e+002f }, { 1.491301916886255e+001f, 5.021647589019842e+002f }, { 8.981980790163151e+000f, 5.752623382069568e+002f }, { 3.590235257401338e+001f, 3.340395754528547e+002f }, // 27 { 3.645102941399667e+001f, 3.352577557073624e+002f }, { 3.544741132080691e+001f, 3.418679971506973e+002f }, { 3.458198501248675e+001f, 3.469712866492980e+002f }, { 3.346316628025932e+001f, 3.565518720676527e+002f }, { 3.214713331001649e+001f, 3.686632605695577e+002f }, { 3.044019291775987e+001f, 3.831257454863961e+002f }, { 2.843759907999025e+001f, 4.019971770784346e+002f }, { 2.601679426426938e+001f, 4.252401993935218e+002f }, { 2.311326674224510e+001f, 4.547937877080431e+002f }, { 1.957995680130961e+001f, 4.930299037765715e+002f }, { 1.514412006191759e+001f, 5.447973778256367e+002f }, { 9.108443573241944e+000f, 6.223721533689906e+002f }, { 1.816654864768749e+001f, 0.000000000000000e+000f }, { 3.703172702934961e+001f, 3.581953299219370e+002f }, // 28 { 3.757508758561276e+001f, 3.627466629503100e+002f }, { 3.638072654013736e+001f, 3.705879673538182e+002f }, { 3.551880500734364e+001f, 3.750888341060493e+002f }, { 3.426202679317626e+001f, 3.866484681974314e+002f }, { 3.287949332454511e+001f, 3.999476407136868e+002f }, { 3.108899056173056e+001f, 4.158553761382784e+002f }, { 2.899953386771190e+001f, 4.363495571960974e+002f }, { 2.649974670354486e+001f, 4.615123231341487e+002f }, { 2.351286021198728e+001f, 4.932638882379442e+002f }, { 1.989481298684565e+001f, 5.341601389962585e+002f }, { 1.536912868864135e+001f, 5.892534306177754e+002f }, { 9.231701245788324e+000f, 6.713851429924438e+002f }, { 3.775535945358558e+001f, 3.563157359531782e+002f } ssr-0.4.2/src/loudspeaker.h000066400000000000000000000103451236416011200156010ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// %Loudspeaker class (definition). #ifndef SSR_LOUDSPEAKER_H #define SSR_LOUDSPEAKER_H #include #include #include "directionalpoint.h" /// Class for saving loudspeaker information. struct Loudspeaker : DirectionalPoint { typedef std::vector container_t; /// %Loudspeaker type. enum model_t { unknown = 0, ///< unknown loudspeaker type // TODO: find better names and better descriptions normal, ///< normal loudspeaker subwoofer ///< always on, regardless of source positions }; /// ctor explicit Loudspeaker( const DirectionalPoint& point = DirectionalPoint(), model_t model = normal, float weight = 1.0f, float delay = 0.0f) : DirectionalPoint(point), // base class copy ctor model(model), weight(weight), delay(delay), mute(false), active(false) {} // auto-generated copy ctor and assignment operator are OK. //std::string name; //std::string audio_file_name; //std::string audio_file_channel; //std::string port_name; model_t model; ///< type of loudspeaker float weight; /// linear! float delay; /// in seconds bool mute; ///< mute/unmute bool active; ///< active/inactive for a given source //float gain; ///< gain friend std::istream& operator>>(std::istream& input, model_t& model) { std::string temp; input >> temp; //if (input.fail()) return input; // redundant? if (temp == "normal") model = normal; else if (temp == "subwoofer") model = subwoofer; // everything else (including empty string on failure) doesn't change model else input.setstate(std::ios_base::badbit); return input; } friend std::ostream& operator<<(std::ostream& output, const model_t& model) { switch (model) { case normal: output << "normal"; break; case subwoofer: output << "subwoofer"; break; default: output << "unknown"; break; } return output; } }; #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'\:'=' ssr-0.4.2/src/loudspeakerrenderer.h000066400000000000000000000347641236416011200173430ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Parent class for loudspeaker-based renderers. #ifndef SSR_LOUDSPEAKERRENDERER_H #define SSR_LOUDSPEAKERRENDERER_H #include "rendererbase.h" #include "loudspeaker.h" #include "xmlparser.h" #include "apf/parameter_map.h" namespace ssr { template class LoudspeakerRenderer : public RendererBase { private: using _base = RendererBase; using Node = XMLParser::Node; public: class Output : public _base::Output, public Loudspeaker { public: struct Params : _base::Output::Params, Loudspeaker {}; // TODO: handle loudspeaker delays? Output(const Params& p) : _base::Output(p), Loudspeaker(p) {} }; LoudspeakerRenderer(const apf::parameter_map& p) : _base(p) , _reproduction_setup(p.get("reproduction_setup", "")) , _xml_schema(p.get("xml_schema", "")) , _next_loudspeaker_channel(1) { this->_show_head = false; } void load_reproduction_setup(); void get_loudspeakers(std::vector& l); private: void _load_loudspeaker(const Node& node); void _load_linear_array(const Node& node); void _load_circular_array(const Node& node); void _set_connection(apf::parameter_map& p); std::unique_ptr _get_position(const Node& node); std::unique_ptr _get_orientation(const Node& node); std::unique_ptr _get_angle(const Node& node); const std::string _reproduction_setup; const std::string _xml_schema; int _next_loudspeaker_channel; }; template void LoudspeakerRenderer::get_loudspeakers(std::vector& l) { using out_list_t = typename _base::template rtlist_proxy; for (const auto& out: out_list_t(this->get_output_list())) { l.push_back(Loudspeaker(out)); } } template void LoudspeakerRenderer::load_reproduction_setup() { if (_reproduction_setup == "") { throw std::runtime_error("No reproduction_setup specified!"); } // read the setup from XML file XMLParser xp; // load XML parser auto setup_file = xp.load_file(_reproduction_setup); if (!setup_file) { throw std::runtime_error("Unable to load reproduction setup file \"" + _reproduction_setup + "\"!"); } if (_xml_schema != "" && !setup_file->validate(_xml_schema)) { throw std::runtime_error("Error validating \"" + _reproduction_setup + "\" with schema file \"" + _xml_schema + "\"!"); } auto xpath_result = setup_file->eval_xpath("//reproduction_setup/*"); // one could use separate xpath expressions like // "//reproduction_setup/loudspeaker", // "//reproduction_setup/circular_array" and // "//reproduction_setup/linear_array", // but this would not preserve the order of the elements! if (!xpath_result) { throw std::runtime_error( "No section found in XML file!"); } // it is not necessary to use xmlFreeNode on "node", it should be freed in the // destructor of xpath_result (hopefully?) for (Node node; (node = xpath_result->node()); ++(*xpath_result)) { if (node == "loudspeaker") { _load_loudspeaker(node); } else if (node == "linear_array") { _load_linear_array(node); } else if (node == "circular_array") { _load_circular_array(node); } else if (node == "skip") { int number = 1; if (!apf::str::S2A(node.get_attribute("number"), number) || (number < 1)) { //VERBOSE("Couldn't get \"number\" attribute of \"skip\" element." // " Using default value 1."); number = 1; } for (int i = 0; i < number; ++i) { ++_next_loudspeaker_channel; } } else { //WARNING("Ignored \"" << node << "\" element!"); } } if (_next_loudspeaker_channel < 2) { throw std::runtime_error("No loudspeakers found in the file \"" + _reproduction_setup + "\"!"); } //VERBOSE("Loaded " << l.size() << " loudspeakers from '" // << setup_file_name << "'."); } template void LoudspeakerRenderer::_set_connection(apf::parameter_map& p) { const std::string prefix = this->params.get("system_output_prefix", ""); if (prefix != "") { p.set("connect_to", prefix + apf::str::A2S(_next_loudspeaker_channel)); } ++_next_loudspeaker_channel; } template void LoudspeakerRenderer::_load_loudspeaker(const Node& node) { if (!node) return; std::unique_ptr position; std::unique_ptr orientation; position = _get_position(node); orientation = _get_orientation(node); if (!position) { throw std::runtime_error( "No position found for the loudspeaker! Not loaded!"); return; } else if (!orientation) { throw std::runtime_error( "No orientation found for the loudspeaker! Not loaded!"); return; } std::string model_str = node.get_attribute("model"); Loudspeaker::model_t model; if (model_str == "normal") model = Loudspeaker::normal; else if (model_str == "subwoofer") model = Loudspeaker::subwoofer; else model = Loudspeaker::unknown; float delay = !node ? 0.0f : apf::str::S2RV(node.get_attribute("delay"), 0.0f); // check validity if (delay < 0) { //ERROR("Loudspeaker delay can not be negative. I'll ignore it"); delay = 0.0f; } float weight = !node ? 1.0f : apf::str::S2RV(node.get_attribute("weight"), 1.0f); typename Output::Params params; params.position = *position; params.orientation = *orientation; params.model = model; params.weight = weight; params.delay = delay; _set_connection(params); this->add(params); } template void LoudspeakerRenderer::_load_linear_array(const Node& node) { if (!node) return; int number; if (!apf::str::S2A(node.get_attribute("number"), number)) { throw std::runtime_error( "Invalid number of loudspeakers! Array not loaded."); return; } if (number < 2) { // This is also checked in the Schema validation. throw std::runtime_error( "Number of loudspeakers in an array must be at least 2!"); return; } std::unique_ptr first_pos, second_pos, last_pos; std::unique_ptr first_dir, second_dir, last_dir; // we search the first occurence of each element, but // the XML Schema should assure that each element comes only once. first_pos = _get_position (node.child("first")); first_dir = _get_orientation(node.child("first")); second_pos = _get_position (node.child("second")); second_dir = _get_orientation(node.child("second")); last_pos = _get_position (node.child("last")); last_dir = _get_orientation(node.child("last")); if (!first_pos) { throw std::runtime_error( "No position found for first loudspeaker! Array not loaded!"); return; } if (!first_dir) { throw std::runtime_error( "No orientation found for first loudspeaker! Array not loaded!"); return; } const DirectionalPoint first(*first_pos, *first_dir); auto increment = DirectionalPoint(); if (second_pos && !last_pos) { // if no orientation was given for "second", it gets the same as "first" if (!second_dir) { second_dir.reset(new Orientation(*first_dir)); } const auto second = DirectionalPoint(*second_pos, *second_dir); increment = second - first; } else if (last_pos && !second_pos) { // if no orientation was given for "last", it gets the same as "first" if (!last_dir) { last_dir.reset(new Orientation(*first_dir)); } const auto last = DirectionalPoint(*last_pos, *last_dir); increment = (last - first) / (number - 1); } else { throw std::runtime_error( "Either \"second\" or \"last\" has to be present! (But not both!) " "Array not loaded."); return; } DirectionalPoint current = first; for (int i = 0; i < number; ++i) { // TODO: get loudspeaker type! typename Output::Params params; params.position = current.position; params.orientation = current.orientation; _set_connection(params); this->add(params); current += increment; } } template void LoudspeakerRenderer::_load_circular_array(const Node& node) { if (!node) return; int number; std::unique_ptr first_pos, center_pos; std::unique_ptr first_dir, second_dir, last_dir; if (!apf::str::S2A(node.get_attribute("number"), number) || (number < 2)) { throw std::runtime_error( "Invalid number of loudspeakers! Array not loaded."); return; } center_pos = _get_position (node.child("center")); first_pos = _get_position (node.child("first")); first_dir = _get_orientation(node.child("first")); second_dir = _get_angle (node.child("second")); last_dir = _get_angle (node.child("last")); if (!first_pos) { throw std::runtime_error( "No position found for first loudspeaker! Array not loaded!"); return; } if (!first_dir) { throw std::runtime_error( "No orientation found for first loudspeaker! Array not loaded!"); return; } // if no center is given, it is set to the origin if (!center_pos) { center_pos.reset(new Position(0,0)); } // WARNING: The center is not supposed to have an orientation! auto radius = DirectionalPoint(*first_pos - *center_pos, *first_dir); auto center = DirectionalPoint(*center_pos, Orientation()); float angle_increment = 360.0f / number; // full circle // division by zero not possible, as number >= 2 (see above) if (second_dir) { angle_increment = second_dir->azimuth; } else if (last_dir) { angle_increment = last_dir->azimuth / (number - 1); // division by zero not possible, as number >= 2 (see above) } for (int i = 0; i < number; ++i) { // Loudspeakers are ordered in mathematic positive direction, i.e. // counterclockwise (seen from above). // TODO: get loudspeaker type! auto point = radius; point.rotate(i * angle_increment); point += center; typename Output::Params params; params.position = point.position; params.orientation = point.orientation; _set_connection(params); this->add(params); } } template std::unique_ptr LoudspeakerRenderer::_get_position(const Node& node) { std::unique_ptr temp; // temp = NULL if (!node) return temp; // return NULL for (Node i = node.child(); !!i; ++i) { if (i == "position") { float x, y; // if read operation successful if (apf::str::S2A(i.get_attribute("x"), x) && apf::str::S2A(i.get_attribute("y"), y)) { temp.reset(new Position(x, y)); return temp; // return sucessfully } else { //ERROR("Invalid position!"); return temp; // return NULL } // if read operation successful } // if (i == "position") } return temp; // return NULL } template std::unique_ptr LoudspeakerRenderer::_get_orientation(const Node& node) { std::unique_ptr temp; // temp = NULL if (!node) return temp; // return NULL for (Node i = node.child(); !!i; ++i) { if (i == "orientation") { float azimuth; if (apf::str::S2A(i.get_attribute("azimuth"), azimuth)) { temp.reset(new Orientation(azimuth)); return temp; // return sucessfully } else { //ERROR("Invalid orientation!"); return temp; // return NULL } } } return temp; // return NULL } template std::unique_ptr LoudspeakerRenderer::_get_angle(const Node& node) { std::unique_ptr temp; // temp = NULL if (!node) return temp; // return NULL for (Node i = node.child(); !!i; ++i) { if (i == "angle") { float azimuth; if (apf::str::S2A(i.get_attribute("azimuth"), azimuth)) { temp.reset(new Orientation(azimuth)); return temp; // return sucessfully } else { //ERROR("Invalid angle!"); return temp; // return NULL } } } return temp; // return NULL } } // namespace ssr #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'\:'=' ssr-0.4.2/src/maptools.h000066400000000000000000000112651236416011200151230ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Some helper functions for std::map's. #ifndef SSR_MAPTOOLS_H #define SSR_MAPTOOLS_H #include /** Some helper functions for std::maps. * We frequently use maps where the second element is a pointer to an object. * The functions in this namespace should facilitate access to and disposal of * those objects. **/ namespace maptools { /// @name several overloaded versions of get_item() /// There are versions for maps of objects and for maps of pointers to /// objects. Furthermore, there are const and non-const versions. //@{ /** Get the pointer to an element of a map. * @param m the std::map we want to get the item from * @param id the key of the element we want to obtain * @return Pointer to the selected object. **/ template const dataT* get_item(const std::map& m, const idT id) { auto iter = m.find(id); if (iter != m.end()) return iter->second; else return nullptr; } // non-const version template dataT* get_item(std::map& m, const idT id) { auto iter = m.find(id); if (iter != m.end()) return iter->second; else return nullptr; } template const dataT* get_item(const std::map& m, const idT id) { auto iter = m.find(id); if (iter != m.end()) return &(iter->second); else return nullptr; } // non-const version template dataT* get_item(std::map& m, const idT id) { auto iter = m.find(id); if (iter != m.end()) return &(iter->second); else return nullptr; } //@} /** Deletes one element of a map after deleting the object it points to. * @param m a std::map * @param id identifier of the element you want to destroy. * @return @b true on success. Well, to be honest, always @b true. **/ template bool delete_item(std::map& m, const idT& id) { auto delinquent = m.find(id); if (delinquent == m.end()) { //WARNING("maptools::delete_item: '" << id << "' does not exist!"); return true; // anyway, the function kind of succeeded, didn't it? } delete delinquent->second; m.erase(delinquent); return true; } /** Delete all map elements and clear the map. * @param delinquent the map to be cleared. **/ template void purge(std::map& delinquent) { for (auto& i: delinquent) delete i.second; delinquent.clear(); } } // namespace maptools #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'\:'=' ssr-0.4.2/src/nfchoarenderer.h000066400000000000000000000460621236416011200162550ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Near Field Compensated Higher Order Ambisonics renderer. #ifndef SSR_NFCHOARENDERER_H #define SSR_NFCHOARENDERER_H #include "apf/math.h" // for apf::math::linear_interpolator #include "apf/fftwtools.h" // for apf::fftw, apf::fftw_allocator #include "apf/iterator.h" // for apf::dual_iterator, apf::discard_iterator, ... #include "apf/combine_channels.h" // for apf::CombineChannelsInterpolation #include "ssr_global.h" // for ssr::c #include "loudspeakerrenderer.h" #include "hoacoefficients.h" namespace ssr { class NfcHoaRenderer : public LoudspeakerRenderer { private: using _base = LoudspeakerRenderer; using coeff_t = HoaCoefficients; public: static const char* name() { return "NFC-HOA-Renderer"; } using matrix_t = apf::fixed_matrix; using fft_matrix_t = apf::fixed_matrix>; using filter_type = apf::Cascade>; class Source; class Mode; class ModePair; struct ModeAccumulatorBase; template class ModeAccumulator; class FftProcessor; class RenderFunction; struct Output; NfcHoaRenderer(const apf::parameter_map& params) : _base(params) , _mode_pair_list(_fifo) , _mode_accumulator_list(_fifo) , _fft_list(_fifo) {} APF_PROCESS(NfcHoaRenderer, _base) { this->_process_list(_source_list); this->_process_list(_mode_pair_list); this->_process_list(_mode_accumulator_list); _fft_matrix.set_channels(_mode_matrix.slices); // transpose matrix this->_process_list(_fft_list); } void load_reproduction_setup(); size_t order; // Ambisonics order float array_radius; private: matrix_t _mode_matrix; fft_matrix_t _fft_matrix; rtlist_t _mode_pair_list, _mode_accumulator_list, _fft_list; }; class NfcHoaRenderer::Source : public _base::Source { public: Source(const Params& p); void connect(); void disconnect(); APF_PROCESS(Source, _base::Source) { // NOTE: reference offset is not taken into account! // distance is only used for point sources (but is updated anyway) this->distance = (this->position - this->parent.state.reference_position).length(); // TODO: distance is not correct for plane waves! // This is only important for the delay auto source_orientation = Orientation(); switch (this->model) { default: case ::Source::point: this->source_model = coeff_t::point_source; source_orientation = (this->position - this->parent.state.reference_position).orientation(); // TODO: proper calculation of attenuation factor, this is temporary! { float distance_limit = 0.25f; this->weighting_factor *= std::sqrt(distance_limit / std::max(this->distance.get(), distance_limit)); } break; case ::Source::plane: this->source_model = coeff_t::plane_wave; source_orientation = this->orientation - Orientation(180); // Note: no distance attenuation for plane waves! // TODO: constant factor using amplitude_reference_distance()? break; } this->angle = apf::math::deg2rad(90 + (source_orientation - this->parent.state.reference_orientation).azimuth); // TODO: calculate delay // TODO: write delayed signal to a buffer? assert(this->distance.exactly_one_assignment()); assert(this->angle.exactly_one_assignment()); assert(this->source_model.exactly_one_assignment()); } apf::BlockParameter distance; apf::BlockParameter angle; apf::BlockParameter source_model; private: // Pointers to Mode objects for (dis-)connecting std::list _modes; // Pointers to ModePair objects for removing when Source is deleted std::list _mode_pairs; }; class NfcHoaRenderer::Mode : public ProcessItem , public apf::fixed_vector { public: Mode(size_t mode_number, const Source& s) : apf::fixed_vector(s.parent.block_size()) , source(s) , rotation1(0) , rotation2(0) , old_rotation1(0) , old_rotation2(0) , _mode_number(mode_number) , _filter(mode_number == 0 ? 1 : (mode_number + 1) / 2) // round up // Coefficients are all zeros by default , _coefficients(mode_number, s.parent.sample_rate() , s.parent.array_radius, ssr::c) , _old_coefficients(_coefficients) {} APF_PROCESS(Mode, ProcessItem) { _process(); } const Source& source; sample_type rotation1, rotation2, old_rotation1, old_rotation2; apf::CombineChannelsResult::type interpolation_mode; private: void _process(); sample_type _mode_number; filter_type _filter; coeff_t _coefficients, _old_coefficients; }; void NfcHoaRenderer::Mode::_process() { // IIR filtering is not done in RenderFunction because workload would be // distributed very un-evenly between threads! if (!this->source.distance.changed() && !this->source.source_model.changed()) { // process filter (entire block) _filter.execute(this->source.begin(), this->source.end() , this->begin()); } else { _old_coefficients.swap(_coefficients); // Avoid focused sources (for now ...): float distance = std::max(this->source.distance.get() , this->source.parent.array_radius); // scale filter coefficients _coefficients.reset(distance, this->source.source_model); apf::dual_iterator first_section(_old_coefficients.begin(), _coefficients.begin()); apf::dual_iterator last_section(_old_coefficients.end(), _coefficients.end()); sample_type block_size = this->size(); auto in = this->source.begin(); auto out = this->begin(); // Calculate each sample separately. The first sample uses the old // coefficients, after the last sample the filter is updated again for the // next block. for (sample_type index = 1; index <= block_size; ++index) { // Calculate one output sample *out++ = _filter(*in++); using result_type = apf::SosCoefficients; using argument_type = const std::pair&; auto interp_coeffs = [index, block_size] (argument_type coeffs) { return coeffs.first + index * (coeffs.second - coeffs.first) / block_size; }; // Set interpolated filter coefficients _filter.set(apf::make_transform_iterator(first_section, interp_coeffs) , apf::make_transform_iterator(last_section, interp_coeffs)); } assert(in == this->source.end()); assert(out == this->end()); } // Note: This must be done if angle OR weighting factor changes this->old_rotation1 = this->rotation1; this->old_rotation2 = this->rotation2; if (this->source.angle.changed()) { this->rotation1 = std::cos(-_mode_number * sample_type(this->source.angle)); // NOTE: the factor for the imaginary components has negative mode number! this->rotation2 = std::sin( _mode_number * sample_type(this->source.angle)); } using namespace apf::CombineChannelsResult; if (this->source.weighting_factor.both() == 0) { this->interpolation_mode = nothing; } else if (this->source.weighting_factor.changed() || this->source.angle.changed() || this->source.distance.changed() || this->source.source_model.changed()) { this->interpolation_mode = change; } else { this->interpolation_mode = constant; } } /** Combination of two Mode%s. * The only reason for this class is distribution of workload. It combines two * Mode%s in a way that each ModePair needs a similar amount of processing * power. **/ class NfcHoaRenderer::ModePair : public ProcessItem { public: ModePair(size_t mode_number, size_t order, const Source& source) : _second(order - mode_number, source) { bool order_is_odd = (order % 2 == 0); if ((mode_number == 0) && order_is_odd) { // With an odd number of Modes, the first ModePair gets only one Mode. // Therefore, _first stays empty. } else { _first.reset(new Mode(mode_number - order_is_odd, source)); } } APF_PROCESS(ModePair, ProcessItem) { if (_first) { _first->process(); } _second.process(); } const Mode* first_ptr() const { return _first.get(); } const Mode* second_ptr() const { return &_second; } private: std::unique_ptr _first; Mode _second; }; NfcHoaRenderer::Source::Source(const Params& p) : _base::Source(p) // Set impossible values to force update in first cycle: , distance(-1.0f) , angle(std::numeric_limits::infinity()) , source_model(coeff_t::source_t(-1)) {} class NfcHoaRenderer::RenderFunction { public: using result_type = std::pair; apf::CombineChannelsResult::type select(const Mode& in) { // TODO: where to apply global scale factor? // Source/master level, mute and distance attenuation are applied here. // All of this is combined in in.source.weighting_factor // TODO: move this to Source class? if (in.interpolation_mode == apf::CombineChannelsResult::change) { sample_type block_size = in.size(); _interpolator1.set(in.old_rotation1 * in.source.weighting_factor.old() , in.rotation1 * in.source.weighting_factor, block_size); _interpolator2.set(in.old_rotation2 * in.source.weighting_factor.old() , in.rotation2 * in.source.weighting_factor, block_size); } else if (in.interpolation_mode == apf::CombineChannelsResult::constant) { _rotation1 = in.rotation1 * in.source.weighting_factor; _rotation2 = in.rotation2 * in.source.weighting_factor; } else { // do nothing } return in.interpolation_mode; } result_type operator()(sample_type in) { return std::make_pair(in * _rotation1, in * _rotation2); } result_type operator()(sample_type in, sample_type index) { return std::make_pair(in * _interpolator1(index) , in * _interpolator2(index)); } private: sample_type _rotation1, _rotation2; apf::math::linear_interpolator _interpolator1, _interpolator2; }; // Template-free base class to be used in Source::connect() struct NfcHoaRenderer::ModeAccumulatorBase : Item { using mode_ptrs_t = std::list; // List of modes to be combined mode_ptrs_t mode_pointers; }; // TODO: proper documentation // First channel for positive mode, second channel for negative mode. // Mode 0 has no negative mode, nor does the highest order if there is an even // number of loudspeakers. template class NfcHoaRenderer::ModeAccumulator : public ModeAccumulatorBase { public: ModeAccumulator(I1 i1, I2 i2, size_t block_size) : _output_channels(i1, i2, block_size) , _combiner(mode_pointers, _output_channels) {} // APF_PROCESS doesn't work here because ModeAccumulatorBase cannot be a // class template. virtual void process() { // TODO: global scale factor (depends only on array size)? _combiner.process(RenderFunction()); } private: class two_matrix_channels { public: using iterator = apf::dual_iterator; two_matrix_channels(I1 i1, I2 i2, size_t block_size) : _begin(i1, i2) , _end(_begin) { std::advance(_end, block_size); } iterator begin() { return _begin; } iterator end() { return _end; } private: iterator _begin, _end; }; two_matrix_channels _output_channels; apf::CombineChannelsInterpolation , two_matrix_channels> _combiner; }; /// Helper function for automatic template type deduction template NfcHoaRenderer::ModeAccumulator* new_mode_accumulator(I1 i1, I2 i2, size_t block_size) { return new NfcHoaRenderer::ModeAccumulator(i1, i2, block_size); } void NfcHoaRenderer::Source::connect() { size_t order = this->parent.order; // create ModePair objects for (size_t mode_number = 0; mode_number <= order / 2; ++mode_number) { _mode_pairs.push_back(new ModePair(mode_number, order, *this)); } // create list of pointers to Mode objects auto reverse_pairs = apf::make_begin_and_end(_mode_pairs.rbegin(), _mode_pairs.rend()); for (auto pair: reverse_pairs) { if (pair->first_ptr()) _modes.push_front(pair->first_ptr()); _modes.push_back(pair->second_ptr()); } // add _mode_pairs to _mode_pair_list this->parent._mode_pair_list.add(_mode_pairs.begin(), _mode_pairs.end()); // connect modes with ModeAccumulator this->parent.add_to_sublist(_modes , apf::make_cast_proxy( this->parent._mode_accumulator_list) , &ModeAccumulatorBase::mode_pointers); } void NfcHoaRenderer::Source::disconnect() { // Note: everything is done in reverse order of connect() this->parent.rem_from_sublist(_modes , apf::make_cast_proxy( this->parent._mode_accumulator_list) , &ModeAccumulatorBase::mode_pointers); // The objects are actually deleted here (via the _fifo): this->parent._mode_pair_list.rem(_mode_pairs.begin(), _mode_pairs.end()); _modes.clear(); _mode_pairs.clear(); } class NfcHoaRenderer::FftProcessor : public ProcessItem { public: FftProcessor(size_t block_size, sample_type* first) : _fft_plan(apf::fftw::plan_r2r_1d, block_size, first, first , FFTW_R2HC, FFTW_PATIENT) {} APF_PROCESS(FftProcessor, ProcessItem) { // TODO: scale result? apf::fftw::execute(_fft_plan); } private: apf::fftw::scoped_plan _fft_plan; }; struct NfcHoaRenderer::Output : _base::Output { Output(const Params& p) : _base::Output(p) {} APF_PROCESS(Output, _base::Output) { std::copy(this->slice.begin(), this->slice.end(), this->buffer.begin()); } fft_matrix_t::Slice slice; }; void NfcHoaRenderer::load_reproduction_setup() { _base::load_reproduction_setup(); // TODO: check if reproduction setup is "reasonable" // TODO: check if clockwise or counterclockwise setup? using output_list_t = apf::cast_proxy; output_list_t outputs(const_cast(this->get_output_list())); auto add_distance = [] (float base, const Output& out) { if (out.model == Loudspeaker::subwoofer) { throw std::logic_error("Subwoofers are currently not supported!"); } return base + out.position.length(); }; float total = std::accumulate(outputs.begin(), outputs.end(), 0.0f , add_distance); // This is only valid if there are no subwoofers: size_t normal_loudspeakers = outputs.size(); this->array_radius = total / normal_loudspeakers; std::cout << "\nWARNING: this is a preliminary implementation of the NFC-HOA " "renderer!\nLoading " << normal_loudspeakers << " loudspeakers with a mean " "distance of " << this->array_radius << " meters.\n" "Assuming circular (counterclockwise) setup!\n" << std::endl; _mode_matrix.initialize(normal_loudspeakers, this->block_size()); _fft_matrix.initialize(this->block_size(), normal_loudspeakers); this->order = normal_loudspeakers / 2; // round down for (size_t i = 0; i <= this->order; ++i) { if (i == 0 || (i == this->order && normal_loudspeakers % 2 == 0)) { _mode_accumulator_list.add(new_mode_accumulator( _mode_matrix.channels[i].begin() , apf::discard_iterator() , this->block_size())); } else { _mode_accumulator_list.add(new_mode_accumulator( _mode_matrix.channels[i].begin() , _mode_matrix.channels[normal_loudspeakers - i].begin() , this->block_size())); } // TODO: documentation, mention half-complex format of FFTW } for (fft_matrix_t::channels_iterator ch = _fft_matrix.channels.begin() ; ch != _fft_matrix.channels.end() ; ++ch) { _fft_list.add(new FftProcessor(normal_loudspeakers, ch->begin())); } assert(outputs.size() == size_t(std::distance(_fft_matrix.slices.begin() , _fft_matrix.slices.end()))); fft_matrix_t::slices_iterator slice = _fft_matrix.slices.begin(); for (output_list_t::iterator out = outputs.begin() ; out != outputs.end() ; ++out) { out->slice = *slice++; } } } // namespace ssr #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'\:'=' ssr-0.4.2/src/orientation.cpp000066400000000000000000000103051236416011200161450ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// %Orientation class and helper function(s) (implementation). #include #include "orientation.h" #include "apf/math.h" #include "ssr_global.h" /// ctor. @param azimuth azimuth (in degrees) Orientation::Orientation(const float azimuth) : azimuth(azimuth) {} /** - operator. * @return difference of Orientations **/ Orientation operator-(const Orientation& lhs, const Orientation& rhs) { return Orientation(lhs.azimuth - rhs.azimuth); } /** + operator. **/ Orientation operator+(const Orientation& lhs, const Orientation& rhs) { return Orientation(lhs.azimuth + rhs.azimuth); } /** unary -operator. * @return negative Orientation **/ Orientation operator-(const Orientation& rhs) { return Orientation(-rhs.azimuth); } /** += operator. * @param other addend. * @return sum of Orientations **/ Orientation& Orientation::operator+=(const Orientation& other) { azimuth += other.azimuth; return *this; } /** -= operator. * @param other minuend. * @return difference of Orientations **/ Orientation& Orientation::operator-=(const Orientation& other) { azimuth -= other.azimuth; return *this; } /** ._ * @param angle angle in degrees. * @return the resulting orientation **/ Orientation& Orientation::rotate(float angle) { this->azimuth += angle; return *this; } // this is only a 2D implementation! Orientation& Orientation::rotate(const Orientation& rotation) { return this->rotate(rotation.azimuth); } /** _. * @param a One orientation * @param b Another orientation * @return Angle between the two orientations in radians. If the angle of @a b * is bigger than the angle of @a a, the result is negative. * @warning 2D implementation! **/ float angle(const Orientation& a, const Orientation& b) { return apf::math::deg2rad(a.azimuth - b.azimuth); } /// output stream operator (<<) std::ostream& operator<<(std::ostream& stream, const Orientation& orientation) { stream << "azimuth = " << orientation.azimuth; return stream; } // 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'\:'=' ssr-0.4.2/src/orientation.h000066400000000000000000000072211236416011200156150ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// %Orientation class and helper function(s) (definition). #ifndef SSR_ORIENTATION_H #define SSR_ORIENTATION_H #include /** Geometric representation of a orientation. * For now, only azimuth value is handled. **/ struct Orientation { // the default orientation is in negative y-direction (facing the listener) explicit Orientation(const float azimuth = 0); float azimuth; ///< (=yaw) azimuth (in degrees) /// plus (+) operator friend Orientation operator+(const Orientation& lhs, const Orientation& rhs); /// minus (-) operator friend Orientation operator-(const Orientation& lhs, const Orientation& rhs); /// unary minus (-) operator friend Orientation operator-(const Orientation& rhs); Orientation& operator+=(const Orientation& other); Orientation& operator-=(const Orientation& other); /// turn Orientation& rotate(float angle); Orientation& rotate(const Orientation& rotation); friend std::ostream& operator<<(std::ostream& stream, const Orientation& orientation); /** division (/) operator. * @param a dividend, a DirectionalPoint. * @param b divisor, any numeric Type.. * @return quotient. **/ template friend Orientation operator/(const Orientation& a, const T& b) { return Orientation(a.azimuth / b); } }; /// Angle (in radians) between two orientations. float angle(const Orientation& a, const Orientation& b); #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'\:'=' ssr-0.4.2/src/position.cpp000066400000000000000000000105421236416011200154610ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// %Position class and helper functions (implementation). #include // for atan2(), sqrt() #include #include "position.h" #include "orientation.h" #include "apf/math.h" Position::Position(const float x, const float y) : x(x), y(y) {} Position& Position::operator+=(const Position& other) { x += other.x; y += other.y; return *this; } Position& Position::operator-=(const Position& other) { x -= other.x; y -= other.y; return *this; } bool Position::operator==(const Position& other) const { return x == other.x && y == other.y; } bool Position::operator!=(const Position& other) const { return !this->operator==(other); } /** convert the orientation given by the position vector (x,y) to an * Orientation. * @return Orientation with the corresponding azimuth value * @warning Works only for 2D! **/ Orientation Position::orientation() const { return Orientation(atan2(y, x) / apf::math::pi_div_180()); } float Position::length() const { return sqrt(apf::math::square(x) + apf::math::square(y)); } /** ._ * @param angle angle in degrees. * @return the resulting position **/ Position& Position::rotate(float angle) { // angle phi in radians! float phi = apf::math::deg2rad(this->orientation().azimuth + angle); float radius = this->length(); return *this = Position(radius * cos(phi), radius * sin(phi)); } // this is a 2D implementation! Position& Position::rotate(const Orientation& rotation) { return this->rotate(rotation.azimuth); } Position operator-(const Position& a, const Position& b) { Position temp(a); return temp -= b; } Position operator+(const Position& a, const Position& b) { Position temp(a); return temp += b; } Position operator-(const Position& a) { return Position(-a.x, -a.y); } /** _. * @param point * @param orientation * @return Angle in radians. **/ float angle(const Position& point, const Orientation& orientation) { return angle(point.orientation(), orientation); } std::ostream& operator<<(std::ostream& stream, const Position& position) { stream << "x = " << position.x << ", y = " << position.y; return stream; } // 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'\:'=' ssr-0.4.2/src/position.h000066400000000000000000000111211236416011200151200ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// %Position class and helper functions (definition). #ifndef SSR_POSITION_H #define SSR_POSITION_H #include "orientation.h" /** Geometric representation of a position. * Stores the position of a point in space and provides some helper functions. * If you want to speak in design patterns, you could call this a "Messenger" * patter. It's the most trivial of all patterns. So maybe it's not even worth * mentioning. But I did it anyway ... * @warning For now, it only uses 2 dimensions (x,y) but a z coordinate can be * added later, if needed. **/ struct Position { /** with no arguments, all member variables are initialized to zero. * @param x x coordinate (in meters) * @param y y coordinate (in meters) **/ explicit Position(const float x = 0, const float y = 0); float x; ///< x coordinate (in meters) float y; ///< y coordinate (in meters) /// length of the position vector float length() const; /// turn around the origin Position& rotate(float angle); Position& rotate(const Orientation& rotation); Orientation orientation() const; Position& operator+=(const Position& other); ///< += operator Position& operator-=(const Position& other); ///< -= operator bool operator==(const Position& other) const; ///< == operator bool operator!=(const Position& other) const; ///< != operator // Declaring the following operators as friend is not really necessary as // their fields are public anyway, but it doesn't hurt either. /// plus (+) operator friend Position operator+(const Position& a, const Position& b); /// minus (-) operator friend Position operator-(const Position& a, const Position& b); /// unary minus (-) operator friend Position operator-(const Position& a); /// output stream operator (<<) friend std::ostream& operator<<(std::ostream& stream, const Position& position); /** division (/) operator. * @param a dividend, a DirectionalPoint. * @param b divisor, any numeric Type.. * @return quotient. **/ template friend Position operator/(const Position& a, const T& b) { return Position(a.x / b, a.y / b); } }; /// Calculate the angle between the position vector of @a point and the /// orientation @a orientation. float angle(const Position& point, const Orientation& orientation); // TODO: declare angle() also as friend of Position? #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'\:'=' ssr-0.4.2/src/posixpathtools.h000066400000000000000000000357521236416011200163740ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Helper functions for UNIX-style filename and path manipulation. #ifndef SSR_POSIXPATHTOOLS_H #define SSR_POSIXPATHTOOLS_H #include #include #include // for istringstream #include // for ostream_iterator #include // for strcmp() #include // for O_RDONLY, dev_t, ino_t #include // for DIR, opendir() #include // for assert() #include // for stat and mkdir (on Mac OSX) #include // for chdir(), fchdir(), close() /** helper functions for filename and path manipulation. * * @warning Code in this file is highly platform dependent! * * @todo Maybe in the future other solutions should be considered, e.g. using * boost.filesystem or using a totally different language (like Python) for such * things. **/ namespace posixpathtools { /** Get current working directory. * @param[out] path the cwd * @return @b true on success * * This is stolen from * http://insanecoding.blogspot.com/2007/11/pathmax-simply-isnt.html * @author http://insanecoding.blogspot.com/ * * Contrary to the original, this version doesn't add a slash at the end. * * @warning This function may not work on all platforms! **/ inline bool getcwd(std::string& path) { using file_id = std::pair; bool success = false; // Keep track of start directory, so can jump back to it later auto start_fd = open(".", O_RDONLY); if (start_fd != -1) { struct stat sb; if (!fstat(start_fd, &sb)) { auto current_id = file_id(sb.st_dev, sb.st_ino); // Get info for root directory, so we can determine when we hit it if (!stat("/", &sb)) { auto path_components = std::list(); file_id root_id(sb.st_dev, sb.st_ino); // If they're equal, we've obtained enough info to build the path while (current_id != root_id) { bool pushed = false; if (!chdir("..")) //Keep recursing towards root each iteration { auto dir = opendir("."); if (dir) { dirent* entry; // We loop through each entry trying to find where we came from while ((entry = readdir(dir))) { if (strcmp(entry->d_name, ".") && strcmp(entry->d_name, "..") && !lstat(entry->d_name, &sb)) { auto child_id = file_id(sb.st_dev, sb.st_ino); // We found where we came from, add its name to the list if (child_id == current_id) { path_components.push_back(entry->d_name); pushed = true; break; } } } closedir(dir); // If we have a reason to contiue, we update the current dir id if (pushed && !stat(".", &sb)) { current_id = file_id(sb.st_dev, sb.st_ino); } } // Else, Uh oh, can't read information at this level } // If we didn't obtain any info this pass, no reason to continue if (!pushed) break; } if (current_id == root_id) // Unless they're equal, we failed above { path = ""; for (std::list::reverse_iterator i = path_components.rbegin(); i != path_components.rend(); ++i) { path += "/" + *i; } if (path == "") path = "/"; success = true; } fchdir(start_fd); } } close(start_fd); } return success; } /** Turn a path into a sequence of path components. * If @p path is absolute (i.e. it starts with a slash), the first element of * @p tokens will be an empty string. * If @p path has a trailing slash, the last element of @p tokens will be an * empty string. A trailing slash can be used to distinguish between files and * directories. **/ template void tokenize(const std::string& path, T& tokens) { // add another slash to get an empty string if there is a trailing slash std::istringstream ss(path + "/"); std::string s; while (std::getline(ss, s, '/')) tokens.push_back(s); } /** Turn a sequence of path components into a path. * @param tokens a list of path components. * @return The string containing the path. If @p tokens is an empty list or if * @p tokens consists of one empty string (and nothing else), "." is returned. * @see tokenize() **/ template std::string untokenize(const T& tokens) { std::ostringstream oss; std::copy(tokens.begin(), tokens.end() , std::ostream_iterator(oss, "/")); // this always adds a trailing slash, which we don't want auto result = oss.str(); if (!result.empty()) { // remove trailing slash result.erase(result.end()-1); } // if it's empty now, this means we are dealing with the current directory if (result.empty()) { result = "."; } return result; } /** Remove "." and ".." from a list of path components. * @note If there are ".."s at the beginning of a relative path, they are not * removed. **/ template void clean_path(T& tokens) { if (tokens.empty()) return; // nothing to do. // check if path is absolute, remove the first empty element temporarily bool absolute = false; if (!tokens.empty() && tokens.front() == "") { absolute = true; tokens.pop_front(); } // check for trailing slash, remove the last empty element temporarily bool directory = false; if (!tokens.empty() && tokens.back() == "") { directory = true; tokens.pop_back(); } auto it = tokens.rbegin(); int levels = 0; while (it != tokens.rend()) { // erasing reverse iterators is tricky, // see http://www.drdobbs.com/cpp/184401406 auto fwd_it = it.base(); --fwd_it; if (*it == ".") { tokens.erase(fwd_it); } else if (*it == "..") { ++levels; tokens.erase(fwd_it); } else if (*it == "") // this should normally not happen, but who knows? { tokens.erase(fwd_it); } else if (levels > 0) { tokens.erase(fwd_it); --levels; } else { // do nothing, except ++it; } } // if there are still "levels" left, we have to prepend ".." for each: while (levels > 0) { tokens.push_front(".."); --levels; } // re-attach empty element for absolute path if (absolute) tokens.push_front(""); // if it is still empty now, it can only be the current directory: if (tokens.empty()) tokens.push_back("."); // re-attach empty element for trailing slash if (directory) tokens.push_back(""); // root directory if (tokens.size() == 1 && tokens.front() == "") tokens.push_back(""); } /** Remove the last item of a list of path components. * This is normally a file name or an empty string (in case of a trailing * slash in the original path). If the last element in the list is ".." or * ".", it is not removed. **/ template void remove_last_component(T& tokens) { if (!tokens.empty() && tokens.back() != ".." && tokens.back() != ".") { tokens.pop_back(); } } /** Turn a list of path components into an absolute path. * @param[in,out] path A list of path components. If it's relative, the * current working directory is prepended. **/ template void make_absolute(T& path) { // if path is relative, prepend cwd if (!path.empty() && path.front() != "") { // get current working directory std::string cwd; if (!getcwd(cwd)) { // this should never happen. assert(false); path = T(); return; } T cwd_tokens; tokenize(cwd, cwd_tokens); path.splice(path.begin(), cwd_tokens); } } /** Make one list of path components relative to another. * @param[in,out] path path to be made relative to @p directory. * @param directory base directory. If empty, the current directory is used. * @note Initially, both @p path and @p directory can be relative or absolute. **/ template void make_relative(T& path, const T& directory = T()) { T dir(directory); if (dir.empty()) dir.push_back("."); // ironically, first we make both paths absolute: make_absolute(path); make_absolute(dir); // remove "." and "..": clean_path(path); clean_path(dir); // remove common parent directories while (!path.empty() && !dir.empty() && path.front() == dir.front()) { path.pop_front(); dir .pop_front(); } while (!dir.empty()) { if (dir.back() != "") { path.push_front(".."); } // TODO: what if dir.back() == ".." (or ".")? dir.pop_back(); } // if path consists of a single empty string // => it's the current directory with trailing slash: if (path.size() == 1 && path.front() == "") { path.push_front("."); } } /** Remove common directory part and prepend "../" if necessary. * If @p path is in the directory of @p filename or below, remove directory * from @p path. For each further sub-directories of @p filename, prepend * "../" to @p path. If they have no common directories, prepend an * appropriate number of parent directories and "../"s. * @param path A path given relative to the current directory. * If @p path is absolute or empty, it is not changed. If @p path is * relative, the result is also relative. If @p path has a trailing slash, * the result will also have a trailing slash. * @param filename This can also be a directory, but then it has to end with a * trailing slash. * If @p filename doesn't have a directory part, @p path is not changed. * @warning This function expects UNIX-style paths! **/ inline std::string make_path_relative_to_file(const std::string& path , const std::string& filename) { if (path != "" && path[0] != '/') { // path components will be stored in lists p1 and p2: std::list p1, p2; tokenize(path, p1); tokenize(filename, p2); remove_last_component(p2); // if "filename" didn't have any path component, p2 is now empty and // "path" could be returned unchanged. However, we will clean it first. // Same thing if "filename" was ".". if (p2.empty() || (p2.size() == 1 && p2.front() == ".")) { clean_path(p1); return untokenize(p1); } make_relative(p1, p2); return untokenize(p1); } return path; // path is absolute or empty; return it unchanged } /** Add directory part of one path to another path. * @param path A path given relative to @p filename. * If @p path is absolute or empty, it is not changed. If @p path has a * trailing slash, the result will also have a trailing slash. * If @p path is relative, the result is also relative. * @param filename The directory part of @p filename is prepended to @p path. * @return @p path prepended with the directory name of @p filename. * @warning This function expects UNIX-style paths! **/ inline std::string make_path_relative_to_current_dir(const std::string& path , const std::string& filename) { if (path != "" && path[0] != '/') { auto result = std::list(); tokenize(filename, result); remove_last_component(result); // add the components of "path" to the end of the list tokenize(path, result); make_relative(result); return untokenize(result); } return path; // path is absolute or empty; return it unchanged } /** Get the extension of a file name. * If an extension contains a dot (e.g. tar.gz), only the last part is * returned (e.g. gz). If you want more, you have to run the function several * times. * @param filename the filename * @return the extension, excluding the dot; empty string if there is no * extension * @warning This function expects UNIX-style paths! **/ inline std::string get_file_extension(const std::string& filename) { auto last_slash = filename.rfind('/'); auto last_dot = filename.rfind('.'); if (// if there is a dot and it is neither the first nor the last character last_dot > 0 && last_dot < filename.length() - 1 // and if there is either no slash or it is before the last dot and at // least one other character is inbetween them && (last_slash == std::string::npos || (last_dot > last_slash && last_dot - last_slash > 1))) { return filename.substr(last_dot + 1); } return ""; } /** Insert escape characters (\) before white spaces. * @param filename the file name * @return the file name with escaped white spaces **/ inline std::string get_escaped_filename(const std::string& filename) { std::string escaped_filename; for (const auto ch: filename) { if (isspace(ch)) { escaped_filename.append(1, '\\'); } escaped_filename.append(1, ch); } return escaped_filename; } } // namespace posixpathtools #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'\:'=' ssr-0.4.2/src/publisher.h000066400000000000000000000163531236416011200152650ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Abstract %Publisher (definition). // TODO: use a less general name? #ifndef SSR_PUBLISHER_H #define SSR_PUBLISHER_H #include // for uint32_t #include "source.h" #include "ssr_global.h" using jack_nframes_t = uint32_t; // from namespace ssr { struct Subscriber; /** Abstract interface to controller class (and similar classes). * All member functions are public, everyone can call them. * @attention For now, none of the member functions has a return value. That is * because if we are connected via a slow IP interface we do not want to wait * for transferring back the return value. * \par * Anyhow, maybe this is not a good decision. Maybe there should be return * values? **/ struct Publisher { virtual ~Publisher() {} ///< dtor. does nothing /// load scene from XML file. /// @param scene_file_name name of XML file to load. virtual bool load_scene(const std::string& scene_file_name) = 0; virtual bool save_scene_as_XML(const std::string& filename) const = 0; virtual void start_processing() = 0; ///< start processing virtual void stop_processing() = 0; ///< stop processing /// create new source. /// @param name name of the new source /// @param model model (=type) of the new source /// @param file_or_port_name name of audio file or JACK port. If a JACK port /// is given, @a channel has to be zero! /// @param channel number of channel in audio file. If zero, a JACK port is /// given instead of an audio file. /// @param position initial position of the new source. /// @param orientation initial orientation of the new source. /// @param gain initial gain of the new source. virtual void new_source(const std::string& name, Source::model_t model, const std::string& file_or_port_name, int channel = 0, const Position& position = Position(), const bool pos_fix = false, const Orientation& orientation = Orientation(), const bool or_fix = false, const float gain = 1.0f, const bool mute_state = false, const std::string& properties_file = "") = 0; /// delete a source /// @param[in] id id of the source to be deleted virtual void delete_source(id_t id) = 0; /// delete all sources virtual void delete_all_sources() = 0; /// set position of a source /// @param id source id. /// @param position source position. /// @todo include some sort of identification who sent the request. virtual void set_source_position(id_t id, const Position& position) = 0; /// set orientation of a source virtual void set_source_orientation(id_t id, const Orientation& orientation) = 0; /// set gain (=volume) of a source virtual void set_source_gain(id_t id, float gain) = 0; /// mute/unmute source virtual void set_source_mute(id_t id, bool mute) = 0; /// instantaneous level of audio stream virtual void set_source_signal_level(const id_t id, const float level) = 0; /// set name of a source virtual void set_source_name(id_t id, const std::string& name) = 0; virtual void set_source_properties_file(id_t id, const std::string& name) = 0; /// set model (=type) of a source virtual void set_source_model(id_t id, Source::model_t model) = 0; /// set JACK port of a source virtual void set_source_port_name(id_t id, const std::string& port_name) = 0; virtual void set_source_position_fixed(id_t id, const bool fix) = 0; /// set position of the reference /// @param position well, the position virtual void set_reference_position(const Position& position) = 0; /// set orientation of the reference virtual void set_reference_orientation(const Orientation& orientation) = 0; virtual void set_reference_offset_position(const Position& position) = 0; virtual void set_reference_offset_orientation(const Orientation& orientation) = 0; /// set master volume of the whole scene virtual void set_master_volume(float volume) = 0; /// set amplitude reference distance virtual void set_amplitude_reference_distance(float distance) = 0; /// set instantaneous overall audio level (linear scale) virtual void set_master_signal_level(float level) = 0; /// set CPU load in percent as estimated by JACK virtual void set_cpu_load(const float load) = 0; /// sets the sample rate in all subscribers virtual void publish_sample_rate(const int sample_rate) = 0; /// returns what type the current renderer actually is virtual std::string get_renderer_name() const = 0; /// show head in GUI? virtual bool show_head() const = 0; virtual void transport_start() = 0; ///< start JACK transport virtual void transport_stop() = 0; ///< stop JACK transport /// set JACK transport location virtual bool transport_locate(float time_in_sec) = 0; /// This is temporarily used to calibrate the tracker virtual void calibrate_client() = 0; virtual void set_processing_state(bool state) = 0; virtual void subscribe(Subscriber* subscriber) = 0; virtual void unsubscribe(Subscriber* subscriber) = 0; virtual std::string get_scene_as_XML() const = 0; }; } // namespace ssr #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'\:'=' ssr-0.4.2/src/razor-ahrs/000077500000000000000000000000001236416011200151775ustar00rootroot00000000000000ssr-0.4.2/src/razor-ahrs/Example.cpp000066400000000000000000000063071236416011200173040ustar00rootroot00000000000000/*************************************************************************************************** * Test Program: Mac OSX / Unix / Linux C++ Interface for Razor AHRS v1.4.0 * 9 Degree of Measurement Attitude and Heading Reference System * for Sparkfun "9DOF Razor IMU" and "9DOF Sensor Stick" * * Released under GNU GPL (General Public License) v3.0 * Copyright (C) 2011, 2012 Quality & Usability Lab, Deutsche Telekom Laboratories, TU Berlin * Written by Peter Bartz (peter-bartz@gmx.de) * * Infos, updates, bug reports and feedback: * http://dev.qu.tu-berlin.de/projects/sf-razor-9dof-ahrs ***************************************************************************************************/ #include // cout() #include // runtime_error #include // getchar() #include "RazorAHRS.h" using namespace std; // Set your serial port here! //const string serial_port_name = "/dev/tty.FireFly-6162-SPP"; const string serial_port_name = "/dev/tty.usbserial-A700eEhN"; //const string serial_port_name = "/dev/ttyUSB0"; // a good guess on linux // Razor error callback handler // Will be called from (and in) Razor background thread! void on_error(const string &msg) { cout << " " << "ERROR: " << msg << endl; // NOTE: make a copy of the message if you want to save it or send it to another thread. do not // save or pass the reference itself, it will not be valid after this function returns! } // Razor data callback handler // Will be called from (and in) Razor background thread! void on_data(const float ypr[]) { cout << " " << "Yaw = " << ypr[0] << ", Pitch = " << ypr[1] << ", Roll = " << ypr[2] << endl; // NOTE: make a copy of the yaw/pitch/roll data if you want to save it or send it to another // thread. do not save or pass the pointer itself, it will not be valid after this function // returns! } RazorAHRS *razor; int main() { cout << endl; cout << " " << "Razor AHRS C++ test" << endl; cout << " " << "Press RETURN to connect to tracker. When you're done press RETURN again to quit." << endl; getchar(); // wait RETURN cout << " " << "Connecting..." << endl << endl; try { // Create Razor AHRS object. Serial I/O will run in background thread and report // errors and data updates using the callbacks on_data() and on_error(). razor = new RazorAHRS(serial_port_name, on_data, on_error); // NOTE: If these callback functions were members of a class and not global // functions, you would have to bind them before passing. Like this: // class Callback // { // public: // void on_data(const float ypr[]) { } // void on_error(const string &msg) { } // }; // Callback c; // razor = new RazorAHRS(serial_port_name, // bind(&Callback::on_data, &c, placeholders::_1), // bind(&Callback::on_error, &c, placeholders::_1)); // If you're calling from inside of "c" you would of course use "this" instead of "&c". } catch(runtime_error &e) { cout << " " << (string("Could not create tracker: ") + string(e.what())) << endl; cout << " " << "Did you set your serial port in Example.cpp?" << endl; return 0; } getchar(); // wait RETURN return 0; } ssr-0.4.2/src/razor-ahrs/GPL.txt000066400000000000000000001045131236416011200163660ustar00rootroot00000000000000 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 . ssr-0.4.2/src/razor-ahrs/README.txt000066400000000000000000000011571236416011200167010ustar00rootroot00000000000000Mac OSX / Unix / Linux C++ Interface for Razor AHRS v1.4.0 Released under GNU GPL (General Public License) v3.0 Copyright (C) 2011, 2012 Quality & Usability Lab, Deutsche Telekom Laboratories, TU Berlin Infos, updates, bug reports and feedback: http://dev.qu.tu-berlin.de/projects/sf-razor-9dof-ahrs You can find a tutorial on the tracker itself at: http://dev.qu.tu-berlin.de/projects/sf-razor-9dof-ahrs/wiki/Tutorial Compile test program: g++ Example.cpp RazorAHRS.cpp -Wall -D_REENTRANT -lpthread -o example And run it: ./example Sorry, no support for Windows. But you could try to compile using cygwin. ssr-0.4.2/src/razor-ahrs/RazorAHRS.cpp000066400000000000000000000216311236416011200174610ustar00rootroot00000000000000/****************************************************************************************** * Mac OSX / Unix / Linux C++ Interface for Razor AHRS v1.4.1 * 9 Degree of Measurement Attitude and Heading Reference System * for Sparkfun "9DOF Razor IMU" and "9DOF Sensor Stick" * * Released under GNU GPL (General Public License) v3.0 * Copyright (C) 2013 Peter Bartz * Copyright (C) 2011-2012 Quality & Usability Lab, Deutsche Telekom Laboratories, TU Berlin * Written by Peter Bartz (peter-bartz@gmx.de) * * Infos, updates, bug reports and feedback: * https://github.com/ptrbrtz/razor-9dof-ahrs ******************************************************************************************/ #include "RazorAHRS.h" #include RazorAHRS::RazorAHRS(const std::string &port, DataCallbackFunc data_func, ErrorCallbackFunc error_func, Mode mode, int connect_timeout_ms, speed_t speed) : _mode(mode) , _input_pos(0) , _connect_timeout_ms(connect_timeout_ms) , data(data_func) , error(error_func) , _thread_id(0) , _stop_thread(false) { // check data type sizes assert(sizeof(char) == 1); assert(sizeof(float) == 4); // open serial port if (port == "") throw std::runtime_error("No port specified!"); if (!_open_serial_port(port.c_str())) throw std::runtime_error("Could not open serial port!"); // get port attributes struct termios tio; if (int errorID = tcgetattr(_serial_port, &tio)) throw std::runtime_error("Could not get serial port attributes! Error # " + to_str(errorID)); /* see http://www.easysw.com/~mike/serial/serial.html */ /* and also http://linux.die.net/man/3/tcsetattr */ // basic raw/non-canonical setup cfmakeraw(&tio); // enable reading and ignore control lines tio.c_cflag |= CREAD | CLOCAL; // set 8N1 tio.c_cflag &= ~PARENB; // no parity bit tio.c_cflag &= ~CSTOPB; // only one stop bit tio.c_cflag &= ~CSIZE; // clear data bit number tio.c_cflag |= CS8; // set 8 data bits // no hardware flow control tio.c_cflag &= ~CRTSCTS; // no software flow control tio.c_iflag &= ~(IXON | IXOFF | IXANY); // poll() is broken on OSX, so we set VTIME and use read(), which is ok since // we're reading only one port anyway tio.c_cc[VMIN] = 0; tio.c_cc[VTIME] = 10; // 10 * 100ms = 1s // set port speed if (int errorID = cfsetispeed(&tio, speed)) throw std::runtime_error(" " + to_str(errorID) + ": Could not set new serial port input speed to " + to_str(speed) + "."); if (int errorID = cfsetospeed(&tio, speed)) throw std::runtime_error(" " + to_str(errorID) + ": Could not set new serial port output speed to " + to_str(speed) + "."); // set port attributes // must be done after setting speed! if (int errorID = tcsetattr(_serial_port, TCSANOW, &tio)) { throw std::runtime_error(" " + to_str(errorID) + ": Could not set new serial port attributes."); } // start input/output thread _start_io_thread(); } RazorAHRS::~RazorAHRS() { // if thread was started, stop thread if (_thread_id) _stop_io_thread(); close(_serial_port); } bool RazorAHRS::_read_token(const std::string &token, char c) { if (c == token[_input_pos++]) { if (_input_pos == token.length()) { // synch token found _input_pos = 0; return true; } } else { _input_pos = 0; } return false; } bool RazorAHRS::_init_razor() { char in; int result; struct timeval t0, t1, t2; const std::string synch_token = "#SYNCH"; const std::string new_line = "\r\n"; // start time gettimeofday(&t0, nullptr); // request synch token to see if Razor is really present const std::string contact_synch_id = "00"; const std::string contact_synch_request = "#s" + contact_synch_id; const std::string contact_synch_reply = synch_token + contact_synch_id + new_line; write(_serial_port, contact_synch_request.data(), contact_synch_request.length()); gettimeofday(&t1, nullptr); // set non-blocking I/O if (!_set_nonblocking_io()) return false; /* look for tracker */ while (true) { // try to read one byte from the port result = read(_serial_port, &in, 1); // one byte read if (result > 0) { if (_read_token(contact_synch_reply, in)) break; } // no data available else if (result == 0) usleep(1000); // sleep 1ms // error? else { if (errno != EAGAIN && errno != EINTR) throw std::runtime_error("Can not read from serial port (1)."); } // check timeout gettimeofday(&t2, nullptr); if (elapsed_ms(t1, t2) > 200) { // 200ms elapsed since last request and no answer -> request synch again // (this happens when DTR is connected and Razor resets on connect) write(_serial_port, contact_synch_request.data(), contact_synch_request.length()); t1 = t2; } if (elapsed_ms(t0, t2) > _connect_timeout_ms) // timeout -> tracker not present throw std::runtime_error("Can not init: tracker does not answer."); } /* configure tracker */ // set correct binary output mode, enable continuous streaming, disable errors and // request synch token. So we're good, no matter what state the tracker // currently is in. const std::string config_synch_id = "01"; const std::string config_synch_reply = synch_token + config_synch_id + new_line; std::string config = "#o1#oe0#s" + config_synch_id; if (_mode == YAW_PITCH_ROLL) config = "#ob" + config; else if (_mode == ACC_MAG_GYR_RAW) config = "#osrb" + config; else if (_mode == ACC_MAG_GYR_CALIBRATED) config = "#oscb" + config; else throw std::runtime_error("Can not init: unknown 'mode' parameter."); write(_serial_port, config.data(), config.length()); // set blocking I/O // (actually semi-blocking, because VTIME is set) if (!_set_blocking_io()) return false; while (true) { // try to read one byte from the port result = read(_serial_port, &in, 1); // one byte read if (result > 0) { if (_read_token(config_synch_reply, in)) break; // alrighty } // error? else { if (errno != EAGAIN && errno != EINTR) throw std::runtime_error("Can not read from serial port (2)."); } } // we keep using blocking I/O //if (_set_blocking_io() == -1) // return false; return true; } bool RazorAHRS::_open_serial_port(const char *port) { // O_NDELAY allows open even with no carrier detect (e.g. needed for Razor) if ((_serial_port = open(port, O_RDWR | O_NOCTTY | O_NDELAY)) != -1) { // make I/O blocking again if (_set_blocking_io()) return true; } // something didn't work close(_serial_port); return false; } bool RazorAHRS::_set_blocking_io() { int flags; // clear O_NDELAY to make I/O blocking again // in fact this is semi-blocking, since we set VTIME on the port if (((flags = fcntl(_serial_port, F_GETFL, 0)) != -1) && (fcntl(_serial_port, F_SETFL, flags & ~O_NDELAY)) != -1) { return true; } return false; } bool RazorAHRS::_set_nonblocking_io() { int flags; // set O_NDELAY to make I/O non-blocking if (((flags = fcntl(_serial_port, F_GETFL, 0)) != -1) && (fcntl(_serial_port, F_SETFL, flags | O_NDELAY)) != -1) { return true; } return false; } bool RazorAHRS::_is_io_blocking() { return (fcntl(_serial_port, F_GETFL, 0) & O_NDELAY); } void* RazorAHRS::_thread(void *arg) { char c; int result; try { if (!_init_razor()) { error("Tracker init failed."); return arg; } } catch(std::runtime_error& e) { error("Tracker init failed: " + std::string(e.what())); return arg; } while (!_stop_thread) { if ((result = read(_serial_port, &c, 1)) > 0) // blocks only for VTIME before returning { // read binary stream // (type-punning: aliasing with char* is ok) (reinterpret_cast (&_input_buf))[_input_pos++] = c; if (_mode == YAW_PITCH_ROLL) { // 3 floats if (_input_pos == 12) // we received a full frame { // convert endianess if necessary if (_big_endian()) { _swap_endianess(_input_buf.ypr, 3); } // invoke callback data(_input_buf.ypr); _input_pos = 0; } } else { // raw or calibrated sensor data (9 floats) if (_input_pos == 36) // we received a full frame { // convert endianess if necessary if (_big_endian()) { _swap_endianess(_input_buf.amg, 9); } // invoke callback data(_input_buf.amg); _input_pos = 0; } } } // error? else if (result < 0) { if (errno != EAGAIN && errno != EINTR) { error("Can not read from serial port (3)."); return arg; } } // else if result is 0, no data was available } return arg; } ssr-0.4.2/src/razor-ahrs/RazorAHRS.h000066400000000000000000000103031236416011200171200ustar00rootroot00000000000000/****************************************************************************************** * Mac OSX / Unix / Linux C++ Interface for Razor AHRS v1.4.1 * 9 Degree of Measurement Attitude and Heading Reference System * for Sparkfun "9DOF Razor IMU" and "9DOF Sensor Stick" * * Released under GNU GPL (General Public License) v3.0 * Copyright (C) 2013 Peter Bartz * Copyright (C) 2011-2012 Quality & Usability Lab, Deutsche Telekom Laboratories, TU Berlin * Written by Peter Bartz (peter-bartz@gmx.de) * * Infos, updates, bug reports and feedback: * https://github.com/ptrbrtz/razor-9dof-ahrs ******************************************************************************************/ #ifndef RAZORAHRS_H #define RAZORAHRS_H #include #include #include #include #include #include // for write(), close(), ... #include // for cfsetispeed(), ... #include // for open(), ... #include #include #ifndef _REENTRANT #error You need to compile with _REENTRANT defined since this uses threads! #endif // Razor AHRS tracker class RazorAHRS { public: enum Mode { YAW_PITCH_ROLL, ACC_MAG_GYR_RAW, ACC_MAG_GYR_CALIBRATED }; typedef std::function DataCallbackFunc; typedef std::function ErrorCallbackFunc; RazorAHRS(const std::string &port, DataCallbackFunc data_func, ErrorCallbackFunc error_func, Mode mode, int connect_timeout_ms = 5000, speed_t speed = B57600); ~RazorAHRS(); private: Mode _mode; // serial port helpers bool _open_serial_port(const char *port); bool _set_blocking_io(); bool _set_nonblocking_io(); bool _is_io_blocking(); bool _read_token(const std::string &token, char c); bool _init_razor(); // timing long elapsed_ms(struct timeval start, struct timeval end) { return static_cast ((end.tv_sec - start.tv_sec) * 1000 + (end.tv_usec - start.tv_usec) / 1000); } // input buffer union { float ypr[3]; // yaw, pitch, roll float amg[9]; // 3 axes of accelerometer, magnetometer and gyroscope } _input_buf; size_t _input_pos; int _connect_timeout_ms; int _serial_port; // callbacks DataCallbackFunc data; ErrorCallbackFunc error; /* threading stuff */ pthread_t _thread_id; void* _thread(void*); // thread main function volatile bool _stop_thread; // thred stop flag // start the tracking thread void _start_io_thread() { // create thread pthread_create(&_thread_id , nullptr, _thread_starter, this); } // stop the tracking thread void _stop_io_thread() { void *thread_exit_status; // dummy _stop_thread = true; pthread_join(_thread_id , &thread_exit_status); } static void* _thread_starter(void *arg) { return reinterpret_cast (arg)->_thread(nullptr); } std::string to_str(int i) { std::stringstream ss; ss << i; return ss.str(); } bool _big_endian() { const int num = 1; return (*(reinterpret_cast (&num))) != 1; } // swap endianess of int void _swap_endianess(int &i) { i = (i >> 24) | ((i << 8) & 0x00FF0000) | ((i >> 8) & 0x0000FF00) | (i << 24); } // swap endianess of float void _swap_endianess(float &f) { float swapped; char *f_as_char = reinterpret_cast (&f); char *swapped_as_char = reinterpret_cast (&swapped); // swap the bytes into a temporary buffer swapped_as_char[0] = f_as_char[3]; swapped_as_char[1] = f_as_char[2]; swapped_as_char[2] = f_as_char[1]; swapped_as_char[3] = f_as_char[0]; f = swapped; } // swap endianess of int array void _swap_endianess(int arr[], int arr_length) { for (int i = 0; i < arr_length; i++) _swap_endianess(arr[i]); } // swap endianess of float array void _swap_endianess(float arr[], int arr_length) { for (int i = 0; i < arr_length; i++) _swap_endianess(arr[i]); } }; #endif // RAZORAHRS_H ssr-0.4.2/src/rendererbase.h000066400000000000000000000412311236416011200157220ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Renderer base class. #ifndef SSR_RENDERERBASE_H #define SSR_RENDERERBASE_H #include #include "apf/mimoprocessor.h" #include "apf/shareddata.h" #include "apf/container.h" // for distribute_list() #include "apf/parameter_map.h" #include "apf/math.h" // for dB2linear() // TODO: avoid multiple ambiguous "Source" classes #include "source.h" // for ::Source::model_t #include "maptools.h" #ifndef SSR_QUERY_POLICY #define SSR_QUERY_POLICY apf::disable_queries #endif namespace ssr { /** Renderer base class. * @todo more documentation! * * The parallel rendering engine uses the non-blocking datastructure RtList to * communicate between realtime and non-realtime threads. * All non-realtime accesses to RtList%s have to be locked with * get_scoped_lock() to ensure single-reader/single-writer operation. **/ template class RendererBase : public apf::MimoProcessor { private: using _base = apf::MimoProcessor; public: using typename _base::rtlist_t; using typename _base::ScopedLock; using typename _base::sample_type; using _base::_fifo; class Source; // TODO: try to remove this: using SourceBase = Source; class Output; #ifdef SSR_SHARED_IO_BUFFERS // Copying the input buffers is only needed if the backend re-uses its input // buffers as output buffers (e.g. Puredata externals) and if the renderer // doesn't copy the buffers anyway at some point (as most renderers do). class Input : public _base::Input { public: using iterator = typename std::vector::const_iterator; using typename _base::Input::Params; explicit Input(const Params& p) : _base::Input(p) , _buffer(this->parent.block_size()) {} APF_PROCESS(Input, _base::Input) { std::copy(this->buffer.begin(), this->buffer.end(), _buffer.begin()); } iterator begin() const { return _buffer.begin(); } iterator end() const { return _buffer.end(); } private: std::vector _buffer; }; #else using Input = typename _base::DefaultInput; #endif struct State { State(apf::CommandQueue& fifo, const apf::parameter_map& params) : reference_position(fifo) , reference_orientation(fifo, Orientation(90)) , reference_offset_position(fifo) , reference_offset_orientation(fifo) , master_volume(fifo, 1) , processing(fifo, true) , amplitude_reference_distance(fifo , params.get("amplitude_reference_distance", 3)) {} apf::SharedData reference_position; apf::SharedData reference_orientation; apf::SharedData reference_offset_position; apf::SharedData reference_offset_orientation; apf::SharedData master_volume; apf::SharedData processing; apf::SharedData amplitude_reference_distance; } state; // If you don't need a list proxy, just use a reference to the list template class AddToSublistCommand : public apf::CommandQueue::Command { public: AddToSublistCommand(L input, ListProxy output, DataMember member) : _input(input) , _output(output) , _member(member) {} virtual void execute() { apf::distribute_list(_input, _output, _member); } // Empty function, because no cleanup is necessary. virtual void cleanup() {} private: L _input; ListProxy _output; DataMember _member; }; template void add_to_sublist(const L& input, ListProxy output, DataMember member) { _fifo.push(new AddToSublistCommand( input, output, member)); } template class RemFromSublistCommand : public apf::CommandQueue::Command { public: RemFromSublistCommand(const L input, ListProxy output , DataMember member) : _input(input) , _output(output) , _member(member) {} virtual void execute() { apf::undistribute_list(_input, _output, _member, _garbage); } virtual void cleanup() { // Nothing to be done. _garbage is taken care of in the destructor. } private: const L _input; ListProxy _output; DataMember _member; typename std::remove_const::type _garbage; }; template void rem_from_sublist(const L& input, ListProxy output, DataMember member) { _fifo.push(new RemFromSublistCommand( input, output, member)); } int add_source(const apf::parameter_map& p = apf::parameter_map()); void rem_source(int id); void rem_all_sources(); Source* get_source(int id); // May only be used in realtime thread! const rtlist_t& get_source_list() const { return _source_list; } bool show_head() const { return _show_head; } // TODO: proper solution for getting the reproduction setup template void get_loudspeakers(SomeListType&) {} std::unique_ptr get_scoped_lock() { // TODO: in C++14, use make_unique() return std::unique_ptr(new ScopedLock(_lock)); } const sample_type master_volume_correction; // linear protected: RendererBase(const apf::parameter_map& p); // TODO: make private? sample_type _master_level; rtlist_t _source_list; // TODO: find a better solution to get loudspeaker vs. headphone renderer bool _show_head; private: apf::parameter_map _add_params(const apf::parameter_map& params) { auto temp = params; temp.set("name", params.get("name", Derived::name())); return temp; } int _get_new_id(); std::map _source_map; int _highest_id; typename _base::Lock _lock; }; /** Constructor. * @param p Parameters for RendererBase and MimoProcessor **/ template RendererBase::RendererBase(const apf::parameter_map& p) : _base(_add_params(p)) , state(_fifo, p) , master_volume_correction(apf::math::dB2linear( this->params.get("master_volume_correction", 0.0))) , _master_level() , _source_list(_fifo) , _show_head(true) , _highest_id(0) {} /** Create a new source. * @return ID of new source * @throw unknown whatever the Derived::Source constructor throws **/ template int RendererBase::add_source(const apf::parameter_map& p) { int id = _get_new_id(); typename Derived::Input::Params in_params; in_params = p; in_params.set("id", in_params.get("id", id)); auto in = this->add(in_params); // WARNING: if Derived::Input throws an exception, the SSR crashes! typename Derived::Source::Params src_params; src_params = p; src_params.parent = &this->derived(); src_params.fifo = &_fifo; src_params.input = in; // For now, Input ID and Source ID are the same: src_params.id = id; typename Derived::Source* src; try { src = _source_list.add(new typename Derived::Source(src_params)); } catch (...) { // TODO: really remove the corresponding Input? this->rem(in); throw; } // This cannot be done in the Derived::Source constructor because then the // connections to the Outputs are active before the Source is properly added // to the source list: src->connect(); _source_map[id] = src; return id; // TODO: what happens on failure? can there be failure? } template void RendererBase::rem_source(int id) { auto delinquent = _source_map.find(id); if (delinquent == _source_map.end()) { // TODO: warning? return; } auto* source = delinquent->second; _source_map.erase(delinquent); assert(source); source->derived().disconnect(); auto input = const_cast(&source->_input); _source_list.rem(source); // TODO: really remove the corresponding Input? // ATTENTION: there may be several sources using the input! (or not?) this->rem(input); } template void RendererBase::rem_all_sources() { while (!_source_map.empty()) { this->rem_source(_source_map.begin()->first); } _highest_id = 0; } template typename RendererBase::Source* RendererBase::get_source(int id) { return maptools::get_item(_source_map, id); } template int RendererBase::_get_new_id() { return ++_highest_id; } /// A sound source. template class RendererBase::Source : public _base::template ProcessItem , public apf::has_begin_and_end { private: using SourceBase = typename _base::template ProcessItem; public: using sample_type = typename std::iterator_traits::value_type; friend class RendererBase; // rem_source() needs access to _input struct Params : apf::parameter_map { Derived* parent = nullptr; const typename Derived::Input* input = nullptr; apf::CommandQueue* fifo = nullptr; int id = 0; using apf::parameter_map::operator=; }; explicit Source(const Params& p) : parent(*(p.parent ? p.parent : throw std::logic_error( "Bug (RendererBase::Source): parent == NULL!"))) , position(*(p.fifo ? p.fifo : throw std::logic_error( "Bug (RendererBase::Source): fifo == NULL!"))) , orientation(*p.fifo) , gain(*p.fifo, sample_type(1.0)) , mute(*p.fifo, false) , model(*p.fifo, ::Source::point) , weighting_factor() , id(p.id) , _input(*(p.input ? p.input : throw std::logic_error( "Bug (RendererBase::Source): input == NULL!"))) , _pre_fader_level() , _level() {} APF_PROCESS(Source, SourceBase) { this->_process(); } sample_type get_level() const { return _level; } // In the default case, the output level are ignored bool get_output_levels(sample_type*, sample_type*) const { return false; } void connect() {} void disconnect() {} Derived& parent; apf::SharedData position; apf::SharedData orientation; apf::SharedData gain; apf::SharedData mute; apf::SharedData< ::Source::model_t> model; apf::BlockParameter weighting_factor; const int id; protected: const typename Derived::Input& _input; private: void _process(); void _level_helper(apf::enable_queries&) { _pre_fader_level = apf::math::max_amplitude(_input.begin(), _input.end()); _level = _pre_fader_level * this->weighting_factor; } void _level_helper(apf::disable_queries&) {} sample_type _pre_fader_level; sample_type _level; }; template void RendererBase::Source::_process() { this->_begin = _input.begin(); this->_end = _input.end(); if (!_input.parent.state.processing || this->mute) { this->weighting_factor = 0.0; } else { this->weighting_factor = this->gain; // If the renderer does something nonlinear, the master volume should // be applied to the output signal ... TODO: shall we care? this->weighting_factor *= _input.parent.state.master_volume; this->weighting_factor *= _input.parent.master_volume_correction; } _level_helper(_input.parent); assert(this->weighting_factor.exactly_one_assignment()); } template class RendererBase::Output : public _base::Output { public: Output(const typename _base::Output::Params& p) : _base::Output(p) , _level() {} struct Process : _base::Output::Process { explicit Process(Output& o) : _base::Output::Process(o) , _out(o) {} ~Process() { _out._level_helper(_out.parent); } private: Output& _out; }; sample_type get_level() const { return _level; } protected: void _level_helper(apf::enable_queries&) { _level = apf::math::max_amplitude(this->buffer.begin() , this->buffer.end()); } void _level_helper(apf::disable_queries&) {} private: sample_type _level; }; // This is a kind of C++ mixin class, but it also includes the CRTP template class Base> struct SourceToOutput : Base { using typename Base::Input; struct Source : Base::Source { using typename Base::Source::Params; using sourcechannels_t = apf::fixed_vector; template Source(const Params& p, Args&&... args) : Base::Source(p) , sourcechannels(std::forward(args)...) {} void connect() { auto temp = std::list(); apf::append_pointers(this->sourcechannels, temp); this->parent.add_to_sublist(temp, apf::make_cast_proxy( const_cast(this->parent.get_output_list())) , &Output::sourcechannels); } void disconnect() { auto temp = std::list(); apf::append_pointers(this->sourcechannels, temp); this->parent.rem_from_sublist(temp, apf::make_cast_proxy( const_cast(this->parent.get_output_list())) , &Output::sourcechannels); } sourcechannels_t sourcechannels; private: using rtlist_t = typename Derived::rtlist_t; }; struct Output : Base::Output { using typename Base::Output::Params; using sourcechannels_t = std::list; Output(const Params& p) : Base::Output(p) {} sourcechannels_t sourcechannels; }; explicit SourceToOutput(const apf::parameter_map& params) : Base(params) {} }; } // namespace ssr #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'\:'=' ssr-0.4.2/src/rendersubscriber.h000066400000000000000000000173421236416011200166320ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// %RenderSubscriber (definition). // TODO: the whole publish/subscribe-thing should be redesigned, so maybe the // RenderSubscriber will be replaced by something else ... // Also, the actual renderer supports only a subset of the current (r1509) // Subscriber interface (no audio port names, no file names, ...). #ifndef SSR_RENDERSUBSCRIBER_H #define SSR_RENDERSUBSCRIBER_H #include "subscriber.h" #include namespace ssr { template class RenderSubscriber : public Subscriber { public: RenderSubscriber(Renderer &renderer) : _renderer(renderer) {} // Subscriber Interface virtual void set_loudspeakers(const Loudspeaker::container_t& loudspeakers); virtual void new_source(id_t id) { (void) id; } virtual void delete_source(id_t id) { auto guard = _renderer.get_scoped_lock(); _renderer.rem_source(id); } virtual void delete_all_sources() { auto guard = _renderer.get_scoped_lock(); _renderer.rem_all_sources(); } virtual bool set_source_position(id_t id, const Position& position) { auto guard = _renderer.get_scoped_lock(); auto src = _renderer.get_source(id); if (!src) return false; src->derived().position = position; return true; } virtual bool set_source_orientation(id_t id, const Orientation& orientation) { auto guard = _renderer.get_scoped_lock(); auto src = _renderer.get_source(id); if (!src) return false; src->derived().orientation = orientation; return true; } virtual bool set_source_gain(id_t id, const float& gain) { auto guard = _renderer.get_scoped_lock(); auto src = _renderer.get_source(id); if (!src) return false; src->derived().gain = gain; return true; } virtual bool set_source_mute(id_t id, const bool& mute) { auto guard = _renderer.get_scoped_lock(); auto src = _renderer.get_source(id); if (!src) return false; src->derived().mute = mute; return true; } virtual bool set_source_name(id_t id, const std::string& name) { (void) id; (void) name; return true; } virtual bool set_source_brir_file_name(id_t id, const std::string& name) { (void) id; (void) name; return true; } virtual bool set_source_model(id_t id, const Source::model_t& model) { auto guard = _renderer.get_scoped_lock(); auto src = _renderer.get_source(id); if (!src) return false; src->derived().model = model; return true; } virtual bool set_source_port_name(id_t id, const std::string& port_name) { (void) id; (void) port_name; return true; } virtual bool set_source_file_name(id_t id, const std::string& file_name) { (void) id; (void) file_name; return 1; } virtual bool set_source_file_channel(id_t id, const int& file_channel) { (void) id; (void) file_channel; return 1; } virtual bool set_source_file_length(id_t id, const long int& length) { (void) id; (void) length; return true; } virtual void set_reference_position(const Position& position) { auto guard = _renderer.get_scoped_lock(); _renderer.state.reference_position = position; } virtual void set_reference_orientation(const Orientation& orientation) { auto guard = _renderer.get_scoped_lock(); _renderer.state.reference_orientation = orientation; } virtual void set_reference_offset_position(const Position& position) { auto guard = _renderer.get_scoped_lock(); _renderer.state.reference_offset_position = position; } virtual void set_reference_offset_orientation(const Orientation& orientation) { auto guard = _renderer.get_scoped_lock(); _renderer.state.reference_offset_orientation = orientation; } virtual void set_master_volume(float volume) { auto guard = _renderer.get_scoped_lock(); _renderer.state.master_volume = volume; } virtual void set_source_output_levels(id_t, float*, float*) {} virtual void set_processing_state(bool state) { auto guard = _renderer.get_scoped_lock(); _renderer.state.processing = state; } virtual void set_transport_state( const std::pair& state) { (void) state; } virtual void set_amplitude_reference_distance(float distance) { auto guard = _renderer.get_scoped_lock(); _renderer.state.amplitude_reference_distance = distance; } virtual void set_master_signal_level(float level) { (void) level; } virtual void set_cpu_load(float load) { (void) load; } virtual void set_sample_rate(int sample_rate) { (void) sample_rate; } virtual bool set_source_signal_level(const id_t id, const float& level) { (void) id; (void) level; return true; } virtual bool set_source_properties_file(ssr::id_t, const std::string&) { return 1; } virtual bool set_source_position_fixed(ssr::id_t id, const bool& fix) { (void) id; (void) fix; return true; } private: Renderer& _renderer; }; template void RenderSubscriber::set_loudspeakers( const Loudspeaker::container_t& loudspeakers) { (void)loudspeakers; // TODO: handle loudspeakers differently. Maybe remove them from the Scene? } } // namespace ssr #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'\:'=' ssr-0.4.2/src/scene.cpp000066400000000000000000000257531236416011200147240ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// %Scene class (implementation). #include #include "scene.h" #include "source.h" ssr::Scene::Scene() : // reference looking straight ahead (in positive y-direction) _reference(Position(0, 0), Orientation(90)), _reference_offset(Position(0, 0), Orientation(0)), _master_volume(1.0f), _amplitude_reference_distance(3.0f), _master_signal_level(0.0f), _cpu_load(0.0f), _sample_rate(0u), _processing_state(true) {} ssr::Scene::~Scene() { //maptools::purge(_source_map); } ssr::Scene::loudspeakers_t::size_type ssr::Scene::get_number_of_loudspeakers() const { return _loudspeakers.size(); } void ssr::Scene::set_loudspeakers(const Loudspeaker::container_t& loudspeakers) { // mind the difference between _loudspeakers and loudspeakers! if (_loudspeakers.size() != 0) { ERROR("BUG: Loudspeaker list can only be set once for a Scene!"); _loudspeakers.clear(); } // reserve memory to avoid unnecessary re-allocation _loudspeakers.reserve(loudspeakers.size()); for (const auto& ls: loudspeakers) { // construction of temporary variable with type conversion ctor // and copy construction into vector _loudspeakers.push_back(Loudspeaker(ls)); } } void ssr::Scene::new_source(const id_t id) { // do nothing if id already exists: if (maptools::get_item(_source_map, id) == nullptr) { VERBOSE("Adding source " << id << " to source map!"); _source_map[id] = Source(_loudspeakers.size()); } } void ssr::Scene::delete_source(id_t id) { // this should call the destructor for the Source object. // IMPORTANT: the map holds the Sources directly, no pointers! _source_map.erase(id); } void ssr::Scene::delete_all_sources() { // this should call the destructor for all Source objects. // IMPORTANT: the map holds the Sources directly, no pointers! _source_map.clear(); } bool ssr::Scene::set_source_position(id_t id, const Position& position) { return _set_source_member(id, &Source::position, position); } bool ssr::Scene::set_source_orientation(id_t id, const Orientation& orientation) { return _set_source_member(id, &Source::orientation, orientation); } bool ssr::Scene::set_source_gain(id_t id, const float& gain) { return _set_source_member(id, &Source::gain, gain); } bool ssr::Scene::set_source_signal_level(id_t id, const float& level) { return _set_source_member(id, &Source::signal_level, level); } bool ssr::Scene::set_source_mute(id_t id, const bool& mute) { return _set_source_member(id, &Source::mute, mute); } bool ssr::Scene::set_source_name(id_t id, const std::string& name) { return _set_source_member(id, &Source::name, name); } bool ssr::Scene::set_source_properties_file(id_t id, const std::string& name) { return _set_source_member(id, &Source::properties_file, name); } bool ssr::Scene::set_source_port_name(id_t id, const std::string& port_name) { return _set_source_member(id, &Source::port_name, port_name); } bool ssr::Scene::set_source_file_name(id_t id, const std::string& file_name) { return _set_source_member(id, &Source::audio_file_name, file_name); } bool ssr::Scene::set_source_file_channel(id_t id, const int& file_channel) { return _set_source_member(id, &Source::audio_file_channel, file_channel); } bool ssr::Scene::set_source_position_fixed(id_t id, const bool& fixed) { return _set_source_member(id, &Source::fixed_position, fixed); } bool ssr::Scene::set_source_file_length(id_t id, const long int& length) { return _set_source_member(id, &Source::file_length, length); } void ssr::Scene::set_reference_position(const Position& position) { _reference.position = position; } void ssr::Scene::set_reference_orientation(const Orientation& orientation) { _reference.orientation = orientation; } void ssr::Scene::set_reference_offset_position(const Position& position) { _reference_offset.position = position; } void ssr::Scene::set_reference_offset_orientation(const Orientation& orientation) { _reference_offset.orientation = orientation; } bool ssr::Scene::set_source_model(id_t id, const Source::model_t& model) { return _set_source_member(id, &Source::model, model); } // linear volume! void ssr::Scene::set_master_volume(float volume) { _master_volume = volume; } void ssr::Scene::set_amplitude_reference_distance(float dist) { _amplitude_reference_distance = dist; } // linear scale! void ssr::Scene::set_master_signal_level(float level) { _master_signal_level = level; } void ssr::Scene::set_cpu_load(float load) { _cpu_load = load; } void ssr::Scene::set_sample_rate(int sr) { _sample_rate = sr; } void ssr::Scene::set_source_output_levels(id_t id, float* first, float* last) { Source* const source_ptr = maptools::get_item(_source_map, id); if (!source_ptr) { ERROR("Source " << id << " doesn't exist!"); return; } if (source_ptr->output_levels.size() == size_t(std::distance(first,last))) { std::copy(first, last, source_ptr->output_levels.begin()); } } void ssr::Scene::set_processing_state(bool state) { _processing_state = state; } /// _. @return processing state bool ssr::Scene::get_processing_state() const { return _processing_state; } /// _. @return master volume float ssr::Scene::get_master_volume() const { return _master_volume; } /// _. @return amplitude reference distance float ssr::Scene::get_amplitude_reference_distance() const { return _amplitude_reference_distance; } /// _. @return instantaneous overall audio signal level float ssr::Scene::get_master_signal_level() const { return _master_signal_level; } /// _. @return CPU load in percent float ssr::Scene::get_cpu_load() const { return _cpu_load; } /// _. @return current sample rate int ssr::Scene::get_sample_rate() const { return _sample_rate; } //void ssr::Scene::set_transport_state(JackClient::State state) void ssr::Scene::set_transport_state(const std::pair& state) { //_transport_state = state; _transport_playing = state.first; _transport_position = state.second; } bool ssr::Scene::is_playing() const { //return _transport_state.playing; return _transport_playing; } ssr::jack_nframes_t ssr::Scene::get_transport_position() const { //return _transport_state.position; return _transport_position; } Source ssr::Scene::get_source(id_t id) const { auto source = Source(_loudspeakers.size()); auto source_ptr = maptools::get_item(_source_map, id); if (!source_ptr) ERROR("Source " << id << " doesn't exist!"); else source = *source_ptr; return source; } /** _. * @param id ID of the source * @return position of the source * @warning If @a id is not found, a unique_ptr to NULL is returned! **/ std::unique_ptr ssr::Scene::get_source_position(id_t id) const { std::unique_ptr position(new Position); // standard ctor if (_get_source_member(id, &Source::position, *position)) return position; position.reset(); // set to NULL return position; } /** _. * @param id ID of the source * @return position of the source * @warning If @a id is not found, a unique_ptr to NULL is returned! **/ std::unique_ptr ssr::Scene::get_source_orientation(id_t id) const { std::unique_ptr orientation(new Orientation); // standard ctor if (_get_source_member(id, &Source::orientation, *orientation)) return orientation; orientation.reset(); // set to NULL return orientation; } /** _. * @param id ID of the source * @return source model * @warning If @a id is not found, Source::unknown is returned **/ Source::model_t ssr::Scene::get_source_model(id_t id) const { Source::model_t model; if (_get_source_member(id, &Source::model, model)) return model; return Source::unknown; } /** _. * @param id ID of the source * @return linear source gain (=volume) * @warning If @a id is not found, 0 is returned **/ float ssr::Scene::get_source_gain(id_t id) const { float gain; if (_get_source_member(id, &Source::gain, gain)) return gain; return 0; } /** _. * @param id ID of the source * @return mute state * @warning If @a id is not found, false is returned **/ bool ssr::Scene::get_source_mute_state(id_t id) const { bool state; if (_get_source_member(id, &Source::mute, state)) return state; return false; } /** _. * @param id ID of the source * @return source name * @warning If @a id is not found, an empty string is returned **/ std::string ssr::Scene::get_source_name(id_t id) const { std::string name; if (_get_source_member(id, &Source::name, name)) return name; return std::string(""); } bool ssr::Scene::get_source_position_fixed(id_t id) const { bool boolean; if (_get_source_member(id, &Source::fixed_position, boolean)) return boolean; return false; } std::string ssr::Scene::get_source_properties_file(id_t id) const { std::string name; if (_get_source_member(id, &Source::properties_file, name)) return name; return std::string(""); } // 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'\:'=' ssr-0.4.2/src/scene.h000066400000000000000000000320421236416011200143560ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// %Scene class (definition). #ifndef SSR_SCENE_H #define SSR_SCENE_H #include #include #include // for assert() #include #include "subscriber.h" #include "maptools.h" // for get_item() #include "source.h" #include "loudspeaker.h" namespace ssr { /// %Scene. Contains current location and other data about sources, the // reference point and other things. class Scene : public Subscriber { public: /// A map of sources. using source_map_t = std::map; /// A vector of loudspeakers. using loudspeakers_t = std::vector; Scene(); ///< ctor ~Scene(); ///< dtor // from Subscriber virtual void set_loudspeakers(const Loudspeaker::container_t& container); virtual void new_source(id_t id); virtual void delete_source(id_t id); virtual void delete_all_sources(); virtual bool set_source_position(id_t id, const Position& position); virtual bool set_source_orientation(id_t id , const Orientation& orientation); virtual bool set_source_gain(id_t id, const float& gain); virtual bool set_source_signal_level(id_t id, const float& level); virtual bool set_source_mute(id_t id, const bool& mute); virtual bool set_source_name(id_t id, const std::string& name); virtual bool set_source_properties_file(id_t id, const std::string& name); virtual bool set_source_model(id_t id, const Source::model_t& model); virtual bool set_source_port_name(id_t id, const std::string& port_name); virtual bool set_source_file_name(id_t id, const std::string& file_name); virtual bool set_source_file_channel(id_t id, const int& file_channel); virtual bool set_source_position_fixed(id_t id, const bool& fixed); virtual bool set_source_file_length(id_t id, const long int& length); virtual void set_reference_position(const Position& position); virtual void set_reference_orientation(const Orientation& orientation); virtual void set_reference_offset_position(const Position& position); virtual void set_reference_offset_orientation(const Orientation& orientation); virtual void set_master_volume(float volume); virtual void set_amplitude_reference_distance(float dist); virtual void set_master_signal_level(float level); virtual void set_cpu_load(float load); virtual void set_sample_rate(int sample_rate); virtual void set_source_output_levels(id_t id, float* first, float* last); virtual void set_processing_state(bool state); //virtual void set_transport_state(JackClient::State state); virtual void set_transport_state( const std::pair& state); loudspeakers_t::size_type get_number_of_loudspeakers() const; // std::pair get_transport_state() const; /// get master volume float get_master_volume() const; /// get master volume with amplitude correction considered float get_corrected_master_volume() const; /// get amplitude reference distance float get_amplitude_reference_distance() const; /// get master audio level float get_master_signal_level() const; /// get CPU load in percent estimated by JACK float get_cpu_load() const; /// get current sample rate int get_sample_rate() const; /// get source Source get_source(id_t id) const; /// get source position std::unique_ptr get_source_position(id_t id) const; /// get source orientation std::unique_ptr get_source_orientation(id_t id) const; /// get source model (=type) Source::model_t get_source_model(id_t id) const; /// get source gain float get_source_gain(id_t id) const; /// get source mute state bool get_source_mute_state(id_t id) const; /// get source name std::string get_source_name(id_t id) const; bool get_source_position_fixed(id_t id) const; std::string get_source_properties_file(id_t id) const; /// check if renderer is processing bool get_processing_state() const; bool is_playing() const; jack_nframes_t get_transport_position() const; // temporarily with inline implementation // should return 0 in case of doubt. // will be removed in the near future /// deprecated! inline virtual int get_max_no_of_sources() const { return 100; } /// get a list of all sources. /// @param container (initially empty) list of sources. /// @pre /// For the template argument @a T you can use any type which has a /// conversion constructor for the Source type. Speaking more exactly, you /// need a constructor of the following form: /// T::T(const pair&) /// @par /// Your type @a T can include any of the members of Source, this way you /// can obtain any subset of data you desire. If you want all available /// data, just use the Source type itself. /// @warning if a std::vector is used as container, the function /// get_number_of_sources() is called to reserve the /// necessary memory to avoid memory re-allocation. /// /// Because a fancy template template is used, any container type with one /// template argument can be used, like std::list, std::vector, ... /// as long as it has the following member functions: .begin(), .end(), /// .push_back(). template class Container, typename T, typename... Args> void get_sources(Container& container) const { assert(container.empty()); // the following struct container_traits is declared in the private part // of the Scene class and defined further down this file. if (container_traits>::has_reserve) { container_traits>::reserve(container, _source_map.size()); } for (const auto& source: _source_map) { // type conversion constructor T::T(const pair&) needed! container.push_back(T(source)); } } /// get a list of all loudspeakers. /// @warning This doesn't return a valid value for the "active" field! (if /// your type T has it) /// /// ID of the loudspeaker == its position in the container /// container[0] has ID 1, container[1] has ID 2, ... template class Container, typename T, typename Allocator> void get_loudspeakers(Container& container, bool absolute_position = true) const { assert(container.empty()); if (container_traits>::has_reserve) { container_traits>::reserve(container, _loudspeakers.size()); } for (loudspeakers_t::const_iterator i = _loudspeakers.begin(); i != _loudspeakers.end(); ++i) { T temp(*i); // copy ctor. is called if (absolute_position) { // T has to be derived from DirectionalPoint temp.DirectionalPoint::operator=(temp.transform(_reference)); } container.push_back(temp); // copy ctor. is called again } } /// get current reference position/orientation. /// @return position/orientation of the reference point. DirectionalPoint get_reference() const { return _reference; } DirectionalPoint get_reference_offset() const { return _reference_offset; } protected: source_map_t _source_map; ///< container for sources private: loudspeakers_t _loudspeakers; DirectionalPoint _reference; ///< position/orientation of the reference DirectionalPoint _reference_offset; float _master_volume; ///< master volume (linear) float _master_volume_correction; ///< dito (linear scale) float _amplitude_reference_distance; ///< distance where plane sources are ///< as loud as the other source types float _master_signal_level; ///< instantaneous overall signal level (linear) float _cpu_load; ///< CPU load in percent int _sample_rate; ///< sample rate /// order of mirror sources. if 0, mirror sources are deactivated int mirror_order; bool _processing_state; ///< is renderer processing? bool _transport_playing; jack_nframes_t _transport_position; ///< current position in the audio file in samples template bool _set_source_member( id_t id, PointerToMember member, const T& arg) { auto source_ptr = maptools::get_item(_source_map, id); if (!source_ptr) { VERBOSE("Source " << id << " doesn't exist!"); return false; } source_ptr->*member = arg; return true; } /// helper function template for get_*() template bool _get_source_member( id_t id, PointerToMember member, T& arg) const { const Source* const source_ptr = maptools::get_item(_source_map, id); if (!source_ptr) { VERBOSE("Source " << id << " doesn't exist!"); return false; } arg = source_ptr->*member; return true; } // forward declaration of the container traits class template struct container_traits; // nested // partial specialization: template struct container_traits>; }; //////////////////////////////////////////////////////////////////////////////// // Definition of the nested struct container_traits //////////////////////////////////////////////////////////////////////////////// /// traits class to check if a container has a reserve() member function // default template for any type of container template struct Scene::container_traits { static const bool has_reserve = false; template static void reserve(Container&, const U&) { // does nothing, because generally, there is no reserve() member // function an we will never call it anyway. // We only declare it to get no errors from the compiler. } }; /// traits class to check if a container has a reserve() member function // partial specialization of the above template. template struct Scene::container_traits> { static const bool has_reserve = true; template static void reserve(std::vector& v, const U& space) { v.reserve(space); } }; // if any other STL container has a reserve() member function it should be // added here as a specialization. // TODO: allow Allocator template argument with default std::allocator // but: default template arguments may not be used in partial specializations // introducing this argument into the default template as well would make this // already quite complicated template thingy even more complicated! } // namespace ssr #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'\:'=' ssr-0.4.2/src/source.h000066400000000000000000000134441236416011200145660ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// %Source class (definition). #ifndef SSR_SOURCE_H #define SSR_SOURCE_H #include #include #include // for operator>> #include "directionalpoint.h" /// Class for saving source information struct Source : DirectionalPoint { /** %Source type. * This enum can be extended arbitrarily, but for starters only point source * and plane wave will be implemented. **/ using model_t = enum { unknown = 0, /// unknown source model point, ///< point source plane, ///< plane wave line, ///< line source (not implemented!) directional, ///< directional source (not implemented!) extended ///< spatially extended source (not implemented!) }; /// ctor. /// @param position position /// @param orientation orientation explicit Source(size_t outputs, const Position& position = Position(), const Orientation& orientation = Orientation()) : DirectionalPoint(position, orientation), // base class ctor audio_file_channel(0), file_length(0), model(Source::point), mute(0), gain(1.0), signal_level(0.0), output_levels(outputs), has_mirror_sources(false), fixed_position(false), properties_file(""), doppler_effect(false) {} // TODO: get rid of output levels? remove one of the constructors? explicit Source( const Position& position = Position(), const Orientation& orientation = Orientation()) : DirectionalPoint(position, orientation), // base class ctor audio_file_channel(0), file_length(0), model(Source::point), mute(0), gain(1.0), signal_level(0.0), has_mirror_sources(false), fixed_position(false), properties_file(""), doppler_effect(false) {} std::string name; ///< source name std::string audio_file_name; ///< audio file int audio_file_channel; ///< channel of audio file std::string port_name; ///< JACK port name long int file_length; ///< length of audio file, 0 if no file exists model_t model; ///< source model (=type) bool mute; ///< mute/unmute float gain; ///< source gain (volume) float signal_level; ///< instantaneous level of audio stream (linear scale, between 0 and 1) std::vector output_levels; bool has_mirror_sources; ///< see Scene::mirror_order bool fixed_position; ///< static position or not std::string properties_file; ///< path to file where BRIRs reside (if available) bool doppler_effect; ///< doppler effect enabled friend std::istream& operator>>(std::istream& input, model_t& model) { std::string temp; input >> temp; //if (input.fail()) return input; // redundant? if (temp == "point") model = point; else if (temp == "plane") model = plane; else if (temp == "line") model = line; else if (temp == "directional") model = directional; // everything else (including empty string on failure) doesn't change model else input.setstate(std::ios_base::badbit); return input; } friend std::ostream& operator<<(std::ostream& output, const model_t& model) { switch (model) { case point: output << "point"; break; case plane: output << "plane"; break; case line: output << "line"; break; case directional: output << "directional"; break; default: output << "unknown"; break; } return output; } }; #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'\:'=' ssr-0.4.2/src/ssr_aap.cpp000066400000000000000000000047131236416011200152500ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Main file for the AAP Renderer. #include "controller.h" #include "aaprenderer.h" int main(int argc, char* argv[]) { ssr::Controller controller(argc, argv); controller.run(); } // 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'\:'=' ssr-0.4.2/src/ssr_binaural.cpp000066400000000000000000000047251236416011200163070ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Main file for BinauralRenderer. #include "controller.h" #include "binauralrenderer.h" int main(int argc, char* argv[]) { ssr::Controller controller(argc, argv); controller.run(); } // 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'\:'=' ssr-0.4.2/src/ssr_brs.cpp000066400000000000000000000047331236416011200152770ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Main file for Binaural Room Synthesis Renderer. #include "controller.h" #include "brsrenderer.h" int main(int argc, char* argv[]) { ssr::Controller controller(argc, argv); controller.run(); } // 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'\:'=' ssr-0.4.2/src/ssr_generic.cpp000066400000000000000000000047231236416011200161240ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Main file for Generic Renderer. #include "controller.h" #include "genericrenderer.h" int main(int argc, char* argv[]) { ssr::Controller controller(argc, argv); controller.run(); } // 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'\:'=' ssr-0.4.2/src/ssr_global.cpp000066400000000000000000000050701236416011200157440ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Initialization of global variables of the ssr namespace. #include "ssr_global.h" int ssr::verbose = 0; float ssr::c = 340.0f; // meters/second float ssr::c_inverse = 1.0f/c; unsigned int ssr::usleeptime = 1000000; // micro-seconds //unsigned int ssr::usleeptime = 350000; // micro-seconds // 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'\:'=' ssr-0.4.2/src/ssr_global.h000066400000000000000000000107731236416011200154170ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Global variables and typedefs and preprocessor macros for the SSR. #ifndef SSR_GLOBAL_H #define SSR_GLOBAL_H #include /// Contains variables and typedefs for the SSR namespace ssr { /// Used as unique identifier for sources, loudspeakers, ... using id_t = unsigned int; /** Verbosity level. * @arg 0 - Only errors and warnings are shown. * @arg 1 - A few more messages are shown. * @arg 2 - Quite a lot messages are shown. * @arg 3 - Even messages which can repeat many times per second are shown. * This is a lot of messages! **/ extern int verbose; extern float c; ///< speed of sound (meters per second) extern float c_inverse; ///< reciprocal value of c /// time to sleep after connecting a new soundfile with ecasound extern unsigned int usleeptime; } // namespace ssr // some preprocessor macros: /// turn the argument into a string #define __STR__(x) #x /// workaround to evaluate the argument and pass the result to __STR__ #define __XSTR__(x) __STR__(x) /// make a string with filename and line number #define __POS__ "(" __FILE__ ":" __XSTR__(__LINE__) ")" /// Write message to stdout, if ssr::verbose is non-zero. #define VERBOSE(msg) __VERBOSE(msg,1) /// Write message to stdout, if ssr::verbose is greater than 1. #define VERBOSE2(msg) __VERBOSE(msg,2) /// Write message to stdout, if ssr::verbose is greater than 2. #define VERBOSE3(msg) __VERBOSE(msg,3) /// Write message to stdout, if ssr::verbose >= level #define __VERBOSE(msg,level) __VERBOSE_NOLF(msg,level) << std::endl // TODO: the NOLF thing is a little ugly, still searching for a better thing ... /// NOLF = no line feed #define __VERBOSE_NOLF(msg,level) \ if (ssr::verbose >= (level)) std::cout << msg << std::flush /// like VERBOSE(), but without line feed at the end. #define VERBOSE_NOLF(msg) __VERBOSE_NOLF(msg,1) /// like VERBOSE2(), but without line feed at the end. #define VERBOSE2_NOLF(msg) __VERBOSE_NOLF(msg,2) /// like VERBOSE3(), but without line feed at the end. #define VERBOSE3_NOLF(msg) __VERBOSE_NOLF(msg,3) /// Write a warning message to stderr. #define WARNING(msg) \ std::cerr << "Warning: " << msg << " " __POS__ << std::endl /// Write an error message to stderr. #define ERROR(msg) \ std::cerr << "Error: " << msg << " " __POS__ << std::endl #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'\:'=' ssr-0.4.2/src/ssr_nfc_hoa.cpp000066400000000000000000000047211236416011200161030ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Main file for NFC HOA renderer. #include "controller.h" #include "nfchoarenderer.h" int main(int argc, char* argv[]) { ssr::Controller controller(argc, argv); controller.run(); } // 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'\:'=' ssr-0.4.2/src/ssr_vbap.cpp000066400000000000000000000047161236416011200154420ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Main file for the VBAP Renderer. #include "controller.h" #include "vbaprenderer.h" int main(int argc, char* argv[]) { ssr::Controller controller(argc, argv); controller.run(); } // 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'\:'=' ssr-0.4.2/src/ssr_wfs.cpp000066400000000000000000000047071236416011200153110ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Main file for WFS Renderer. #include "controller.h" #include "wfsrenderer.h" int main(int argc, char* argv[]) { ssr::Controller controller(argc, argv); controller.run(); } // 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'\:'=' ssr-0.4.2/src/subscriber.h000066400000000000000000000157301236416011200154310ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Abstract %Subscriber (definition). #ifndef SSR_SUBSCRIBER_H #define SSR_SUBSCRIBER_H #include #include #include "ssr_global.h" #include "source.h" #include "loudspeaker.h" namespace ssr { using jack_nframes_t = uint32_t; // from /** Abstract interface to Scene class (and similar classes). **/ struct Subscriber { /// dtor. does nothing. virtual ~Subscriber() {} virtual void set_loudspeakers(const Loudspeaker::container_t& loudspeakers) = 0; /// Create a new source with default values. /// @param id ID of the new source virtual void new_source(id_t id) {(void)id;} /// Remove a source from the Scene. /// @param id ID of the source virtual void delete_source(id_t id) = 0; /// delete all sources, quite obviously. virtual void delete_all_sources() = 0; /// Set source position. /// @param id ID of the source /// @param position new position virtual bool set_source_position(id_t id, const Position& position) = 0; /// Set source orientation. /// @param id ID of the source /// @param orientation new orientation virtual bool set_source_orientation(id_t id, const Orientation& orientation) = 0; /// Set source gain. /// @param id ID of the source /// @param gain new gain /// @attention 2nd argument is given by const reference to facilitate the /// _publish() function in the Controller class. virtual bool set_source_gain(id_t id, const float& gain) = 0; /// Set instantaneous level of audio stream. /// @param id ID of the source /// @param level new level (linear scale) /// @param intitiator not implemented virtual bool set_source_signal_level(const id_t id, const float& level) = 0; /// mute/unmute source. /// @param id ID of the source /// @param mute mute if @b true, unmute if @b false /// @attention 2nd argument is given by const reference to facilitate the /// _publish() function in the Controller class. virtual bool set_source_mute(id_t id, const bool& mute) = 0; /// Set source name. /// @param id ID of the source /// @param name new name virtual bool set_source_name(id_t id, const std::string& name) = 0; /// Set name of file containing impulse responses. /// @param id ID of the source /// @param name file name virtual bool set_source_properties_file(id_t id, const std::string& name) = 0; /// Set source model (=type). /// @param id ID of the source /// @param model new model /// @attention 2nd argument is given by const reference to facilitate the /// _publish() function in the Controller class. virtual bool set_source_model(id_t id, const Source::model_t& model) = 0; /// Set port name of a source. /// @param id ID of the source /// @param port_name JACK port name virtual bool set_source_port_name(id_t id, const std::string& port_name) = 0; virtual bool set_source_file_name(id_t id, const std::string& file_name) = 0; virtual bool set_source_file_channel(id_t id, const int& file_channel) = 0; virtual bool set_source_position_fixed(id_t id, const bool& fixed) = 0; /// Set length of associated audio file. /// @param id ID of the source /// @param length length in samples /// @attention 2nd argument is given by const reference to facilitate the /// _publish() function in the Controller class. virtual bool set_source_file_length(id_t id, const long int& length) = 0; /// Set reference position. /// @param position new position virtual void set_reference_position(const Position& position) = 0; /// Set reference orientation. /// @param orientation new orientation virtual void set_reference_orientation(const Orientation& orientation) = 0; virtual void set_reference_offset_position(const Position& position) = 0; virtual void set_reference_offset_orientation(const Orientation& orientation) = 0; /// Set master volume. /// @param volume volume virtual void set_master_volume(float volume) = 0; /// Set amplitude reference distance. /// @param distance amplitude reference distance virtual void set_amplitude_reference_distance(float distance) = 0; /// Set master volume. /// @param level instantaneous overall audio level (linear scale) virtual void set_master_signal_level(float level) = 0; /// Set CPU load. /// @param load CPU load in percent virtual void set_cpu_load(float load) = 0; /// Set sample rate. /// @param sample_rate sample rate virtual void set_sample_rate(int sample_rate) = 0; virtual void set_source_output_levels(id_t id, float* first, float* last) = 0; /// Update information about audio processing; virtual void set_processing_state(bool state) = 0; virtual void set_transport_state(const std::pair& state) = 0; }; } // namespace ssr #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'\:'=' ssr-0.4.2/src/timetools.h000066400000000000000000000060231236416011200153000ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Provides helper functions for calculations concerning time. #ifndef SSR_TIMETOOLS_H #define SSR_TIMETOOLS_H /// Provides helper functions for calculations concerning time. namespace timetools { /** Returns time interval in seconds between two time instances * @param start time instance where interval started * @param stop time instance where interval started * @return time interval in seconds **/ inline float get_time_interval(struct timeval start, struct timeval stop) { return static_cast(stop.tv_sec - start.tv_sec + (stop.tv_usec - start.tv_usec) / 1000000.0); } inline bool is_time_stamp_valid(struct timeval time_stamp) { if (time_stamp.tv_sec == 0 && time_stamp.tv_usec == 0) return false; else return true; } } // namespace timetools #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'\:'=' ssr-0.4.2/src/tracker.h000066400000000000000000000050741236416011200147210ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Abstract %Tracker class (definition). #ifndef SSR_TRACKER_H #define SSR_TRACKER_H /// Class definition struct Tracker { Tracker() {} ///< constructor virtual ~Tracker() {} ///< destructor /// calibrate tracker; set the instantaneous position to be the reference virtual void calibrate() = 0; }; #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'\:'=' ssr-0.4.2/src/trackerintersense.cpp000066400000000000000000000146241236416011200173550ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// InterSense tracker (implementation). #ifdef HAVE_CONFIG_H #include // for ENABLE_*, HAVE_*, WITH_* #endif #include #include #include "trackerintersense.h" #include "publisher.h" #include "ssr_global.h" #include "posixpathtools.h" #ifndef _REENTRANT #error You need to compile with _REENTRANT defined since this uses threads! #endif ssr::TrackerInterSense::TrackerInterSense(Publisher& controller , const std::string& ports, const unsigned int read_interval) : Tracker() , _controller(controller) , _read_interval(read_interval) , _stopped(false) , _thread_id(0) { // suppress output of libisense.so //int stdout_fileno = fileno(stdout); //int stdout_handle = dup(stdout_fileno); //int stderr_fileno = fileno(stderr); //int stderr_handle = dup(stderr_fileno); VERBOSE("Looking for InterSense tracker."); //close outputs of libisense.so //close(stdout_fileno); //close(stderr_fileno); // save current working directory std::string current_path; posixpathtools::getcwd(current_path); // if specific serial ports were given: use them if (ports != "") { // switch working directory chdir("/tmp"); VERBOSE("Creating /tmp/isports.ini to configure InterSense tracker ports."); // create isports.ini std::ofstream file; file.open("isports.ini", std::ios::trunc); if (file.is_open()) { std::istringstream ports_stream(ports); std::string port; int i = 1; while (ports_stream >> port) { file << "Port" << i++ << " = " << port << std::endl; } file.close(); } else { ERROR("Could not create /tmp/isports.ini to configure InterSense tracker ports"); } } else { VERBOSE("Letting InterSense tracker look for isports.ini in current working directory."); } // start tracker (will automatically try all listed ports in isports.ini file in // current working directory) _tracker_h = ISD_OpenTracker(static_cast(0), 0, FALSE, FALSE); if (ports != "") { // restore working directory chdir(current_path.c_str()); } // restore stdout and stderr //dup2(stdout_handle, stdout_fileno); //dup2(stderr_handle, stderr_fileno); // no tracker found if (_tracker_h <= 0) { throw std::runtime_error("InterSense tracker not found!"); } // if tracker is there else { VERBOSE("InterSense tracker found."); _start(); // wait 100ms to make sure that tracker gives reliable values usleep(100000u); // and then calibrate it calibrate(); } } ssr::TrackerInterSense::~TrackerInterSense() { // if thread was started if (_thread_id) _stop(); ISD_CloseTracker(_tracker_h); } ssr::TrackerInterSense::ptr_t ssr::TrackerInterSense::create(Publisher& controller, const std::string& ports , const unsigned int read_interval) { ptr_t temp; // temp = NULL try { temp.reset(new TrackerInterSense(controller, ports, read_interval)); } catch(std::runtime_error& e) { ERROR(e.what()); } return temp; } void ssr::TrackerInterSense::calibrate() { ISD_ResetHeading(_tracker_h, 1); } void ssr::TrackerInterSense::_start() { // create thread pthread_create(&_thread_id , nullptr, _thread, this); WARNING("Tracker started."); } void ssr::TrackerInterSense::_stop() { // dummy void *thread_exit_status; _stopped = true; pthread_join(_thread_id , &thread_exit_status); } void* ssr::TrackerInterSense::_thread(void *arg) { return reinterpret_cast (arg)->thread(nullptr); } void* ssr::TrackerInterSense::thread(void *arg) { #ifdef HAVE_INTERSENSE_404 ISD_TRACKING_DATA_TYPE tracker_data; #else ISD_TRACKER_DATA_TYPE tracker_data; #endif while(!_stopped) { #ifdef HAVE_INTERSENSE_404 ISD_GetTrackingData(_tracker_h, &tracker_data); _controller.set_reference_orientation( Orientation(-tracker_data.Station[0].Euler[0] + 90.0f)); #else ISD_GetData(_tracker_h, &tracker_data); _controller.set_reference_orientation( Orientation(-static_cast(tracker_data.Station[0].Orientation[0]) + 90.0f)); #endif // wait a bit usleep(_read_interval*1000u); }; return arg; } // 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'\:'=' ssr-0.4.2/src/trackerintersense.h000066400000000000000000000070051236416011200170150ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// InterSense tracker (definition). #ifndef SSR_TRACKERINTERSENSE_H #define SSR_TRACKERINTERSENSE_H #include #include #include #include #include // for std::runtime_error #include "tracker.h" namespace ssr { struct Publisher; /// Intersense InertiaCube3 head tracker class TrackerInterSense : public Tracker { public: using ptr_t = std::unique_ptr; virtual ~TrackerInterSense(); ///< destructor /// "named constructor" static ptr_t create(Publisher& controller, const std::string& ports = "", const unsigned int read_interval = 20); virtual void calibrate(); private: /// constructor TrackerInterSense(Publisher& controller, const std::string& ports , const unsigned int read_interval); Publisher& _controller; /// interval in ms to wait after each read cycle unsigned int _read_interval; bool _stopped; ///< stops the tracking thread ISD_TRACKER_HANDLE _tracker_h; ///< tracker handle void _start(); ///< start the tracking thread void _stop(); ///< stop the tracking thread // thread related stuff pthread_t _thread_id; static void* _thread(void*); void* thread(void*); }; } // namespace ssr #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'\:'=' ssr-0.4.2/src/trackerpolhemus.cpp000066400000000000000000000204611236416011200170260ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Polhemus tracker (implementation). #include // for write(), fsync(), close(), ... #include // for cfsetispeed(), ... #include // for open(), ... #include // for std::stringstream #include // for poll(), pollfd, ... #include "trackerpolhemus.h" #include "publisher.h" #include "ssr_global.h" #include "apf/stringtools.h" using apf::str::A2S; #ifndef _REENTRANT #error You need to compile with _REENTRANT defined since this uses threads! #endif ssr::TrackerPolhemus::TrackerPolhemus(Publisher& controller , const std::string& ports) : Tracker() , _controller(controller) , _stopped(false) , _az_corr(90.0f) , _thread_id(0) { if (ports == "") { throw std::runtime_error("No serial port(s) specified!"); } VERBOSE("Opening serial port for Polhemus Fastrak ..."); std::istringstream iss(ports); std::string port; while (iss >> port) { if (port != "") { VERBOSE_NOLF("Trying to open port " << port << " ... "); _tracker_port = _open_serial_port(port.c_str()); if (_tracker_port == -1) { VERBOSE("failure!"); } else { VERBOSE("success!"); break; // stop trying } } } if (_tracker_port == -1) { throw std::runtime_error("Could not open serial port!"); } // get port attributes struct termios tio; if (int errorID = tcgetattr(_tracker_port, &tio)) { throw std::runtime_error("Could not get serial port attributes! Error # " + A2S(errorID)); } // set port attributes cfmakeraw(&tio); tio.c_cflag |= CLOCAL; tio.c_cflag |= CREAD; // set port speed speed_t newSpeed = B115200; if (int errorID = cfsetispeed(&tio, newSpeed)) { throw std::runtime_error(" " + A2S(errorID) + ": Could not set new serial port input speed to " + A2S(newSpeed) + "."); } if (int errorID = cfsetospeed(&tio, newSpeed)) { throw std::runtime_error(" " + A2S(errorID) + ": Could not set new serial port output speed to " + A2S(newSpeed) + "."); } // set port attributes // must be done after setting speed! if (int errorID = tcsetattr(_tracker_port, TCSANOW, &tio)) { throw std::runtime_error(" " + A2S(errorID) + ": Could not set new serial port attributes."); } // this is necessary to activate the serial port write(_tracker_port, "C", 1); fsync(_tracker_port); _start(); // wait until tracker has started usleep(50000); this->calibrate(); } ssr::TrackerPolhemus::~TrackerPolhemus() { // if thread was started if (_thread_id) _stop(); close(_tracker_port); } ssr::TrackerPolhemus::ptr_t ssr::TrackerPolhemus::create(Publisher& controller, const std::string& ports) { ptr_t temp; // temp = NULL try { temp.reset(new TrackerPolhemus(controller, ports)); } catch(std::runtime_error& e) { ERROR(e.what()); } return temp; } int ssr::TrackerPolhemus::_open_serial_port(const char *portname) { int port; int flags; // O_NDELAY allows open even with no carrier detect (e.g. needed for Razor) if ((port = open(portname, O_RDWR | O_NOCTTY | O_NDELAY)) != -1) { // clear O_NDELAY to make I/O blocking again if (((flags = fcntl(port, F_GETFL, 0)) != -1) && (fcntl(port, F_SETFL, flags & ~O_NDELAY)) != -1) { return port; } } // something didn't work close(port); return -1; } void ssr::TrackerPolhemus::calibrate() { _az_corr = _current_data.azimuth + 90.0f; } void ssr::TrackerPolhemus::_start() { // create thread pthread_create(&_thread_id , nullptr, _thread, this); VERBOSE("Starting tracker ..."); } void ssr::TrackerPolhemus::_stop() { // dummy void *thread_exit_status; _stopped = true; pthread_join(_thread_id , &thread_exit_status); } void* ssr::TrackerPolhemus::_thread(void *arg) { return reinterpret_cast (arg)->thread(nullptr); } void* ssr::TrackerPolhemus::thread(void *arg) { char c; std::string line; while (!_stopped) { c = 0; line.clear(); while (c != '\n') { struct pollfd fds; int error; fds.fd = _tracker_port; fds.events = POLLRDNORM; error = poll(&fds, 1, 100); if (error < 1) { ERROR("Can not read from serial port. Stopping Polhemus tracker."); return arg; } if ((error = read(_tracker_port, &c, 1))) { line += c; } else { ERROR("Can not read from serial port."); return arg; } } if (line.size() != 47) { _current_data.azimuth = 0.0f; continue; } std::stringstream lineparse(line); // By the way, the data coming from the Polhemus Fastrak looks like this: // // 02 20.40 -61.06 30.01-150.70 -42.08 156.93 // 02 20.59 -60.99 30.01-150.34 -42.24 156.96 // 02 20.81 -60.86 30.01-150.09 -42.70 156.86 // // At the end of each line there is a and a , including them, each line // has 47 ASCII bytes. // The first 3 bytes identify the tracker, our Fastrak has 4 sockets, named // "ONE", "TWO", "THREE" and "FOUR", resulting in IDs of "01 ", "02 ", "03 " and // "04 ", respectively. When hot-plugging, the IDs sometimes change to "02d" or // similar, which seems to be kind of an error message. // Hot-plugging isn't such a good idea anyway. // If several trackers are connected, their lines are interleaved. // Then, there are 6 x 7 bytes for the actual data in 6 degrees of freedom: // x, y, z, azimuth, elevation and tilt, we only use azimuth, the others are // untested! // The 7-byte-groups contain optional spaces for padding, an optional sign and a // decimal number with 2 digits after the comma. // The last two bytes are the abovementioned and . // extract data lineparse >> _current_data.header >> _current_data.x >> _current_data.y >> _current_data.z >> _current_data.azimuth >> _current_data.elevation >> _current_data.roll; _controller.set_reference_orientation( Orientation(-_current_data.azimuth + _az_corr)); }; return arg; } // 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'\:'=' ssr-0.4.2/src/trackerpolhemus.h000066400000000000000000000074001236416011200164710ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Polhemus tracker (definition). #ifndef SSR_TRACKERPOLHEMUS_H #define SSR_TRACKERPOLHEMUS_H #include #include #include #include // for std::runtime_error #include "tracker.h" namespace ssr { struct Publisher; // forward declaration /// Polhemus Fastrack tracker class TrackerPolhemus : public Tracker { public: using ptr_t = std::unique_ptr; virtual ~TrackerPolhemus(); ///< destructor /// "named constructor" static ptr_t create(Publisher& controller, const std::string& ports); virtual void calibrate(); private: /// constructor TrackerPolhemus(Publisher& controller, const std::string& ports); struct tracker_data_t { float header; float x; float y; float z; float azimuth; float elevation; float roll; // contructor tracker_data_t() : header(0.0f), x(0.0f), y(0.0f), z(0.0f) , azimuth(0.0f), elevation(0.0f), roll(0.0f) {} }; Publisher& _controller; tracker_data_t _current_data; int _tracker_port; int _open_serial_port(const char *portname); volatile bool _stopped; ///< stops the tracking thread float _az_corr; ///< correction of the azimuth due to calibration void _start(); ///< start the tracking thread void _stop(); ///< stop the tracking thread // thread related stuff pthread_t _thread_id; static void* _thread(void*); void* thread(void*); }; } // namespace ssr #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'\:'=' ssr-0.4.2/src/trackerrazor.cpp000066400000000000000000000072741236416011200163360ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Razor AHRS tracker (implementation). /// See http://dev.qu.tu-berlin.de/projects/sf-razor-9dof-ahrs/wiki #include "trackerrazor.h" ssr::TrackerRazor::TrackerRazor(Publisher& controller, const std::string& ports) : Tracker() , _controller(controller) , _current_azimuth(0.0f) , _az_corr(90.0f) , _init_az_corr(true) , _tracker(nullptr) { if (ports == "") { throw std::runtime_error("No serial port(s) specified!"); } VERBOSE("Initializing Razor AHRS ..."); std::istringstream iss(ports); std::string port; while (iss >> port) { if (port != "") { VERBOSE_NOLF("Trying port " << port << " ... "); try { _tracker = new RazorAHRS(port, std::bind(&TrackerRazor::on_data, this, std::placeholders::_1), std::bind(&TrackerRazor::on_error, this, std::placeholders::_1), RazorAHRS::YAW_PITCH_ROLL); } catch(std::runtime_error& e) { VERBOSE("failure! (" << std::string(e.what()) + ")"); continue; } VERBOSE("success!"); break; // stop trying } } if (_tracker == nullptr) { throw std::runtime_error("Could not open serial port!"); } } ssr::TrackerRazor::ptr_t ssr::TrackerRazor::create(Publisher& controller, const std::string& ports) { ptr_t temp; // temp = NULL try { temp.reset(new TrackerRazor(controller, ports)); } catch(std::runtime_error& e) { ERROR(e.what()); } return temp; } // 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'\:'=' ssr-0.4.2/src/trackerrazor.h000066400000000000000000000070771236416011200160040ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Razor AHRS tracker (definition). /// See http://dev.qu.tu-berlin.de/projects/sf-razor-9dof-ahrs/wiki #ifndef SSR_TRACKERRAZOR_H #define SSR_TRACKERRAZOR_H #include "tracker.h" // base class #include "razor-ahrs/RazorAHRS.h" #include "publisher.h" namespace ssr { /// Razor AHRS tracker class TrackerRazor : public Tracker { public: using ptr_t = std::unique_ptr; /// "named constructor" static ptr_t create(Publisher& controller, const std::string& ports); /// destructor ~TrackerRazor() { if (_tracker != nullptr) delete _tracker; } virtual void calibrate() { _az_corr = _current_azimuth + 90.0f; } private: /// constructor TrackerRazor(Publisher& controller, const std::string& ports); /// Razor AHRS callback functions void on_data(const float ypr[]) { _current_azimuth = ypr[0]; if (_init_az_corr) { calibrate(); _init_az_corr = false; } _controller.set_reference_orientation(Orientation(-_current_azimuth + _az_corr)); } void on_error(const std::string &msg) { ERROR("Razor AHRS: " << msg); } Publisher& _controller; volatile float _current_azimuth; volatile float _az_corr; volatile bool _init_az_corr; RazorAHRS* _tracker; }; } // namespace ssr #endif // TRACKERRAZOR_H // 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'\:'=' ssr-0.4.2/src/trackervrpn.cpp000066400000000000000000000110461236416011200161560ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// VRPN tracker (implementation). #ifndef _REENTRANT #error You need to compile with _REENTRANT defined since this uses threads! #endif #include "trackervrpn.h" #include // for runtime_error #include "publisher.h" #include "ssr_global.h" #include "apf/math.h" // for pi ssr::TrackerVrpn::TrackerVrpn(Publisher& controller, const std::string& address) : vrpn_Tracker_Remote(address.c_str()) , _controller(controller) , _stopped(false) , _az_corr(90.0f) , _thread_id(0) { VERBOSE("Starting VRPN tracker \"" << address << "\""); // TODO: what exactly is this supposed to do? //this->set_update_rate(120); this->register_change_handler(this, _vrpn_change_handler); _start(); // wait until tracker has started vrpn_SleepMsecs(50); this->calibrate(); } ssr::TrackerVrpn::~TrackerVrpn() { if (_thread_id) _stop(); } ssr::TrackerVrpn::ptr_t ssr::TrackerVrpn::create(Publisher& controller, const std::string& ports) { ptr_t temp; // temp = NULL try { temp.reset(new TrackerVrpn(controller, ports)); } catch(std::runtime_error& e) { ERROR(e.what()); } return temp; } void ssr::TrackerVrpn::calibrate() { _az_corr = _current_azimuth + 90.0f; } void ssr::TrackerVrpn::_start() { pthread_create(&_thread_id, nullptr, _thread, this); VERBOSE("Starting tracker ..."); } void ssr::TrackerVrpn::_stop() { _stopped = true; pthread_join(_thread_id, 0); } void* ssr::TrackerVrpn::_thread(void *arg) { return static_cast(arg)->thread(nullptr); } void* ssr::TrackerVrpn::thread(void *arg) { while (!_stopped) { this->mainloop(); // TODO: make this configurable: vrpn_SleepMsecs(10); }; return arg; } void VRPN_CALLBACK ssr::TrackerVrpn::_vrpn_change_handler(void* arg, const vrpn_TRACKERCB t) { return static_cast(arg)->vrpn_change_handler(t); } void ssr::TrackerVrpn::vrpn_change_handler(const vrpn_TRACKERCB t) { // TODO: check t.sensor for sensor number! // get quaternions information double w = t.quat[0]; double x = t.quat[1]; double y = t.quat[2]; double z = t.quat[3]; // calculate yaw (azimuth) from quaternions double azi = atan2(2*(w*x+y*z),1-2*(x*x+y*y))*(180/apf::math::pi()); _current_azimuth = azi; _controller.set_reference_orientation(Orientation(-azi + _az_corr)); } // 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'\:'=' ssr-0.4.2/src/trackervrpn.h000066400000000000000000000070241236416011200156240ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// VRPN tracker (definition). #ifndef SSR_TRACKERVRPN_H #define SSR_TRACKERVRPN_H #include #include #include // for std::unique_ptr #include #include "tracker.h" namespace ssr { struct Publisher; // forward declaration /// VRPN tracker class TrackerVrpn : public vrpn_Tracker_Remote, public Tracker { public: using ptr_t = std::unique_ptr; virtual ~TrackerVrpn(); /// "named constructor" static ptr_t create(Publisher& controller, const std::string& portsz); virtual void calibrate(); void set_value(double azi); private: TrackerVrpn(Publisher& controller, const std::string& ports); Publisher& _controller; std::string _address; double _current_azimuth; volatile bool _stopped; ///< stops the tracking thread float _az_corr; ///< correction of the azimuth due to calibration void _start(); ///< start the tracking thread void _stop(); ///< stop the tracking thread // thread related stuff pthread_t _thread_id; static void* _thread(void*); void* thread(void*); static void VRPN_CALLBACK _vrpn_change_handler(void* arg , const vrpn_TRACKERCB t); void vrpn_change_handler(const vrpn_TRACKERCB t); }; } // namespace ssr #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'\:'=' ssr-0.4.2/src/vbaprenderer.h000066400000000000000000000326501236416011200157450ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Vector Base Amplitude Panning renderer. #ifndef SSR_VBAPRENDERER_H #define SSR_VBAPRENDERER_H #include "loudspeakerrenderer.h" #include "apf/combine_channels.h" namespace ssr { /** Vector Base Amplitude Panning Renderer. * The loudspeaker weights are calculated according the the vector base * formulation proposed by Ville Pulkki in "Virtual Sound Source Positioning * Using Vector Base Amplitude Panning", Journal of the Audio Engineering * Society (JAES), Vol.45(6), June 1997.\par * The speaker weights are calculated as follows: * \f$ \displaystyle g_{left} = \frac{\cos \phi \sin \phi_0 + * \sin \phi \cos \phi_0}{2 \cos \phi_0 \sin \phi_0} \f$ * \f$ \displaystyle g_{right} = \frac{\cos \phi \sin \phi_0 - * \sin \phi \cos \phi_0}{2 \cos \phi_0 \sin \phi_0} \f$ * \par * whereby \f$ \displaystyle \phi\f$ denotes blah, blah **/ class VbapRenderer : public LoudspeakerRenderer { private: using _base = ssr::LoudspeakerRenderer; public: static const char* name() { return "VBAP-Renderer"; } class Source; class Output; class RenderFunction; explicit VbapRenderer(const apf::parameter_map& params) : _base(params) , _max_angle(params.get("vbap_max_angle", apf::math::deg2rad(180.0))) , _overhang_angle( params.get("vbap_overhang_angle", apf::math::deg2rad(30.0))) , _overhang_func(2 * _overhang_angle) , _reference_offset_position(this->state.reference_offset_position.get()) {} void load_reproduction_setup(); APF_PROCESS(VbapRenderer, _base) { // WARNING: The reference offset is currently broken! // To make it work, we have to fiddle a bit. auto temp = this->state.reference_offset_position.get(); temp.rotate(-90.0); _reference_offset_position = temp; // TODO: once the reference offset is implemented correctly, do only this: //_reference_offset_position = this->state.reference_offset_position(); if (_reference_offset_position.changed()) { // TODO: check if reference is 'inside' the array? _update_angles(); // The (circular) order will always be the same in a convex array. // However, angles may be wrapped around 0 and 2*pi. // Only in this case the list has to be re-sorted. if (_sorted_loudspeakers.back() < _sorted_loudspeakers.front()) { _sort_loudspeakers(); } _update_valid_sections(); } _absolute_reference_offset_position = Position(_reference_offset_position).rotate( this->state.reference_orientation) + this->state.reference_position; _process_list(_source_list); } private: struct LoudspeakerEntry { // Note: This is non-explicit to allow comparison with angle LoudspeakerEntry(float angle_, bool valid_section_ = false , const Output* output_ = nullptr) : angle(angle_) , valid_section(valid_section_) , ls_ptr(output_) {} // Comparison operator is needed for std::sort() and std::upper_bound() friend bool operator<(const LoudspeakerEntry& lhs, const LoudspeakerEntry& rhs) { return lhs.angle < rhs.angle; } float angle; // radians bool valid_section; // Is the angle to the next loudspeaker < _max_angle? const Output* ls_ptr; }; struct LoudspeakerWeight { const Output* ls_ptr = nullptr; float weight = 0.0; }; void _update_angles(); void _sort_loudspeakers(); void _update_valid_sections(); float _max_angle, _overhang_angle; apf::math::raised_cosine _overhang_func; std::vector _sorted_loudspeakers; apf::BlockParameter _reference_offset_position; Position _absolute_reference_offset_position; }; class VbapRenderer::Source : public _base::Source { public: Source(const Params& p) : _base::Source(p) {} APF_PROCESS(Source, _base::Source) { // NOTE: reference_offset_orientation doesn't affect rendering float incidence_angle = apf::math::wrap_two_pi(apf::math::deg2rad( ((this->position - this->parent._absolute_reference_offset_position).orientation() - this->parent.state.reference_orientation).azimuth)); auto l_begin = this->parent._sorted_loudspeakers.begin(); auto l_end = this->parent._sorted_loudspeakers.end(); auto second = apf::make_circular_iterator(l_begin, l_end , std::upper_bound(l_begin, l_end, incidence_angle)); auto first = second; --first; auto weights = _calculate_loudspeaker_weights(incidence_angle , *first, *second); // Apply source volume, mute, ... weights.first.weight *= this->weighting_factor; weights.second.weight *= this->weighting_factor; this->loudspeaker_weights = weights; assert(this->loudspeaker_weights.first.exactly_one_assignment()); assert(this->loudspeaker_weights.second.exactly_one_assignment()); } bool get_output_levels(sample_type* first, sample_type* last) const { auto current = first; for (const auto& out: rtlist_proxy(parent.get_output_list())) { // TODO: handle subwoofers! if (this->loudspeaker_weights.first.get().ls_ptr == &out) { *current = this->loudspeaker_weights.first.get().weight; } else if (this->loudspeaker_weights.second.get().ls_ptr == &out) { *current = this->loudspeaker_weights.second.get().weight; } else { *current = 0; } ++current; } assert(current == last); (void)last; return true; } private: std::pair _calculate_loudspeaker_weights(float angle , const LoudspeakerEntry& first, const LoudspeakerEntry& second); public: std::pair , apf::BlockParameter> loudspeaker_weights; }; class VbapRenderer::RenderFunction { public: RenderFunction(const Output& out) : _out(out) {} apf::CombineChannelsResult::type select(const Source& in); sample_type operator()(sample_type in) { return in * _weight; } sample_type operator()(sample_type in, sample_type index) { return in * _interpolator(index); } private: sample_type _weight; apf::math::linear_interpolator _interpolator; const Output& _out; }; class VbapRenderer::Output : public _base::Output { public: Output(const Params& p) : _base::Output(p) , _combiner(this->parent._source_list, this->buffer) { // TODO: handle loudspeaker delays? // TODO: optional delay line? } APF_PROCESS(Output, _base::Output) { _combiner.process(RenderFunction(*this)); } private: apf::CombineChannelsInterpolation, buffer_type> _combiner; }; void VbapRenderer::load_reproduction_setup() { // TODO: find a way to avoid overwriting load_reproduction_setup() _base::load_reproduction_setup(); // TODO: check somehow if loudspeaker setup is reasonable? // TODO: get loudspeaker delays from setup? // delay_samples = size_t(delay * sample_rate + 0.5f) for (const auto& out: rtlist_proxy(this->get_output_list())) { if (out.model == Loudspeaker::subwoofer) { // TODO: put subwoofers in separate list? (for get_output_levels()) } else // loudspeaker type == normal { _sorted_loudspeakers.emplace_back(0, false, &out); } } if (_sorted_loudspeakers.size() < 1) { throw std::logic_error("No loudspeakers found!"); } _update_angles(); _sort_loudspeakers(); _update_valid_sections(); } apf::CombineChannelsResult::type VbapRenderer::RenderFunction::select(const Source& in) { const auto& ls = in.loudspeaker_weights; assert(ls.first.get().ls_ptr != ls.second.get().ls_ptr || ls.first.get().ls_ptr == nullptr); auto get_weight = [this] (const LoudspeakerWeight& first , const LoudspeakerWeight& second) { float weight = 0; if (first.ls_ptr == &_out) { weight = first.weight; } else if (second.ls_ptr == &_out) { weight = second.weight; } return weight; }; auto old_weight = get_weight(ls.first.old(), ls.second.old()); auto new_weight = get_weight(ls.first, ls.second); using namespace apf::CombineChannelsResult; if (old_weight == 0 && new_weight == 0) { return nothing; } else if (old_weight == new_weight) { _weight = new_weight; return constant; } else { _interpolator.set(old_weight, new_weight, in.parent.block_size()); return change; } } void VbapRenderer::_update_angles() { for (auto& ls: _sorted_loudspeakers) { // NOTE: reference_offset_orientation doesn't affect rendering ls.angle = apf::math::wrap_two_pi(apf::math::deg2rad((ls.ls_ptr->position - _reference_offset_position).orientation().azimuth)); } } void VbapRenderer::_sort_loudspeakers() { std::sort(_sorted_loudspeakers.begin(), _sorted_loudspeakers.end()); } void VbapRenderer::_update_valid_sections() { for (auto ls = _sorted_loudspeakers.begin() ; ls != _sorted_loudspeakers.end() ; ++ls) { auto next_ls = ls; if (++next_ls == _sorted_loudspeakers.end()) { // If there is only one loudspeaker, it will always be invalid. next_ls = _sorted_loudspeakers.begin(); ls->valid_section = (ls->angle + _max_angle) >= (next_ls->angle + apf::math::deg2rad(360.0)); } else { ls->valid_section = (ls->angle + _max_angle) >= next_ls->angle; } } } std::pair VbapRenderer::Source::_calculate_loudspeaker_weights(float source_angle , const LoudspeakerEntry& first, const LoudspeakerEntry& second) { using namespace apf::math; // Constructed with defaults: nullptr/0.0 std::pair weights; if (first.valid_section) { // phi_0: angle from halfway between loudspeakers to left loudspeaker // phi: angle from halfway between loudspeakers to source float phi_0 = wrap_two_pi(second.angle - first.angle) / 2.0f; float phi = wrap_two_pi(source_angle - first.angle - phi_0); float num1 = std::cos(phi) * std::sin(phi_0); float num2 = std::sin(phi) * std::cos(phi_0); float den = 2 * std::cos(phi_0) * std::sin(phi_0); weights.second.weight = (num1 + num2) / den; weights.first.weight = (num1 - num2) / den; weights.second.ls_ptr = second.ls_ptr; weights.first.ls_ptr = first.ls_ptr; } else { float max_overhang = this->parent._overhang_angle; const auto& overhang_func = this->parent._overhang_func; float overhang = wrap_two_pi(source_angle - first.angle); if (overhang < max_overhang) { weights.first.weight = overhang_func(overhang); weights.first.ls_ptr = first.ls_ptr; } overhang = wrap_two_pi(second.angle - source_angle); if (overhang < max_overhang) { weights.second.weight = overhang_func(overhang); weights.second.ls_ptr = second.ls_ptr; } } return weights; } } // namespace ssr #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'\:'=' ssr-0.4.2/src/wfsrenderer.h000066400000000000000000000414341236416011200156140ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// Wave Field Synthesis renderer. #ifndef SSR_WFSRENDERER_H #define SSR_WFSRENDERER_H #include "loudspeakerrenderer.h" #include "ssr_global.h" #include "apf/convolver.h" // for apf::conv::... #include "apf/blockdelayline.h" // for NonCausalBlockDelayLine #include "apf/sndfiletools.h" // for apf::load_sndfile #include "apf/combine_channels.h" // for apf::raised_cosine_fade, ... // TODO: make more flexible option: #define WEIGHTING_OLD //#define WEIGHTING_DELFT namespace ssr { class WfsRenderer : public SourceToOutput { private: using _base = SourceToOutput; public: static const char* name() { return "WFS-Renderer"; } class Input; class Source; class SourceChannel; class Output; class RenderFunction; WfsRenderer(const apf::parameter_map& params) : _base(params) , _fade(this->block_size()) , _max_delay(this->params.get("delayline_size", 0)) , _initial_delay(this->params.get("initial_delay", 0)) { // TODO: compute "ideal" initial delay? // TODO: check if given initial delay is sufficient? // TODO: make option --prefilter=none? // TODO: get pre-filter from reproduction setup! // TODO: allow alternative files for different sample rates SndfileHandle prefilter; try { prefilter = apf::load_sndfile( this->params.get("prefilter_file", ""), this->sample_rate(), 1); } catch (const std::logic_error& e) { throw std::logic_error( "Error loading WFS pre-equalization filter file: " + std::string(e.what())); } size_t size = prefilter.frames(); auto ir = apf::fixed_vector(size); size = prefilter.readf(ir.data(), size); // TODO: warning if size changed? // TODO: warning if size == 0? _pre_filter.reset(new apf::conv::Filter(this->block_size() , ir.begin(), ir.end())); } APF_PROCESS(WfsRenderer, _base) { this->_process_list(_source_list); } private: apf::raised_cosine_fade _fade; std::unique_ptr _pre_filter; size_t _max_delay, _initial_delay; }; class WfsRenderer::Input : public _base::Input { public: friend class Source; // give access to _delayline Input(const Params& p) : _base::Input(p) // TODO: check if _pre_filter != 0! , _convolver(*this->parent._pre_filter) , _delayline(this->parent.block_size(), this->parent._max_delay , this->parent._initial_delay) {} APF_PROCESS(Input, _base::Input) { _convolver.add_block(this->buffer.begin()); _delayline.write_block(_convolver.convolve()); } private: apf::conv::StaticConvolver _convolver; apf::NonCausalBlockDelayLine _delayline; }; class WfsRenderer::SourceChannel : public apf::has_begin_and_end< apf::NonCausalBlockDelayLine::circulator> { public: SourceChannel(const Source& s) : crossfade_mode(0) , weighting_factor(0.0f) , delay(0) , source(s) {} void update(); int crossfade_mode; apf::BlockParameter weighting_factor; apf::BlockParameter delay; const Source& source; // TODO: avoid making those public: using apf::has_begin_and_end ::circulator>::_begin; using apf::has_begin_and_end ::circulator>::_end; }; class WfsRenderer::RenderFunction { public: RenderFunction(const Output& out) : _in(0), _out(out) {} apf::CombineChannelsResult::type select(SourceChannel& in); sample_type operator()(sample_type in) { return in * _new_factor; } sample_type operator()(sample_type in, apf::fade_out_tag) { return in * _old_factor; } void update() { assert(_in); _in->update(); } private: sample_type _old_factor, _new_factor; SourceChannel* _in; const Output& _out; }; class WfsRenderer::Output : public _base::Output { public: friend class Source; // to be able to see _sourcechannels Output(const Params& p) : _base::Output(p) , _combiner(this->sourcechannels, this->buffer, this->parent._fade) {} APF_PROCESS(Output, _base::Output) { _combiner.process(RenderFunction(*this)); } private: apf::CombineChannelsCrossfade, buffer_type , apf::raised_cosine_fade> _combiner; }; class WfsRenderer::Source : public _base::Source { private: void _process(); public: Source(const Params& p) : _base::Source(p, p.parent->get_output_list().size(), *this) , delayline(p.input->_delayline) {} APF_PROCESS(Source, _base::Source) { _process(); } bool get_output_levels(sample_type* first, sample_type* last) const { assert(size_t(std::distance(first, last)) == this->sourcechannels.size()); auto channel = this->sourcechannels.begin(); for ( ; first != last; ++first) { *first = channel->weighting_factor; ++channel; } return true; } const apf::NonCausalBlockDelayLine& delayline; //private: bool _focused; }; void WfsRenderer::Source::_process() { if (this->model == ::Source::plane) { // do nothing, focused-ness is irrelevant for plane waves _focused = false; } else { _focused = true; for (const auto& out: rtlist_proxy(_input.parent.get_output_list())) { // subwoofers have to be ignored! if (out.model == Loudspeaker::subwoofer) continue; // TODO: calculate with inner product // angle (modulo) between the line connecting source<->loudspeaker // and the loudspeaker orientation // TODO: avoid getting reference 2 times (see select()) auto ls = DirectionalPoint(out); auto ref = DirectionalPoint(out.parent.state.reference_position , out.parent.state.reference_orientation); ls.transform(ref); auto a = apf::math::wrap(angle(ls.position - this->position , ls.orientation), 2 * apf::math::pi()); auto halfpi = apf::math::pi()/2; if (a < halfpi || a > 3 * halfpi) { // if at least one loudspeaker "turns its back" to the source, the // source is considered non-focused _focused = false; break; } } } // TODO: active sources? } void WfsRenderer::SourceChannel::update() { _begin = this->source.delayline.get_read_circulator(this->delay); _end = _begin + source.parent.block_size(); } apf::CombineChannelsResult::type WfsRenderer::RenderFunction::select(SourceChannel& in) { _in = ∈ // define a restricted area around loudspeakers to avoid division by zero: const float safety_radius = 0.01f; // 1 cm // TODO: move reference calculation to WfsRenderer::Process? auto ref = DirectionalPoint(_out.parent.state.reference_position , _out.parent.state.reference_orientation); // TODO: this is actually wrong! // We use it to be compatible with the (also wrong) GUI implementation. auto ref_off = ref; ref_off.transform(DirectionalPoint( _out.parent.state.reference_offset_position , _out.parent.state.reference_offset_orientation)); sample_type weighting_factor = 1; float float_delay = 0; auto ls = Loudspeaker(_out); auto src_pos = in.source.position; // TODO: shortcut if in.source.weighting_factor == 0 // Transform loudspeaker position according to reference and offset ls.transform(ref); float reference_distance = (ls.position - ref_off.position).length(); float source_ls_distance = (ls.position - src_pos).length(); switch (in.source.model) // check if point source or plane wave or ... { case ::Source::point: if (ls.model == Loudspeaker::subwoofer) { // the delay is calculated to be correct on the reference position // delay can be negative! float_delay = (src_pos - ref_off.position).length() - reference_distance; if (std::abs(float_delay) < safety_radius) { weighting_factor = 1.0f / std::sqrt(safety_radius); } else { weighting_factor = 1.0f / std::sqrt(std::abs(float_delay)); } break; // step out of switch } float_delay = (ls.position - src_pos).length(); assert(float_delay >= 0); float denominator; if (float_delay < safety_radius) denominator = std::sqrt(safety_radius); else denominator = std::sqrt(float_delay); // TODO: does this really do the right thing? weighting_factor = cos(angle(ls.position - src_pos, ls.orientation)) / denominator; if (weighting_factor < 0.0f) { // negative weighting factor is only valid for focused sources if (in.source._focused) { // loudspeaker selection: // this could also be done by using the cosine function instead of // inner product // calculate inner product of those two vectors: // this = source auto lhs = ls.position - src_pos; auto rhs = ref_off.position - src_pos; // TODO: write inner product function in Position class if ((lhs.x * rhs.x + lhs.y * rhs.y) < 0.0f) { // if the inner product is less than zero, the source is more or // less between the loudspeaker and the reference float_delay = -float_delay; weighting_factor = -weighting_factor; #if defined(WEIGHTING_OLD) (void)source_ls_distance; // avoid "unused variable" warning #elif defined(WEIGHTING_DELFT) // limit to a maximum of 2.0 weighting_factor *= std::min(2.0f, std::sqrt(source_ls_distance / (reference_distance + source_ls_distance))); #endif } else { // ignored focused point source weighting_factor = 0; break; } } else // non-focused and weighting_factor < 0 { // ignored non-focused point source weighting_factor = 0; break; } } else if(weighting_factor > 0.0f) // positive weighting factor { if (!in.source._focused) { // non-focused point source #if defined(WEIGHTING_OLD) #elif defined(WEIGHTING_DELFT) // WARNING: division by zero is possible! weighting_factor *= std::sqrt(source_ls_distance / (reference_distance + source_ls_distance)); #endif } else // focused { // ignored focused point source break; } } else { // this should never happen: Weighting factor is 0 or +-Inf or NaN! break; } break; case ::Source::plane: if (ls.model == Loudspeaker::subwoofer) { weighting_factor = 1.0f; // TODO: is this correct? // the delay is calculated to be correct on the reference position // delay can be negative! float_delay = DirectionalPoint(in.source.position, in.source.orientation) .plane_to_point_distance(ref_off.position) - reference_distance; break; // step out of switch } // weighting factor is determined by the cosine of the angle // difference between plane wave direction and loudspeaker direction weighting_factor = cos(angle(in.source.orientation, ls.orientation)); // check if loudspeaker is active for this source if (weighting_factor < 0) { // ignored plane wave weighting_factor = 0; break; } float_delay = DirectionalPoint(in.source.position, in.source.orientation) .plane_to_point_distance(ls.position); if (float_delay < 0.0) { // "focused" plane wave } else // positive delay { // plane wave } break; default: //WARNING("Unknown source model"); break; } // switch source model // no distance attenuation for plane waves if (in.source.model == ::Source::plane) { float ampl_ref = _out.parent.state.amplitude_reference_distance; assert(ampl_ref > 0); // 1/r: weighting_factor *= 0.5f / ampl_ref; // 1/sqrt(r) //weighting_factor *= 0.25f / std::sqrt(ampl_ref); } else { #if defined(WEIGHTING_OLD) // consider distance attenuation float source_distance = (src_pos - ref_off.position).length(); // no volume increase for sources closer than 0.5m to reference position source_distance = std::max(source_distance, 0.5f); weighting_factor *= 0.5f / source_distance; // 1/r // weighting_factor *= 0.25f / std::sqrt(source_distance); // 1/sqrt(r) #elif defined(WEIGHTING_DELFT) #endif } // apply the gain factor of the current source weighting_factor *= in.source.weighting_factor; // apply tapering weighting_factor *= ls.weight; assert(weighting_factor >= 0.0f); // delay in seconds float_delay *= c_inverse; // delay in samples float_delay *= _out.parent.sample_rate(); // TODO: check for negative delay and print an error if > initial_delay // TODO: do proper rounding // TODO: enable interpolated reading from delay line. int int_delay = static_cast(float_delay + 0.5f); if (in.source.delayline.delay_is_valid(int_delay)) { in.delay = int_delay; in.weighting_factor = weighting_factor; } else { // TODO: some sort of warning message? in.delay = 0; in.weighting_factor = 0; } assert(in.weighting_factor.exactly_one_assignment()); assert(in.delay.exactly_one_assignment()); _old_factor = in.weighting_factor.old(); _new_factor = in.weighting_factor; using namespace apf::CombineChannelsResult; auto crossfade_mode = apf::CombineChannelsResult::type(); if (_old_factor == 0 && _new_factor == 0) { crossfade_mode = nothing; } else if (_old_factor == _new_factor && !in.delay.changed()) { crossfade_mode = constant; } else if (_old_factor == 0) { crossfade_mode = fade_in; } else if (_new_factor == 0) { crossfade_mode = fade_out; } else { crossfade_mode = change; } if (crossfade_mode == nothing || crossfade_mode == fade_in) { // No need to read the delayline here } else { in._begin = in.source.delayline.get_read_circulator(in.delay.old()); in._end = in._begin + _out.parent.block_size(); } return crossfade_mode; } } // namespace ssr #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'\:'=' ssr-0.4.2/src/xmlparser.cpp000066400000000000000000000300611236416011200156300ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// XML parser class (implementation). #include #include #include "xmlparser.h" // TODO: avoid error messages in this kind of "library"? /// print error message to standard error #define ERR(msg) std::cerr << msg << std::endl //#define PRINT(msg) std::cout << msg << std::endl XMLParser::~XMLParser() { } //xmlCleanupParser(); } void XMLParser::Init() { xmlInitParser(); } XMLParser::doc_t XMLParser::load_file(const std::string& file_name) const { doc_t temp; // temp = NULL try { temp.reset(new Document(file_name, true)); } catch(Document::document_error& e) { ERR(e.what()); } return temp; } XMLParser::doc_t XMLParser::load_string(const std::string& xml_string) const { doc_t temp; // temp = NULL try { temp.reset(new Document(xml_string, false)); } catch(Document::document_error& e) { ERR(e.what()); } return temp; } std::string XMLParser::replace_entities(const std::string& input) const { xmlChar* xml_string; xml_string = xmlEncodeEntitiesReentrant(nullptr, BAD_CAST input.c_str()); if (!xml_string) return ""; std::string temp((char*)xml_string); xmlFree(xml_string); return temp; } XMLParser::Document::~Document() { if (_xpath_context) xmlXPathFreeContext(_xpath_context); if (_doc) xmlFreeDoc(_doc); } bool XMLParser::Document::validate(const std::string& schema_file_name) const { if (!_doc) { ERR("Document not loaded!"); return false; } xmlSchemaParserCtxtPtr schema_ctxt; schema_ctxt = xmlSchemaNewParserCtxt(schema_file_name.c_str()); if (schema_ctxt == nullptr) { ERR("error in xmlSchemaNewParserCtxt!"); return false; } xmlSchemaPtr schema; schema = xmlSchemaParse(schema_ctxt); if (schema == nullptr) { ERR("error in xmlSchemaParse!"); return false; } xmlSchemaFreeParserCtxt(schema_ctxt); xmlSchemaValidCtxtPtr valid_schema; valid_schema = xmlSchemaNewValidCtxt(schema); if (valid_schema == nullptr) { ERR("error in xmlSchemaNewValidCtxt!"); return false; } int schema_result; // Returns 1 if valid so far, 0 if errors were detected, // and -1 in case of internal error. schema_result = xmlSchemaIsValid(valid_schema); switch (schema_result) { case 1: //PRINT("Valid schema!"); break; case 0: ERR("Invalid schema!"); return false; case -1: ERR("Internal error in xmlSchemaIsValid()!"); return false; default: ERR("Unknown return value of xmlSchemaIsValid()!"); return false; } int doc_result; // Returns 0 if the document is schemas valid, a positive error code // otherwise and -1 in case of internal or API error. doc_result = xmlSchemaValidateDoc(valid_schema, _doc); //////////////////////////////////////////////////////////////////// // WARNING // WARNING // WARNING // WARNING // WARNING // WARNING // // // the return values of xmlSchemaIsValid() and xmlSchemaValidateDoc() // have a completely different meaning! ///////////////////////////////////////////////////////////////////// if (doc_result > 0) { ERR("Invalid doc! (error code: " << doc_result << ")"); return false; } else if (doc_result < 0) { ERR("Internal error in xmlSchemaValidateDoc()! " "(error code: " << doc_result << ")"); return false; } xmlSchemaFreeValidCtxt(valid_schema); return true; } //int xmlSchemaValidateOneElement(valid_schema, xmlNodePtr elem); XMLParser::XPathResult::XPathResult(const xmlXPathObjectPtr xpath_object) : _xpath_object(xpath_object), _current(0) {} XMLParser::XPathResult::~XPathResult() { if (_xpath_object) xmlXPathFreeObject(_xpath_object); } xmlNodePtr XMLParser::XPathResult::node() const { if (!_xpath_object) return nullptr; if (_current >= _xpath_object->nodesetval->nodeNr) return nullptr; return _xpath_object->nodesetval->nodeTab[_current]; } int XMLParser::XPathResult::size() const { if (!_xpath_object) return 0; return _xpath_object->nodesetval->nodeNr; } /** _. * The corresponding postfix operator (xpath++) is not implemented because it * would also need a copy constructor to be implemented. For now, the * preincrement operator is sufficient. **/ XMLParser::XPathResult& XMLParser::XPathResult::operator++() { _current++; return *this; } // not implemented to avoid implementing the copy ctor: /* XPathResult XMLParser::XPathResult::operator++(int) { XPathResult old(*this); // copy ctor needed! operator++(); return old; } */ XMLParser::xpath_t XMLParser::Document::eval_xpath( const std::string& expression) const { if (!_xpath_available()) { ERR("Couldn't create XPath context!"); return xpath_t(nullptr); } xmlXPathObjectPtr result; result = xmlXPathEvalExpression(BAD_CAST expression.c_str(), _xpath_context); if (result == nullptr) { ERR("Error in xmlXPathEvalExpression!"); return xpath_t(nullptr); } if(xmlXPathNodeSetIsEmpty(result->nodesetval)) { xmlXPathFreeObject(result); //ERR("No result!"); return xpath_t(nullptr); } return xpath_t(new XPathResult(result)); } XMLParser::Document::Document(const std::string& input, bool file) throw (document_error) : _doc(nullptr), _xpath_context(nullptr) { if (file) { _doc = xmlParseFile(input.c_str()); if (_doc == nullptr) { throw document_error("Error in xmlParseFile (" + input + ")!"); } } else { _doc = xmlParseDoc(BAD_CAST input.c_str()); if (_doc == nullptr) { throw document_error("error in xmlParseDoc (" + input + ")!"); } } } bool XMLParser::Document::_xpath_available() const { if (_xpath_context == nullptr) { _xpath_context = xmlXPathNewContext(_doc); if (_xpath_context == nullptr) { ERR("Error in xmlXPathNewContext"); return false; } } return true; } XMLParser::Node::Node(const xmlNodePtr node) : _node(node) {} XMLParser::Node XMLParser::new_node(const std::string& name) { return Node(xmlNewNode(nullptr, BAD_CAST name.c_str())); } /* bool XMLParser::Node::add_child(const Node& child_node) { return xmlAddChild(_node, child_node.get()); } */ xmlNodePtr XMLParser::Node::new_child(const std::string& name, const std::string& content) { if (!_node) return nullptr; return xmlNewChild(_node, nullptr, BAD_CAST name.c_str(), content == "" ? nullptr : BAD_CAST content.c_str()); } void XMLParser::Node::new_attribute(const std::string& name, const std::string& value) { xmlNewProp(_node, BAD_CAST name.c_str(), BAD_CAST value.c_str()); } std::string XMLParser::Node::get_attribute(const std::string& attr_name) const { if (!_node) return ""; xmlChar* xml_string; xml_string = xmlGetProp(_node, BAD_CAST attr_name.c_str()); if (!xml_string) return ""; std::string temp((char*)xml_string); xmlFree(xml_string); return temp; } std::string get_content(const xmlNodePtr& node) { if (!node) return ""; xmlChar* xml_string; xml_string = xmlNodeGetContent(node); std::string result((char*)xml_string); xmlFree(xml_string); return result; } std::string get_content(const XMLParser::Node& node) { return get_content(node.get()); } /* ._ * @todo return pointer to the old node? or to the new node? or nothing? **/ void XMLParser::Node::descend() throw (std::underflow_error) { if (!_node) throw std::underflow_error( "XMLParser::Node::descend(): empty node!"); _node = _node->xmlChildrenNode; } XMLParser::Node XMLParser::Node::child() const throw (std::underflow_error) { if (!_node) throw std::underflow_error( "XMLParser::Node::child(): empty node!"); return(Node(_node->xmlChildrenNode)); } XMLParser::Node XMLParser::Node::child(const std::string& name) const { for (Node i = this->child(); !!i; ++i) { if (i == name) return i; } return Node(nullptr); } xmlNodePtr XMLParser::Node::get() const { return _node; } std::string XMLParser::Node::to_string() { //xmlDocPtr doc = xmlNewDoc(BAD_CAST "1.0"); //xmlDocSetRootElement(doc, _node); //int size; //xmlChar * temp; //xmlDocDumpFormatMemory(doc, &temp, &size, 1); //xmlDocDumpMemory(doc, &temp, &size); xmlBufferPtr buffer = xmlBufferCreate(); //if (xmlNodeDump(buffer, doc, _node, 0, 1) == -1) if ( !_node ) { std::cout << "Empty _node!\n"; return ""; } // last arg: 1 = format, 0 = everything in one line if (xmlNodeDump(buffer, _node->doc, _node, 0, 0) == -1) //if (xmlNodeBufGetContent(buffer, _node) != 0) { std::cout << "couldn't convert XML document to string!\n"; return ""; } std::string result = (char *)xmlBufferContent(buffer); //std::string result = (char *)temp; xmlBufferFree(buffer); //xmlFree(temp); //xmlFreeDoc(doc); // when doc is freed, all nodes are deleted, too //_node = nullptr; return result; } /* std::string XMLParser::Node::to_string() { xmlDocPtr doc = xmlNewDoc(BAD_CAST "1.0"); xmlNodePtr node = xmlCopyNode(_node, 1); xmlDocSetRootElement(doc, node); int size; xmlChar * temp; xmlDocDumpFormatMemory(doc, &temp, &size, 1); //xmlChar * temp = xmlNodeListGetString(_node->doc, _node, 1); std::string result = (char *)temp; xmlFree(temp); xmlFreeDoc(doc); return result; } */ // preincrement XMLParser::Node& XMLParser::Node::operator++() throw (std::overflow_error) { if (!_node) throw std::overflow_error( "XMLParser::Node::operator++: empty node!"); _node = _node->next; return *this; } xmlNodePtr XMLParser::Node::operator=(const xmlNodePtr node) { _node = node; return node; } bool XMLParser::Node::operator!() const { return !_node; } bool operator==(const XMLParser::Node& node, const std::string& str) { return !xmlStrcmp(node.get()->name, BAD_CAST str.c_str()); } bool operator==(const std::string& str, const XMLParser::Node& node) { return node == str; } std::ostream& operator<<(std::ostream& stream, const XMLParser::Node& node) { stream << (char*)node.get()->name; return stream; } // 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'\:'=' ssr-0.4.2/src/xmlparser.h000066400000000000000000000152521236416011200153020ustar00rootroot00000000000000/****************************************************************************** * Copyright © 2012-2014 Institut für Nachrichtentechnik, Universität Rostock * * Copyright © 2006-2012 Quality & Usability Lab, * * Telekom Innovation Laboratories, TU Berlin * * * * This file is part of the SoundScape Renderer (SSR). * * * * The SSR 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 SSR 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 . * * * * The SSR is a tool for real-time spatial audio reproduction providing a * * variety of rendering algorithms. * * * * http://spatialaudio.net/ssr ssr@spatialaudio.net * ******************************************************************************/ /// @file /// XML parser class (definition). #ifndef SSR_XMLPARSER_H #define SSR_XMLPARSER_H #include #include // for std::unique_ptr #include #include #include #include #include "apf/misc.h" // for NonCopyable /// Wrapper for libxml2 struct XMLParser { // using automatically generated ctor. ~XMLParser(); static void Init(); class Document; // forward declaration class XPathResult; // forward declaration class Node; // forward declaration typedef std::unique_ptr doc_t; typedef std::unique_ptr xpath_t; doc_t load_file(const std::string& file_name) const; doc_t load_string(const std::string& xml_string) const; // we should put that outside of the class in the surrounding namespace, but // we don't have one ... std::string replace_entities(const std::string& input) const; Node new_node(const std::string& name); }; //////////////////////////////////////////////////////////////////////////////// // Definition of the nested class Document //////////////////////////////////////////////////////////////////////////////// /// XML document. class XMLParser::Document { public: /// exception to be thrown by ctor. struct document_error : public std::runtime_error { document_error(const std::string& s): std::runtime_error(s) {} }; explicit Document(const std::string& input, bool file = true) throw (document_error); ~Document(); bool validate(const std::string& schema_file_name) const; xpath_t eval_xpath(const std::string& expression) const; private: bool _xpath_available() const; xmlDocPtr _doc; mutable xmlXPathContextPtr _xpath_context; }; //////////////////////////////////////////////////////////////////////////////// // Definition of the nested class XPathResult //////////////////////////////////////////////////////////////////////////////// /** The result of an XPath query. * Holds an xmlXPathObjectPtr and allows to iterate through the results. **/ class XMLParser::XPathResult : apf::NonCopyable { public: explicit XPathResult(const xmlXPathObjectPtr xpath_object); ///< ctor. ~XPathResult(); ///< dtor. xmlNodePtr node() const; ///< return the current node of the nodeset int size() const; ///< return the number of results in the nodeset XPathResult& operator++(); ///< prefix operator (++xpath) // not implemented to avoid implementing the copy ctor: // XPathResult operator++(int); // postfix operator (xpath++) private: xmlXPathObjectPtr _xpath_object; ///< pointer to the original result int _current; ///< number of the current node in the nodeset }; //////////////////////////////////////////////////////////////////////////////// // Definition of the nested class Node //////////////////////////////////////////////////////////////////////////////// /// node of a DOM tree class XMLParser::Node { public: // ctor is not explicit, can be used for implicit conversions! Node(const xmlNodePtr node = nullptr); ///< ctor //bool add_child(const Node& child_node); xmlNodePtr new_child(const std::string& name, const std::string& content = ""); void new_attribute(const std::string& name, const std::string& value); /// get a named attribute from the current node. std::string get_attribute(const std::string& attr_name) const; void descend() throw (std::underflow_error); ///< go to the first child Node child() const throw (std::underflow_error); ///< get the first child Node child(const std::string& name) const; ///< get element named @a name xmlNodePtr get() const; std::string to_string(); Node& operator++() throw (std::overflow_error); xmlNodePtr operator= (const xmlNodePtr); bool operator! () const; /// identity operator (==) between a node and a node name friend bool operator==(const Node& node, const std::string& str); /// identity operator (==) between a node name and a node friend bool operator==(const std::string& str, const Node& node); /// output stream operator (<<) friend std::ostream& operator<<(std::ostream& stream, const Node& node); private: xmlNodePtr _node; }; std::string get_content(const xmlNodePtr& node); ///< get node content std::string get_content(const XMLParser::Node& node); ///< get node content #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'\:'=' ssr-0.4.2/update_apf.txt000066400000000000000000000011111236416011200151630ustar00rootroot00000000000000How to update the Audio Processing Framework (APF) code. Prerequisites: 'git subtree' (included in git >= 1.7.11, but only in "contrib") WARNING: all of this should be done on the master branch! # adding a remote: git remote add -f apf https://github.com/AudioProcessingFramework/apf.git # repository setup (only the very first time): git subtree add --prefix=apf --squash apf/master # pulling: git fetch apf git subtree merge --prefix=apf --squash apf/master # pushing: git subtree split --prefix=apf --annotate='(SSR) ' --branch apf-changes git push apf apf-changes:master