pax_global_header00006660000000000000000000000064122603637310014515gustar00rootroot0000000000000052 comment=d97bd72879939ebf314637068218335d5005f3fe gqrx-sdr-2.2.0.74.d97bd7/000077500000000000000000000000001226036373100145155ustar00rootroot00000000000000gqrx-sdr-2.2.0.74.d97bd7/.gitignore000066400000000000000000000001111226036373100164760ustar00rootroot00000000000000build/ Makefile gqrx.pro.user* *.o moc_* qrc_* ui_* .DS_Store Info.plist gqrx-sdr-2.2.0.74.d97bd7/COPYING000066400000000000000000001045131226036373100155540ustar00rootroot00000000000000 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 . gqrx-sdr-2.2.0.74.d97bd7/README-mac.txt000066400000000000000000000017301226036373100167520ustar00rootroot00000000000000This is Gqrx 2.2 for Mac. Gqrx is a simple software defined radio receiver application for Linux and Mac, powered by GNU Radio and the Qt GUI toolkit. This binary distribution includes support for the following devices: - Funcube Dongle Pro and Pro+ - RTL-SDR (via USB or TCP) - USRP devices from Ettus Research - HackRF Jawbreaker - OsmoSDR Plug in your device and start Gqrx. Your device should be detected and shown in the drop-down list. Note that some input devices (e.g. hackrf) will always be present in the drop-down list even if you don't have plugged in. The bundle also includes various command line utilities that come with the driver libraries. They are located in Gqrx.app/Contents/MacOS/ Gqrx can also be started from the command line with various options that are useful for manipulating the configuration. Run 'Gqrx.app/Contents/MacOS/Gqrx -h' to get a list of options. Gqrx 2.2 for Mac should be considered beta software. Feedback is welcome: http://gqrx.dk/ gqrx-sdr-2.2.0.74.d97bd7/README.md000066400000000000000000000113441226036373100157770ustar00rootroot00000000000000Gqrx ==== Gqrx is an experimental software defined radio receiver implemented using GNU Radio and the Qt GUI toolkit. Currently it works on Linux and Mac and supports the following devices: - Funcube Dongle Pro and Pro+ - RTL2832U-based DVB-T dongles (rtlsdr via USB and TCP) - OsmoSDR - USRP - HackRF Jawbreaker - Nuand bladeRF - RFspace SDR-IQ, SDR-IP and NetSDR - any other device supported by the gr-osmosdr library Gqrx can operate as a traditional AM/FM/SSB receiver with audio output or as an FFT-only instrument. Download -------- Gqrx is distributed as source code package and binaries for Linux and Mac. Please see http://gqrx.dk/download for a list of official and third party download resources. Usage ----- The first time you start gqrx it will open a device configuration dialog. Supported devices that are connected to the computer are discovered automatically and you can select any of them in the drop-down list. If you don't see your device listed in the drop-down list it could be because: - The driver has not included in a binary distribution - The udev rule has not been properly configured You can test your device first with rtl_test, qthid, or uhd_usrp_probe that come with the respective packages. Gqrx supports multiple configurations and sessions if you have several devices or if you want to use the same device under different configurations. You can load a configuration from the GUI or using the -c command line argument. See "gqrx --help" for a complete list of command line arguments. Tutorials and howtos are being written and published on the website http://gqrx.dk/ Known problems -------------- See the bug tracker on Github: https://github.com/csete/gqrx/issues Getting help and reporting bugs ------------------------------- There is now a Google group for discussing anything related to Gqrx: https://groups.google.com/forum/#!forum/gqrx This includes getting help with installation and troubleshooting. Please remember to provide detailed description of your problem, your setup, what steps you followed, etc. Installation from source ------------------------ The source code is hosted on Github: https://github.com/csete/gqrx To compile gqrx from source you need the following dependencies: - GNU Radio 3.7 with the following components: - gnuradio-runtime - gnuradio-analog - gnuradio-blocks - gnuradio-filter - gnuradio-fft - gnuradio-audio - The gr-iqbalance library (optional) - At least one of: - Funcube Dongle Pro driver via gnuradio-fcd - UHD driver via gnuradio-uhd - Funcube Dongle Pro+ driver from https://github.com/dl1ksv/gr-fcdproplus - RTL-SDR driver from http://cgit.osmocom.org/cgit/rtl-sdr/ - OsmoSDR driver from http://cgit.osmocom.org/cgit/osmo-sdr/ - HackRF Jawbreaker driver from http://greatscottgadgets.com/hackrf/ - gnuradio-osmosdr from http://cgit.osmocom.org/cgit/gr-osmosdr/ - pulseaudio (Linux only and optional) - Qt 4.7 or later and optionally Qt Creator (gqrx works with Qt 5) Gqrx comes with a simple qmake build setup. It can be compiled from within Qt Creator or in a terminal:
$ git clone https://github.com/csete/gqrx.git gqrx.git
$ cd gqrx.git
$ mkdir build
$ cd build
$ qmake ..
$ make
To build in debug mode add "CONFIG+=debug" to the qmake step above. There are also some other qmake options, see the gqrx.pro file. Credits and License ------------------- Gqrx is designed and written by Alexandru Csete OZ9AEC, and it is licensed under the GNU General Public License. Some of the source files were adopted from Cutesdr by Moe Weatley and these come with a BSD license. Following people and organisations have contributed: Alex Grinkov: - FM stereo demodulator. Anthony Willard: - Various fixes and improvements Elias Önal: - Building Gqrx on Mac OS X. - Crash recovery dialog. Frank Brickle, AB2KT: Bob McGwier, N4HY: - Noise blanker (from dttsp). Göran Weinholt, SM6-8239: - Various GUI improvements. Jiří Pinkava: - Port to gnuradio 3.7 API. Kobra @ Xiatek: - Auto squelch. Michael Dickens: - Bugfixes on OSX. Moe Weatley: - FFT plotter and waterfall. - Frequency selector. - Signal strength indicator. - AGC Nadeem Hasan: - Bug fixes. Nokia: - QtColorPicker widget. Stefano Leucci: - Peak detection and hold for the FFT plot. Vesa Solonen: - DC removal in AM demodulator. Vincent Pelletier - Initial work on the horizontal zooming / scrolling. Will Scales - Bug fixes. Also thanks to Volker Schroer and Alexey Bazhin for bringing Funcube Dongle Pro+ support to GNU Radio and Gqrx. Some of the icons are from the GNOME and Tango icon themes. The scope.svg icon is based on the utilities-system-monitor.svg icon from the Mint-X icon theme licensed under GNU GPL. Let me know if somebody or someting is missing from the list! Alex OZ9AEC gqrx-sdr-2.2.0.74.d97bd7/applications/000077500000000000000000000000001226036373100172035ustar00rootroot00000000000000gqrx-sdr-2.2.0.74.d97bd7/applications/gqrx/000077500000000000000000000000001226036373100201645ustar00rootroot00000000000000gqrx-sdr-2.2.0.74.d97bd7/applications/gqrx/gqrx.h000066400000000000000000000021061226036373100213150ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2012 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #define GQRX_ORG_NAME "gqrx" #define GQRX_ORG_DOMAIN "gqrx.org" #define GQRX_APP_NAME "gqrx" #define GQRX_CONFIG_VERSION 2 //#define GQRX_VERSION_MAJOR 2 //#define GQRX_VERSION_MINOR 0 //#define GQRX_VERSION_MICRO 0 gqrx-sdr-2.2.0.74.d97bd7/applications/gqrx/main.cpp000066400000000000000000000113041226036373100216130ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2013 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include "mainwindow.h" #include "gqrx.h" #include #include namespace po = boost::program_options; static void reset_conf(const QString &file_name); int main(int argc, char *argv[]) { QString cfg_file; std::string conf; bool clierr=false; bool edit_conf = false; QApplication a(argc, argv); QCoreApplication::setOrganizationName(GQRX_ORG_NAME); QCoreApplication::setOrganizationDomain(GQRX_ORG_DOMAIN); QCoreApplication::setApplicationName(GQRX_APP_NAME); QCoreApplication::setApplicationVersion(VERSION); // setup controlport via environment variables // see http://lists.gnu.org/archive/html/discuss-gnuradio/2013-05/msg00270.html // Note: tried using gr::prefs().save() but that doesn't have effect until the next time if (qputenv("GR_CONF_CONTROLPORT_ON", "False")) qDebug() << "Controlport disabled"; else qDebug() << "Failed to disable controlport"; //#ifdef GQRX_OS_MACX #ifdef WITH_PORTAUDIO // FIXME: This should be user configurable although for now // the audio-osx-source is useless and the only way to use the // Funcube Dongle Pro and Pro+ is via portaudio. if (qputenv("GR_CONF_AUDIO_AUDIO_MODULE", "portaudio")) qDebug() << "GR_CONF_AUDIO_AUDIO_MODULE set to portaudio"; else qDebug() << "Failed to set GR_CONF_AUDIO_AUDIO_MODULE=portaudio"; #endif // setup the program options po::options_description desc("Command line options"); desc.add_options() ("help,h", "This help message") ("conf,c", po::value(&conf), "Start with this config file") ("edit,e", "Edit the config file before using it") ("reset,r", "Reset configuration file") ; po::variables_map vm; try { po::store(po::parse_command_line(argc, argv, desc), vm); } catch(const boost::program_options::invalid_command_line_syntax& ex) { /* happens if e.g. -c without file name */ clierr = true; } catch(const boost::program_options::unknown_option& ex) { /* happens if e.g. -c without file name */ clierr = true; } po::notify(vm); // print the help message if (vm.count("help") || clierr) { std::cout << "Gqrx software defined radio receiver " << VERSION << std::endl << desc << std::endl; return 1; } if (!conf.empty()) { cfg_file = QString::fromStdString(conf); qDebug() << "User specified config file:" << cfg_file; } else { cfg_file = "default.conf"; qDebug() << "No user supplied config file. Using" << cfg_file; } if (vm.count("reset")) reset_conf(cfg_file); else if (vm.count("edit")) edit_conf = true; // Mainwindow will check whether we have a configuration // and open the config dialog if there is none or the specified // file does not exist. MainWindow w(cfg_file, edit_conf); if (w.configOk) { w.show(); return a.exec(); } else { return 1; } } /*! \brief Reset configuration file specified by file_name. */ static void reset_conf(const QString &file_name) { QString cfg_file; QByteArray xdg_dir = qgetenv("XDG_CONFIG_HOME"); if (xdg_dir.isEmpty()) cfg_file = QString("%1/.config/gqrx/%2").arg(QDir::homePath()).arg(file_name); else cfg_file = QString("%1/gqrx/%2").arg(xdg_dir.data()).arg(file_name); if (QFile::exists(cfg_file)) { if (QFile::remove(cfg_file)) qDebug() << cfg_file << "deleted"; else qDebug() << "Failed to remove" << cfg_file; } else { qDebug() << "Cano not delete" << cfg_file << "- file does not exist!"; } } gqrx-sdr-2.2.0.74.d97bd7/applications/gqrx/mainwindow.cpp000066400000000000000000001501121226036373100230440ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2013 Alexandru Csete OZ9AEC. * Copyright (C) 2013 by Elias Oenal * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include "qtgui/ioconfig.h" #include "mainwindow.h" /* Qt Designer files */ #include "ui_mainwindow.h" /* DSP */ #include "receiver.h" #include "remote_control_settings.h" MainWindow::MainWindow(const QString cfgfile, bool edit_conf, QWidget *parent) : QMainWindow(parent), configOk(true), ui(new Ui::MainWindow), d_lnb_lo(0), d_hw_freq(0), d_fftAvg(0.5), d_have_audio(true), dec_afsk1200(0) { ui->setupUi(this); /* Initialise default configuration directory */ QByteArray xdg_dir = qgetenv("XDG_CONFIG_HOME"); if (xdg_dir.isEmpty()) m_cfg_dir = QString("%1/.config/gqrx").arg(QDir::homePath()); // Qt takes care of conversion to native separators else m_cfg_dir = QString("%1/gqrx").arg(xdg_dir.data()); setWindowTitle(QString("Gqrx %1").arg(VERSION)); /* frequency control widget */ ui->freqCtrl->setup(10, (quint64) 0, (quint64) 9999e6, 1, UNITS_MHZ); ui->freqCtrl->setFrequency(144500000); d_filter_shape = receiver::FILTER_SHAPE_NORMAL; /* create receiver object */ rx = new receiver("", ""); rx->set_rf_freq(144500000.0f); // remote controller remote = new RemoteControl(); /* meter timer */ meter_timer = new QTimer(this); connect(meter_timer, SIGNAL(timeout()), this, SLOT(meterTimeout())); /* FFT timer & data */ iq_fft_timer = new QTimer(this); connect(iq_fft_timer, SIGNAL(timeout()), this, SLOT(iqFftTimeout())); audio_fft_timer = new QTimer(this); connect(audio_fft_timer, SIGNAL(timeout()), this, SLOT(audioFftTimeout())); d_fftData = new std::complex[MAX_FFT_SIZE]; d_realFftData = new double[MAX_FFT_SIZE]; d_pwrFftData = new double[MAX_FFT_SIZE](); d_iirFftData = new double[MAX_FFT_SIZE]; for (int i = 0; i < MAX_FFT_SIZE; i++) d_iirFftData[i] = -120.0; // dBFS /* timer for data decoders */ dec_timer = new QTimer(this); connect(dec_timer, SIGNAL(timeout()), this, SLOT(decoderTimeout())); /* create dock widgets */ uiDockRxOpt = new DockRxOpt(); uiDockAudio = new DockAudio(); uiDockInputCtl = new DockInputCtl(); //uiDockIqPlay = new DockIqPlayer(); uiDockFft = new DockFft(); /* Add dock widgets to main window. This should be done even for dock widgets that are going to be hidden, otherwise they will end up floating in their own top-level window and can not be docked to the mainwindow. */ addDockWidget(Qt::RightDockWidgetArea, uiDockInputCtl); addDockWidget(Qt::RightDockWidgetArea, uiDockRxOpt); tabifyDockWidget(uiDockInputCtl, uiDockRxOpt); addDockWidget(Qt::RightDockWidgetArea, uiDockAudio); addDockWidget(Qt::RightDockWidgetArea, uiDockFft); tabifyDockWidget(uiDockFft, uiDockAudio); //addDockWidget(Qt::BottomDockWidgetArea, uiDockIqPlay); /* hide docks that we don't want to show initially */ /** FIXME: Hide them initially but store layout in config **/ // uiDockInputCtl->hide(); // uiDockFft->hide(); //uiDockIqPlay->hide(); /* misc configurations */ //uiDockAudio->setFftRange(0, 8000); // FM /* Add dock widget actions to View menu. By doing it this way all signal/slot connections will be established automagially. */ ui->menu_View->addAction(uiDockInputCtl->toggleViewAction()); ui->menu_View->addAction(uiDockRxOpt->toggleViewAction()); ui->menu_View->addAction(uiDockAudio->toggleViewAction()); ui->menu_View->addAction(uiDockFft->toggleViewAction()); //ui->menu_View->addAction(uiDockIqPlay->toggleViewAction()); ui->menu_View->addSeparator(); ui->menu_View->addAction(ui->mainToolBar->toggleViewAction()); ui->menu_View->addSeparator(); ui->menu_View->addAction(ui->actionFullScreen); /* connect signals and slots */ connect(ui->freqCtrl, SIGNAL(newFrequency(qint64)), this, SLOT(setNewFrequency(qint64))); connect(ui->freqCtrl, SIGNAL(newFrequency(qint64)), remote, SLOT(setNewFrequency(qint64))); connect(uiDockInputCtl, SIGNAL(lnbLoChanged(double)), this, SLOT(setLnbLo(double))); connect(uiDockInputCtl, SIGNAL(gainChanged(QString, double)), this, SLOT(setGain(QString,double))); connect(uiDockInputCtl, SIGNAL(autoGainChanged(bool)), this, SLOT(setAutoGain(bool))); connect(uiDockInputCtl, SIGNAL(freqCorrChanged(int)), this, SLOT(setFreqCorr(int))); connect(uiDockInputCtl, SIGNAL(iqSwapChanged(bool)), this, SLOT(setIqSwap(bool))); connect(uiDockInputCtl, SIGNAL(dcCancelChanged(bool)), this, SLOT(setDcCancel(bool))); connect(uiDockInputCtl, SIGNAL(iqBalanceChanged(bool)), this, SLOT(setIqBalance(bool))); connect(uiDockInputCtl, SIGNAL(ignoreLimitsChanged(bool)), this, SLOT(setIgnoreLimits(bool))); connect(uiDockInputCtl, SIGNAL(antennaSelected(QString)), this, SLOT(setAntenna(QString))); connect(uiDockRxOpt, SIGNAL(filterOffsetChanged(qint64)), this, SLOT(setFilterOffset(qint64))); connect(uiDockRxOpt, SIGNAL(filterOffsetChanged(qint64)), remote, SLOT(setFilterOffset(qint64))); connect(uiDockRxOpt, SIGNAL(demodSelected(int)), this, SLOT(selectDemod(int))); connect(uiDockRxOpt, SIGNAL(fmMaxdevSelected(float)), this, SLOT(setFmMaxdev(float))); connect(uiDockRxOpt, SIGNAL(fmEmphSelected(double)), this, SLOT(setFmEmph(double))); connect(uiDockRxOpt, SIGNAL(amDcrToggled(bool)), this, SLOT(setAmDcr(bool))); connect(uiDockRxOpt, SIGNAL(agcToggled(bool)), this, SLOT(setAgcOn(bool))); connect(uiDockRxOpt, SIGNAL(agcHangToggled(bool)), this, SLOT(setAgcHang(bool))); connect(uiDockRxOpt, SIGNAL(agcThresholdChanged(int)), this, SLOT(setAgcThreshold(int))); connect(uiDockRxOpt, SIGNAL(agcSlopeChanged(int)), this, SLOT(setAgcSlope(int))); connect(uiDockRxOpt, SIGNAL(agcGainChanged(int)), this, SLOT(setAgcGain(int))); connect(uiDockRxOpt, SIGNAL(agcDecayChanged(int)), this, SLOT(setAgcDecay(int))); connect(uiDockRxOpt, SIGNAL(noiseBlankerChanged(int,bool,float)), this, SLOT(setNoiseBlanker(int,bool,float))); connect(uiDockRxOpt, SIGNAL(sqlLevelChanged(double)), this, SLOT(setSqlLevel(double))); connect(uiDockRxOpt, SIGNAL(sqlAutoClicked()), this, SLOT(setSqlLevelAuto())); connect(uiDockAudio, SIGNAL(audioGainChanged(float)), this, SLOT(setAudioGain(float))); connect(uiDockAudio, SIGNAL(audioStreamingStarted(QString,int)), this, SLOT(startAudioStream(QString,int))); connect(uiDockAudio, SIGNAL(audioStreamingStopped()), this, SLOT(stopAudioStreaming())); connect(uiDockAudio, SIGNAL(audioRecStarted(QString)), this, SLOT(startAudioRec(QString))); connect(uiDockAudio, SIGNAL(audioRecStopped()), this, SLOT(stopAudioRec())); connect(uiDockAudio, SIGNAL(audioPlayStarted(QString)), this, SLOT(startAudioPlayback(QString))); connect(uiDockAudio, SIGNAL(audioPlayStopped()), this, SLOT(stopAudioPlayback())); connect(uiDockAudio, SIGNAL(fftRateChanged(int)), this, SLOT(setAudioFftRate(int))); connect(uiDockFft, SIGNAL(fftSizeChanged(int)), this, SLOT(setIqFftSize(int))); connect(uiDockFft, SIGNAL(fftRateChanged(int)), this, SLOT(setIqFftRate(int))); connect(uiDockFft, SIGNAL(fftSplitChanged(int)), this, SLOT(setIqFftSplit(int))); connect(uiDockFft, SIGNAL(fftAvgChanged(double)), this, SLOT(setIqFftAvg(double))); connect(uiDockFft, SIGNAL(resetFftZoom()), ui->plotter, SLOT(resetHorizontalZoom())); connect(uiDockFft, SIGNAL(gotoFftCenter()), ui->plotter, SLOT(moveToCenterFreq())); connect(uiDockFft, SIGNAL(gotoDemodFreq()), ui->plotter, SLOT(moveToDemodFreq())); connect(uiDockFft, SIGNAL(fftColorChanged(QColor)), this, SLOT(setFftColor(QColor))); connect(uiDockFft, SIGNAL(fftFillToggled(bool)), this, SLOT(setFftFill(bool))); connect(uiDockFft, SIGNAL(fftPeakHoldToggled(bool)), this, SLOT(setFftPeakHold(bool))); connect(uiDockFft, SIGNAL(peakDetectionToggled(bool)), this, SLOT(setPeakDetection(bool))); connect(remote, SIGNAL(newFilterOffset(qint64)), this, SLOT(setFilterOffset(qint64))); connect(remote, SIGNAL(newFilterOffset(qint64)), uiDockRxOpt, SLOT(setFilterOffset(qint64))); connect(remote, SIGNAL(newFrequency(qint64)), this, SLOT(setNewFrequency(qint64))); // satellite events connect(remote, SIGNAL(satAosEvent()), uiDockAudio, SLOT(startAudioRecorder())); connect(remote, SIGNAL(satLosEvent()), uiDockAudio, SLOT(stopAudioRecorder())); // restore last session if (!loadConfig(cfgfile, true)) { // first time config qDebug() << "Launching I/O device editor"; if (firstTimeConfig() != QDialog::Accepted) { qDebug() << "I/O device configuration cancelled."; configOk = false; } else { configOk = true; } } else if (edit_conf == true) { qDebug() << "Launching I/O device editor"; if (on_actionIoConfig_triggered() != QDialog::Accepted) { qDebug() << "I/O device configuration cancelled."; configOk = false; } else { configOk = true; } } } MainWindow::~MainWindow() { /* stop and delete timers */ dec_timer->stop(); delete dec_timer; meter_timer->stop(); delete meter_timer; iq_fft_timer->stop(); delete iq_fft_timer; audio_fft_timer->stop(); delete audio_fft_timer; if (m_settings) { m_settings->setValue("configversion", 2); m_settings->setValue("crashed", false); // hide toolbar (default=false) if (ui->mainToolBar->isHidden()) m_settings->setValue("gui/hide_toolbar", true); else m_settings->remove("gui/hide_toolbar"); m_settings->setValue("gui/geometry", saveGeometry()); m_settings->setValue("gui/state", saveState()); // save session storeSession(); m_settings->sync(); delete m_settings; } delete ui; delete uiDockRxOpt; delete uiDockAudio; delete uiDockFft; //delete uiDockIqPlay; delete uiDockInputCtl; delete rx; delete remote; delete [] d_fftData; delete [] d_realFftData; delete [] d_iirFftData; delete [] d_pwrFftData; } /*! \brief Load new configuration. * \param cfgfile * \returns True if config is OK, False if not (e.g. no input device specified). * * If cfgfile is an absolute path it will be used as is, otherwise it is assumed to be the * name of a file under m_cfg_dir. * * If cfgfile does not exist it will be created. * * If no input device is specified, we return false to signal that the I/O configuration * dialog should be run. * * FIXME: Refactor. */ bool MainWindow::loadConfig(const QString cfgfile, bool check_crash) { bool conf_ok = false; bool skip_loading_cfg = false; qDebug() << "Loading configuration from:" << cfgfile; if (m_settings) delete m_settings; if (QDir::isAbsolutePath(cfgfile)) m_settings = new QSettings(cfgfile, QSettings::IniFormat); else m_settings = new QSettings(QString("%1/%2").arg(m_cfg_dir).arg(cfgfile), QSettings::IniFormat); qDebug() << "Configuration file:" << m_settings->fileName(); if (check_crash) { if (m_settings->value("crashed", false).toBool()) { qDebug() << "Crash guard triggered!" << endl; QMessageBox* askUserAboutConfig = new QMessageBox(QMessageBox::Warning, tr("Crash Detected!"), tr("

Gqrx has detected problems with the current configuration. " "Loading the configuration again could cause the application to crash.

" "

Do you want to edit the settings?

"), QMessageBox::Yes | QMessageBox::No); askUserAboutConfig->setDefaultButton(QMessageBox::Yes); askUserAboutConfig->setTextFormat(Qt::RichText); askUserAboutConfig->exec(); if (askUserAboutConfig->result() == QMessageBox::Yes) skip_loading_cfg = true; delete askUserAboutConfig; } else { m_settings->setValue("crashed", true); // clean exit will set this to FALSE m_settings->sync(); } } if (skip_loading_cfg) return false; emit configChanged(m_settings); // manual reconf (FIXME: check status) bool conv_ok = false; // hide toolbar bool bool_val = m_settings->value("gui/hide_toolbar", false).toBool(); if (bool_val) ui->mainToolBar->hide(); // main window settings restoreGeometry(m_settings->value("gui/geometry", saveGeometry()).toByteArray()); restoreState(m_settings->value("gui/state", saveState()).toByteArray()); QString indev = m_settings->value("input/device", "").toString(); if (!indev.isEmpty()) { conf_ok = true; rx->set_input_device(indev.toStdString()); // Update window title QRegExp regexp("'([a-zA-Z0-9 \\-\\_\\/\\.\\,\\(\\)]+)'"); QString devlabel; if (regexp.indexIn(indev, 0) != -1) devlabel = regexp.cap(1); else devlabel = indev; //"Unknown"; setWindowTitle(QString("Gqrx %1 - %2").arg(VERSION).arg(devlabel)); // Add available antenna connectors to the UI std::vector antennas = rx->get_antennas(); uiDockInputCtl->setAntennas(antennas); // update gain stages updateGainStages(); } QString outdev = m_settings->value("output/device", "").toString(); rx->set_output_device(outdev.toStdString()); int sr = m_settings->value("input/sample_rate", 0).toInt(&conv_ok); if (conv_ok && (sr > 0)) { double actual_rate = rx->set_input_rate(sr); qDebug() << "Requested sample rate:" << sr; qDebug() << "Actual sample rate :" << QString("%1").arg(actual_rate, 0, 'f', 6); uiDockRxOpt->setFilterOffsetRange((qint64)(0.9*actual_rate)); ui->plotter->setSampleRate(actual_rate); ui->plotter->setSpanFreq((quint32)actual_rate); remote->setBandwidth(sr); } qint64 bw = m_settings->value("input/bandwidth", 0).toInt(&conv_ok); if (conv_ok) { // set analog bw even if 0 since for some devices 0 Hz means "auto" double actual_bw = rx->set_analog_bandwidth((double)bw); qDebug() << "Requested bandwidth:" << bw << "Hz"; qDebug() << "Actual bandwidth :" << actual_bw << "Hz"; } uiDockInputCtl->readSettings(m_settings); uiDockRxOpt->readSettings(m_settings); uiDockFft->readSettings(m_settings); uiDockAudio->readSettings(m_settings); ui->freqCtrl->setFrequency(m_settings->value("input/frequency", 144500000).toLongLong(&conv_ok)); setNewFrequency(ui->freqCtrl->getFrequency()); // ensure all GUI and RF is updated remote->readSettings(m_settings); return conf_ok; } /*! \brief Save current configuration to a file. * \param cfgfile * \returns True if the operation was successful. * * If cfgfile is an absolute path it will be used as is, otherwise it is assumed to be the * name of a file under m_cfg_dir. * * If cfgfile already exists it will be overwritten (we assume that a file selection dialog * has already asked for confirmation of overwrite. * * Since QSettings does not support "save as" we do this by copying the current * settings to a new file. */ bool MainWindow::saveConfig(const QString cfgfile) { QString oldfile = m_settings->fileName(); QString newfile; qDebug() << "Saving configuration to:" << cfgfile; m_settings->sync(); if (QDir::isAbsolutePath(cfgfile)) newfile = cfgfile; else newfile = QString("%1/%2").arg(m_cfg_dir).arg(cfgfile); if (QFile::exists(newfile)) { qDebug() << "File" << newfile << "already exists => DELETING..."; if (QFile::remove(newfile)) qDebug() << "Deleted" << newfile; else qDebug() << "Failed to delete" << newfile; } if (QFile::copy(oldfile, newfile)) { // ensure that old config has crash cleared m_settings->setValue("crashed", false); m_settings->sync(); loadConfig(cfgfile, false); return true; } else { qDebug() << "Error saving configuration to" << newfile; return false; } } /*! \brief Store session-related parameters (frequency, gain,...) * * This needs to be called when we switch input source, otherwise the * new source would use the parameters stored on last exit. */ void MainWindow::storeSession() { if (m_settings) { m_settings->setValue("input/frequency", ui->freqCtrl->getFrequency()); uiDockInputCtl->saveSettings(m_settings); uiDockRxOpt->saveSettings(m_settings); uiDockFft->saveSettings(m_settings); uiDockAudio->saveSettings(m_settings); remote->saveSettings(m_settings); } } /*! \brief Update RF frequency range. * \param ignore_limits Whether ignore the hardware specd and allow DC-to-light range. * * Useful when we read a new configuration with a new input device. This function will * fetch the frequency range of the receiver and update the frequency control and frequency * bar widgets. * * This function must also be called when the LNB LO has changed. */ void MainWindow::updateFrequencyRange(bool ignore_limits) { double startd, stopd, stepd; if (ignore_limits) { ui->freqCtrl->setup(10, (quint64) 0, (quint64) 9999e6, 1, UNITS_MHZ); } else if (rx->get_rf_range(&startd, &stopd, &stepd) == receiver::STATUS_OK) { qDebug() << QString("New frequnecy range: %1 - %2 MHz (step is %3 Hz but we use 1 Hz)."). arg(startd*1.0e-6).arg(stopd*1.0e-6).arg(stepd); qint64 start = (qint64)startd + d_lnb_lo; qint64 stop = (qint64)stopd + d_lnb_lo; ui->freqCtrl->setup(10, start, stop, 1, UNITS_MHZ); } else { qDebug() << __func__ << "failed fetching new frequency range"; } } /*! \brief Update gain stages. * * This function fetches a list of available gain stages with their range * and sends them to the input control UI widget. */ void MainWindow::updateGainStages() { gain_list_t gain_list; std::vector gain_names = rx->get_gain_names(); gain_t gain; for (std::vector::iterator it = gain_names.begin(); it != gain_names.end(); ++it) { gain.name = *it; rx->get_gain_range(gain.name, &gain.start, &gain.stop, &gain.step); gain.value = rx->get_gain(gain.name); gain_list.push_back(gain); } uiDockInputCtl->setGainStages(gain_list); } /*! \brief Slot for receiving frequency change signals. * \param[in] freq The new frequency. * * This slot is connected to the CFreqCtrl::newFrequency() signal and is used * to set new receive frequency. */ void MainWindow::setNewFrequency(qint64 rx_freq) { double hw_freq = (double)(rx_freq-d_lnb_lo) - rx->get_filter_offset(); qint64 center_freq = rx_freq - (qint64)rx->get_filter_offset(); d_hw_freq = (qint64)hw_freq; // set receiver frequency rx->set_rf_freq(hw_freq); // update widgets ui->plotter->setCenterFreq(center_freq); uiDockRxOpt->setHwFreq(d_hw_freq); } /*! \brief Set new LNB LO frequency. * \param freq_mhz The new frequency in MHz. */ void MainWindow::setLnbLo(double freq_mhz) { // calculate current RF frequency qint64 rf_freq = ui->freqCtrl->getFrequency() - d_lnb_lo; d_lnb_lo = qint64(freq_mhz*1e6); qDebug() << "New LNB LO:" << d_lnb_lo << "Hz"; // Update ranges and show updated frequency updateFrequencyRange(uiDockInputCtl->ignoreLimits()); ui->freqCtrl->setFrequency(d_lnb_lo + rf_freq); ui->plotter->setCenterFreq(d_lnb_lo + d_hw_freq); } /*! \brief Select new antenna connector. */ void MainWindow::setAntenna(const QString antenna) { qDebug() << "New antenna selected:" << antenna; rx->set_antenna(antenna.toStdString()); } /*! \brief Set new channel filter offset. * \param freq_hz The new filter offset in Hz. */ void MainWindow::setFilterOffset(qint64 freq_hz) { rx->set_filter_offset((double) freq_hz); ui->plotter->setFilterOffset(freq_hz); qint64 rx_freq = d_hw_freq + d_lnb_lo + freq_hz; ui->freqCtrl->setFrequency(rx_freq); } /*! \brief Set a specific gain. * \param name The name of the gain stage to adjust. * \param gain The new value. */ void MainWindow::setGain(QString name, double gain) { rx->set_gain(name.toStdString(), gain); } /*! \brief Enable / disable hardware AGC. */ void MainWindow::setAutoGain(bool enabled) { rx->set_auto_gain(enabled); } /*! \brief Set new frequency offset value. * \param ppm Frequency correction. * * The valid range is between -200 and 200, though this is not checked. */ void MainWindow::setFreqCorr(int ppm) { qDebug() << __FUNCTION__ << ":" << ppm << "ppm"; rx->set_freq_corr(ppm); } /*! \brief Enable/disable I/Q reversion. */ void MainWindow::setIqSwap(bool reversed) { rx->set_iq_swap(reversed); } /*! \brief Enable/disable automatic DC removal. */ void MainWindow::setDcCancel(bool enabled) { rx->set_dc_cancel(enabled); } /*! \brief Enable/disable automatic IQ balance. */ void MainWindow::setIqBalance(bool enabled) { rx->set_iq_balance(enabled); } /*! \brief Ignore hardware limits. * \param ignore_limits Whether harware limits should be ignored or not. * * This slot is triggered when the user changes the "Ignore hardware limits" option. * It will update the allowed frequency range and also update the current RF center * frequency, which may change when we swich from ignore to don't ignore. */ void MainWindow::setIgnoreLimits(bool ignore_limits) { updateFrequencyRange(ignore_limits); qint64 freq = (qint64)rx->get_rf_freq(); ui->freqCtrl->setFrequency(d_lnb_lo + freq); // This will ensure that if frequency is clamped, the UI // will be updated with the correct frequwncy. freq = ui->freqCtrl->getFrequency(); setNewFrequency(freq); } /*! \brief Select new demodulator. * \param demod New demodulator index. * * This slot basically maps the index of the mode selector to receiver::demod * and configures the default channel filter. * */ void MainWindow::selectDemod(int index) { double quad_rate; float maxdev; int filter_preset = uiDockRxOpt->currentFilter(); int flo=0, fhi=0, click_res=100; switch (index) { case DockRxOpt::MODE_OFF: /* Spectrum analyzer only */ rx->set_demod(receiver::RX_DEMOD_OFF); flo = 0; fhi = 0; click_res = 100; break; case DockRxOpt::MODE_RAW: /* Raw I/Q */ qDebug() << "RAW I/Q mode not implemented!"; break; /* AM */ case DockRxOpt::MODE_AM: rx->set_demod(receiver::RX_DEMOD_AM); ui->plotter->setDemodRanges(-20000, -250, 250, 20000, true); uiDockAudio->setFftRange(0,15000); click_res = 100; switch (filter_preset) { case 0: //wide flo = -10000; fhi = 10000; break; case 2: // narrow flo = -2500; fhi = 2500; break; default: // normal flo = -5000; fhi = 5000; break; } break; /* Narrow FM */ case DockRxOpt::MODE_NFM: rx->set_demod(receiver::RX_DEMOD_NFM); click_res = 100; maxdev = uiDockRxOpt->currentMaxdev(); if (maxdev < 20000.0) { /** FIXME **/ ui->plotter->setDemodRanges(-25000, -250, 250, 25000, true); uiDockAudio->setFftRange(0,12000); switch (filter_preset) { case 0: //wide flo = -10000; fhi = 10000; break; case 2: // narrow flo = -2500; fhi = 2500; break; default: // normal flo = -5000; fhi = 5000; break; } } else { ui->plotter->setDemodRanges(-45000, -10000, 10000, 45000, true); uiDockAudio->setFftRange(0,24000); switch (filter_preset) { /** FIXME: not sure about these **/ case 0: //wide flo = -45000; fhi = 45000; break; case 2: // narrow flo = -10000; fhi = 10000; break; default: // normal flo = -35000; fhi = 35000; break; } } break; /* Broadcast FM */ case DockRxOpt::MODE_WFM_MONO: case DockRxOpt::MODE_WFM_STEREO: quad_rate = rx->get_input_rate(); if (quad_rate < 200.0e3) ui->plotter->setDemodRanges(-0.9*quad_rate/2.0, -10000, 10000, 0.9*quad_rate/2.0, true); else ui->plotter->setDemodRanges(-250000, -10000, 10000, 250000, true); uiDockAudio->setFftRange(0,24000); /** FIXME: get audio rate from rx **/ click_res = 1000; switch (filter_preset) { case 0: //wide flo = -100000; fhi = 100000; break; case 2: // narrow flo = -60000; fhi = 60000; break; default: // normal flo = -80000; fhi = 80000; break; } if (index == DockRxOpt::MODE_WFM_MONO) rx->set_demod(receiver::RX_DEMOD_WFM_M); else rx->set_demod(receiver::RX_DEMOD_WFM_S); break; /* LSB */ case DockRxOpt::MODE_LSB: rx->set_demod(receiver::RX_DEMOD_SSB); ui->plotter->setDemodRanges(-10000, -100, -5000, 0, false); uiDockAudio->setFftRange(0,3500); click_res = 10; switch (filter_preset) { case 0: //wide flo = -4100; fhi = -100; break; case 2: // narrow flo = -1600; fhi = -200; break; default: // normal flo = -3000; fhi = -200; break; } break; /* USB */ case DockRxOpt::MODE_USB: rx->set_demod(receiver::RX_DEMOD_SSB); ui->plotter->setDemodRanges(0, 5000, 100, 10000, false); uiDockAudio->setFftRange(0,3500); click_res = 10; switch (filter_preset) { case 0: //wide flo = 100; fhi = 4100; break; case 2: // narrow flo = 200; fhi = 1600; break; default: // normal flo = 200; fhi = 3000; break; } break; /* CW-L */ case DockRxOpt::MODE_CWL: rx->set_demod(receiver::RX_DEMOD_SSB); ui->plotter->setDemodRanges(-10000, -100, -5000, 0, false); uiDockAudio->setFftRange(0,1500); click_res = 10; switch (filter_preset) { case 0: //wide flo = -2300; fhi = -200; break; case 2: // narrow flo = -900; fhi = -400; break; default: // normal flo = -1200; fhi = -200; break; } break; /* CW-U */ case DockRxOpt::MODE_CWU: rx->set_demod(receiver::RX_DEMOD_SSB); ui->plotter->setDemodRanges(0, 5000, 100, 10000, false); uiDockAudio->setFftRange(0,1500); click_res = 10; switch (filter_preset) { case 0: //wide flo = 200; fhi = 2300; break; case 2: // narrow flo = 400; fhi = 900; break; default: // normal flo = 200; fhi = 1200; break; } break; default: qDebug() << "Unsupported mode selection: " << index; flo = -5000; fhi = 5000; click_res = 100; break; } qDebug() << "Filter preset for mode" << index << "LO:" << flo << "HI:" << fhi; ui->plotter->setHiLowCutFrequencies(flo, fhi); ui->plotter->setClickResolution(click_res); ui->plotter->setFilterClickResolution(click_res); rx->set_filter((double)flo, (double)fhi, receiver::FILTER_SHAPE_NORMAL); d_have_audio = ((index != DockRxOpt::MODE_OFF) && (index != DockRxOpt::MODE_RAW)); } /*! \brief New FM deviation selected. * \param max_dev The enw FM deviation. */ void MainWindow::setFmMaxdev(float max_dev) { qDebug() << "FM MAX_DEV: " << max_dev; /* receiver will check range */ rx->set_fm_maxdev(max_dev); /* update filter */ if (max_dev < 20000.0) { ui->plotter->setDemodRanges(-25000, -1000, 1000, 25000, true); ui->plotter->setHiLowCutFrequencies(-5000, 5000); rx->set_filter(-5000.0, 5000.0, receiver::FILTER_SHAPE_NORMAL); } else { ui->plotter->setDemodRanges(-45000, -10000, 10000, 45000, true); ui->plotter->setHiLowCutFrequencies(-35000, 35000); rx->set_filter(-35000.0, 35000.0, receiver::FILTER_SHAPE_NORMAL); } } /*! \brief New FM de-emphasis time consant selected. * \param tau The new time constant */ void MainWindow::setFmEmph(double tau) { qDebug() << "FM TAU: " << tau; /* receiver will check range */ rx->set_fm_deemph(tau); } /*! \brief AM DCR status changed (slot). * \param enabled Whether DCR is enabled or not. */ void MainWindow::setAmDcr(bool enabled) { rx->set_am_dcr(enabled); } /*! \brief Audio gain changed. * \param value The new audio gain in dB. */ void MainWindow::setAudioGain(float value) { rx->set_af_gain(value); } /*! \brief Set AGC ON/OFF. * \param agc_on Whether AGC is ON (true) or OFF (false). */ void MainWindow::setAgcOn(bool agc_on) { rx->set_agc_on(agc_on); } /*! \brief AGC hang ON/OFF. * \param use_hang Whether to use hang. */ void MainWindow::setAgcHang(bool use_hang) { rx->set_agc_hang(use_hang); } /*! \brief AGC threshold changed. * \param threshold The new threshold. */ void MainWindow::setAgcThreshold(int threshold) { rx->set_agc_threshold(threshold); } /*! \brief AGC slope factor changed. * \param factor The new slope factor. */ void MainWindow::setAgcSlope(int factor) { rx->set_agc_slope(factor); } /*! \brief AGC manual gain changed. * \param gain The new manual gain in dB. */ void MainWindow::setAgcGain(int gain) { rx->set_agc_manual_gain(gain); } /*! \brief AGC decay changed. * \param factor The new AGC decay. */ void MainWindow::setAgcDecay(int msec) { rx->set_agc_decay(msec); } /*! \brief Noide blanker configuration changed. * \param nb1 Noise blanker 1 ON/OFF. * \param nb2 Noise blanker 2 ON/OFF. * \param threshold Noise blanker threshold. */ void MainWindow::setNoiseBlanker(int nbid, bool on, float threshold) { qDebug() << "Noise blanker NB:" << nbid << " ON:" << on << "THLD:" << threshold; rx->set_nb_on(nbid, on); rx->set_nb_threshold(nbid, threshold); } /*! \brief Squelch level changed. * \param level_db The new squelch level in dBFS. */ void MainWindow::setSqlLevel(double level_db) { rx->set_sql_level(level_db); } /*! \brief Squelch level auto clicked * \return new squeltch value */ double MainWindow::setSqlLevelAuto() { double level = rx->get_signal_pwr(true) + 1.0; setSqlLevel(level); return level; } /*! \brief Signal strength meter timeout */ void MainWindow::meterTimeout() { float level; level = rx->get_signal_pwr(true); ui->sMeter->setLevel(level); } /*! \brief Baseband FFT plot timeout. */ void MainWindow::iqFftTimeout() { unsigned int fftsize; unsigned int i; double gain; double pwr; std::complex pt; /* a single FFT point used in calculations */ std::complex scaleFactor; /* normalizing factor (fftsize cast to complex) */ rx->get_iq_fft_data(d_fftData, fftsize); if (fftsize == 0) { /* nothing to do, wait until next activation. */ return; } scaleFactor = std::complex((float)fftsize); /** FIXME: move post processing to rx_fft_c **/ /* Normalize, calculcate power and shift the FFT */ for (i = 0; i < fftsize; i++) { /* normalize and shift */ if (i < fftsize/2) { pt = d_fftData[fftsize/2+i] / scaleFactor; } else { pt = d_fftData[i-fftsize/2] / scaleFactor; } pwr = pt.imag()*pt.imag() + pt.real()*pt.real(); /* calculate power in dBFS */ d_realFftData[i] = 10.0 * log10(pwr + 1.0e-20); /* FFT averaging (aka. video filter) */ gain = d_fftAvg * (150.0+d_realFftData[i])/150.0; //gain = 0.1; d_iirFftData[i] = (1.0 - gain) * d_iirFftData[i] + gain * d_realFftData[i]; } ui->plotter->setNewFttData(d_iirFftData, d_realFftData, fftsize); } /*! \brief Audio FFT plot timeout. */ void MainWindow::audioFftTimeout() { unsigned int fftsize; unsigned int i; std::complex pt; /* a single FFT point used in calculations */ std::complex scaleFactor; /* normalizing factor (fftsize cast to complex) */ if (!d_have_audio) return; rx->get_audio_fft_data(d_fftData, fftsize); if (fftsize == 0) { /* nothing to do, wait until next activation. */ qDebug() << "No audio FFT data."; return; } scaleFactor = std::complex((float)fftsize); /** FIXME: move post processing to rx_fft_f **/ /* Normalize, calculcate power and shift the FFT */ for (i = 0; i < fftsize; i++) { /* normalize and shift */ if (i < fftsize/2) { pt = d_fftData[fftsize/2+i] / scaleFactor; } else { pt = d_fftData[i-fftsize/2] / scaleFactor; } /* calculate power in dBFS */ d_realFftData[i] = 10.0 * log10(pt.imag()*pt.imag() + pt.real()*pt.real() + 1.0e-20); } uiDockAudio->setNewFttData(d_realFftData, fftsize); } /*! \brief Start audio recorder. * \param filename The file name into which audio should be recorded. */ void MainWindow::startAudioRec(const QString filename) { if (rx->start_audio_recording(filename.toStdString())) { ui->statusBar->showMessage(tr("Error starting audio recorder")); /* reset state of record button */ uiDockAudio->setAudioRecButtonState(false); } else { ui->statusBar->showMessage(tr("Recording audio to %1").arg(filename)); } } /*! \brief Stop audio recorder. */ void MainWindow::stopAudioRec() { if (rx->stop_audio_recording()) { /* okay, this one would be weird if it really happened */ ui->statusBar->showMessage(tr("Error stopping audio recorder")); uiDockAudio->setAudioRecButtonState(true); } else { ui->statusBar->showMessage(tr("Audio recorder stopped"), 5000); } } /*! \brief Start playback of audio file. */ void MainWindow::startAudioPlayback(const QString filename) { if (rx->start_audio_playback(filename.toStdString())) { ui->statusBar->showMessage(tr("Error trying to play %1").arg(filename)); /* reset state of record button */ uiDockAudio->setAudioPlayButtonState(false); } else { ui->statusBar->showMessage(tr("Playing %1").arg(filename)); } } /*! \brief Stop playback of audio file. */ void MainWindow::stopAudioPlayback() { if (rx->stop_audio_playback()) { /* okay, this one would be weird if it really happened */ ui->statusBar->showMessage(tr("Error stopping audio playback")); uiDockAudio->setAudioPlayButtonState(true); } else { ui->statusBar->showMessage(tr("Audio playback stopped"), 5000); } } /*! \brief Start streaming audio over UDP. */ void MainWindow::startAudioStream(const QString udp_host, int udp_port) { rx->start_udp_streaming(udp_host.toStdString(), udp_port); } /*! \brief Stop streaming audio over UDP. */ void MainWindow::stopAudioStreaming() { rx->stop_udp_streaming(); } /*! \brief Start/stop I/Q data playback. * \param play True if playback is started, false if it is stopped. * \param filename Full path of the I/Q data file. */ void MainWindow::toggleIqPlayback(bool play, const QString filename) { if (play) { /* starting playback */ if (rx->start_iq_playback(filename.toStdString(), 96000.0)) { ui->statusBar->showMessage(tr("Error trying to play %1").arg(filename)); } else { ui->statusBar->showMessage(tr("Playing %1").arg(filename)); /* disable REC button */ ui->actionIqRec->setEnabled(false); } } else { /* stopping playback */ if (rx->stop_iq_playback()) { /* okay, this one would be weird if it really happened */ ui->statusBar->showMessage(tr("Error stopping I/Q playback")); } else { ui->statusBar->showMessage(tr("I/Q playback stopped"), 5000); } /* enable REC button */ ui->actionIqRec->setEnabled(true); } } /*! \brief FFT size has changed. */ void MainWindow::setIqFftSize(int size) { qDebug() << "Changing baseband FFT size to" << size; rx->set_iq_fft_size(size); } /*! \brief Baseband FFT rate has changed. */ void MainWindow::setIqFftRate(int fps) { int interval; if (fps == 0) { interval = 36e7; // 100 hours ui->plotter->setRunningState(false); } else { interval = 1000 / fps; if (iq_fft_timer->isActive()) ui->plotter->setRunningState(true); } if (interval > 9 && iq_fft_timer->isActive()) iq_fft_timer->setInterval(interval); } /*! \brief Vertical split between waterfall and pandapter changed. * \param pct_pand The percentage of the waterfall. */ void MainWindow::setIqFftSplit(int pct_wf) { if ((pct_wf >= 10) && (pct_wf <= 100)) { ui->plotter->setPercent2DScreen(pct_wf); } } void MainWindow::setIqFftAvg(double avg) { if ((avg >= 0) && (avg <= 1.0)) d_fftAvg = avg; } /*! \brief Audio FFT rate has changed. */ void MainWindow::setAudioFftRate(int fps) { int interval = 1000 / fps; if (interval < 10) return; if (audio_fft_timer->isActive()) audio_fft_timer->setInterval(interval); } /*! Set FFT plot color. */ void MainWindow::setFftColor(const QColor color) { ui->plotter->setFftPlotColor(color); uiDockAudio->setFftColor(color); } /*! Enalbe/disable filling the aread below the FFT plot. */ void MainWindow::setFftFill(bool enable) { ui->plotter->setFftFill(enable); uiDockAudio->setFftFill(enable); } void MainWindow::setFftPeakHold(bool enable) { ui->plotter->setPeakHold(enable); } void MainWindow::setPeakDetection(bool enabled) { ui->plotter->setPeakDetection(enabled ,2); } /*! \brief Force receiver reconfiguration. * * Aka. jerky dongle workaround. * * This function forces a receiver reconfiguration by sending a fake * selectDemod() signal using the current demodulator selection. * * This function provides a workaround for the "jerky streaming" that has * been experienced using some RTL-SDR dongles when DSP processing is * started. The jerkyness disappears when trhe receiver is reconfigured * by selecting a new demodulator. */ void MainWindow::forceRxReconf() { qDebug() << "Force RX reconf (jerky dongle workarond)..."; selectDemod(uiDockRxOpt->currentDemod()); } /*! \brief Start/Stop DSP processing. * \param checked Flag indicating whether DSP processing should be ON or OFF. * * This slot is executed when the actionDSP is toggled by the user. This can either be * via the menu bar or the "power on" button in the main toolbar. */ void MainWindow::on_actionDSP_triggered(bool checked) { if (checked) { /* start receiver */ rx->start(); /* start GUI timers */ meter_timer->start(100); if (uiDockFft->fftRate()) { iq_fft_timer->start(1000/uiDockFft->fftRate()); ui->plotter->setRunningState(true); } else { iq_fft_timer->start(36e7); // 100 hours ui->plotter->setRunningState(false); } audio_fft_timer->start(40); /* update menu text and button tooltip */ ui->actionDSP->setToolTip(tr("Stop DSP processing")); ui->actionDSP->setText(tr("Stop DSP")); // reconfigure RX after 1s to counteract possible jerky streaming from rtl dongles QTimer::singleShot(1000, this, SLOT(forceRxReconf())); } else { /* stop GUI timers */ meter_timer->stop(); iq_fft_timer->stop(); audio_fft_timer->stop(); /* stop receiver */ rx->stop(); /* update menu text and button tooltip */ ui->actionDSP->setToolTip(tr("Start DSP processing")); ui->actionDSP->setText(tr("Start DSP")); ui->plotter->setRunningState(false); } } /*! \brief Action: I/O device configurator triggered. * * This slot is activated when the user selects "I/O Devices" in the * menu. It activates the I/O configurator and if the user closes the * configurator using the OK button, the new configuration is read and * sent to the receiver. */ int MainWindow::on_actionIoConfig_triggered() { qDebug() << "Configure I/O devices."; CIoConfig *ioconf = new CIoConfig(m_settings); int confres = ioconf->exec(); if (confres == QDialog::Accepted) { if (ui->actionDSP->isChecked()) // suspend DSP while we reload settings on_actionDSP_triggered(false); storeSession(); loadConfig(m_settings->fileName(), false); if (ui->actionDSP->isChecked()) // restsart DSP on_actionDSP_triggered(true); } delete ioconf; return confres; } /*! \brief Runc first time configurator. */ int MainWindow::firstTimeConfig() { qDebug() << __func__; CIoConfig *ioconf = new CIoConfig(m_settings); int confres = ioconf->exec(); if (confres == QDialog::Accepted) loadConfig(m_settings->fileName(), false); delete ioconf; return confres; } /*! \brief Load configuration activated by user. */ void MainWindow::on_actionLoadSettings_triggered() { QString cfgfile = QFileDialog::getOpenFileName(this, tr("Load settings"), m_last_dir.isEmpty() ? m_cfg_dir : m_last_dir, tr("Settings (*.conf)")); qDebug() << "File to open:" << cfgfile; if (cfgfile.isEmpty()) return; if (!cfgfile.endsWith(".conf", Qt::CaseSensitive)) cfgfile.append(".conf"); loadConfig(cfgfile, cfgfile != m_settings->fileName()); // store last dir QFileInfo fi(cfgfile); if (m_cfg_dir != fi.absolutePath()) m_last_dir = fi.absolutePath(); } /*! \brief Save configuration activated by user. */ void MainWindow::on_actionSaveSettings_triggered() { QString cfgfile = QFileDialog::getSaveFileName(this, tr("Save settings"), m_last_dir.isEmpty() ? m_cfg_dir : m_last_dir, tr("Settings (*.conf)")); qDebug() << "File to save:" << cfgfile; if (cfgfile.isEmpty()) return; if (!cfgfile.endsWith(".conf", Qt::CaseSensitive)) cfgfile.append(".conf"); storeSession(); saveConfig(cfgfile); // store last dir QFileInfo fi(cfgfile); if (m_cfg_dir != fi.absolutePath()) m_last_dir = fi.absolutePath(); } /*! \brief Toggle I/Q recording. */ void MainWindow::on_actionIqRec_triggered(bool checked) { Q_UNUSED(checked) #if 0 if (checked) { /* generate file name using date, time, rf freq and BW */ int freq = (int)rx->get_rf_freq()/1000; // FIXME: option to use local time QString lastRec = QDateTime::currentDateTimeUtc().toString("gqrx-yyyyMMdd-hhmmss-%1-96.'bin'").arg(freq); /* start recorder */ if (rx->start_iq_recording(lastRec.toStdString())) { /* reset action status */ ui->actionIqRec->toggle(); ui->statusBar->showMessage(tr("Error starting I/Q recoder")); } else { ui->statusBar->showMessage(tr("Recording I/Q data to: %1").arg(lastRec), 5000); /* disable I/Q player */ uiDockIqPlay->setEnabled(false); } } else { /* stop current recording */ if (rx->stop_iq_recording()) { ui->statusBar->showMessage(tr("Error stopping I/Q recoder")); } else { ui->statusBar->showMessage(tr("I/Q data recoding stopped"), 5000); } /* enable I/Q player */ uiDockIqPlay->setEnabled(true); } #endif } /* CPlotter::NewDemodFreq() is emitted */ void MainWindow::on_plotter_newDemodFreq(qint64 freq, qint64 delta) { // set RX filter rx->set_filter_offset((double) delta); // update RF freq label and channel filter offset uiDockRxOpt->setFilterOffset(delta); ui->freqCtrl->setFrequency(freq); } /* CPlotter::NewfilterFreq() is emitted */ void MainWindow::on_plotter_newFilterFreq(int low, int high) { receiver::status retcode; /* parameter correctness will be checked in receiver class */ retcode = rx->set_filter((double) low, (double) high, d_filter_shape); if (retcode == receiver::STATUS_OK) uiDockRxOpt->setFilterParam(low, high); } void MainWindow::on_plotter_newCenterFreq(qint64 f) { rx->set_rf_freq(f); ui->freqCtrl->setFrequency(f); } /*! \brief Full screen button or menu item toggled. */ void MainWindow::on_actionFullScreen_triggered(bool checked) { if (checked) { ui->statusBar->hide(); showFullScreen(); } else { ui->statusBar->show(); showNormal(); } } /*! \brief Remote control button (or menu item) toggled. */ void MainWindow::on_actionRemoteControl_triggered(bool checked) { if (checked) remote->start_server(); else remote->stop_server(); } /*! \brief Remote control configuration button (or menu item) clicked. */ void MainWindow::on_actionRemoteConfig_triggered() { RemoteControlSettings *rcs = new RemoteControlSettings(); rcs->setPort(remote->getPort()); rcs->setHosts(remote->getHosts()); if (rcs->exec() == QDialog::Accepted) { remote->setPort(rcs->getPort()); remote->setHosts(rcs->getHosts()); } delete rcs; } #define DATA_BUFFER_SIZE 48000 /*! \brief AFSK1200 decoder action triggered. * * This slot is called when the user activates the AFSK1200 * action. It will create an AFSK1200 decoder window and start * and start pushing data from the receiver to it. */ void MainWindow::on_actionAFSK1200_triggered() { if (dec_afsk1200 != 0) { qDebug() << "AFSK1200 decoder already active."; dec_afsk1200->raise(); } else { qDebug() << "Starting AFSK1200 decoder."; /* start sample sniffer */ if (rx->start_sniffer(22050, DATA_BUFFER_SIZE) == receiver::STATUS_OK) { dec_afsk1200 = new Afsk1200Win(this); connect(dec_afsk1200, SIGNAL(windowClosed()), this, SLOT(afsk1200win_closed())); dec_afsk1200->show(); dec_timer->start(100); } else { QMessageBox::warning(this, tr("Gqrx error"), tr("Error starting sample sniffer.\n" "Close all data decoders and try again."), QMessageBox::Ok, QMessageBox::Ok); } } } /*! \brief Destroy AFSK1200 decoder window got closed. * * This slot is connected to the windowClosed() signal of the AFSK1200 decoder * object. We need this to properly destroy the object, stop timeout and clean * up whatever need to be cleaned up. */ void MainWindow::afsk1200win_closed() { /* stop cyclic processing */ dec_timer->stop(); rx->stop_sniffer(); /* delete decoder object */ delete dec_afsk1200; dec_afsk1200 = 0; } /*! \brief Cyclic processing for acquiring samples from receiver and * processing them with data decoders (see dec_* objects) */ void MainWindow::decoderTimeout() { float buffer[DATA_BUFFER_SIZE]; unsigned int num; //qDebug() << "Process decoder"; rx->get_sniffer_data(&buffer[0], num); if (dec_afsk1200) { dec_afsk1200->process_samples(&buffer[0], num); } /* else stop timeout and sniffer? */ } /*! \brief Launch Gqrx google group website. */ void MainWindow::on_actionUserGroup_triggered() { bool res = QDesktopServices::openUrl(QUrl("https://groups.google.com/forum/#!forum/gqrx", QUrl::TolerantMode)); if (!res) { QMessageBox::warning(this, tr("Error"), tr("Failed to open website:\n" "https://groups.google.com/forum/#!forum/gqrx"), QMessageBox::Close); } } /*! \brief Action: About Qthid * * This slot is called when the user activates the * Help|About menu item (or Gqrx|About on Mac) */ void MainWindow::on_actionAbout_triggered() { QMessageBox::about(this, tr("About Gqrx"), tr("

This is Gqrx %1

" "

Copyright (C) 2011-2013 Alexandru Csete & contributors.

" "

Gqrx is a software defined radio receiver powered by " "GNU Radio and the Qt toolkit. " "

Gqrx uses the GrOsmoSDR " "input source block and and works with any input device supported by it including:" "

" "

You can download the latest version from the " "Gqrx website." "

" "

" "Gqrx is licensed under the GNU General Public License." "

").arg(VERSION)); } /*! \brief Action: About Qt * * This slot is called when the user activates the * Help|About Qt menu item (or Gqrx|About Qt on Mac) */ void MainWindow::on_actionAboutQt_triggered() { QMessageBox::aboutQt(this, tr("About Qt")); } gqrx-sdr-2.2.0.74.d97bd7/applications/gqrx/mainwindow.h000066400000000000000000000136311226036373100225150ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2013 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef MAINWINDOW_H #define MAINWINDOW_H #include #include #include #include #include #include #include #include #include "qtgui/dockrxopt.h" #include "qtgui/dockaudio.h" #include "qtgui/dockinputctl.h" #include "qtgui/dockiqplayer.h" #include "qtgui/dockfft.h" #include "qtgui/afsk1200win.h" #include "applications/gqrx/remote_control.h" // see https://bugreports.qt-project.org/browse/QTBUG-22829 #ifndef Q_MOC_RUN #include "applications/gqrx/receiver.h" #endif namespace Ui { class MainWindow; /*! The main window UI */ } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(const QString cfgfile, bool edit_conf, QWidget *parent = 0); ~MainWindow(); bool loadConfig(const QString cfgfile, bool check_crash); bool saveConfig(const QString cfgfile); void storeSession(); bool configOk; /*!< Main app uses this flag to know whether we should abort or continue. */ signals: void configChanged(QSettings *settings); /*!< New configuration has been loaded. */ public slots: void setNewFrequency(qint64 rx_freq); private: Ui::MainWindow *ui; QPointer m_settings; /*!< Application wide settings. */ QString m_cfg_dir; /*!< Default config dir, e.g. XDG_CONFIG_HOME. */ QString m_last_dir; qint64 d_lnb_lo; /* LNB LO in Hz. */ qint64 d_hw_freq; enum receiver::filter_shape d_filter_shape; std::complex* d_fftData; double *d_realFftData; /** FIXME: use vector */ double *d_iirFftData; /** FIXME: use vector */ double *d_pwrFftData; /** FIXME: use vector */ //double *d_audioFttData; double d_fftAvg; /*!< FFT averaging parameter set by user (not the true gain). */ bool d_have_audio; /*!< Whether we have audio (i.e. not with demod_off. */ /* dock widgets */ DockRxOpt *uiDockRxOpt; DockAudio *uiDockAudio; DockInputCtl *uiDockInputCtl; //DockIqPlayer *uiDockIqPlay; DockFft *uiDockFft; /* data decoders */ Afsk1200Win *dec_afsk1200; QTimer *dec_timer; QTimer *meter_timer; QTimer *iq_fft_timer; QTimer *audio_fft_timer; receiver *rx; RemoteControl *remote; private: void updateFrequencyRange(bool ignore_limits); void updateGainStages(); private slots: /* rf */ void setLnbLo(double freq_mhz); void setAntenna(const QString antenna); /* baseband receiver */ void setFilterOffset(qint64 freq_hz); void setGain(QString name, double gain); void setAutoGain(bool enabled); void setFreqCorr(int ppm); void setIqSwap(bool reversed); void setDcCancel(bool enabled); void setIqBalance(bool enabled); void setIgnoreLimits(bool ignore_limits); void selectDemod(int index); void setFmMaxdev(float max_dev); void setFmEmph(double tau); void setAmDcr(bool enabled); void setAgcOn(bool agc_on); void setAgcHang(bool use_hang); void setAgcThreshold(int threshold); void setAgcSlope(int factor); void setAgcDecay(int msec); void setAgcGain(int gain); void setNoiseBlanker(int nbid, bool on, float threshold); void setSqlLevel(double level_db); double setSqlLevelAuto(); void setAudioGain(float gain); /* audio recording and playback */ void startAudioRec(const QString filename); void stopAudioRec(); void startAudioPlayback(const QString filename); void stopAudioPlayback(); void startAudioStream(const QString udp_host, int udp_port); void stopAudioStreaming(); void toggleIqPlayback(bool play, const QString filename); /* FFT settings */ void setIqFftSize(int size); void setIqFftRate(int fps); void setIqFftSplit(int pct_wf); void setIqFftAvg(double avg); void setAudioFftRate(int fps); void setFftColor(const QColor color); void setFftFill(bool enable); void setPeakDetection(bool enabled); void setFftPeakHold(bool enable); void on_plotter_newDemodFreq(qint64 freq, qint64 delta); /*! New demod freq (aka. filter offset). */ void on_plotter_newFilterFreq(int low, int high); /*! New filter width */ void on_plotter_newCenterFreq(qint64 f); /* menu and toolbar actions */ void on_actionDSP_triggered(bool checked); int on_actionIoConfig_triggered(); void on_actionLoadSettings_triggered(); void on_actionSaveSettings_triggered(); void on_actionIqRec_triggered(bool checked); void on_actionFullScreen_triggered(bool checked); void on_actionRemoteControl_triggered(bool checked); void on_actionRemoteConfig_triggered(); void on_actionAFSK1200_triggered(); void on_actionUserGroup_triggered(); void on_actionAbout_triggered(); void on_actionAboutQt_triggered(); /* window close signals */ void afsk1200win_closed(); void forceRxReconf(); int firstTimeConfig(); /* cyclic processing */ void decoderTimeout(); void meterTimeout(); void iqFftTimeout(); void audioFftTimeout(); }; #endif // MAINWINDOW_H gqrx-sdr-2.2.0.74.d97bd7/applications/gqrx/mainwindow.ui000066400000000000000000000302721226036373100227030ustar00rootroot00000000000000 MainWindow 0 0 950 600 gqrx :/icons/icons/scope.svg:/icons/icons/scope.svg QMainWindow::AllowNestedDocks|QMainWindow::AllowTabbedDocks|QMainWindow::AnimatedDocks 2 0 2 0 0 2 0 0 371 41 QFrame::StyledPanel QFrame::Raised 0 0 171 41 QFrame::StyledPanel QFrame::Raised Qt::Horizontal 40 20 0 0 QFrame::StyledPanel QFrame::Raised 0 0 950 29 &File &View &Help &Tools Main toolbar TopToolBarArea false true Quit Quit application Ctrl+Q About Gqrx Information about Gqrx About Qt About the Qt toolkit true :/icons/icons/power-off.svg:/icons/icons/power-off.svg Start DSP Start DSP Ctrl+D AFSK1200 Decoder Start AFSK1200 decoder true false :/icons/icons/clock.svg:/icons/icons/clock.svg Scheduler Schedule recordings and other actions true false :/icons/icons/record.svg:/icons/icons/record.svg Record I/Q Record the raw I/Q samples to a binary file (will be large) Ctrl+R true :/icons/icons/flash.svg:/icons/icons/flash.svg I/O Devices Configure I/O devices true :/icons/icons/help.svg:/icons/icons/help.svg Gqrx user group Discussion group for Gqrx users true :/icons/icons/fullscreen.svg:/icons/icons/fullscreen.svg Full screen Toggle full screen mode F11 :/icons/icons/folder.svg:/icons/icons/folder.svg Load settings Load previously stored configuration Ctrl+L :/icons/icons/floppy.svg:/icons/icons/floppy.svg Save settings Save current configuration Ctrl+S true :/icons/icons/tangeo-network-idle.svg:/icons/icons/tangeo-network-idle.svg &Remote control Remote control via TCP :/icons/icons/settings.svg:/icons/icons/settings.svg Remote &control settings Configure remote control settings CFreqCtrl QFrame
qtgui/freqctrl.h
1
CPlotter QFrame
qtgui/plotter.h
1
CMeter QFrame
qtgui/meter.h
1
actionQuit triggered() MainWindow close() -1 -1 450 257
gqrx-sdr-2.2.0.74.d97bd7/applications/gqrx/receiver.cpp000066400000000000000000000737621226036373100225130ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2013 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include "applications/gqrx/receiver.h" #include "dsp/correct_iq_cc.h" #include "dsp/rx_fft.h" #include "receivers/nbrx.h" #include "receivers/wfmrx.h" #ifdef WITH_PULSEAUDIO //pafix #include "pulseaudio/pa_sink.h" #else #include #endif /*! \brief Public contructor. * \param input_device Input device specifier. * \param audio_device Audio output device specifier, * e.g. hw:0 when using ALSA or Portaudio. * * \todo Option to use UHD device instead of FCD. */ receiver::receiver(const std::string input_device, const std::string audio_device) : d_running(false), d_input_rate(96000.0), d_audio_rate(48000), d_rf_freq(144800000.0), d_filter_offset(0.0), d_recording_wav(false), d_sniffer_active(false), d_iq_rev(false), d_dc_cancel(false), d_iq_balance(false), d_demod(RX_DEMOD_OFF) { tb = gr::make_top_block("gqrx"); if (input_device.empty()) { // FIXME: other OS src = osmosdr::source::make("file=/dev/random,freq=428e6,rate=96000,repeat=true,throttle=true"); } else { input_devstr = input_device; src = osmosdr::source::make(input_device); } rx = make_nbrx(d_input_rate, d_audio_rate); lo = gr::analog::sig_source_c::make(d_input_rate, gr::analog::GR_SIN_WAVE, 0.0, 1.0); mixer = gr::blocks::multiply_cc::make(); iq_swap = make_iq_swap_cc(false); dc_corr = make_dc_corr_cc(d_input_rate, 1.0); iq_fft = make_rx_fft_c(4096u, 0); audio_fft = make_rx_fft_f(4096u); audio_gain0 = gr::blocks::multiply_const_ff::make(0.1); audio_gain1 = gr::blocks::multiply_const_ff::make(0.1); audio_udp_sink = make_udp_sink_f(); #ifdef WITH_PULSEAUDIO //pafix audio_snk = make_pa_sink(audio_device, d_audio_rate, "GQRX", "Audio output"); #else audio_snk = gr::audio::sink::make(d_audio_rate, audio_device, true); #endif output_devstr = audio_device; /* wav sink and source is created when rec/play is started */ audio_null_sink0 = gr::blocks::null_sink::make(sizeof(float)); audio_null_sink1 = gr::blocks::null_sink::make(sizeof(float)); sniffer = make_sniffer_f(); /* sniffer_rr is created at each activation. */ set_demod(RX_DEMOD_NFM); #ifndef QT_NO_DEBUG_OUTPUT gr::prefs pref; std::cout << "Using audio backend: " << pref.get_string("audio", "audio_module", "N/A") << std::endl; #endif } /*! \brief Public destructor. */ receiver::~receiver() { tb->stop(); /* FIXME: delete blocks? */ } /*! \brief Start the receiver. */ void receiver::start() { /* FIXME: Check that flow graph is not running */ if (!d_running) { #ifndef WITH_PULSEAUDIO if(d_demod != RX_DEMOD_OFF) set_output_device(""); #endif tb->start(); d_running = true; } } /*! \brief Stop the receiver. */ void receiver::stop() { if (d_running) { tb->stop(); tb->wait(); // If the graph is needed to run again, wait() must be called after stop d_running = false; } } /*! \brief Select new input device. * * \bug When using ALSA, program will crash if the new device * is the same as the previously used device: * audio_alsa_source[hw:1]: Device or resource busy */ void receiver::set_input_device(const std::string device) { if (device.empty()) return; if (input_devstr.compare(device) == 0) { #ifndef QT_NO_DEBUG_OUTPUT std::cout << "No change in input device:" << std::endl << " old: " << input_devstr << std::endl << " new: " << device << std::endl; #endif return; } input_devstr = device; tb->lock(); tb->disconnect(src, 0, iq_swap, 0); src.reset(); src = osmosdr::source::make(device); tb->connect(src, 0, iq_swap, 0); tb->unlock(); } /*! \brief Select new audio output device. */ void receiver::set_output_device(const std::string device) { if (output_devstr.compare(device) == 0) { #ifndef QT_NO_DEBUG_OUTPUT std::cout << "No change in output device:" << std::endl << " old: " << output_devstr << std::endl << " new: " << device << std::endl; #endif #ifndef GQRX_OS_MACX // we can return on any platform but OS X becasue of // https://github.com/csete/gqrx/issues/66 return; #endif } output_devstr = device; tb->lock(); tb->disconnect(audio_gain0, 0, audio_snk, 0); tb->disconnect(audio_gain1, 0, audio_snk, 1); audio_snk.reset(); #ifdef WITH_PULSEAUDIO audio_snk = make_pa_sink(device, d_audio_rate); #else audio_snk = gr::audio::sink::make(d_audio_rate, device, true); #endif tb->connect(audio_gain0, 0, audio_snk, 0); tb->connect(audio_gain1, 0, audio_snk, 1); tb->unlock(); } /*! \brief Get a list of available antenna connectors. */ std::vector receiver::get_antennas(void) { return src->get_antennas(); } /*! \brief Select antenna conenctor. */ void receiver::set_antenna(const std::string &antenna) { src->set_antenna(antenna); } /*! \brief Set new input sample rate. * \param rate The desired input rate * \return The actual sample rate set. */ double receiver::set_input_rate(double rate) { // don't bother with sub-hertz changes if (fabs(rate-d_input_rate) < 1.0) { #ifndef QT_NO_DEBUG_OUTPUT std::cout << "No siginficant change in input sample rate" << std::endl; #endif } else { tb->lock(); d_input_rate = src->set_sample_rate(rate); dc_corr->set_sample_rate(d_input_rate); rx->set_quad_rate(d_input_rate); lo->set_sampling_freq(d_input_rate); tb->unlock(); } return d_input_rate; } /*! \brief Get current input sample rate. */ double receiver::get_input_rate() { return d_input_rate; } /*! \brief Set new analog bandwidth. * \param bw The new bandwidth. * \return The actual bandwidth. */ double receiver::set_analog_bandwidth(double bw) { return src->set_bandwidth(bw); } /*! \brief Get current analog bandwidth. */ double receiver::get_analog_bandwidth() { return src->get_bandwidth(); } /*! \brief Set I/Q reversed. */ void receiver::set_iq_swap(bool reversed) { if (reversed == d_iq_rev) return; d_iq_rev = reversed; iq_swap->set_enabled(d_iq_rev); } /*! \brief Get current I/Q reversed setting. * \retval true I/Q swappign is enabled. * \retval false I/Q swapping is disabled. */ bool receiver::get_iq_swap(void) { return d_iq_rev; } /*! \brief Enable/disable automatic DC removal in the I/Q stream. * \param enable Whether DC removal should enabled or not. */ void receiver::set_dc_cancel(bool enable) { if (enable == d_dc_cancel) return; d_dc_cancel = enable; // until we have a way to switch on/off // inside the dc_corr_cc we do a reconf rx_demod demod = d_demod; d_demod = RX_DEMOD_OFF; set_demod(demod); } /*! \brief Get auto DC cancel status. * \retval true Automatic DC removal is enabled. * \retval false Automatic DC removal is disabled. */ bool receiver::get_dc_cancel(void) { return d_dc_cancel; } /*! \brief Enable/disable automatic I/Q balance. * \param enable Whether automatic I/Q balance should be enabled. */ void receiver::set_iq_balance(bool enable) { if (enable == d_iq_balance) return; d_iq_balance = enable; src->set_iq_balance_mode(enable ? 2 : 0); } /*! \brief Get auto I/Q balance status. * \retval true Automatic I/Q balance is enabled. * \retval false Automatic I/Q balance is disabled. */ bool receiver::get_iq_balance(void) { return d_iq_balance; } /*! \brief Set RF frequency. * \param freq_hz The desired frequency in Hz. * \return RX_STATUS_ERROR if an error occurs, e.g. the frequency is out of range. * \sa get_rf_freq() */ receiver::status receiver::set_rf_freq(double freq_hz) { d_rf_freq = freq_hz; src->set_center_freq(d_rf_freq); // FIXME: read back frequency? return STATUS_OK; } /*! \brief Get RF frequency. * \return The current RF frequency. * \sa set_rf_freq() */ double receiver::get_rf_freq() { d_rf_freq = src->get_center_freq(); return d_rf_freq; } /*! \brief Get the RF frequency range of the current input device. * \param start The lower limit of the range in Hz. * \param stop The upper limit of the range in Hz. * \param step The frequency step in Hz. * \returns STATUS_OK if the range could be retrieved, STATUS_ERROR if an error has occurred. */ receiver::status receiver::get_rf_range(double *start, double *stop, double *step) { osmosdr::freq_range_t range; range = src->get_freq_range(); // currently range is empty for all but E4000 if (!range.empty()) { if (range.start() < range.stop()) { *start = range.start(); *stop = range.stop(); *step = range.step(); /** FIXME: got 0 for rtl-sdr? **/ return STATUS_OK; } } return STATUS_ERROR; } /*! \brief Get the names of available gain stages. */ std::vector receiver::get_gain_names() { return src->get_gain_names(); } /*! \brief Get gain range for a specific stage. * \param[in] name The name of the gain stage. * \param[out] start Lower limit for this gain setting. * \param[out] stop Upper limit for this gain setting. * \param[out] step The resolution for this gain setting. * * This function retunrs the range for the requested gain stage. */ receiver::status receiver::get_gain_range(std::string &name, double *start, double *stop, double *step) { osmosdr::gain_range_t range; range = src->get_gain_range(name); *start = range.start(); *stop = range.stop(); *step = range.step(); return STATUS_OK; } receiver::status receiver::set_gain(std::string name, double value) { src->set_gain(value, name); return STATUS_OK; } double receiver::get_gain(std::string name) { return src->get_gain(name); } /*! \brief Set RF gain. * \param gain_rel The desired relative gain between 0.0 and 1.0 (use -1 for AGC where supported). * \return RX_STATUS_ERROR if an error occurs, e.g. the gain is out of valid range. */ receiver::status receiver::set_auto_gain(bool automatic) { src->set_gain_mode(automatic); return STATUS_OK; } /*! \brief Set filter offset. * \param offset_hz The desired filter offset in Hz. * \return RX_STATUS_ERROR if the tuning offset is out of range. * * This method sets a new tuning offset for the receiver. The tuning offset is used * to tune within the passband, i.e. select a specific channel within the received * spectrum. * * The valid range for the tuning is +/- 0.5 * the bandwidth although this is just a * logical limit. * * \sa get_filter_offset() */ receiver::status receiver::set_filter_offset(double offset_hz) { d_filter_offset = offset_hz; lo->set_frequency(-d_filter_offset); return STATUS_OK; } /*! \brief Get filterm offset. * \return The current filter offset. * \sa set_filter_offset() */ double receiver::get_filter_offset() { return d_filter_offset; } receiver::status receiver::set_filter(double low, double high, filter_shape shape) { double trans_width; if ((low >= high) || (abs(high-low) < RX_FILTER_MIN_WIDTH)) return STATUS_ERROR; switch (shape) { case FILTER_SHAPE_SOFT: trans_width = abs(high-low)*0.2; break; case FILTER_SHAPE_SHARP: trans_width = abs(high-low)*0.01; break; case FILTER_SHAPE_NORMAL: default: trans_width = abs(high-low)*0.1; break; } rx->set_filter(low, high, trans_width); return STATUS_OK; } /** receiver::status receiver::set_filter_low(double freq_hz) { return STATUS_OK; } receiver::status receiver::set_filter_high(double freq_hz) { return STATUS_OK; } receiver::status receiver::set_filter_shape(filter_shape shape) { return STATUS_OK; } **/ receiver::status receiver::set_freq_corr(int ppm) { src->set_freq_corr(ppm); return STATUS_OK; } /*! \brief Get current signal power. * \param dbfs Whether to use dbfs or absolute power. * \return The current signal power. * * This method returns the current signal power detected by the receiver. The detector * is located after the band pass filter. The full scale is 1.0 */ float receiver::get_signal_pwr(bool dbfs) { return rx->get_signal_level(dbfs); } /*! \brief Set new FFT size. */ void receiver::set_iq_fft_size(int newsize) { iq_fft->set_fft_size(newsize); } /*! \brief Get latest baseband FFT data. */ void receiver::get_iq_fft_data(std::complex* fftPoints, unsigned int &fftsize) { iq_fft->get_fft_data(fftPoints, fftsize); } /*! \brief Get latest audio FFT data. */ void receiver::get_audio_fft_data(std::complex* fftPoints, unsigned int &fftsize) { audio_fft->get_fft_data(fftPoints, fftsize); } receiver::status receiver::set_nb_on(int nbid, bool on) { if (rx->has_nb()) rx->set_nb_on(nbid, on); return STATUS_OK; // FIXME } receiver::status receiver::set_nb_threshold(int nbid, float threshold) { if (rx->has_nb()) rx->set_nb_threshold(nbid, threshold); return STATUS_OK; // FIXME } /*! \brief Set squelch level. * \param level_db The new level in dBFS. */ receiver::status receiver::set_sql_level(double level_db) { if (rx->has_sql()) rx->set_sql_level(level_db); return STATUS_OK; // FIXME } /*! \brief Set squelch alpha */ receiver::status receiver::set_sql_alpha(double alpha) { if (rx->has_sql()) rx->set_sql_alpha(alpha); return STATUS_OK; // FIXME } /*! \brief Enable/disable receiver AGC. * * When AGC is disabled a fixed manual gain is used, see set_agc_manual_gain(). */ receiver::status receiver::set_agc_on(bool agc_on) { if (rx->has_agc()) rx->set_agc_on(agc_on); return STATUS_OK; // FIXME } /*! \brief Enable/disable AGC hang. */ receiver::status receiver::set_agc_hang(bool use_hang) { if (rx->has_agc()) rx->set_agc_hang(use_hang); return STATUS_OK; // FIXME } /*! \brief Set AGC threshold. */ receiver::status receiver::set_agc_threshold(int threshold) { if (rx->has_agc()) rx->set_agc_threshold(threshold); return STATUS_OK; // FIXME } /*! \brief Set AGC slope. */ receiver::status receiver::set_agc_slope(int slope) { if (rx->has_agc()) rx->set_agc_slope(slope); return STATUS_OK; // FIXME } /*! \brief Set AGC decay time. */ receiver::status receiver::set_agc_decay(int decay_ms) { if (rx->has_agc()) rx->set_agc_decay(decay_ms); return STATUS_OK; // FIXME } /*! \brief Set fixed gain used when AGC is OFF. */ receiver::status receiver::set_agc_manual_gain(int gain) { if (rx->has_agc()) rx->set_agc_manual_gain(gain); return STATUS_OK; // FIXME } receiver::status receiver::set_demod(rx_demod demod) { bool needs_restart = d_running; bool wide_fm = (d_demod == RX_DEMOD_WFM_M) || (d_demod == RX_DEMOD_WFM_S); status ret = STATUS_OK; // Allow reconf using same demod to provide a workaround // for the "jerky streaming" we may experience with rtl // dongles (the jerkyness disappears when we run this function) //if (demod == d_demod) // return ret; if (d_running) stop(); switch (demod) { case RX_DEMOD_OFF: tb->disconnect_all(); connect_all(RX_CHAIN_NONE); break; case RX_DEMOD_NONE: if ((d_demod == RX_DEMOD_OFF) || wide_fm) { tb->disconnect_all(); connect_all(RX_CHAIN_NBRX); } rx->set_demod(nbrx::NBRX_DEMOD_NONE); break; case RX_DEMOD_AM: if ((d_demod == RX_DEMOD_OFF) || wide_fm) { tb->disconnect_all(); connect_all(RX_CHAIN_NBRX); } rx->set_demod(nbrx::NBRX_DEMOD_AM); break; case RX_DEMOD_NFM: if ((d_demod == RX_DEMOD_OFF) || wide_fm) { tb->disconnect_all(); connect_all(RX_CHAIN_NBRX); } rx->set_demod(nbrx::NBRX_DEMOD_FM); break; case RX_DEMOD_WFM_M: if (!wide_fm) { tb->disconnect_all(); connect_all(RX_CHAIN_WFMRX); } rx->set_demod(wfmrx::WFMRX_DEMOD_MONO); break; case RX_DEMOD_WFM_S: if (!wide_fm) { tb->disconnect_all(); connect_all(RX_CHAIN_WFMRX); } rx->set_demod(wfmrx::WFMRX_DEMOD_STEREO); break; case RX_DEMOD_SSB: if ((d_demod == RX_DEMOD_OFF) || wide_fm) { tb->disconnect_all(); connect_all(RX_CHAIN_NBRX); } rx->set_demod(nbrx::NBRX_DEMOD_SSB); break; default: ret = STATUS_ERROR; break; } d_demod = demod; if (needs_restart) start(); return ret; } /*! \brief Set maximum deviation of the FM demodulator. * \param maxdev_hz The new maximum deviation in Hz. */ receiver::status receiver::set_fm_maxdev(float maxdev_hz) { if (rx->has_fm()) rx->set_fm_maxdev(maxdev_hz); return STATUS_OK; } receiver::status receiver::set_fm_deemph(double tau) { if (rx->has_fm()) rx->set_fm_deemph(tau); return STATUS_OK; } receiver::status receiver::set_am_dcr(bool enabled) { if (rx->has_am()) rx->set_am_dcr(enabled); return STATUS_OK; } receiver::status receiver::set_af_gain(float gain_db) { float k; /* convert dB to factor */ k = pow(10.0, gain_db / 20.0); //std::cout << "G:" << gain_db << "dB / K:" << k << std::endl; audio_gain0->set_k(k); audio_gain1->set_k(k); return STATUS_OK; } /*! \brief Start WAV file recorder. * \param filename The filename where to record. * * A new recorder object is created every time we start recording and deleted every time * we stop recording. The idea of creating one object and starting/stopping using different * file names does not work with WAV files (the initial /tmp/gqrx.wav will not be stopped * because the wav file can not be empty). See https://github.com/csete/gqrx/issues/36 */ receiver::status receiver::start_audio_recording(const std::string filename) { if (d_recording_wav) { /* error - we are already recording */ std::cout << "ERROR: Can not start audio recorder (already recording)" << std::endl; return STATUS_ERROR; } if (!d_running) { /* receiver is not running */ std::cout << "Can not start audio recorder (receiver not running)" << std::endl; return STATUS_ERROR; } // not strictly necessary to lock but I think it is safer tb->lock(); wav_sink = gr::blocks::wavfile_sink::make(filename.c_str(), 2, (unsigned int) d_audio_rate, 16); tb->connect(rx, 0, wav_sink, 0); tb->connect(rx, 1, wav_sink, 1); tb->unlock(); d_recording_wav = true; std::cout << "Recording audio to " << filename << std::endl; return STATUS_OK; } /*! \brief Stop WAV file recorder. */ receiver::status receiver::stop_audio_recording() { if (!d_recording_wav) { /* error: we are not recording */ std::cout << "ERROR: Can not stop audio recorder (not recording)" << std::endl; return STATUS_ERROR; } if (!d_running) { /* receiver is not running */ std::cout << "Can not start audio recorder (receiver not running)" << std::endl; return STATUS_ERROR; } // not strictly necessary to lock but I think it is safer tb->lock(); wav_sink->close(); tb->disconnect(rx, 0, wav_sink, 0); tb->disconnect(rx, 1, wav_sink, 1); wav_sink.reset(); /** FIXME **/ tb->unlock(); d_recording_wav = false; std::cout << "Audio recorder stopped" << std::endl; return STATUS_OK; } /*! \brief Start audio playback. */ receiver::status receiver::start_audio_playback(const std::string filename) { if (!d_running) { /* receiver is not running */ std::cout << "Can not start audio playback (receiver not running)" << std::endl; return STATUS_ERROR; } try { // output ports set automatically from file wav_src = gr::blocks::wavfile_source::make(filename.c_str(), false); } catch (std::runtime_error &e) { std::cout << "Error loading " << filename << ": " << e.what() << std::endl; return STATUS_ERROR; } /** FIXME: We can only handle native rate (should maybe use the audio_rr)? */ unsigned int audio_rate = (unsigned int) d_audio_rate; if (wav_src->sample_rate() != audio_rate) { std::cout << "BUG: Can not handle sample rate " << wav_src->sample_rate() << std::endl; wav_src.reset(); return STATUS_ERROR; } /** FIXME: We can only handle stereo files */ if (wav_src->channels() != 2) { std::cout << "BUG: Can not handle other than 2 channels. File has " << wav_src->channels() << std::endl; wav_src.reset(); return STATUS_ERROR; } stop(); /* route demodulator output to null sink */ tb->disconnect(rx, 0, audio_gain0, 0); tb->disconnect(rx, 1, audio_gain1, 0); tb->disconnect(rx, 0, audio_fft, 0); tb->disconnect(rx, 0, audio_udp_sink, 0); tb->connect(rx, 0, audio_null_sink0, 0); /** FIXME: other channel? */ tb->connect(rx, 1, audio_null_sink1, 0); /** FIXME: other channel? */ tb->connect(wav_src, 0, audio_gain0, 0); tb->connect(wav_src, 1, audio_gain1, 0); tb->connect(wav_src, 0, audio_fft, 0); tb->connect(wav_src, 0, audio_udp_sink, 0); start(); std::cout << "Playing audio from " << filename << std::endl; return STATUS_OK; } /*! \brief Stop audio playback. */ receiver::status receiver::stop_audio_playback() { /* disconnect wav source and reconnect receiver */ stop(); tb->disconnect(wav_src, 0, audio_gain0, 0); tb->disconnect(wav_src, 1, audio_gain1, 0); tb->disconnect(wav_src, 0, audio_fft, 0); tb->disconnect(wav_src, 0, audio_udp_sink, 0); tb->disconnect(rx, 0, audio_null_sink0, 0); tb->disconnect(rx, 1, audio_null_sink1, 0); tb->connect(rx, 0, audio_gain0, 0); tb->connect(rx, 1, audio_gain1, 0); tb->connect(rx, 0, audio_fft, 0); /** FIXME: other channel? */ tb->connect(rx, 0, audio_udp_sink, 0); start(); /* delete wav_src since we can not change file name */ wav_src.reset(); return STATUS_OK; } /*! \brief Start UDP streaming of audio. */ receiver::status receiver::start_udp_streaming(const std::string host, int port) { audio_udp_sink->start_streaming(host, port); return STATUS_OK; } /*! \brief Stop UDP streaming of audio. */ receiver::status receiver::stop_udp_streaming() { audio_udp_sink->stop_streaming(); return STATUS_OK; } /*! \brief Start I/Q data recorder. * \param filename The filename where to record. */ receiver::status receiver::start_iq_recording(const std::string filename) { (void) filename; #if 0 if (d_recording_iq) { /* error - we are already recording */ return STATUS_ERROR; } /* iq_sink was created in the constructor */ if (iq_sink) { /* not strictly necessary to lock but I think it is safer */ tb->lock(); iq_sink->open(filename.c_str()); tb->unlock(); d_recording_iq = true; } else { std::cout << "BUG: I/Q file sink does not exist" << std::endl; } #endif return STATUS_OK; } /*! \brief Stop I/Q data recorder. */ receiver::status receiver::stop_iq_recording() { #if 0 if (!d_recording_iq) { /* error: we are not recording */ return STATUS_ERROR; } tb->lock(); iq_sink->close(); tb->unlock(); d_recording_iq = false; #endif return STATUS_OK; } /*! \brief Start playback of recorded I/Q data file. * \param filename The file to play from. Must be raw file containing gr_complex samples. * \param samprate The sample rate (currently fixed at 96ksps) */ receiver::status receiver::start_iq_playback(const std::string filename, float samprate) { (void) filename; (void) samprate; #if 0 if (samprate != d_bandwidth) { return STATUS_ERROR; } try { iq_src = gr_make_file_source(sizeof(gr_complex), filename.c_str(), false); } catch (std::runtime_error &e) { std::cout << "Error loading " << filename << ": " << e.what() << std::endl; return STATUS_ERROR; } tb->lock(); /* disconenct hardware source */ tb->disconnect(src, 0, nb, 0); tb->disconnect(src, 0, iq_sink, 0); /* connect I/Q source via throttle block */ tb->connect(iq_src, 0, nb, 0); tb->connect(iq_src, 0, iq_sink, 0); tb->unlock(); #endif return STATUS_OK; } /*! \brief Stop I/Q data file playback. * \return STATUS_OK * * This method will stop the I/Q data playback, disconnect the file source and throttle * blocks, and reconnect the hardware source. * * FIXME: will probably crash if we try to stop playback that is not running. */ receiver::status receiver::stop_iq_playback() { #if 0 tb->lock(); /* disconnect I/Q source and throttle block */ tb->disconnect(iq_src, 0, nb, 0); tb->disconnect(iq_src, 0, iq_sink, 0); /* reconenct hardware source */ tb->connect(src, 0, nb, 0); tb->connect(src, 0, iq_sink, 0); tb->unlock(); /* delete iq_src since we can not reuse for other files */ iq_src.reset(); #endif return STATUS_OK; } /*! \brief Start data sniffer. * \param buffsize The buffer that should be used in the sniffer. * \return STATUS_OK if the sniffer was started, STATUS_ERROR if the sniffer is already in use. */ receiver::status receiver::start_sniffer(unsigned int samprate, int buffsize) { if (d_sniffer_active) { /* sniffer already in use */ return STATUS_ERROR; } sniffer->set_buffer_size(buffsize); sniffer_rr = make_resampler_ff((float)samprate/(float)d_audio_rate); tb->lock(); tb->connect(rx, 0, sniffer_rr, 0); tb->connect(sniffer_rr, 0, sniffer, 0); tb->unlock(); d_sniffer_active = true; return STATUS_OK; } /*! \brief Stop data sniffer. * \return STATUS_ERROR i the sniffer is not currently active. */ receiver::status receiver::stop_sniffer() { if (!d_sniffer_active) { return STATUS_ERROR; } tb->lock(); tb->disconnect(rx, 0, sniffer_rr, 0); tb->disconnect(sniffer_rr, 0, sniffer, 0); tb->unlock(); d_sniffer_active = false; /* delete resampler */ sniffer_rr.reset(); return STATUS_OK; } /*! \brief Get sniffer data. */ void receiver::get_sniffer_data(float * outbuff, unsigned int &num) { sniffer->get_samples(outbuff, num); } /*! \brief Convenience function to connect all blocks. */ void receiver::connect_all(rx_chain type) { switch (type) { case RX_CHAIN_NONE: tb->connect(src, 0, iq_swap, 0); if (d_dc_cancel) { tb->connect(iq_swap, 0, dc_corr, 0); tb->connect(dc_corr, 0, iq_fft, 0); } else { tb->connect(iq_swap, 0, iq_fft, 0); } break; case RX_CHAIN_NBRX: if (rx->name() != "NBRX") { rx.reset(); rx = make_nbrx(d_input_rate, d_audio_rate); } tb->connect(src, 0, iq_swap, 0); if (d_dc_cancel) { tb->connect(iq_swap, 0, dc_corr, 0); tb->connect(dc_corr, 0, iq_fft, 0); tb->connect(dc_corr, 0, mixer, 0); } else { tb->connect(iq_swap, 0, iq_fft, 0); tb->connect(iq_swap, 0, mixer, 0); } tb->connect(lo, 0, mixer, 1); tb->connect(mixer, 0, rx, 0); tb->connect(rx, 0, audio_fft, 0); tb->connect(rx, 0, audio_udp_sink, 0); tb->connect(rx, 0, audio_gain0, 0); tb->connect(rx, 1, audio_gain1, 0); tb->connect(audio_gain0, 0, audio_snk, 0); tb->connect(audio_gain1, 0, audio_snk, 1); break; case RX_CHAIN_WFMRX: if (rx->name() != "WFMRX") { rx.reset(); rx = make_wfmrx(d_input_rate, d_audio_rate); } tb->connect(src, 0, iq_swap, 0); if (d_dc_cancel) { tb->connect(iq_swap, 0, dc_corr, 0); tb->connect(dc_corr, 0, iq_fft, 0); tb->connect(dc_corr, 0, mixer, 0); } else { tb->connect(iq_swap, 0, iq_fft, 0); tb->connect(iq_swap, 0, mixer, 0); } tb->connect(lo, 0, mixer, 1); tb->connect(mixer, 0, rx, 0); tb->connect(rx, 0, audio_fft, 0); tb->connect(rx, 0, audio_udp_sink, 0); tb->connect(rx, 0, audio_gain0, 0); tb->connect(rx, 1, audio_gain1, 0); tb->connect(audio_gain0, 0, audio_snk, 0); tb->connect(audio_gain1, 0, audio_snk, 1); break; default: break; } // re-connect audio data sniffer if it is activated if (d_sniffer_active) { tb->connect(rx, 0, sniffer_rr, 0); tb->connect(sniffer_rr, 0, sniffer, 0); } } gqrx-sdr-2.2.0.74.d97bd7/applications/gqrx/receiver.h000066400000000000000000000217441226036373100221510ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2013 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef RECEIVER_H #define RECEIVER_H #include #include #include #include #include #include #include #include #include #include "dsp/correct_iq_cc.h" #include "dsp/rx_noise_blanker_cc.h" #include "dsp/rx_filter.h" #include "dsp/rx_meter.h" #include "dsp/rx_agc_xx.h" #include "dsp/rx_demod_fm.h" #include "dsp/rx_demod_am.h" #include "dsp/rx_fft.h" #include "dsp/sniffer_f.h" #include "dsp/resampler_xx.h" #include "interfaces/udp_sink_f.h" #include "receivers/receiver_base.h" #ifdef WITH_PULSEAUDIO #include #else #include #endif /*! \defgroup DSP Digital signal processing library based on GNU Radio */ /*! \brief Top-level receiver class. * \ingroup DSP * * This class encapsulates the GNU Radio flow graph for the receiver. * Front-ends should only control the receiver through the interface provided * by this class. * */ class receiver { public: /*! \brief Flag used to indicate success or failure of an operation, usually a set_something(). */ enum status { STATUS_OK = 0, /*!< Operation was successful. */ STATUS_ERROR = 1 /*!< There was an error. */ }; /*! \brief Available demodulators. */ enum rx_demod { RX_DEMOD_OFF = 0, /*!< No receiver. */ RX_DEMOD_NONE = 1, /*!< No demod. Raw I/Q to audio. */ RX_DEMOD_AM = 2, /*!< Amplitude modulation. */ RX_DEMOD_NFM = 3, /*!< Frequency modulation. */ RX_DEMOD_WFM_M = 4, /*!< Frequency modulation (wide, mono). */ RX_DEMOD_WFM_S = 5, /*!< Frequency modulation (wide, stereo). */ RX_DEMOD_SSB = 6 /*!< Single Side Band. */ }; /*! \brief Supported receiver types. */ enum rx_chain { RX_CHAIN_NONE = 0, /*!< No receiver, just spectrum analyzer. */ RX_CHAIN_NBRX = 1, /*!< Narrow band receiver (AM, FM, SSB). */ RX_CHAIN_WFMRX = 2 /*!< Wide band FM receiver (for broadcast). */ }; /*! \brief Filter shape (convenience wrappers for "transition width"). */ enum filter_shape { FILTER_SHAPE_SOFT = 0, /*!< Soft: Transition band is TBD of width. */ FILTER_SHAPE_NORMAL = 1, /*!< Normal: Transition band is TBD of width. */ FILTER_SHAPE_SHARP = 2 /*!< Sharp: Transition band is TBD of width. */ }; receiver(const std::string input_device="", const std::string audio_device=""); ~receiver(); void start(); void stop(); void set_input_device(const std::string device); void set_output_device(const std::string device); std::vector get_antennas(void); void set_antenna(const std::string &antenna); double set_input_rate(double rate); double get_input_rate(); double set_analog_bandwidth(double bw); double get_analog_bandwidth(); void set_iq_swap(bool reversed); bool get_iq_swap(void); void set_dc_cancel(bool enable); bool get_dc_cancel(void); void set_iq_balance(bool enable); bool get_iq_balance(void); status set_rf_freq(double freq_hz); double get_rf_freq(); status get_rf_range(double *start, double *stop, double *step); std::vector get_gain_names(); status get_gain_range(std::string &name, double *start, double *stop, double *step); status set_auto_gain(bool automatic); status set_gain(std::string name, double value); double get_gain(std::string name); status set_filter_offset(double offset_hz); double get_filter_offset(); status set_filter(double low, double high, filter_shape shape); //status set_filter_low(double freq_hz); //status set_filter_high(double freq_hz); //status set_filter_shape(filter_shape shape); status set_freq_corr(int ppm); float get_signal_pwr(bool dbfs); void set_iq_fft_size(int newsize); void get_iq_fft_data(std::complex* fftPoints, unsigned int &fftsize); void get_audio_fft_data(std::complex* fftPoints, unsigned int &fftsize); /* Noise blanker */ status set_nb_on(int nbid, bool on); status set_nb_threshold(int nbid, float threshold); /* Squelch parameter */ status set_sql_level(double level_db); status set_sql_alpha(double alpha); /* AGC */ status set_agc_on(bool agc_on); status set_agc_hang(bool use_hang); status set_agc_threshold(int threshold); status set_agc_slope(int slope); status set_agc_decay(int decay_ms); status set_agc_manual_gain(int gain); status set_demod(rx_demod demod); /* FM parameters */ status set_fm_maxdev(float maxdev_hz); status set_fm_deemph(double tau); /* AM parameters */ status set_am_dcr(bool enabled); /* Audio parameters */ status set_af_gain(float gain_db); status start_audio_recording(const std::string filename); status stop_audio_recording(); status start_audio_playback(const std::string filename); status stop_audio_playback(); status start_udp_streaming(const std::string host, int port); status stop_udp_streaming(); /* I/Q recording and playback */ status start_iq_recording(const std::string filename); status stop_iq_recording(); status start_iq_playback(const std::string filename, float samprate); status stop_iq_playback(); /* sample sniffer */ status start_sniffer(unsigned int samplrate, int buffsize); status stop_sniffer(); void get_sniffer_data(float * outbuff, unsigned int &num); private: void connect_all(rx_chain type); private: bool d_running; /*!< Whether receiver is running or not. */ double d_input_rate; /*!< Input sample rate. */ double d_audio_rate; /*!< Audio output rate. */ double d_rf_freq; /*!< Current RF frequency. */ double d_filter_offset; /*!< Current filter offset (tune within passband). */ bool d_recording_wav; /*!< Whether we are recording WAV file. */ bool d_sniffer_active; /*!< Only one data decoder allowed. */ bool d_iq_rev; /*!< Whether I/Q is reversed or not. */ bool d_dc_cancel; /*!< Enable automatic DC removal. */ bool d_iq_balance; /*!< Enable automatic IQ balance. */ std::string input_devstr; /*!< Current input device string. */ std::string output_devstr; /*!< Current output device string. */ rx_demod d_demod; /*!< Current demodulator. */ gr::top_block_sptr tb; /*!< The GNU Radio top block. */ osmosdr::source::sptr src; /*!< Real time I/Q source. */ //rx_source_base::sptr src; /*!< Real time I/Q source. */ receiver_base_cf_sptr rx; /*!< receiver. */ dc_corr_cc_sptr dc_corr; /*!< DC corrector block. */ iq_swap_cc_sptr iq_swap; /*!< I/Q swapping block. */ rx_fft_c_sptr iq_fft; /*!< Baseband FFT block. */ rx_fft_f_sptr audio_fft; /*!< Audio FFT block. */ gr::analog::sig_source_c::sptr lo; /*!< oscillator used for tuning. */ gr::blocks::multiply_cc::sptr mixer; gr::blocks::multiply_const_ff::sptr audio_gain0; /*!< Audio gain block. */ gr::blocks::multiply_const_ff::sptr audio_gain1; /*!< Audio gain block. */ gr::blocks::wavfile_sink::sptr wav_sink; /*!< WAV file sink for recording. */ gr::blocks::wavfile_source::sptr wav_src; /*!< WAV file source for playback. */ gr::blocks::null_sink::sptr audio_null_sink0; /*!< Audio null sink used during playback. */ gr::blocks::null_sink::sptr audio_null_sink1; /*!< Audio null sink used during playback. */ udp_sink_f_sptr audio_udp_sink; /*!< UDP sink to stream audio over the network. */ sniffer_f_sptr sniffer; /*!< Sample sniffer for data decoders. */ resampler_ff_sptr sniffer_rr; /*!< Sniffer resampler. */ #ifdef WITH_PULSEAUDIO pa_sink_sptr audio_snk; /*!< Pulse audio sink. */ #else gr::audio::sink::sptr audio_snk; /*!< gr audio sink */ #endif }; #endif // RECEIVER_H gqrx-sdr-2.2.0.74.d97bd7/applications/gqrx/remote_control.cpp000066400000000000000000000144331226036373100237300ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2013 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include #include "remote_control.h" RemoteControl::RemoteControl(QObject *parent) : QObject(parent) { rc_freq = 0; rc_filter_offset = 0; bw_half = 740e3; rc_port = 7356; rc_allowed_hosts.append("127.0.0.1"); rc_socket = 0; connect(&rc_server, SIGNAL(newConnection()), this, SLOT(acceptConnection())); } RemoteControl::~RemoteControl() { stop_server(); } /*! \brief Start the server. */ void RemoteControl::start_server() { rc_server.listen(QHostAddress::Any, rc_port); } /*! \brief Stop the server. */ void RemoteControl::stop_server() { if (rc_socket != 0) rc_socket->close(); if (rc_server.isListening()) rc_server.close(); } /*! \brief Read settings. */ void RemoteControl::readSettings(QSettings *settings) { bool conv_ok; rc_freq = settings->value("input/frequency", 144500000).toLongLong(&conv_ok); rc_filter_offset = settings->value("receiver/offset", 0).toInt(&conv_ok); // Get port number; restart server if running rc_port = settings->value("remote_control/port", 7356).toInt(&conv_ok); if (rc_server.isListening()) { rc_server.close(); rc_server.listen(QHostAddress::Any, rc_port); } // get list of allowed hosts if (settings->contains("remote_control/allowed_hosts")) rc_allowed_hosts = settings->value("remote_control/allowed_hosts").toStringList(); } void RemoteControl::saveSettings(QSettings *settings) const { if (rc_port != 7356) settings->setValue("remote_control/port", rc_port); else settings->remove("remote_control/port"); if ((rc_allowed_hosts.count() != 1) || (rc_allowed_hosts.at(0) != "127.0.0.1")) settings->setValue("remote_control/allowed_hosts", rc_allowed_hosts); else settings->remove("remote_control/allowed_hosts"); } /*! \brief Set new network port. * \param port The new network port. * * If the server is running it will be restarted. * */ void RemoteControl::setPort(int port) { if (port == rc_port) return; rc_port = port; if (rc_server.isListening()) { rc_server.close(); rc_server.listen(QHostAddress::Any, rc_port); } } void RemoteControl::setHosts(QStringList hosts) { rc_allowed_hosts.clear(); for (int i = 0; i < hosts.count(); i++) rc_allowed_hosts << hosts.at(i); } /*! \brief Accept a new client connection. * * This slot is called when a client opens a new connection. */ void RemoteControl::acceptConnection() { rc_socket = rc_server.nextPendingConnection(); // check if host is allowed QString address = rc_socket->peerAddress().toString(); if (rc_allowed_hosts.indexOf(address) == -1) { qDebug() << "Connection attempt from" << address << "(not in allowed list)"; rc_socket->close(); } else { connect(rc_socket, SIGNAL(readyRead()), this, SLOT(startRead())); } } /*! \brief Start reading from the socket. * * This slot is called when the client TCP socket emits a readyRead() signal, * i.e. when there is data to read. */ void RemoteControl::startRead() { char buffer[1024] = {0}; int bytes_read; qint64 freq; bytes_read = rc_socket->readLine(buffer, 1024); if (bytes_read < 2) // command + '\n' return; if (buffer[0] == 'F') { // set frequency if (sscanf(buffer,"F %lld\n", &freq) == 1) { setNewRemoteFreq(freq); rc_socket->write("RPRT 0\n"); } else { rc_socket->write("RPRT 1\n"); } } else if (buffer[0] == 'f') { // get frequency rc_socket->write(QString("%1\n").arg(rc_freq).toLatin1()); } else if (buffer[0] == 'c') { // FIXME: for now we assume 'close' command rc_socket->close(); } // Gpredict / Gqrx specific commands: // AOS - satellite AOS event // LOS - satellite LOS event else if (bytes_read >= 4 && buffer[1] == 'O' && buffer[2] == 'S') { if (buffer[0] == 'A') { emit satAosEvent(); rc_socket->write("RPRT 0\n"); } else if (buffer[0] == 'L') { emit satLosEvent(); rc_socket->write("RPRT 0\n"); } else { rc_socket->write("RPRT 1\n"); } } else { // respond with an error rc_socket->write("RPRT 1\n"); } } /*! \brief Slot called when the receiver is tuned to a new frequency. * \param freq The new frequency in Hz. * * Note that this is the frequency gqrx is receiveing on, i.e. the * hardware frequency + the filter offset. */ void RemoteControl::setNewFrequency(qint64 freq) { rc_freq = freq; } /*! \brief Slot called when the filter offset is changed. */ void RemoteControl::setFilterOffset(qint64 freq) { rc_filter_offset = freq; } void RemoteControl::setBandwidth(qint64 bw) { // we want to leave some margin bw_half = 0.9 * (bw / 2); } /*! \brief New remote frequency received. */ void RemoteControl::setNewRemoteFreq(qint64 freq) { qint64 delta = freq - rc_freq; if (abs(rc_filter_offset + delta) < bw_half) { // move filter offset rc_filter_offset += delta; emit newFilterOffset(rc_filter_offset); } else { // move rx freqeucy and let MainWindow deal with it // (will usually change hardware PLL) emit newFrequency(freq); } rc_freq = freq; } gqrx-sdr-2.2.0.74.d97bd7/applications/gqrx/remote_control.h000066400000000000000000000062561226036373100234010ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2013 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef REMOTE_CONTROL_H #define REMOTE_CONTROL_H #include #include #include #include #include #include /*! \brief Simple TCP server for remote control. * * The TCP interface is compatible with the hamlib rigtctld so that applications * gpredict can be used with gqrx without any modifications. * * The hamlib rigctld protocol is described in the man page * http://hamlib.sourceforge.net/pdf/rigctld.8.pdf * but here is a summary. * * client: F 144500000\n # set frequency in Hz * gqrx: RPRT 0\n # 0 means no error * * client: f\n # get frequency * gqrx: 144500000\n # gqrx replies with frequency in Hz * * We also have some gqrx specific commands: * * close: Close connection (useful for interactive telnet sessions). * * * FIXME: The server code is very minimalistic and probably not very robust. */ class RemoteControl : public QObject { Q_OBJECT public: explicit RemoteControl(QObject *parent = 0); ~RemoteControl(); void start_server(void); void stop_server(void); void readSettings(QSettings *settings); void saveSettings(QSettings *settings) const; void setPort(int port); int getPort(void) const { return rc_port; } void setHosts(QStringList hosts); QStringList getHosts(void) const { return rc_allowed_hosts; } public slots: void setNewFrequency(qint64 freq); void setFilterOffset(qint64 freq); void setBandwidth(qint64 bw); signals: void newFrequency(qint64 freq); void newFilterOffset(qint64 offset); void satAosEvent(void); /*! Satellite AOS event received through the socket. */ void satLosEvent(void); /*! Satellite LOS event received through the socket. */ private slots: void acceptConnection(); void startRead(); private: QTcpServer rc_server; /*!< The active server object. */ QTcpSocket* rc_socket; /*!< The active socket object. */ QStringList rc_allowed_hosts; /*!< Hosts where we accept connection from. */ int rc_port; /*!< The port we are listening on. */ qint64 rc_freq; qint64 rc_filter_offset; qint64 bw_half; void setNewRemoteFreq(qint64 freq); }; #endif // REMOTE_CONTROL_H gqrx-sdr-2.2.0.74.d97bd7/applications/gqrx/remote_control_settings.cpp000066400000000000000000000057111226036373100256470ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2013 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include #include #include "remote_control_settings.h" #include "ui_remote_control_settings.h" RemoteControlSettings::RemoteControlSettings(QWidget *parent) : QDialog(parent), ui(new Ui::RemoteControlSettings) { ui->setupUi(this); } RemoteControlSettings::~RemoteControlSettings() { delete ui; } /*! \brief Set new network port. * \param port The new network port. */ void RemoteControlSettings::setPort(int port) { ui->portSpinBox->setValue(port); } /*! \brief Get current value from the port spin box. * \return The current port value. */ int RemoteControlSettings::getPort(void) const { return ui->portSpinBox->value(); } /*! \brief Add items to the list of allowed hosts. * \param hosts A list with the IP addresses of the allowed hosts. * * Note that setting the list wil lclear the current contents of the * list widget. */ void RemoteControlSettings::setHosts(QStringList hosts) { ui->hostListWidget->clear(); ui->hostListWidget->addItems(hosts); } /*! \brief Get list of allowed hosts. */ QStringList RemoteControlSettings::getHosts(void) const { QStringList list; for (int i = 0; i < ui->hostListWidget->count(); i++) { list << ui->hostListWidget->item(i)->text(); } return list; } /*! \brief Add a new entry to the list of allowed hosts. * * The function inserts a new entry at the end of the list and enables * editing. */ void RemoteControlSettings::on_hostAddButton_clicked(void) { ui->hostListWidget->addItem(tr("Enter IP")); QListWidgetItem *item = ui->hostListWidget->item(ui->hostListWidget->count()-1); item->setFlags(item->flags() | Qt::ItemIsEditable); ui->hostListWidget->setCurrentItem(item, QItemSelectionModel::ClearAndSelect); ui->hostListWidget->editItem(item); } /*! \brief Delete the selected entries from the list of allowed hosts. */ void RemoteControlSettings::on_hostDelButton_clicked(void) { // wondering WTF? // see http://stackoverflow.com/questions/7008423/how-do-i-remove-all-the-selected-items-in-a-qlistwidget qDeleteAll(ui->hostListWidget->selectedItems()); } gqrx-sdr-2.2.0.74.d97bd7/applications/gqrx/remote_control_settings.h000066400000000000000000000030651226036373100253140ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2013 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef REMOTE_CONTROL_SETTINGS_H #define REMOTE_CONTROL_SETTINGS_H #include #include #include namespace Ui { class RemoteControlSettings; } /*! \brief Class to configure remote control settiongs. */ class RemoteControlSettings : public QDialog { Q_OBJECT public: explicit RemoteControlSettings(QWidget *parent = 0); ~RemoteControlSettings(); void setPort(int port); int getPort(void) const; void setHosts(QStringList hosts); QStringList getHosts(void) const; private slots: void on_hostAddButton_clicked(void); void on_hostDelButton_clicked(void); private: Ui::RemoteControlSettings *ui; }; #endif // REMOTE_CONTROL_SETTINGS_H gqrx-sdr-2.2.0.74.d97bd7/applications/gqrx/remote_control_settings.ui000066400000000000000000000134701226036373100255030ustar00rootroot00000000000000 RemoteControlSettings 0 0 314 269 Gqrx remote control settings :/icons/icons/settings.svg:/icons/icons/settings.svg <html><head/><body><p>Select which port to listen on</p><p>(0 selects a random port)</p></body></html> Listen on port 75 0 <html><head/><body><p>Select which port to listen on</p><p>(0 selects a random port)</p></body></html> 0 65535 7356 Qt::Horizontal 40 20 List of IP addresses where gqrx should accept connections from Allowed hosts List of IP addresses where gqrx should accept connections from QAbstractItemView::ExtendedSelection QListView::Fixed 127.0.0.1 ItemIsSelectable|ItemIsEditable|ItemIsDragEnabled|ItemIsUserCheckable|ItemIsEnabled 56 16777215 Add a new host to the list Add 56 16777215 Delete the selected host from the list Del Qt::Vertical 20 40 Qt::Horizontal Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox accepted() RemoteControlSettings accept() 248 254 157 274 buttonBox rejected() RemoteControlSettings reject() 316 260 286 274 gqrx-sdr-2.2.0.74.d97bd7/dsp/000077500000000000000000000000001226036373100153035ustar00rootroot00000000000000gqrx-sdr-2.2.0.74.d97bd7/dsp/afsk1200/000077500000000000000000000000001226036373100165325ustar00rootroot00000000000000gqrx-sdr-2.2.0.74.d97bd7/dsp/afsk1200/cafsk12.cpp000066400000000000000000000341031226036373100204710ustar00rootroot00000000000000/* * cafsk12.cpp -- AFSK1200 demodulator class * * Copyright (C) 1996 * Thomas Sailer (sailer@ife.ee.ethz.ch, hb9jnx@hb9w.che.eu) * * Copyright (C) 2011 Alexandru Csete (oz9aec at gmail.com) * * 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include "filter.h" #include "cafsk12.h" CAfsk12::CAfsk12(QObject *parent) : QObject(parent) { state = (demod_state *) malloc(sizeof(demod_state)); reset(); } CAfsk12::~CAfsk12() { free(state); } /*! \brief Reset the decoder. */ void CAfsk12::reset() { float f; int i; hdlc_init(state); memset(&state->l1.afsk12, 0, sizeof(state->l1.afsk12)); for (f = 0, i = 0; i < CORRLEN; i++) { corr_mark_i[i] = cos(f); corr_mark_q[i] = sin(f); f += 2.0*M_PI*FREQ_MARK/FREQ_SAMP; } for (f = 0, i = 0; i < CORRLEN; i++) { corr_space_i[i] = cos(f); corr_space_q[i] = sin(f); f += 2.0*M_PI*FREQ_SPACE/FREQ_SAMP; } } void CAfsk12::demod(float *buffer, int length) { float f; unsigned char curbit; if (state->l1.afsk12.subsamp) { int numfill = SUBSAMP - state->l1.afsk12.subsamp; if (length < numfill) { state->l1.afsk12.subsamp += length; return; } buffer += numfill; length -= numfill; state->l1.afsk12.subsamp = 0; } for (; length >= SUBSAMP; length -= SUBSAMP, buffer += SUBSAMP) { f = fsqr(mac(buffer, corr_mark_i, CORRLEN)) + fsqr(mac(buffer, corr_mark_q, CORRLEN)) - fsqr(mac(buffer, corr_space_i, CORRLEN)) - fsqr(mac(buffer, corr_space_q, CORRLEN)); state->l1.afsk12.dcd_shreg <<= 1; state->l1.afsk12.dcd_shreg |= (f > 0); verbprintf(10, "%c", '0'+(state->l1.afsk12.dcd_shreg & 1)); /* * check if transition */ if ((state->l1.afsk12.dcd_shreg ^ (state->l1.afsk12.dcd_shreg >> 1)) & 1) { if (state->l1.afsk12.sphase < (0x8000u-(SPHASEINC/2))) state->l1.afsk12.sphase += SPHASEINC/8; else state->l1.afsk12.sphase -= SPHASEINC/8; } state->l1.afsk12.sphase += SPHASEINC; if (state->l1.afsk12.sphase >= 0x10000u) { state->l1.afsk12.sphase &= 0xffffu; state->l1.afsk12.lasts <<= 1; state->l1.afsk12.lasts |= state->l1.afsk12.dcd_shreg & 1; curbit = (state->l1.afsk12.lasts ^ (state->l1.afsk12.lasts >> 1) ^ 1) & 1; verbprintf(9, " %c ", '0'+curbit); hdlc_rxbit(state, curbit); } } state->l1.afsk12.subsamp = length; } /** HDLC functions **/ /* * the CRC routines are stolen from WAMPES * by Dieter Deyke */ static const unsigned short crc_ccitt_table[] = { 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, 0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, 0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb, 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78 }; void CAfsk12::hdlc_init(struct demod_state *s) { memset(&s->l2.hdlc, 0, sizeof(s->l2.hdlc)); } void CAfsk12::hdlc_rxbit(struct demod_state *s, int bit) { s->l2.hdlc.rxbitstream <<= 1; s->l2.hdlc.rxbitstream |= !!bit; if ((s->l2.hdlc.rxbitstream & 0xff) == 0x7e) { if (s->l2.hdlc.rxstate && (s->l2.hdlc.rxptr - s->l2.hdlc.rxbuf) > 2) ax25_disp_packet(s->l2.hdlc.rxbuf, s->l2.hdlc.rxptr - s->l2.hdlc.rxbuf); s->l2.hdlc.rxstate = 1; s->l2.hdlc.rxptr = s->l2.hdlc.rxbuf; s->l2.hdlc.rxbitbuf = 0x80; return; } if ((s->l2.hdlc.rxbitstream & 0x7f) == 0x7f) { s->l2.hdlc.rxstate = 0; return; } if (!s->l2.hdlc.rxstate) return; if ((s->l2.hdlc.rxbitstream & 0x3f) == 0x3e) /* stuffed bit */ return; if (s->l2.hdlc.rxbitstream & 1) s->l2.hdlc.rxbitbuf |= 0x100; if (s->l2.hdlc.rxbitbuf & 1) { if (s->l2.hdlc.rxptr >= s->l2.hdlc.rxbuf+sizeof(s->l2.hdlc.rxbuf)) { s->l2.hdlc.rxstate = 0; verbprintf(1, "Error: packet size too large\n"); return; } *s->l2.hdlc.rxptr++ = s->l2.hdlc.rxbitbuf >> 1; s->l2.hdlc.rxbitbuf = 0x80; return; } s->l2.hdlc.rxbitbuf >>= 1; } static int verbose_level = 2; void CAfsk12::verbprintf(int verb_level, const char *fmt, ...) { va_list args; va_start(args, fmt); if (verb_level <= verbose_level) { vfprintf(stdout, fmt, args); fflush(stdout); } va_end(args); } static inline int check_crc_ccitt(const unsigned char *buf, int cnt) { unsigned int crc = 0xffff; for (; cnt > 0; cnt--) crc = (crc >> 8) ^ crc_ccitt_table[(crc ^ *buf++) & 0xff]; return (crc & 0xffff) == 0xf0b8; } void CAfsk12::ax25_disp_packet(unsigned char *bp, unsigned int len) { QString message; unsigned char v1=1,cmd=0; unsigned char i,j; verbprintf(6, "AX.25 PKT; L=%d\n", len); if (!bp || len < 10) return; #if 1 if (!check_crc_ccitt(bp, len)) { verbprintf(6, "CRC check failed\n"); return; } #endif /* get current time that will be prepended to packet display */ QTime time = QTime::currentTime(); len -= 2; if (bp[1] & 1) { /* * FlexNet Header Compression */ v1 = 0; cmd = (bp[1] & 2) != 0; verbprintf(0, "AFSK1200: fm ? to "); message.append(QString("%1$ fm ? to ").arg(time.toString("hh:mm:ss"))); i = (bp[2] >> 2) & 0x3f; if (i) { verbprintf(0, "%c",i+0x20); message.append(QChar(i+0x20)); } i = ((bp[2] << 4) | ((bp[3] >> 4) & 0xf)) & 0x3f; if (i) { verbprintf(0, "%c",i+0x20); message.append(QChar(i+0x20)); } i = ((bp[3] << 2) | ((bp[4] >> 6) & 3)) & 0x3f; if (i) { verbprintf(0, "%c",i+0x20); message.append(QChar(i+0x20)); } i = bp[4] & 0x3f; if (i) { verbprintf(0, "%c",i+0x20); message.append(QChar(i+0x20)); } i = (bp[5] >> 2) & 0x3f; if (i) { verbprintf(0, "%c",i+0x20); message.append(QChar(i+0x20)); } i = ((bp[5] << 4) | ((bp[6] >> 4) & 0xf)) & 0x3f; if (i) { verbprintf(0, "%c",i+0x20); message.append(QChar(i+0x20)); } verbprintf(0, "-%u QSO Nr %u", bp[6] & 0xf, (bp[0] << 6) | (bp[1] >> 2)); message.append(QString("-%1 QSO Nr %1").arg(bp[6] & 0xf).arg((bp[0] << 6) | (bp[1] >> 2))); bp += 7; len -= 7; } else { /* * normal header */ if (len < 15) goto finished; if ((bp[6] & 0x80) != (bp[13] & 0x80)) { v1 = 0; cmd = (bp[6] & 0x80); } verbprintf(0, "AFSK1200: fm "); message.append(QString("%1$ fm ").arg(time.toString("hh:mm:ss"))); for(i = 7; i < 13; i++) if ((bp[i] &0xfe) != 0x40) { verbprintf(0, "%c",bp[i] >> 1); message.append(QChar(bp[i] >> 1)); } verbprintf(0, "-%u to ",(bp[13] >> 1) & 0xf); message.append(QString("-%1 to ").arg((bp[13] >> 1) & 0xf)); for(i = 0; i < 6; i++) if ((bp[i] &0xfe) != 0x40) { verbprintf(0, "%c",bp[i] >> 1); message.append(QChar(bp[i] >> 1)); } verbprintf(0, "-%u",(bp[6] >> 1) & 0xf); message.append(QString("-%1").arg((bp[6] >> 1) & 0xf)); bp += 14; len -= 14; if ((!(bp[-1] & 1)) && (len >= 7)) { verbprintf(0, " via "); message.append(" via "); } while ((!(bp[-1] & 1)) && (len >= 7)) { for(i = 0; i < 6; i++) if ((bp[i] &0xfe) != 0x40) { verbprintf(0, "%c",bp[i] >> 1); message.append(QChar(bp[i] >> 1)); } verbprintf(0, "-%u",(bp[6] >> 1) & 0xf); message.append(QString("-%1").arg((bp[6] >> 1) & 0xf)); bp += 7; len -= 7; if ((!(bp[-1] & 1)) && (len >= 7)) { verbprintf(0, ","); message.append(","); } } } if(!len) goto finished; i = *bp++; len--; j = v1 ? ((i & 0x10) ? '!' : ' ') : ((i & 0x10) ? (cmd ? '+' : '-') : (cmd ? '^' : 'v')); if (!(i & 1)) { /* Info frame */ verbprintf(0, " I%u%u%c",(i >> 5) & 7,(i >> 1) & 7,j); message.append(QString(" I%1%2%3").arg((i >> 5) & 7).arg((i >> 1) & 7).arg(j)); } else if (i & 2) { /* U frame */ switch (i & (~0x10)) { case 0x03: verbprintf(0, " UI%c",j); message.append(QString(" UI%1").arg(QChar(j))); break; case 0x2f: verbprintf(0, " SABM%c",j); message.append(QString(" SABM%1").arg(QChar(j))); break; case 0x43: verbprintf(0, " DISC%c",j); message.append(QString(" DISC%1").arg(QChar(j))); break; case 0x0f: verbprintf(0, " DM%c",j); message.append(QString(" DM%1").arg(QChar(j))); break; case 0x63: verbprintf(0, " UA%c",j); message.append(QString(" UA%1").arg(QChar(j))); break; case 0x87: verbprintf(0, " FRMR%c",j); message.append(QString(" FRMR%1").arg(QChar(j))); break; default: verbprintf(0, " unknown U (0x%x)%c",i & (~0x10),j); message.append(QString(" unknown U (0x%1)%2").arg(i & (~0x10),0,16).arg(QChar(j))); break; } } else { /* supervisory */ switch (i & 0xf) { case 0x1: verbprintf(0, " RR%u%c",(i >> 5) & 7,j); message.append(QString(" RR%1%2").arg((i >> 5) & 7).arg(QChar(j))); break; case 0x5: verbprintf(0, " RNR%u%c",(i >> 5) & 7,j); message.append(QString(" RNR%1%2").arg((i >> 5) & 7).arg(QChar(j))); break; case 0x9: verbprintf(0, " REJ%u%c",(i >> 5) & 7,j); message.append(QString(" REJ%1%2").arg((i >> 5) & 7).arg(QChar(j))); break; default: verbprintf(0, " unknown S (0x%x)%u%c", i & 0xf, (i >> 5) & 7, j); message.append(QString(" unknown S (0x%1)%2%3").arg(i & 0xf,0,16).arg((i >> 5) & 7).arg(QChar(j))); break; } } if (!len) { verbprintf(0, "\n"); //message.append("\n"); goto finished; } i = *bp++; verbprintf(0, " pid=%02X\n", i); message.append(QString(" pid=%1\n ").arg(i,0,16).toUpper()); len--; j = 0; while (len) { i = *bp++; if ((i >= 32) && (i < 128)) { verbprintf(0, "%c",i); message.append(QChar(i)); } else if (i == 13) { if (j) { verbprintf(0, "\n"); //message.append("\n"); } j = 0; } else { verbprintf(0, "."); message.append("."); } if (i >= 32) j = 1; len--; } if (j) { verbprintf(0, "\n"); //message.append("\n"); } /* I just secured myself a ticket to hell */ finished: if (message.size() > 0) { emit newMessage(message); } } gqrx-sdr-2.2.0.74.d97bd7/dsp/afsk1200/cafsk12.h000066400000000000000000000111201226036373100201300ustar00rootroot00000000000000/* * cafsk12.h -- AFSK1200 demodulator class * * Copyright (C) 1996 * Thomas Sailer (sailer@ife.ee.ethz.ch, hb9jnx@hb9w.che.eu) * * Copyright (C) 2011 Alexandru Csete (oz9aec at gmail.com) * * 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ #ifndef CAFSK12_H #define CAFSK12_H #include extern const float costabf[0x400]; #define COS(x) costabf[(((x)>>6)&0x3ffu)] #define SIN(x) COS((x)+0xc000) /* * Standard TCM3105 clock frequency: 4.4336MHz * Mark frequency: 2200 Hz * Space frequency: 1200 Hz */ #define FREQ_MARK 1200 #define FREQ_SPACE 2200 #define FREQ_SAMP 22050 #define BAUD 1200 #define SUBSAMP 2 #define CORRLEN ((int)(FREQ_SAMP/BAUD)) #define SPHASEINC (0x10000u*BAUD*SUBSAMP/FREQ_SAMP) struct demod_state { const struct demod_param *dem_par; union { struct l2_state_hdlc { unsigned char rxbuf[512]; unsigned char *rxptr; unsigned int rxstate; unsigned int rxbitstream; unsigned int rxbitbuf; } hdlc; struct l2_state_pocsag { unsigned long rx_data; struct l2_pocsag_rx { unsigned char rx_sync; unsigned char rx_word; unsigned char rx_bit; char func; unsigned long adr; unsigned char buffer[128]; unsigned int numnibbles; } rx[2]; } pocsag; } l2; union { struct l1_state_poc5 { unsigned int dcd_shreg; unsigned int sphase; unsigned int subsamp; } poc5; struct l1_state_poc12 { unsigned int dcd_shreg; unsigned int sphase; unsigned int subsamp; } poc12; struct l1_state_poc24 { unsigned int dcd_shreg; unsigned int sphase; } poc24; struct l1_state_afsk12 { unsigned int dcd_shreg; unsigned int sphase; unsigned int lasts; unsigned int subsamp; } afsk12; struct l1_state_afsk24 { unsigned int dcd_shreg; unsigned int sphase; unsigned int lasts; } afsk24; struct l1_state_hapn48 { unsigned int shreg; unsigned int sphase; float lvllo, lvlhi; } hapn48; struct l1_state_fsk96 { unsigned int dcd_shreg; unsigned int sphase; unsigned int descram; } fsk96; struct l1_state_dtmf { unsigned int ph[8]; float energy[4]; float tenergy[4][16]; int blkcount; int lastch; } dtmf; struct l1_state_zvei { unsigned int ph[16]; float energy[4]; float tenergy[4][32]; int blkcount; int lastch; } zvei; struct l1_state_scope { int datalen; int dispnum; float data[512]; } scope; } l1; }; struct demod_param { const char *name; unsigned int samplerate; unsigned int overlap; //void (*init)(struct demod_state *s); //void (*demod)(struct demod_state *s, float *buffer, int length); }; class CAfsk12 : public QObject { Q_OBJECT public: explicit CAfsk12(QObject *parent = 0); ~CAfsk12(); void demod(float *buffer, int length); void reset(); signals: void newMessage(const QString &message); public slots: private: float corr_mark_i[CORRLEN]; float corr_mark_q[CORRLEN]; float corr_space_i[CORRLEN]; float corr_space_q[CORRLEN]; struct demod_state *state; /* HDLC functions */ void hdlc_init(struct demod_state *s); void hdlc_rxbit(struct demod_state *s, int bit); void verbprintf(int verb_level, const char *fmt, ...); void ax25_disp_packet(unsigned char *bp, unsigned int len); }; #endif // CAFSK12_H gqrx-sdr-2.2.0.74.d97bd7/dsp/afsk1200/costabf.c000066400000000000000000000351211226036373100203210ustar00rootroot00000000000000/* * This file is machine generated, DO NOT EDIT! */ float costabf[1024] = { 1.000000000, 0.999981165, 0.999924719, 0.999830604, 0.999698818, 0.999529421, 0.999322355, 0.999077737, 0.998795450, 0.998475552, 0.998118103, 0.997723043, 0.997290432, 0.996820271, 0.996312618, 0.995767415, 0.995184720, 0.994564593, 0.993906975, 0.993211925, 0.992479563, 0.991709769, 0.990902662, 0.990058184, 0.989176512, 0.988257587, 0.987301409, 0.986308098, 0.985277653, 0.984210074, 0.983105481, 0.981963873, 0.980785251, 0.979569793, 0.978317380, 0.977028131, 0.975702107, 0.974339366, 0.972939968, 0.971503913, 0.970031261, 0.968522072, 0.966976464, 0.965394437, 0.963776052, 0.962121427, 0.960430503, 0.958703458, 0.956940353, 0.955141187, 0.953306019, 0.951435030, 0.949528158, 0.947585583, 0.945607305, 0.943593442, 0.941544056, 0.939459205, 0.937339008, 0.935183525, 0.932992816, 0.930766940, 0.928506076, 0.926210225, 0.923879504, 0.921514034, 0.919113874, 0.916679084, 0.914209783, 0.911706030, 0.909168005, 0.906595707, 0.903989315, 0.901348829, 0.898674488, 0.895966232, 0.893224299, 0.890448749, 0.887639642, 0.884797096, 0.881921291, 0.879012227, 0.876070082, 0.873094976, 0.870086968, 0.867046237, 0.863972843, 0.860866964, 0.857728601, 0.854557991, 0.851355195, 0.848120332, 0.844853580, 0.841554999, 0.838224709, 0.834862888, 0.831469595, 0.828045070, 0.824589312, 0.821102500, 0.817584813, 0.814036310, 0.810457170, 0.806847572, 0.803207517, 0.799537241, 0.795836926, 0.792106569, 0.788346410, 0.784556568, 0.780737221, 0.776888490, 0.773010433, 0.769103348, 0.765167236, 0.761202395, 0.757208824, 0.753186822, 0.749136388, 0.745057762, 0.740951121, 0.736816585, 0.732654274, 0.728464365, 0.724247098, 0.720002532, 0.715730846, 0.711432219, 0.707106769, 0.702754736, 0.698376238, 0.693971455, 0.689540565, 0.685083687, 0.680601001, 0.676092684, 0.671558976, 0.666999936, 0.662415802, 0.657806695, 0.653172851, 0.648514390, 0.643831551, 0.639124453, 0.634393275, 0.629638255, 0.624859512, 0.620057225, 0.615231574, 0.610382795, 0.605511069, 0.600616455, 0.595699310, 0.590759695, 0.585797846, 0.580813944, 0.575808167, 0.570780754, 0.565731823, 0.560661554, 0.555570245, 0.550457954, 0.545324981, 0.540171444, 0.534997642, 0.529803634, 0.524589658, 0.519356012, 0.514102757, 0.508830130, 0.503538370, 0.498227656, 0.492898196, 0.487550169, 0.482183784, 0.476799220, 0.471396744, 0.465976506, 0.460538715, 0.455083579, 0.449611336, 0.444122136, 0.438616246, 0.433093816, 0.427555084, 0.422000259, 0.416429549, 0.410843164, 0.405241311, 0.399624199, 0.393992037, 0.388345033, 0.382683426, 0.377007425, 0.371317208, 0.365612984, 0.359895051, 0.354163527, 0.348418683, 0.342660725, 0.336889863, 0.331106305, 0.325310290, 0.319502026, 0.313681751, 0.307849646, 0.302005947, 0.296150893, 0.290284663, 0.284407526, 0.278519690, 0.272621363, 0.266712755, 0.260794103, 0.254865646, 0.248927608, 0.242980182, 0.237023607, 0.231058106, 0.225083917, 0.219101235, 0.213110313, 0.207111374, 0.201104641, 0.195090324, 0.189068660, 0.183039889, 0.177004218, 0.170961887, 0.164913118, 0.158858150, 0.152797192, 0.146730468, 0.140658244, 0.134580702, 0.128498107, 0.122410677, 0.116318628, 0.110222206, 0.104121633, 0.098017141, 0.091908954, 0.085797310, 0.079682440, 0.073564567, 0.067443922, 0.061320737, 0.055195246, 0.049067676, 0.042938258, 0.036807224, 0.030674804, 0.024541229, 0.018406730, 0.012271538, 0.006135885, 0.000000000, -0.006135885, -0.012271538, -0.018406730, -0.024541229, -0.030674804, -0.036807224, -0.042938258, -0.049067676, -0.055195246, -0.061320737, -0.067443922, -0.073564567, -0.079682440, -0.085797310, -0.091908954, -0.098017141, -0.104121633, -0.110222206, -0.116318628, -0.122410677, -0.128498107, -0.134580702, -0.140658244, -0.146730468, -0.152797192, -0.158858150, -0.164913118, -0.170961887, -0.177004218, -0.183039889, -0.189068660, -0.195090324, -0.201104641, -0.207111374, -0.213110313, -0.219101235, -0.225083917, -0.231058106, -0.237023607, -0.242980182, -0.248927608, -0.254865646, -0.260794103, -0.266712755, -0.272621363, -0.278519690, -0.284407526, -0.290284663, -0.296150893, -0.302005947, -0.307849646, -0.313681751, -0.319502026, -0.325310290, -0.331106305, -0.336889863, -0.342660725, -0.348418683, -0.354163527, -0.359895051, -0.365612984, -0.371317208, -0.377007425, -0.382683426, -0.388345033, -0.393992037, -0.399624199, -0.405241311, -0.410843164, -0.416429549, -0.422000259, -0.427555084, -0.433093816, -0.438616246, -0.444122136, -0.449611336, -0.455083579, -0.460538715, -0.465976506, -0.471396744, -0.476799220, -0.482183784, -0.487550169, -0.492898196, -0.498227656, -0.503538370, -0.508830130, -0.514102757, -0.519356012, -0.524589658, -0.529803634, -0.534997642, -0.540171444, -0.545324981, -0.550457954, -0.555570245, -0.560661554, -0.565731823, -0.570780754, -0.575808167, -0.580813944, -0.585797846, -0.590759695, -0.595699310, -0.600616455, -0.605511069, -0.610382795, -0.615231574, -0.620057225, -0.624859512, -0.629638255, -0.634393275, -0.639124453, -0.643831551, -0.648514390, -0.653172851, -0.657806695, -0.662415802, -0.666999936, -0.671558976, -0.676092684, -0.680601001, -0.685083687, -0.689540565, -0.693971455, -0.698376238, -0.702754736, -0.707106769, -0.711432219, -0.715730846, -0.720002532, -0.724247098, -0.728464365, -0.732654274, -0.736816585, -0.740951121, -0.745057762, -0.749136388, -0.753186822, -0.757208824, -0.761202395, -0.765167236, -0.769103348, -0.773010433, -0.776888490, -0.780737221, -0.784556568, -0.788346410, -0.792106569, -0.795836926, -0.799537241, -0.803207517, -0.806847572, -0.810457170, -0.814036310, -0.817584813, -0.821102500, -0.824589312, -0.828045070, -0.831469595, -0.834862888, -0.838224709, -0.841554999, -0.844853580, -0.848120332, -0.851355195, -0.854557991, -0.857728601, -0.860866964, -0.863972843, -0.867046237, -0.870086968, -0.873094976, -0.876070082, -0.879012227, -0.881921291, -0.884797096, -0.887639642, -0.890448749, -0.893224299, -0.895966232, -0.898674488, -0.901348829, -0.903989315, -0.906595707, -0.909168005, -0.911706030, -0.914209783, -0.916679084, -0.919113874, -0.921514034, -0.923879504, -0.926210225, -0.928506076, -0.930766940, -0.932992816, -0.935183525, -0.937339008, -0.939459205, -0.941544056, -0.943593442, -0.945607305, -0.947585583, -0.949528158, -0.951435030, -0.953306019, -0.955141187, -0.956940353, -0.958703458, -0.960430503, -0.962121427, -0.963776052, -0.965394437, -0.966976464, -0.968522072, -0.970031261, -0.971503913, -0.972939968, -0.974339366, -0.975702107, -0.977028131, -0.978317380, -0.979569793, -0.980785251, -0.981963873, -0.983105481, -0.984210074, -0.985277653, -0.986308098, -0.987301409, -0.988257587, -0.989176512, -0.990058184, -0.990902662, -0.991709769, -0.992479563, -0.993211925, -0.993906975, -0.994564593, -0.995184720, -0.995767415, -0.996312618, -0.996820271, -0.997290432, -0.997723043, -0.998118103, -0.998475552, -0.998795450, -0.999077737, -0.999322355, -0.999529421, -0.999698818, -0.999830604, -0.999924719, -0.999981165, -1.000000000, -0.999981165, -0.999924719, -0.999830604, -0.999698818, -0.999529421, -0.999322355, -0.999077737, -0.998795450, -0.998475552, -0.998118103, -0.997723043, -0.997290432, -0.996820271, -0.996312618, -0.995767415, -0.995184720, -0.994564593, -0.993906975, -0.993211925, -0.992479563, -0.991709769, -0.990902662, -0.990058184, -0.989176512, -0.988257587, -0.987301409, -0.986308098, -0.985277653, -0.984210074, -0.983105481, -0.981963873, -0.980785251, -0.979569793, -0.978317380, -0.977028131, -0.975702107, -0.974339366, -0.972939968, -0.971503913, -0.970031261, -0.968522072, -0.966976464, -0.965394437, -0.963776052, -0.962121427, -0.960430503, -0.958703458, -0.956940353, -0.955141187, -0.953306019, -0.951435030, -0.949528158, -0.947585583, -0.945607305, -0.943593442, -0.941544056, -0.939459205, -0.937339008, -0.935183525, -0.932992816, -0.930766940, -0.928506076, -0.926210225, -0.923879504, -0.921514034, -0.919113874, -0.916679084, -0.914209783, -0.911706030, -0.909168005, -0.906595707, -0.903989315, -0.901348829, -0.898674488, -0.895966232, -0.893224299, -0.890448749, -0.887639642, -0.884797096, -0.881921291, -0.879012227, -0.876070082, -0.873094976, -0.870086968, -0.867046237, -0.863972843, -0.860866964, -0.857728601, -0.854557991, -0.851355195, -0.848120332, -0.844853580, -0.841554999, -0.838224709, -0.834862888, -0.831469595, -0.828045070, -0.824589312, -0.821102500, -0.817584813, -0.814036310, -0.810457170, -0.806847572, -0.803207517, -0.799537241, -0.795836926, -0.792106569, -0.788346410, -0.784556568, -0.780737221, -0.776888490, -0.773010433, -0.769103348, -0.765167236, -0.761202395, -0.757208824, -0.753186822, -0.749136388, -0.745057762, -0.740951121, -0.736816585, -0.732654274, -0.728464365, -0.724247098, -0.720002532, -0.715730846, -0.711432219, -0.707106769, -0.702754736, -0.698376238, -0.693971455, -0.689540565, -0.685083687, -0.680601001, -0.676092684, -0.671558976, -0.666999936, -0.662415802, -0.657806695, -0.653172851, -0.648514390, -0.643831551, -0.639124453, -0.634393275, -0.629638255, -0.624859512, -0.620057225, -0.615231574, -0.610382795, -0.605511069, -0.600616455, -0.595699310, -0.590759695, -0.585797846, -0.580813944, -0.575808167, -0.570780754, -0.565731823, -0.560661554, -0.555570245, -0.550457954, -0.545324981, -0.540171444, -0.534997642, -0.529803634, -0.524589658, -0.519356012, -0.514102757, -0.508830130, -0.503538370, -0.498227656, -0.492898196, -0.487550169, -0.482183784, -0.476799220, -0.471396744, -0.465976506, -0.460538715, -0.455083579, -0.449611336, -0.444122136, -0.438616246, -0.433093816, -0.427555084, -0.422000259, -0.416429549, -0.410843164, -0.405241311, -0.399624199, -0.393992037, -0.388345033, -0.382683426, -0.377007425, -0.371317208, -0.365612984, -0.359895051, -0.354163527, -0.348418683, -0.342660725, -0.336889863, -0.331106305, -0.325310290, -0.319502026, -0.313681751, -0.307849646, -0.302005947, -0.296150893, -0.290284663, -0.284407526, -0.278519690, -0.272621363, -0.266712755, -0.260794103, -0.254865646, -0.248927608, -0.242980182, -0.237023607, -0.231058106, -0.225083917, -0.219101235, -0.213110313, -0.207111374, -0.201104641, -0.195090324, -0.189068660, -0.183039889, -0.177004218, -0.170961887, -0.164913118, -0.158858150, -0.152797192, -0.146730468, -0.140658244, -0.134580702, -0.128498107, -0.122410677, -0.116318628, -0.110222206, -0.104121633, -0.098017141, -0.091908954, -0.085797310, -0.079682440, -0.073564567, -0.067443922, -0.061320737, -0.055195246, -0.049067676, -0.042938258, -0.036807224, -0.030674804, -0.024541229, -0.018406730, -0.012271538, -0.006135885, -0.000000000, 0.006135885, 0.012271538, 0.018406730, 0.024541229, 0.030674804, 0.036807224, 0.042938258, 0.049067676, 0.055195246, 0.061320737, 0.067443922, 0.073564567, 0.079682440, 0.085797310, 0.091908954, 0.098017141, 0.104121633, 0.110222206, 0.116318628, 0.122410677, 0.128498107, 0.134580702, 0.140658244, 0.146730468, 0.152797192, 0.158858150, 0.164913118, 0.170961887, 0.177004218, 0.183039889, 0.189068660, 0.195090324, 0.201104641, 0.207111374, 0.213110313, 0.219101235, 0.225083917, 0.231058106, 0.237023607, 0.242980182, 0.248927608, 0.254865646, 0.260794103, 0.266712755, 0.272621363, 0.278519690, 0.284407526, 0.290284663, 0.296150893, 0.302005947, 0.307849646, 0.313681751, 0.319502026, 0.325310290, 0.331106305, 0.336889863, 0.342660725, 0.348418683, 0.354163527, 0.359895051, 0.365612984, 0.371317208, 0.377007425, 0.382683426, 0.388345033, 0.393992037, 0.399624199, 0.405241311, 0.410843164, 0.416429549, 0.422000259, 0.427555084, 0.433093816, 0.438616246, 0.444122136, 0.449611336, 0.455083579, 0.460538715, 0.465976506, 0.471396744, 0.476799220, 0.482183784, 0.487550169, 0.492898196, 0.498227656, 0.503538370, 0.508830130, 0.514102757, 0.519356012, 0.524589658, 0.529803634, 0.534997642, 0.540171444, 0.545324981, 0.550457954, 0.555570245, 0.560661554, 0.565731823, 0.570780754, 0.575808167, 0.580813944, 0.585797846, 0.590759695, 0.595699310, 0.600616455, 0.605511069, 0.610382795, 0.615231574, 0.620057225, 0.624859512, 0.629638255, 0.634393275, 0.639124453, 0.643831551, 0.648514390, 0.653172851, 0.657806695, 0.662415802, 0.666999936, 0.671558976, 0.676092684, 0.680601001, 0.685083687, 0.689540565, 0.693971455, 0.698376238, 0.702754736, 0.707106769, 0.711432219, 0.715730846, 0.720002532, 0.724247098, 0.728464365, 0.732654274, 0.736816585, 0.740951121, 0.745057762, 0.749136388, 0.753186822, 0.757208824, 0.761202395, 0.765167236, 0.769103348, 0.773010433, 0.776888490, 0.780737221, 0.784556568, 0.788346410, 0.792106569, 0.795836926, 0.799537241, 0.803207517, 0.806847572, 0.810457170, 0.814036310, 0.817584813, 0.821102500, 0.824589312, 0.828045070, 0.831469595, 0.834862888, 0.838224709, 0.841554999, 0.844853580, 0.848120332, 0.851355195, 0.854557991, 0.857728601, 0.860866964, 0.863972843, 0.867046237, 0.870086968, 0.873094976, 0.876070082, 0.879012227, 0.881921291, 0.884797096, 0.887639642, 0.890448749, 0.893224299, 0.895966232, 0.898674488, 0.901348829, 0.903989315, 0.906595707, 0.909168005, 0.911706030, 0.914209783, 0.916679084, 0.919113874, 0.921514034, 0.923879504, 0.926210225, 0.928506076, 0.930766940, 0.932992816, 0.935183525, 0.937339008, 0.939459205, 0.941544056, 0.943593442, 0.945607305, 0.947585583, 0.949528158, 0.951435030, 0.953306019, 0.955141187, 0.956940353, 0.958703458, 0.960430503, 0.962121427, 0.963776052, 0.965394437, 0.966976464, 0.968522072, 0.970031261, 0.971503913, 0.972939968, 0.974339366, 0.975702107, 0.977028131, 0.978317380, 0.979569793, 0.980785251, 0.981963873, 0.983105481, 0.984210074, 0.985277653, 0.986308098, 0.987301409, 0.988257587, 0.989176512, 0.990058184, 0.990902662, 0.991709769, 0.992479563, 0.993211925, 0.993906975, 0.994564593, 0.995184720, 0.995767415, 0.996312618, 0.996820271, 0.997290432, 0.997723043, 0.998118103, 0.998475552, 0.998795450, 0.999077737, 0.999322355, 0.999529421, 0.999698818, 0.999830604, 0.999924719, 0.999981165 }; gqrx-sdr-2.2.0.74.d97bd7/dsp/afsk1200/filter-i386.h000066400000000000000000000163211226036373100206620ustar00rootroot00000000000000/* * filter-i386.h -- optimized filter routines * * Copyright (C) 1996 * Thomas Sailer (sailer@ife.ee.ethz.ch, hb9jnx@hb9w.che.eu) * * 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* ---------------------------------------------------------------------- */ #ifndef _FILTER_I386_H #define _FILTER_I386_H /* ---------------------------------------------------------------------- */ #define __HAVE_ARCH_MAC #define mac(a,b,size) \ (__builtin_constant_p(size) ? __mac_c((a),(b),(size)) : __mac_g((a),(b),(size))) #include extern inline float __mac_g(const float *a, const float *b, unsigned int size) { float sum = 0; unsigned int i; for (i = 0; i < size; i++) sum += (*a++) * (*b++); return sum; } extern inline float __mac_c(const float *a, const float *b, unsigned int size) { float f; /* * inspired from Phil Karn, KA9Q's home page */ switch (size) { case 9: asm volatile ("flds (%1);\n\t" "fmuls (%2);\n\t" "flds 4(%1);\n\t" "fmuls 4(%2);\n\t" "flds 8(%1);\n\t" "fmuls 8(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 12(%1);\n\t" "fmuls 12(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 16(%1);\n\t" "fmuls 16(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 20(%1);\n\t" "fmuls 20(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 24(%1);\n\t" "fmuls 24(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 28(%1);\n\t" "fmuls 28(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 32(%1);\n\t" "fmuls 32(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "faddp;\n\t" : "=t" (f) : "r" (a), "r" (b) : "memory"); return f; case 18: asm volatile ("flds (%1);\n\t" "fmuls (%2);\n\t" "flds 4(%1);\n\t" "fmuls 4(%2);\n\t" "flds 8(%1);\n\t" "fmuls 8(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 12(%1);\n\t" "fmuls 12(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 16(%1);\n\t" "fmuls 16(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 20(%1);\n\t" "fmuls 20(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 24(%1);\n\t" "fmuls 24(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 28(%1);\n\t" "fmuls 28(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 32(%1);\n\t" "fmuls 32(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 36(%1);\n\t" "fmuls 36(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 40(%1);\n\t" "fmuls 40(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 44(%1);\n\t" "fmuls 44(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 48(%1);\n\t" "fmuls 48(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 52(%1);\n\t" "fmuls 52(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 56(%1);\n\t" "fmuls 56(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 60(%1);\n\t" "fmuls 60(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 64(%1);\n\t" "fmuls 64(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 68(%1);\n\t" "fmuls 68(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "faddp;\n\t" : "=t" (f) : "r" (a), "r" (b) : "memory"); return f; case 24: asm volatile ("flds (%1);\n\t" "fmuls (%2);\n\t" "flds 4(%1);\n\t" "fmuls 4(%2);\n\t" "flds 8(%1);\n\t" "fmuls 8(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 12(%1);\n\t" "fmuls 12(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 16(%1);\n\t" "fmuls 16(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 20(%1);\n\t" "fmuls 20(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 24(%1);\n\t" "fmuls 24(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 28(%1);\n\t" "fmuls 28(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 32(%1);\n\t" "fmuls 32(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 36(%1);\n\t" "fmuls 36(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 40(%1);\n\t" "fmuls 40(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 44(%1);\n\t" "fmuls 44(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 48(%1);\n\t" "fmuls 48(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 52(%1);\n\t" "fmuls 52(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 56(%1);\n\t" "fmuls 56(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 60(%1);\n\t" "fmuls 60(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 64(%1);\n\t" "fmuls 64(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 68(%1);\n\t" "fmuls 68(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 72(%1);\n\t" "fmuls 72(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 76(%1);\n\t" "fmuls 76(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 80(%1);\n\t" "fmuls 80(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 84(%1);\n\t" "fmuls 84(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 88(%1);\n\t" "fmuls 88(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "flds 92(%1);\n\t" "fmuls 92(%2);\n\t" "fxch %%st(2);\n\t" "faddp;\n\t" "faddp;\n\t" : "=t" (f) : "r" (a), "r" (b) : "memory"); return f; default: printf("Warning: optimize __mac_c(..., ..., %d)\n", size); return __mac_g(a, b, size); } } /* ---------------------------------------------------------------------- */ #endif /* _FILTER_I386_H */ gqrx-sdr-2.2.0.74.d97bd7/dsp/afsk1200/filter.h000066400000000000000000000066251226036373100202010ustar00rootroot00000000000000/* * filter.h -- optimized filter routines * * Copyright (C) 1996 * Thomas Sailer (sailer@ife.ee.ethz.ch, hb9jnx@hb9w.che.eu) * * 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* ---------------------------------------------------------------------- */ #ifndef _FILTER_H #define _FILTER_H /* ---------------------------------------------------------------------- */ #ifdef ARCH_I386 #include "filter-i386.h" #endif /* ARCH_I386 */ /* ---------------------------------------------------------------------- */ extern inline unsigned int hweight32(unsigned int w) __attribute__ ((unused)); extern inline unsigned int hweight16(unsigned short w) __attribute__ ((unused)); extern inline unsigned int hweight8(unsigned char w) __attribute__ ((unused)); extern inline unsigned int hweight32(unsigned int w) { unsigned int res = (w & 0x55555555) + ((w >> 1) & 0x55555555); res = (res & 0x33333333) + ((res >> 2) & 0x33333333); res = (res & 0x0F0F0F0F) + ((res >> 4) & 0x0F0F0F0F); res = (res & 0x00FF00FF) + ((res >> 8) & 0x00FF00FF); return (res & 0x0000FFFF) + ((res >> 16) & 0x0000FFFF); } extern inline unsigned int hweight16(unsigned short w) { unsigned short res = (w & 0x5555) + ((w >> 1) & 0x5555); res = (res & 0x3333) + ((res >> 2) & 0x3333); res = (res & 0x0F0F) + ((res >> 4) & 0x0F0F); return (res & 0x00FF) + ((res >> 8) & 0x00FF); } extern inline unsigned int hweight8(unsigned char w) { unsigned short res = (w & 0x55) + ((w >> 1) & 0x55); res = (res & 0x33) + ((res >> 2) & 0x33); return (res & 0x0F) + ((res >> 4) & 0x0F); } extern inline unsigned int gcd(unsigned int x, unsigned int y) __attribute__ ((unused)); extern inline unsigned int lcm(unsigned int x, unsigned int y) __attribute__ ((unused)); extern inline unsigned int gcd(unsigned int x, unsigned int y) { for (;;) { if (!x) return y; if (!y) return x; if (x > y) x %= y; else y %= x; } } extern inline unsigned int lcm(unsigned int x, unsigned int y) { return x * y / gcd(x, y); } /* ---------------------------------------------------------------------- */ //#ifndef __HAVE_ARCH_MAC inline float mac(const float *a, const float *b, unsigned int size) { float sum = 0; unsigned int i; for (i = 0; i < size; i++) sum += (*a++) * (*b++); return sum; } //#endif /* __HAVE_ARCH_MAC */ inline float fsqr(float f) { return f*f; } /* ---------------------------------------------------------------------- */ #endif /* _FILTER_H */ gqrx-sdr-2.2.0.74.d97bd7/dsp/agc_impl.cpp000066400000000000000000000436721226036373100175760ustar00rootroot00000000000000////////////////////////////////////////////////////////////////////// // agc_impl.cpp: implementation of the CAgc class. // // This class implements an automatic gain function. // // History: // 2010-09-15 Initial creation MSW // 2011-03-27 Initial release // 2011-09-24 Adapted for gqrx ////////////////////////////////////////////////////////////////////// //========================================================================================== // + + + This Software is released under the "Simplified BSD License" + + + //Copyright 2010 Moe Wheatley. All rights reserved. // //Redistribution and use in source and binary forms, with or without modification, are //permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, this list of // conditions and the following disclaimer. // // 2. Redistributions in binary form must reproduce the above copyright notice, this list // of conditions and the following disclaimer in the documentation and/or other materials // provided with the distribution. // //THIS SOFTWARE IS PROVIDED BY Moe Wheatley ``AS IS'' AND ANY EXPRESS OR IMPLIED //WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND //FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Moe Wheatley OR //CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR //CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR //SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON //ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING //NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF //ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // //The views and conclusions contained in the software and documentation are those of the //authors and should not be interpreted as representing official policies, either expressed //or implied, of Moe Wheatley. //========================================================================================== #include #include ////////////////////////////////////////////////////////////////////// // Local Defines ////////////////////////////////////////////////////////////////////// //signal delay line time delay in seconds. //adjust to cover the impulse response time of filter #define DELAY_TIMECONST .015 //Peak Detector window time delay in seconds. #define WINDOW_TIMECONST .018 //attack time constant in seconds //just small enough to let attackave charge up within the DELAY_TIMECONST time #define ATTACK_RISE_TIMECONST .002 #define ATTACK_FALL_TIMECONST .005 #define DECAY_RISEFALL_RATIO .3 //ratio between rise and fall times of Decay time constants //adjust for best action with SSB // hang timer release decay time constant in seconds #define RELEASE_TIMECONST .05 //limit output to about 3db of max #define AGC_OUTSCALE 0.7 // keep max in and out the same #define MAX_AMPLITUDE 1.0 //32767.0 #define MAX_MANUAL_AMPLITUDE 1.0 //32767.0 #define MIN_CONSTANT 3.2767e-4 // const for calc log() so that a value of 0 magnitude == -8 //corresponding to -160dB. //K = 10^( -8 + log(32767) ) ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// CAgc::CAgc() { m_AgcOn = true; m_UseHang = false; m_Threshold = 0; m_ManualGain = 0; m_SlopeFactor = 0; m_Decay = 0; m_SampleRate = 100.0; } CAgc::~CAgc() { } //////////////////////////////////////////////////////////////////////////////// // Sets and calculates various AGC parameters // "On" switches between AGC on and off. // "Threshold" specifies AGC Knee in dB if AGC is active.( nominal range -160 to 0dB) // "ManualGain" specifies AGC manual gain in dB if AGC is not active.(nominal range 0 to 100dB) // "SlopeFactor" specifies dB reduction in output at knee from maximum output level(nominal range 0 to 10dB) // "Decay" is AGC decay value in milliseconds ( nominal range 20 to 5000 milliSeconds) // "SampleRate" is current sample rate of AGC data //////////////////////////////////////////////////////////////////////////////// void CAgc::SetParameters(bool AgcOn, bool UseHang, int Threshold, int ManualGain, int SlopeFactor, int Decay, double SampleRate) { if( (AgcOn == m_AgcOn) && (UseHang == m_UseHang) && (Threshold == m_Threshold) && (ManualGain == m_ManualGain) && (SlopeFactor == m_SlopeFactor) && (Decay == m_Decay) && (SampleRate == m_SampleRate) ) { return; //just return if no parameter changed } //m_Mutex.lock(); m_AgcOn = AgcOn; m_UseHang = UseHang; m_Threshold = Threshold; m_ManualGain = ManualGain; m_SlopeFactor = SlopeFactor; m_Decay = Decay; if (m_SampleRate != SampleRate) { //clear out delay buffer and init some things if sample rate changes m_SampleRate = SampleRate; for (int i=0; i #define MAX_DELAY_BUF 2048 typedef struct _dCplx { double re; double im; } tDComplex; #define TYPECPX tDComplex class CAgc { public: CAgc(); virtual ~CAgc(); void SetParameters(bool AgcOn, bool UseHang, int Threshold, int ManualGain, int Slope, int Decay, double SampleRate); void ProcessData(int Length, TYPECPX* pInData, TYPECPX* pOutData); void ProcessData(int Length, double* pInData, double* pOutData); private: bool m_AgcOn; //internal copy of AGC settings parameters bool m_UseHang; int m_Threshold; int m_ManualGain; int m_Slope; int m_Decay; double m_SampleRate; double m_SlopeFactor; double m_ManualAgcGain; double m_DecayAve; double m_AttackAve; double m_AttackRiseAlpha; double m_AttackFallAlpha; double m_DecayRiseAlpha; double m_DecayFallAlpha; double m_FixedGain; double m_Knee; double m_GainSlope; double m_Peak; int m_SigDelayPtr; int m_MagBufPos; int m_DelaySize; int m_DelaySamples; int m_WindowSamples; int m_HangTime; int m_HangTimer; //QMutex m_Mutex; //for keeping threads from stomping on each other TYPECPX m_SigDelayBuf[MAX_DELAY_BUF]; double m_MagBuf[MAX_DELAY_BUF]; }; #endif // AGC_IMPL_H gqrx-sdr-2.2.0.74.d97bd7/dsp/correct_iq_cc.cpp000066400000000000000000000102551226036373100206110ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2012-2013 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include #include #include #include "dsp/correct_iq_cc.h" dc_corr_cc_sptr make_dc_corr_cc(double sample_rate, double tau) { return gnuradio::get_initial_sptr(new dc_corr_cc(sample_rate, tau)); } /*! \brief Create DC correction object. * * Use make_dc_corr_cc() instead. */ dc_corr_cc::dc_corr_cc(double sample_rate, double tau) : gr::hier_block2 ("dc_corr_cc", gr::io_signature::make(1, 1, sizeof(gr_complex)), gr::io_signature::make(1, 1, sizeof(gr_complex))) { d_sr = sample_rate; d_tau = tau; d_alpha = 1.0 / (1.0 + d_tau * sample_rate); #ifndef QT_NO_DEBUG_OUTPUT std::cout << "IQ DCR alpha: " << d_alpha << std::endl; #endif d_iir = gr::filter::single_pole_iir_filter_cc::make(d_alpha, 1); d_sub = gr::blocks::sub_cc::make(1); connect(self(), 0, d_iir, 0); connect(self(), 0, d_sub, 0); connect(d_iir, 0, d_sub, 1); connect(d_sub, 0, self(), 0); } dc_corr_cc::~dc_corr_cc() { } /*! \brief Set new sample rate. */ void dc_corr_cc::set_sample_rate(double sample_rate) { d_sr = sample_rate; d_alpha = 1.0 / (1.0 + d_tau * sample_rate); d_iir->set_taps(d_alpha); #ifndef QT_NO_DEBUG_OUTPUT std::cout << "IQ DCR samp_rate: " << sample_rate << std::endl; std::cout << "IQ DCR alpha: " << d_alpha << std::endl; #endif } /*! \brief Set new time constant. */ void dc_corr_cc::set_tau(double tau) { d_tau = tau; d_alpha = 1.0 / (1.0 + d_tau * d_sr); d_iir->set_taps(d_alpha); #ifndef QT_NO_DEBUG_OUTPUT std::cout << "IQ DCR alpha: " << d_alpha << std::endl; #endif } /** I/Q swap **/ iq_swap_cc_sptr make_iq_swap_cc(bool enabled) { return gnuradio::get_initial_sptr(new iq_swap_cc(enabled)); } iq_swap_cc::iq_swap_cc(bool enabled) : gr::hier_block2 ("iq_swap_cc", gr::io_signature::make(1, 1, sizeof(gr_complex)), gr::io_signature::make(1, 1, sizeof(gr_complex))) { d_enabled = enabled; d_c2f = gr::blocks::complex_to_float::make(); d_f2c = gr::blocks::float_to_complex::make(); connect(self(), 0, d_c2f, 0); if (enabled) { connect(d_c2f, 0, d_f2c, 1); connect(d_c2f, 1, d_f2c, 0); } else { connect(d_c2f, 0, d_f2c, 0); connect(d_c2f, 1, d_f2c, 1); } connect(d_f2c, 0, self(), 0); } iq_swap_cc::~iq_swap_cc() { } /*! \brief Enabled or disable I/Q swapping. */ void iq_swap_cc::set_enabled(bool enabled) { if (enabled == d_enabled) return; #ifndef QT_NO_DEBUG_OUTPUT std::cout << "IQ swap: " << enabled << std::endl; #endif d_enabled = enabled; lock(); if (d_enabled) { disconnect(d_c2f, 0, d_f2c, 0); disconnect(d_c2f, 1, d_f2c, 1); connect(d_c2f, 0, d_f2c, 1); connect(d_c2f, 1, d_f2c, 0); } else { disconnect(d_c2f, 0, d_f2c, 1); disconnect(d_c2f, 1, d_f2c, 0); connect(d_c2f, 0, d_f2c, 0); connect(d_c2f, 1, d_f2c, 1); } /** DEBUG **/ /** disconnect_all() does not work here **/ /* disconnect_all(); connect(self(), 0, d_c2f, 0); if (enabled) { connect(d_c2f, 0, d_f2c, 1); connect(d_c2f, 1, d_f2c, 0); } else { connect(d_c2f, 0, d_f2c, 0); connect(d_c2f, 1, d_f2c, 1); } connect(d_f2c, 0, self(), 0); */ unlock(); } gqrx-sdr-2.2.0.74.d97bd7/dsp/correct_iq_cc.h000066400000000000000000000053631226036373100202620ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2012-2013 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef CORRECT_IQ_CC_H #define CORRECT_IQ_CC_H #include #include #include #include #include #include class dc_corr_cc; class iq_swap_cc; typedef boost::shared_ptr dc_corr_cc_sptr; typedef boost::shared_ptr iq_swap_cc_sptr; /*! \brief Return a shared_ptr to a new instance of dc_corr_cc. * \param sample_rate The sample rate * \param tau The time constant for the filter */ dc_corr_cc_sptr make_dc_corr_cc(double sample_rate, double tau=1.0); /*! \brief Single pole IIR filter-based DC offset correction block. * \ingroup DSP * * This block performs automatic DC offset removal using a single pole IIR * filter */ class dc_corr_cc : public gr::hier_block2 { friend dc_corr_cc_sptr make_dc_corr_cc(double sample_rate, double tau); protected: dc_corr_cc(double sample_rate, double tau); public: ~dc_corr_cc(); void set_sample_rate(double sample_rate); void set_tau(double tau); private: gr::filter::single_pole_iir_filter_cc::sptr d_iir; gr::blocks::sub_cc::sptr d_sub; double d_sr; /*!< Sample rate. */ double d_tau; /*!< Time constant. */ double d_alpha; /*!< 1/(1+tau/T). */ }; /*! \brief Return a shared_ptr to a new instance of iq_swap_cc. */ iq_swap_cc_sptr make_iq_swap_cc(bool enabled); /*! \brief Block to swap I and Q channels. * \ingroup DSP */ class iq_swap_cc : public gr::hier_block2 { friend iq_swap_cc_sptr make_iq_swap_cc(bool enabled); protected: iq_swap_cc(bool enabled); public: ~iq_swap_cc(); void set_enabled(bool enabled); private: gr::blocks::complex_to_float::sptr d_c2f; gr::blocks::float_to_complex::sptr d_f2c; bool d_enabled; }; #endif /* CORRECT_IQ_CC_H */ gqrx-sdr-2.2.0.74.d97bd7/dsp/lpf.cpp000066400000000000000000000052771226036373100166030ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2012 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include #include #include #include "dsp/lpf.h" static const int MIN_IN = 1; /* Mininum number of input streams. */ static const int MAX_IN = 1; /* Maximum number of input streams. */ static const int MIN_OUT = 1; /* Minimum number of output streams. */ static const int MAX_OUT = 1; /* Maximum number of output streams. */ /* * Create a new instance of lpf and return * a boost shared_ptr. This is effectively the public constructor. */ lpf_ff_sptr make_lpf_ff(double sample_rate, double cutoff_freq, double trans_width, double gain) { return gnuradio::get_initial_sptr(new lpf_ff(sample_rate, cutoff_freq, trans_width, gain)); } lpf_ff::lpf_ff(double sample_rate, double cutoff_freq, double trans_width, double gain) : gr::hier_block2("lpf_ff", gr::io_signature::make(MIN_IN, MAX_IN, sizeof (float)), gr::io_signature::make(MIN_OUT, MAX_OUT, sizeof (float))), d_sample_rate(sample_rate), d_cutoff_freq(cutoff_freq), d_trans_width(trans_width), d_gain(gain) { /* generate taps */ d_taps = gr::filter::firdes::low_pass(d_gain, d_sample_rate, d_cutoff_freq, d_trans_width); /* create low-pass filter (decimation=1) */ lpf = gr::filter::fir_filter_fff::make(1, d_taps); /* connect filter */ connect(self(), 0, lpf, 0); connect(lpf, 0, self(), 0); } lpf_ff::~lpf_ff() { } void lpf_ff::set_param(double cutoff_freq, double trans_width) { d_cutoff_freq = cutoff_freq; d_trans_width = trans_width; /* generate new taps */ d_taps = gr::filter::firdes::low_pass(d_gain, d_sample_rate, d_cutoff_freq, d_trans_width); lpf->set_taps(d_taps); } gqrx-sdr-2.2.0.74.d97bd7/dsp/lpf.h000066400000000000000000000054601226036373100162420ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2012 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef LPF_H #define LPF_H #include #include #include class lpf_ff; typedef boost::shared_ptr lpf_ff_sptr; /*! \brief Return a shared_ptr to a new instance of lpf. * \param sample_rate The sample rate (Hz). * \param cutoff_freq Center of transition band (Hz). * \param transition_width Width of transition band (Hz). * \param . * \param gain Overall gain of filter (typically 1.0). * * This is effectively the public constructor. To avoid accidental use * of raw pointers, lpf's constructor is private. * make_lpf is the public interface for creating new instances. */ lpf_ff_sptr make_lpf_ff(double sample_rate=48000., double cutoff_freq=5000., double trans_width=1000., double gain=1.0); /*! \brief low-pass filter (LPF) with float taps. * \ingroup DSP * * This class encapsulates a low-pass FIR filter and the code * required to generate filter taps. It provides a simple * interface to set the filter parameters. * * The user of this class is expected to provide valid parameters and no checks are * performed by the accessors (though the taps generator from gr::filter::firdes does perform * some sanity checks and throws std::out_of_range in case of bad parameter). */ class lpf_ff : public gr::hier_block2 { friend lpf_ff_sptr make_lpf_ff(double sample_rate, double cutoff_freq, double trans_width, double gain); protected: lpf_ff(double sample_rate, double cutoff_freq, double trans_width, double gain); public: ~lpf_ff(); void set_param(double cutoff_freq, double trans_width); private: /* GR blocks */ gr::filter::fir_filter_fff::sptr lpf; /* other parameters */ std::vector d_taps; double d_sample_rate; double d_cutoff_freq; double d_trans_width; double d_gain; }; #endif // LPF_H gqrx-sdr-2.2.0.74.d97bd7/dsp/resampler_ff_old.cpp000066400000000000000000000062451226036373100213210ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Copyright 2011 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include /* * Create a new instance of resampler_ff and return * a boost shared_ptr. This is effectively the public constructor. */ resampler_ffo_sptr make_resampler_ffo(unsigned int input_rate, unsigned int output_rate) { return gnuradio::get_initial_sptr(new resampler_ffo(input_rate, output_rate)); } static const int MIN_IN = 1; /* Mininum number of input streams. */ static const int MAX_IN = 1; /* Maximum number of input streams. */ static const int MIN_OUT = 1; /* Minimum number of output streams. */ static const int MAX_OUT = 1; /* Maximum number of output streams. */ resampler_ffo::resampler_ffo(unsigned int input_rate, unsigned int output_rate) : gr_hier_block2 ("resampler_ffo", gr_make_io_signature (MIN_IN, MAX_IN, sizeof (float)), gr_make_io_signature (MIN_OUT, MAX_OUT, sizeof (float))) { /* calculate interpolation and decimation */ d_interp = lcm(input_rate, output_rate) / input_rate; d_decim = lcm(input_rate, output_rate) / output_rate; std::cout << "resampler_ff:" << std::endl; std::cout << " inter: " << d_interp << std::endl; std::cout << " decim: " << d_decim << std::endl; /* generate taps */ float fract_bw = 0.4; float trans_width = 0.5 - fract_bw; float mid_trans_band = 0.5 - trans_width/2.0; d_taps = gr_firdes::low_pass(d_interp, // gain 1.0, // sampling freq mid_trans_band/d_interp, trans_width/d_interp, gr_firdes::WIN_KAISER, 5.0 // beta ); /* create band pass filter */ d_rrb = gr_make_rational_resampler_base_fff(d_interp, d_decim, d_taps); /* connect filter */ connect(self(), 0, d_rrb, 0); connect(d_rrb, 0, self(), 0); } resampler_ffo::~resampler_ffo () { } /*! \brief Greatest common divisor. */ unsigned long long resampler_ffo::gcd(unsigned long long a, unsigned long long b) { unsigned long long c = a % b; while (c!= 0) { a = b; b = c; c = a % b; } return b; } /*! \brief Least common multiple. */ unsigned long long resampler_ffo::lcm(unsigned long long a, unsigned long long b) { return ((a*b) / gcd(a,b)); } gqrx-sdr-2.2.0.74.d97bd7/dsp/resampler_ff_old.h000066400000000000000000000040761226036373100207660ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Copyright 2011 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef RESAMPLER_FF_H #define RESAMPELR_FF_H #include #include class resampler_ffo; typedef boost::shared_ptr resampler_ffo_sptr; /*! \brief Return a shared_ptr to a new instance of resampler_ff. * \param input_rate Input sample rate in Hz. * \param output_rate Output sample rate Hz. * * This is effectively the public constructor. */ resampler_ffo_sptr make_resampler_ffo(unsigned int input_rate, unsigned int output_rate); /*! \brief Rational resampler based on gr_rational_resampler_base_fff * \ingroup DSP * * This block is a convenience wrapper around gr_rational_resampler_fff. It takes care * of generating filter taps that can be used for the resampler, as well as calculating * the interpolation and decimation given the input/output sample rates. */ class resampler_ffo : public gr_hier_block2 { public: resampler_ffo(unsigned int input_rate, unsigned int output_rate); // FIXME: should be private ~resampler_ffo(); private: std::vector d_taps; gr_rational_resampler_base_fff_sptr d_rrb; unsigned int d_interp; unsigned int d_decim; unsigned long long gcd(unsigned long long a, unsigned long long b); unsigned long long lcm(unsigned long long a, unsigned long long b); }; #endif // RESAMPLER_FF_H gqrx-sdr-2.2.0.74.d97bd7/dsp/resampler_xx.cpp000066400000000000000000000113441226036373100205230ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2012 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include #include #include #include "dsp/resampler_xx.h" /* Create a new instance of resampler_cc and return * a boost shared_ptr. This is effectively the public constructor. */ resampler_cc_sptr make_resampler_cc(float rate) { return gnuradio::get_initial_sptr(new resampler_cc(rate)); } resampler_cc::resampler_cc(float rate) : gr::hier_block2 ("resampler_cc", gr::io_signature::make (1, 1, sizeof(gr_complex)), gr::io_signature::make (1, 1, sizeof(gr_complex))) { /* I ceated this code based on: http://gnuradio.squarespace.com/blog/2010/12/6/new-interface-for-pfb_arb_resampler_ccf.html and blks2.pfb_arb_resampler.py Note: In case of decimation, we limit the cutoff to the output bandwidth to avoid "phantom" signals when we have a frequency translation in front of the PFB resampler. */ /* generate taps */ double cutoff = rate > 1.0 ? 0.4 : 0.4*rate; double trans_width = rate > 1.0 ? 0.2 : 0.2*rate; unsigned int flt_size = 32; d_taps = gr::filter::firdes::low_pass(flt_size, flt_size, cutoff, trans_width); /* create the filter */ d_filter = gr::filter::pfb_arb_resampler_ccf::make(rate, d_taps, flt_size); /* connect filter */ connect(self(), 0, d_filter, 0); connect(d_filter, 0, self(), 0); } resampler_cc::~resampler_cc() { } void resampler_cc::set_rate(float rate) { /* generate taps */ double cutoff = rate > 1.0 ? 0.4 : 0.4*rate; double trans_width = rate > 1.0 ? 0.2 : 0.2*rate; unsigned int flt_size = 32; d_taps = gr::filter::firdes::low_pass(flt_size, flt_size, cutoff, trans_width); /* FIXME: Should implement set_taps() in PFB */ lock(); disconnect(self(), 0, d_filter, 0); disconnect(d_filter, 0, self(), 0); d_filter.reset(); d_filter = gr::filter::pfb_arb_resampler_ccf::make(rate, d_taps, flt_size); connect(self(), 0, d_filter, 0); connect(d_filter, 0, self(), 0); unlock(); } /* Create a new instance of resampler_ff and return * a boost shared_ptr. This is effectively the public constructor. */ resampler_ff_sptr make_resampler_ff(float rate) { return gnuradio::get_initial_sptr(new resampler_ff(rate)); } resampler_ff::resampler_ff(float rate) : gr::hier_block2 ("resampler_ff", gr::io_signature::make (1, 1, sizeof(float)), gr::io_signature::make (1, 1, sizeof(float))) { /* I ceated this code based on: http://gnuradio.squarespace.com/blog/2010/12/6/new-interface-for-pfb_arb_resampler_ccf.html and blks2.pfb_arb_resampler.py Note: In case of decimation, we limit the cutoff to the output bandwidth to avoid "phantom" signals when we have a frequency translation in front of the PFB resampler. */ /* generate taps */ double cutoff = rate > 1.0 ? 0.4 : 0.4*rate; double trans_width = rate > 1.0 ? 0.2 : 0.2*rate; unsigned int flt_size = 32; d_taps = gr::filter::firdes::low_pass(flt_size, flt_size, cutoff, trans_width); /* create the filter */ d_filter = gr::filter::pfb_arb_resampler_fff::make(rate, d_taps, flt_size); /* connect filter */ connect(self(), 0, d_filter, 0); connect(d_filter, 0, self(), 0); } resampler_ff::~resampler_ff() { } void resampler_ff::set_rate(float rate) { /* generate taps */ double cutoff = rate > 1.0 ? 0.4 : 0.4*rate; double trans_width = rate > 1.0 ? 0.2 : 0.2*rate; unsigned int flt_size = 32; d_taps = gr::filter::firdes::low_pass(flt_size, flt_size, cutoff, trans_width); /* FIXME: Should implement set_taps() in PFB */ lock(); disconnect(self(), 0, d_filter, 0); disconnect(d_filter, 0, self(), 0); d_filter.reset(); d_filter = gr::filter::pfb_arb_resampler_fff::make(rate, d_taps, flt_size); connect(self(), 0, d_filter, 0); connect(d_filter, 0, self(), 0); unlock(); } gqrx-sdr-2.2.0.74.d97bd7/dsp/resampler_xx.h000066400000000000000000000054271226036373100201750ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2012 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef RESAMPLER_XX_H #define RESAMPLER_XX_H #include #include #include class resampler_cc; class resampler_ff; typedef boost::shared_ptr resampler_cc_sptr; typedef boost::shared_ptr resampler_ff_sptr; /*! \brief Return a shared_ptr to a new instance of resampler_cc. * \param rate Resampling rate, i.e. output/input. * * This is effectively the public constructor. */ resampler_cc_sptr make_resampler_cc(float rate); /*! \brief Arbitrary rate resampler based on gr_pfb_arb_resampler_ccf * \ingroup DSP * * This block is a convenience wrapper around gr_pfb_arb_resampler_ccf. It takes care * of generating filter taps that can be used for the filter, as well as calculating * the other required parameters. */ class resampler_cc : public gr::hier_block2 { public: resampler_cc(float rate); // FIXME: should be private ~resampler_cc(); void set_rate(float rate); private: std::vector d_taps; gr::filter::pfb_arb_resampler_ccf::sptr d_filter; }; /*! \brief Return a shared_ptr to a new instance of resampler_ff. * \param rate Resampling rate, i.e. output/input. * * This is effectively the public constructor. */ resampler_ff_sptr make_resampler_ff(float rate); /*! \brief Arbitrary rate resampler based on gr_pfb_arb_resampler_fff * \ingroup DSP * * This block is a convenience wrapper around gr_pfb_arb_resampler_fff. It takes care * of generating filter taps that can be used for the filter, as well as calculating * the other required parameters. */ class resampler_ff : public gr::hier_block2 { public: resampler_ff(float rate); // FIXME: should be private ~resampler_ff(); void set_rate(float rate); private: std::vector d_taps; gr::filter::pfb_arb_resampler_fff::sptr d_filter; }; #endif // RESAMPLER_XX_H gqrx-sdr-2.2.0.74.d97bd7/dsp/rx_agc_xx.cpp000066400000000000000000000143551226036373100200010ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include #include #include #include rx_agc_cc_sptr make_rx_agc_cc(double sample_rate, bool agc_on, int threshold, int manual_gain, int slope, int decay, bool use_hang) { return gnuradio::get_initial_sptr(new rx_agc_cc(sample_rate, agc_on, threshold, manual_gain, slope, decay, use_hang)); } /*! \brief Create receiver AGC object. * * Use make_rx_agc_cc() instead. */ rx_agc_cc::rx_agc_cc(double sample_rate, bool agc_on, int threshold, int manual_gain, int slope, int decay, bool use_hang) : gr::sync_block ("rx_agc_cc", gr::io_signature::make(1, 1, sizeof(gr_complex)), gr::io_signature::make(1, 1, sizeof(gr_complex))), d_agc_on(agc_on), d_sample_rate(sample_rate), d_threshold(threshold), d_manual_gain(manual_gain), d_slope(slope), d_decay(decay), d_use_hang(use_hang) { d_agc = new CAgc(); d_agc->SetParameters(d_agc_on, d_use_hang, d_threshold, d_manual_gain, d_slope, d_decay, d_sample_rate); } rx_agc_cc::~rx_agc_cc() { delete d_agc; } /*! \brief Receiver AGC work method. * \param mooutput_items * \param input_items * \param output_items */ int rx_agc_cc::work(int noutput_items, gr_vector_const_void_star &input_items, gr_vector_void_star &output_items) { const gr_complex *in = (const gr_complex *) input_items[0]; gr_complex *out = (gr_complex *) output_items[0]; int i; // lock mutex boost::mutex::scoped_lock lock(d_mutex); for (i = 0; i < noutput_items; i++) { // implicit conversion from float to double ib[i].im = in[i].imag(); ib[i].re = in[i].real(); } d_agc->ProcessData(noutput_items, &ib[0], &ob[0]); for (i = 0; i < noutput_items; i++) { // implicit conversion from double to float out[i] = gr_complex(ob[i].re, ob[i].im); } return noutput_items; } /*! \brief Enable or disable AGC. * \param agc_on Whether AGC should be endabled. * * When AGC is disabled a fixed gain is used. * * \sa set_manual_gain() */ void rx_agc_cc::set_agc_on(bool agc_on) { if (agc_on != d_agc_on) { boost::mutex::scoped_lock lock(d_mutex); d_agc_on = agc_on; d_agc->SetParameters(d_agc_on, d_use_hang, d_threshold, d_manual_gain, d_slope, d_decay, d_sample_rate); } } /*! \brief Set AGC sample rate. * \param sample_rate The sample rate. * * The AGC uses knowledge about the sample rate to calculate various delays and * time constants. */ void rx_agc_cc::set_sample_rate(double sample_rate) { if (sample_rate != d_sample_rate) { boost::mutex::scoped_lock lock(d_mutex); d_sample_rate = sample_rate; d_agc->SetParameters(d_agc_on, d_use_hang, d_threshold, d_manual_gain, d_slope, d_decay, d_sample_rate); } } /*! \brief Set new AGC threshold. * \param threshold The new threshold between -160 and 0dB. * * The threshold specifies AGC "knee" in dB when the AGC is active. */ void rx_agc_cc::set_threshold(int threshold) { if ((threshold != d_threshold) && (threshold >= -160) && (threshold <= 0)) { boost::mutex::scoped_lock lock(d_mutex); d_threshold = threshold; d_agc->SetParameters(d_agc_on, d_use_hang, d_threshold, d_manual_gain, d_slope, d_decay, d_sample_rate); } } /*! \brief Set new manual gain. * \param gain The new manual gain between 0 and 100dB. * * The manual gain is used when AGC is switched off. * * \sa set_agc_on() */ void rx_agc_cc::set_manual_gain(int gain) { if ((gain != d_manual_gain) && (gain >= 0) && (gain <= 100)) { boost::mutex::scoped_lock lock(d_mutex); d_manual_gain = gain; d_agc->SetParameters(d_agc_on, d_use_hang, d_threshold, d_manual_gain, d_slope, d_decay, d_sample_rate); } } /*! \brief Set AGC slope factor. * \param slope The new slope factor between 0 and 10dB. * * The slope factor specifies dB reduction in output at knee from maximum output level */ void rx_agc_cc::set_slope(int slope) { if ((slope != d_slope) && (slope >= 0) && (slope <= 10)) { boost::mutex::scoped_lock lock(d_mutex); d_slope = slope; d_agc->SetParameters(d_agc_on, d_use_hang, d_threshold, d_manual_gain, d_slope, d_decay, d_sample_rate); } } /*! \brief Set AGC decay time. * \param decay The new AGC decay time between 20 to 5000 ms. */ void rx_agc_cc::set_decay(int decay) { if ((decay != d_decay) && (decay >= 20) && (decay <= 5000)) { boost::mutex::scoped_lock lock(d_mutex); d_decay = decay; d_agc->SetParameters(d_agc_on, d_use_hang, d_threshold, d_manual_gain, d_slope, d_decay, d_sample_rate); } } /*! \brief Enable/disable AGC hang. * \param use_hang Whether to use hang or not. */ void rx_agc_cc::set_use_hang(bool use_hang) { if (use_hang != d_use_hang) { boost::mutex::scoped_lock lock(d_mutex); d_use_hang = use_hang; d_agc->SetParameters(d_agc_on, d_use_hang, d_threshold, d_manual_gain, d_slope, d_decay, d_sample_rate); } } gqrx-sdr-2.2.0.74.d97bd7/dsp/rx_agc_xx.h000066400000000000000000000102241226036373100174350ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef RX_AGC_XX_H #define RX_AGC_XX_H #include #include #include #include class rx_agc_cc; typedef boost::shared_ptr rx_agc_cc_sptr; /*! \brief Return a shared_ptr to a new instance of rx_agc_cc. * \param sample_rate The samle rate (default = 96000). * \param agc_on Whether AGC should be ON (default = true). * \param threshold AGC Knee in dB if AGC is active. Range -160 to 0dB * (default = -100dB). * \param manual_gain Manual gain when AGC is OFF. Range 0 to 100dB * (default = 0dB). * \param slope AGC slope factor. Specifies dB reduction in output at * knee from maximum output level. Range 0 to 10dB * (default = 2dB TBC). * \param decay AGC decay time in milliseconds. Range 20 to 5000. This * parameter determines whether AGC is fast/slow/medium. * The default value is 100ms (fast AGC). * \param use_hang Whether AGC should "hang" before starting to decay. * The default is false. * * This is effectively the public constructor for a new AGC block. * To avoid accidental use of raw pointers, the rx_agc_cc constructor is private. * make_rx_agc_cc is the public interface for creating new instances. */ rx_agc_cc_sptr make_rx_agc_cc(double sample_rate = 96000.0, bool agc_on = true, int threshold = -100, int manual_gain = 0, int slope = 2, int decay = 100, bool use_hang = false); /*! \brief Experimental AGC block for analog voice modes (AM, SSB, CW). * \ingroup DSP * * This block performs automatic gain control. * To be written... * * \todo rx_agc_ff */ class rx_agc_cc : public gr::sync_block { friend rx_agc_cc_sptr make_rx_agc_cc(double sample_rate, bool agc_on, int threshold, int manual_gain, int slope, int decay, bool use_hang); protected: rx_agc_cc(double sample_rate, bool agc_on, int threshold, int manual_gain, int slope, int decay, bool use_hang); public: ~rx_agc_cc(); int work(int noutput_items, gr_vector_const_void_star &input_items, gr_vector_void_star &output_items); void set_agc_on(bool agc_on); void set_sample_rate(double sample_rate); void set_threshold(int threshold); void set_manual_gain(int gain); void set_slope(int slope); void set_decay(int decay); void set_use_hang(bool use_hang); private: CAgc *d_agc; boost::mutex d_mutex; /*! Used to lock internal data while processing or setting parameters. */ bool d_agc_on; /*! Current AGC status (true/false). */ double d_sample_rate; /*! Current sample rate. */ int d_threshold; /*! Current AGC threshold (-160...0 dB). */ int d_manual_gain; /*! Current gain when AGC is OFF. */ int d_slope; /*! Current AGC slope (0...10 dB). */ int d_decay; /*! Current AGC decay (20...5000 ms). */ bool d_use_hang; /*! Current AGC hang status (true/false). */ TYPECPX ib[64000]; TYPECPX ob[64000]; }; #endif /* RX_AGC_XX_H */ gqrx-sdr-2.2.0.74.d97bd7/dsp/rx_demod_am.cpp000066400000000000000000000062211226036373100202660ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011 Alexandru Csete OZ9AEC. * Copyright 2013 Vesa Solonen OH2JCP. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include #include /* Create a new instance of rx_demod_am and return a boost shared_ptr. */ rx_demod_am_sptr make_rx_demod_am(float quad_rate, float audio_rate, bool dcr) { return gnuradio::get_initial_sptr(new rx_demod_am(quad_rate, audio_rate, dcr)); } static const int MIN_IN = 1; /* Mininum number of input streams. */ static const int MAX_IN = 1; /* Maximum number of input streams. */ static const int MIN_OUT = 1; /* Minimum number of output streams. */ static const int MAX_OUT = 1; /* Maximum number of output streams. */ rx_demod_am::rx_demod_am(float quad_rate, float audio_rate, bool dcr) : gr::hier_block2 ("rx_demod_am", gr::io_signature::make (MIN_IN, MAX_IN, sizeof (gr_complex)), gr::io_signature::make (MIN_OUT, MAX_OUT, sizeof (float))), d_quad_rate(quad_rate), d_audio_rate(audio_rate), d_dcr_enabled(dcr) { /* demodulator */ d_demod = gr::blocks::complex_to_mag::make(1); /* connect blocks */ connect(self(), 0, d_demod, 0); /* DC removal */ d_fftaps.resize(2); d_fbtaps.resize(2); d_fftaps[0] = 1.0; // FIXME: could be configurable with a specified time constant d_fftaps[1] = -1.0; d_fbtaps[0] = 0.0; d_fbtaps[1] = 0.999; d_dcr = gr::filter::iir_filter_ffd::make(d_fftaps, d_fbtaps); if (d_dcr_enabled) { connect(d_demod, 0, d_dcr, 0); connect(d_dcr, 0, self(), 0); } else { connect(d_demod, 0, self(), 0); } } rx_demod_am::~rx_demod_am () { } /*! \brief Set DCR status. * \param dcr The new status (on or off). */ void rx_demod_am::set_dcr(bool dcr) { if (dcr == d_dcr_enabled) { return; } if (d_dcr_enabled) { // Switching from ON to OFF lock(); disconnect(d_demod, 0, d_dcr, 0); disconnect(d_dcr, 0, self(), 0); connect(d_demod, 0, self(), 0); unlock(); } else { // Switching from OFF to ON lock(); disconnect(d_demod, 0, self(), 0); connect(d_demod, 0, d_dcr, 0); connect(d_dcr, 0, self(), 0); unlock(); } d_dcr_enabled = dcr; } /*! \brief Get current DCR status. */ bool rx_demod_am::dcr() { return d_dcr_enabled; } gqrx-sdr-2.2.0.74.d97bd7/dsp/rx_demod_am.h000066400000000000000000000050121226036373100177300ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011 Alexandru Csete OZ9AEC. * Copyright 2013 Vesa Solonen OH2JCP. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef RX_DEMOD_AM_H #define RX_DEMOD_AM_H #include #include #include #include class rx_demod_am; typedef boost::shared_ptr rx_demod_am_sptr; /*! \brief Return a shared_ptr to a new instance of rx_demod_am. * \param quad_rate The input sample rate. * \param audio_rate The audio rate. * \param dcr Enable DCR * * This is effectively the public constructor. */ rx_demod_am_sptr make_rx_demod_am(float quad_rate, float audio_rate, bool dcr=true); /*! \brief AM demodulator. * \ingroup DSP * * This class implements the AM demodulator as envelope detector. * AM demodulation is simply a conversion from complex to magnitude. * This block implements an optional IIR DC-removal filter for the demodulated signal. * */ class rx_demod_am : public gr::hier_block2 { public: rx_demod_am(float quad_rate=48000.0, float audio_rate=48000.0, bool dcr=true); // FIXME: could be private ~rx_demod_am(); void set_dcr(bool dcr); bool dcr(); private: /* GR blocks */ gr::blocks::complex_to_mag::sptr d_demod; /*! AM demodulation (complex to magnitude). */ gr::filter::iir_filter_ffd::sptr d_dcr; /*! DC removal (IIR high pass). */ /* other parameters */ float d_quad_rate; /*! Quadrature rate. */ float d_audio_rate; /*! Audio rate. */ bool d_dcr_enabled; /*! DC removal flag. */ /* IIR DC-removal filter taps */ std::vector d_fftaps; /*! Feed forward taps. */ std::vector d_fbtaps; /*! Feed back taps. */ }; #endif // RX_DEMOD_AM_H gqrx-sdr-2.2.0.74.d97bd7/dsp/rx_demod_fm.cpp000066400000000000000000000111521226036373100202720ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include /* Create a new instance of rx_demod_fm and return a boost shared_ptr. */ rx_demod_fm_sptr make_rx_demod_fm(float quad_rate, float audio_rate, float max_dev, double tau) { return gnuradio::get_initial_sptr(new rx_demod_fm(quad_rate, audio_rate, max_dev, tau)); } static const int MIN_IN = 1; /* Mininum number of input streams. */ static const int MAX_IN = 1; /* Maximum number of input streams. */ static const int MIN_OUT = 1; /* Minimum number of output streams. */ static const int MAX_OUT = 1; /* Maximum number of output streams. */ rx_demod_fm::rx_demod_fm(float quad_rate, float audio_rate, float max_dev, double tau) : gr::hier_block2 ("rx_demod_fm", gr::io_signature::make (MIN_IN, MAX_IN, sizeof (gr_complex)), gr::io_signature::make (MIN_OUT, MAX_OUT, sizeof (float))), d_quad_rate(quad_rate), d_audio_rate(audio_rate), d_max_dev(max_dev), d_tau(tau) { float gain; /* demodulator gain */ gain = d_quad_rate / (2.0 * M_PI * d_max_dev); //std::cout << "G: " << gain << std::endl; /* demodulator */ d_quad = gr::analog::quadrature_demod_cf::make(gain); /* de-emphasis */ d_fftaps.resize(2); d_fbtaps.resize(2); calculate_iir_taps(d_tau); d_deemph = gr::filter::iir_filter_ffd::make(d_fftaps, d_fbtaps); /* connect block */ connect(self(), 0, d_quad, 0); if (d_tau > 1.0e-9) { connect(d_quad, 0, d_deemph, 0); connect(d_deemph, 0, self(), 0); } else { connect(d_quad, 0, self(), 0); } } rx_demod_fm::~rx_demod_fm () { } /*! \brief Set maximum FM deviation. * \param max_dev The new mximum deviation in Hz * * The maximum deviation is related to the gain of the * quadrature demodulator by: * * gain = quad_rate / (2 * PI * max_dev) */ void rx_demod_fm::set_max_dev(float max_dev) { float gain; if ((max_dev < 500.0) || (max_dev > d_quad_rate/2.0)) { return; } d_max_dev = max_dev; gain = d_quad_rate / (2.0 * M_PI * max_dev); d_quad->set_gain(gain); } /*! \brief Set FM de-emphasis time constant. * \param tau The new time costant. * * \bug Assumes that IIR filter has already been constructed so that we * can use the set_taps() method. */ void rx_demod_fm::set_tau(double tau) { if (fabs(tau - d_tau) < 1.0e-9) { /* no change */ return; } if (tau > 1.0e-9) { calculate_iir_taps(tau); d_deemph->set_taps(d_fftaps, d_fbtaps); /* check to see if we need to rewire flow graph */ if (d_tau <= 1.0e-9) { /* need to put deemph into the flowgraph */ lock(); disconnect(d_quad, 0, self(), 0); connect(d_quad, 0, d_deemph, 0); connect(d_deemph, 0, self(), 0); unlock(); } d_tau = tau; } else { //std::cout << "TAU is 0: " << tau << std::endl; /* diable de-emph if conencted */ if (d_tau > 1.0e-9) { //std::cout << " Disable deemph" << std::endl; lock(); disconnect(d_quad, 0, d_deemph, 0); disconnect(d_deemph, 0, self(), 0); connect(d_quad, 0, self(), 0); unlock(); } d_tau = 0.0; } } /*! \brief Calculate taps for FM de-emph IIR filter. */ void rx_demod_fm::calculate_iir_taps(double tau) { /* copied from fm_emph.py in gnuradio-core */ double w_p, w_pp; w_p = 1.0/tau; w_pp = tan(w_p / (d_quad_rate * 2.0)); /* prewarped analog freq */ d_fftaps[0] = w_pp/(1 + w_pp); d_fftaps[1] = d_fftaps[0]; d_fbtaps[0] = 1.0; d_fbtaps[1] = (w_pp - 1)/(w_pp + 1); } gqrx-sdr-2.2.0.74.d97bd7/dsp/rx_demod_fm.h000066400000000000000000000060751226036373100177470ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef RX_DEMOD_FM_H #define RX_DEMOD_FM_H #include #include #include #include #include class rx_demod_fm; typedef boost::shared_ptr rx_demod_fm_sptr; /*! \brief Return a shared_ptr to a new instance of rx_demod_fm. * \param quad_rate The input sample rate. * \param audio_rate The audio rate. * \param max_dev Maximum deviation in Hz * \param tau De-emphasis time constant in seconds (75us in US, 50us in EUR, 0.0 disables). * * This is effectively the public constructor. To avoid accidental use * of raw pointers, rx_demod_fm's constructor is private. * make_rx_dmod_fm is the public interface for creating new instances. */ rx_demod_fm_sptr make_rx_demod_fm(float quad_rate, float audio_rate, float max_dev=5000.0, double tau=50.0e-6); /*! \brief FM demodulator. * \ingroup DSP * * This class implements the FM demodulator using the gr_quadrature_demod block. * It also provides de-emphasis with variable time constant (use 0.0 to disable). * */ class rx_demod_fm : public gr::hier_block2 { public: rx_demod_fm(float quad_rate=48000.0, float audio_rate=48000.0, float max_dev=5000.0, double tau=50.0e-6); // FIXME: should be private ~rx_demod_fm(); void set_max_dev(float max_dev); void set_tau(double tau); private: /* GR blocks */ gr::analog::quadrature_demod_cf::sptr d_quad; /*! The quadrature demodulator block. */ gr::filter::iir_filter_ffd::sptr d_deemph; /*! De-emphasis IIR filter. */ gr::filter::pfb_arb_resampler_ccf::sptr d_resampler; /*! PFB resampler. */ std::vector d_taps; /*! Taps for the PFB resampler. */ /* other parameters */ float d_quad_rate; /*! Quadrature rate. */ float d_audio_rate; /*! Audio rate. */ float d_max_dev; /*! Max deviation. */ double d_tau; /*! De-emphasis time constant. */ /* De-emph IIR filter taps */ std::vector d_fftaps; /*! Feed forward taps. */ std::vector d_fbtaps; /*! Feed back taps. */ void calculate_iir_taps(double tau); }; #endif // RX_DEMOD_FM_H gqrx-sdr-2.2.0.74.d97bd7/dsp/rx_fft.cpp000066400000000000000000000223601226036373100173020ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2013 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include "dsp/rx_fft.h" rx_fft_c_sptr make_rx_fft_c (unsigned int fftsize, int wintype) { return gnuradio::get_initial_sptr(new rx_fft_c (fftsize, wintype)); } /*! \brief Create receiver FFT object. * \param fftsize The FFT size. * \param wintype The window type (see gr::filter::firdes::win_type). * */ rx_fft_c::rx_fft_c(unsigned int fftsize, int wintype) : gr::sync_block ("rx_fft_c", gr::io_signature::make(1, 1, sizeof(gr_complex)), gr::io_signature::make(0, 0, 0)), d_fftsize(fftsize), d_wintype(-1) { /* create FFT object */ d_fft = new gr::fft::fft_complex(d_fftsize, true); /* allocate circular buffer */ d_cbuf.set_capacity(d_fftsize); /* create FFT window */ set_window_type(wintype); } rx_fft_c::~rx_fft_c() { delete d_fft; } /*! \brief Receiver FFT work method. * \param noutput_items * \param input_items * \param output_items * * This method does nothing except throwing the incoming samples into the * circular buffer. * FFT is only executed when the GUI asks for new FFT data via get_fft_data(). */ int rx_fft_c::work(int noutput_items, gr_vector_const_void_star &input_items, gr_vector_void_star &output_items) { int i; const gr_complex *in = (const gr_complex*)input_items[0]; (void) output_items; /* just throw new samples into the buffer */ boost::mutex::scoped_lock lock(d_mutex); for (i = 0; i < noutput_items; i++) { d_cbuf.push_back(in[i]); } return noutput_items; } /*! \brief Get FFT data. * \param fftPoints Buffer to copy FFT data * \param fftSize Current FFT size (output). */ void rx_fft_c::get_fft_data(std::complex* fftPoints, unsigned int &fftSize) { boost::mutex::scoped_lock lock(d_mutex); if (d_cbuf.size() < d_fftsize) { // not enough samples in the buffer fftSize = 0; return; } /* perform FFT */ do_fft(d_cbuf.linearize(), d_cbuf.size()); // FIXME: array_one() and two() may be faster //d_cbuf.clear(); /* get FFT data */ memcpy(fftPoints, d_fft->get_outbuf(), sizeof(gr_complex)*d_fftsize); fftSize = d_fftsize; } /*! \brief Compute FFT on the available input data. * \param data_in The data to compute FFT on. * \param size The size of data_in. * * Note that this function does not lock the mutex since the caller, get_fft_data() * has alrady locked it. */ void rx_fft_c::do_fft(const gr_complex *data_in, unsigned int size) { /* apply window, if any */ if (d_window.size()) { gr_complex *dst = d_fft->get_inbuf(); for (unsigned int i = 0; i < size; i++) dst[i] = data_in[i] * d_window[i]; } else { memcpy(d_fft->get_inbuf(), data_in, sizeof(gr_complex)*size); } /* compute FFT */ d_fft->execute(); } /*! \brief Set new FFT size. */ void rx_fft_c::set_fft_size(unsigned int fftsize) { if (fftsize != d_fftsize) { boost::mutex::scoped_lock lock(d_mutex); d_fftsize = fftsize; /* clear and resize circular buffer */ d_cbuf.clear(); d_cbuf.set_capacity(d_fftsize); /* reset window */ int wintype = d_wintype; // FIXME: would be nicer with a window_reset() d_wintype = -1; set_window_type(wintype); /* reset FFT object (also reset FFTW plan) */ delete d_fft; d_fft = new gr::fft::fft_complex (d_fftsize, true); } } /*! \brief Get currently used FFT size. */ unsigned int rx_fft_c::get_fft_size() { return d_fftsize; } /*! \brief Set new window type. */ void rx_fft_c::set_window_type(int wintype) { if (wintype == d_wintype) { /* nothing to do */ return; } d_wintype = wintype; if ((d_wintype < gr::filter::firdes::WIN_HAMMING) || (d_wintype > gr::filter::firdes::WIN_BLACKMAN_hARRIS)) { d_wintype = gr::filter::firdes::WIN_HAMMING; } d_window.clear(); d_window = gr::filter::firdes::window((gr::filter::firdes::win_type)d_wintype, d_fftsize, 6.76); } /*! \brief Get currently used window type. */ int rx_fft_c::get_window_type() { return d_wintype; } /** rx_fft_f **/ rx_fft_f_sptr make_rx_fft_f(unsigned int fftsize, int wintype) { return gnuradio::get_initial_sptr(new rx_fft_f (fftsize, wintype)); } /*! \brief Create receiver FFT object. * \param fftsize The FFT size. * \param wintype The window type (see gr::filter::firdes::win_type). * */ rx_fft_f::rx_fft_f(unsigned int fftsize, int wintype) : gr::sync_block ("rx_fft_f", gr::io_signature::make(1, 1, sizeof(float)), gr::io_signature::make(0, 0, 0)), d_fftsize(fftsize), d_wintype(-1) { /* create FFT object */ d_fft = new gr::fft::fft_complex(d_fftsize, true); /* allocate circular buffer */ d_cbuf.set_capacity(d_fftsize); /* create FFT window */ set_window_type(wintype); } rx_fft_f::~rx_fft_f() { delete d_fft; } /*! \brief Audio FFT work method. * \param noutput_items * \param input_items * \param output_items * * This method does nothing except throwing the incoming samples into the * circular buffer. * FFT is only executed when the GUI asks for new FFT data via get_fft_data(). */ int rx_fft_f::work(int noutput_items, gr_vector_const_void_star &input_items, gr_vector_void_star &output_items) { int i; const float *in = (const float*)input_items[0]; (void) output_items; /* just throw new samples into the buffer */ boost::mutex::scoped_lock lock(d_mutex); for (i = 0; i < noutput_items; i++) { d_cbuf.push_back(in[i]); } return noutput_items; } /*! \brief Get FFT data. * \param fftPoints Buffer to copy FFT data * \param fftSize Current FFT size (output). */ void rx_fft_f::get_fft_data(std::complex* fftPoints, unsigned int &fftSize) { boost::mutex::scoped_lock lock(d_mutex); if (d_cbuf.size() < d_fftsize) { // not enough samples in the buffer fftSize = 0; return; } /* perform FFT */ do_fft(d_cbuf.linearize(), d_cbuf.size()); // FIXME: array_one() and two() may be faster //d_cbuf.clear(); /* get FFT data */ memcpy(fftPoints, d_fft->get_outbuf(), sizeof(gr_complex)*d_fftsize); fftSize = d_fftsize; } /*! \brief Compute FFT on the available input data. * \param data_in The data to compute FFT on. * \param size The size of data_in. * * Note that this function does not lock the mutex since the caller, get_fft_data() * has alrady locked it. */ void rx_fft_f::do_fft(const float *data_in, unsigned int size) { gr_complex *dst = d_fft->get_inbuf(); unsigned int i; /* apply window, and convert to complex */ if (d_window.size()) { for (i = 0; i < size; i++) dst[i] = data_in[i] * d_window[i]; } else { for (i = 0; i < size; i++) dst[i] = data_in[i]; } /* compute FFT */ d_fft->execute(); } /*! \brief Set new FFT size. */ void rx_fft_f::set_fft_size(unsigned int fftsize) { if (fftsize != d_fftsize) { boost::mutex::scoped_lock lock(d_mutex); d_fftsize = fftsize; /* clear and resize circular buffer */ d_cbuf.clear(); d_cbuf.set_capacity(d_fftsize); /* reset window */ int wintype = d_wintype; // FIXME: would be nicer with a window_reset() d_wintype = -1; set_window_type(wintype); /* reset FFT object (also reset FFTW plan) */ delete d_fft; d_fft = new gr::fft::fft_complex(d_fftsize, true); } } /*! \brief Get currently used FFT size. */ unsigned int rx_fft_f::get_fft_size() { return d_fftsize; } /*! \brief Set new window type. */ void rx_fft_f::set_window_type(int wintype) { if (wintype == d_wintype) { /* nothing to do */ return; } d_wintype = wintype; if ((d_wintype < gr::filter::firdes::WIN_HAMMING) || (d_wintype > gr::filter::firdes::WIN_BLACKMAN_hARRIS)) { d_wintype = gr::filter::firdes::WIN_HAMMING; } d_window.clear(); d_window = gr::filter::firdes::window((gr::filter::firdes::win_type)d_wintype, d_fftsize, 6.76); } /*! \brief Get currently used window type. */ int rx_fft_f::get_window_type() { return d_wintype; } gqrx-sdr-2.2.0.74.d97bd7/dsp/rx_fft.h000066400000000000000000000123261226036373100167500ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2013 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef RX_FFT_H #define RX_FFT_H #include #include #include /* contains enum win_type */ #include #include #include #define MAX_FFT_SIZE 32768 class rx_fft_c; class rx_fft_f; typedef boost::shared_ptr rx_fft_c_sptr; typedef boost::shared_ptr rx_fft_f_sptr; /*! \brief Return a shared_ptr to a new instance of rx_fft_c. * \param fftsize The FFT size * \param winttype The window type (see gnuradio/filter/firdes.h) * * This is effectively the public constructor. To avoid accidental use * of raw pointers, the rx_fft_c constructor is private. * make_rx_fft_c is the public interface for creating new instances. */ rx_fft_c_sptr make_rx_fft_c(unsigned int fftsize=4096, int wintype=gr::filter::firdes::WIN_HAMMING); /*! \brief Block for computing complex FFT. * \ingroup DSP * * This block is used to compute the FFT of the received spectrum. * * The samples are collected in a cicular buffer with size FFT_SIZE. * When the GUI asks for a new set of FFT data via get_fft_data() an FFT * will be performed on the data stored in the circular buffer - assuming * of course that the buffer contains at least fftsize samples. * * \note Uses code from qtgui_sink_c */ class rx_fft_c : public gr::sync_block { friend rx_fft_c_sptr make_rx_fft_c(unsigned int fftsize, int wintype); protected: rx_fft_c(unsigned int fftsize=4096, int wintype=gr::filter::firdes::WIN_HAMMING); public: ~rx_fft_c(); int work(int noutput_items, gr_vector_const_void_star &input_items, gr_vector_void_star &output_items); void get_fft_data(std::complex* fftPoints, unsigned int &fftSize); void set_window_type(int wintype); int get_window_type(); void set_fft_size(unsigned int fftsize); unsigned int get_fft_size(); private: unsigned int d_fftsize; /*! Current FFT size. */ int d_wintype; /*! Current window type. */ boost::mutex d_mutex; /*! Used to lock FFT output buffer. */ gr::fft::fft_complex *d_fft; /*! FFT object. */ std::vector d_window; /*! FFT window taps. */ boost::circular_buffer d_cbuf; /*! buffer to accumulate samples. */ void do_fft(const gr_complex *data_in, unsigned int size); }; /*! \brief Return a shared_ptr to a new instance of rx_fft_f. * \param fftsize The FFT size * \param winttype The window type (see gnuradio/filter/firdes.h) * * This is effectively the public constructor. To avoid accidental use * of raw pointers, the rx_fft_f constructor is private. * make_rx_fft_f is the public interface for creating new instances. */ rx_fft_f_sptr make_rx_fft_f(unsigned int fftsize=1024, int wintype=gr::filter::firdes::WIN_HAMMING); /*! \brief Block for computing real FFT. * \ingroup DSP * * This block is used to compute the FFT of the audio spectrum or anything * else where real FFT is useful. * * The samples are collected in a cicular buffer with size FFT_SIZE. * When the GUI asks for a new set of FFT data using get_fft_data() an FFT * will be performed on the data stored in the circular buffer - assuming * that the buffer contains at least fftsize samples. * * \note Uses code from qtgui_sink_f */ class rx_fft_f : public gr::sync_block { friend rx_fft_f_sptr make_rx_fft_f(unsigned int fftsize, int wintype); protected: rx_fft_f(unsigned int fftsize=1024, int wintype=gr::filter::firdes::WIN_HAMMING); public: ~rx_fft_f(); int work(int noutput_items, gr_vector_const_void_star &input_items, gr_vector_void_star &output_items); void get_fft_data(std::complex* fftPoints, unsigned int &fftSize); void set_window_type(int wintype); int get_window_type(); void set_fft_size(unsigned int fftsize); unsigned int get_fft_size(); private: unsigned int d_fftsize; /*! Current FFT size. */ int d_wintype; /*! Current window type. */ boost::mutex d_mutex; /*! Used to lock FFT output buffer. */ gr::fft::fft_complex *d_fft; /*! FFT object. */ std::vector d_window; /*! FFT window taps. */ boost::circular_buffer d_cbuf; /*! buffer to accumulate samples. */ void do_fft(const float *data_in, unsigned int size); }; #endif /* RX_FFT_H */ gqrx-sdr-2.2.0.74.d97bd7/dsp/rx_filter.cpp000066400000000000000000000123011226036373100200020ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include "dsp/rx_filter.h" static const int MIN_IN = 1; /* Mininum number of input streams. */ static const int MAX_IN = 1; /* Maximum number of input streams. */ static const int MIN_OUT = 1; /* Minimum number of output streams. */ static const int MAX_OUT = 1; /* Maximum number of output streams. */ /* * Create a new instance of rx_filter and return * a boost shared_ptr. This is effectively the public constructor. */ rx_filter_sptr make_rx_filter(double sample_rate, double low, double high, double trans_width) { return gnuradio::get_initial_sptr(new rx_filter(sample_rate, low, high, trans_width)); } rx_filter::rx_filter(double sample_rate, double low, double high, double trans_width) : gr::hier_block2 ("rx_filter", gr::io_signature::make (MIN_IN, MAX_IN, sizeof (gr_complex)), gr::io_signature::make (MIN_OUT, MAX_OUT, sizeof (gr_complex))), d_sample_rate(sample_rate), d_low(low), d_high(high), d_trans_width(trans_width) { if (low < -0.95*sample_rate/2.0) d_low = -0.95*sample_rate/2.0; if (high > 0.95*sample_rate/2.0) d_high = 0.95*sample_rate/2.0; /* generate taps */ d_taps = gr::filter::firdes::complex_band_pass(1.0, d_sample_rate, d_low, d_high, d_trans_width); /* create band pass filter */ d_bpf = gr::filter::fir_filter_ccc::make(1, d_taps); /* connect filter */ connect(self(), 0, d_bpf, 0); connect(d_bpf, 0, self(), 0); } rx_filter::~rx_filter () { } void rx_filter::set_param(double low, double high, double trans_width) { d_trans_width = trans_width; d_low = low; d_high = high; if (d_low < -0.95*d_sample_rate/2.0) d_low = -0.95*d_sample_rate/2.0; if (d_high > 0.95*d_sample_rate/2.0) d_high = 0.95*d_sample_rate/2.0; /* generate new taps */ d_taps = gr::filter::firdes::complex_band_pass(1.0, d_sample_rate, d_low, d_high, d_trans_width); #ifndef QT_NO_DEBUG_OUTPUT std::cout << "Genrating taps for new filter LO:" << d_low << " HI:" << d_high << " TW:" << d_trans_width << std::endl; std::cout << "Required number of taps: " << d_taps.size() << std::endl; #endif d_bpf->set_taps(d_taps); } /** Frequency translating filter **/ /* * Create a new instance of rx_xlating_filter and return * a boost shared_ptr. This is effectively the public constructor. */ rx_xlating_filter_sptr make_rx_xlating_filter(double sample_rate, double center, double low, double high, double trans_width) { return gnuradio::get_initial_sptr(new rx_xlating_filter(sample_rate, center, low, high, trans_width)); } rx_xlating_filter::rx_xlating_filter(double sample_rate, double center, double low, double high, double trans_width) : gr::hier_block2 ("rx_xlating_filter", gr::io_signature::make (MIN_IN, MAX_IN, sizeof (gr_complex)), gr::io_signature::make (MIN_OUT, MAX_OUT, sizeof (gr_complex))), d_sample_rate(sample_rate), d_center(center), d_low(low), d_high(high), d_trans_width(trans_width) { /* generate taps */ d_taps = gr::filter::firdes::complex_band_pass(1.0, d_sample_rate, -d_high, -d_low, d_trans_width); /* create band pass filter */ d_bpf = gr::filter::freq_xlating_fir_filter_ccc::make(1, d_taps, d_center, d_sample_rate); /* connect filter */ connect(self(), 0, d_bpf, 0); connect(d_bpf, 0, self(), 0); } rx_xlating_filter::~rx_xlating_filter() { } void rx_xlating_filter::set_offset(double center) { /* we have to change sign because the set_center_freq() actually shifts the passband with the specified amount, which has opposite sign of selecting a center frequency. */ d_center = -center; d_bpf->set_center_freq(d_center); } void rx_xlating_filter::set_param(double low, double high, double trans_width) { d_trans_width = trans_width; d_low = low; d_high = high; /* generate new taps */ d_taps = gr::filter::firdes::complex_band_pass(1.0, d_sample_rate, -d_high, -d_low, d_trans_width); d_bpf->set_taps(d_taps); } void rx_xlating_filter::set_param(double center, double low, double high, double trans_width) { set_offset(center); set_param(low, high, trans_width); } gqrx-sdr-2.2.0.74.d97bd7/dsp/rx_filter.h000066400000000000000000000124551226036373100174610ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef RX_FILTER_H #define RX_FILTER_H #include #include #include #define RX_FILTER_MIN_WIDTH 100 /*! Minimum width of filter */ class rx_filter; class rx_xlating_filter; typedef boost::shared_ptr rx_filter_sptr; typedef boost::shared_ptr rx_xlating_filter_sptr; /*! \brief Return a shared_ptr to a new instance of rx_filter. * \param sample_rate The sample rate. * \param low The lower limit of the bandpass filter. * \param high The upper limit of the filter. * \param trans_width The width of the transition band from * * This is effectively the public constructor. To avoid accidental use * of raw pointers, rx_filter's constructor is private. * make_rxfilter is the public interface for creating new instances. */ rx_filter_sptr make_rx_filter(double sample_rate, double low=-5000.0, double high=5000.0, double trans_width=1000.0); /*! \brief Complex band-pass filter with complex taps. * \ingroup DSP * * This class encapsulates a complex FIR filter and the code * required to generate complex band pass filter taps. It provides a simple * interface to set the filter parameters. * * The user of this class is expected to provide valid parameters and no checks are * performed by the accessors (though the taps generator from gr::filter::firdes does perform * some sanity checks and throws std::out_of_range in case of bad parameter). * * \note In order to have proper LSB/USB, we must exchange low and high and reverse their sign */ class rx_filter : public gr::hier_block2 { public: rx_filter(double sample_rate=96000.0, double low=-5000.0, double high=5000.0, double trans_width=1000.0); // FIXME: should be private ~rx_filter(); void set_param(double low, double high, double trans_width); private: std::vector d_taps; gr::filter::fir_filter_ccc::sptr d_bpf; double d_sample_rate; double d_low; double d_high; double d_trans_width; }; /*! \brief Return a shared_ptr to a new instance of rx_xlating_filter. * \param sample_rate The sample rate. * \param offset The filter offset. * \param low The lower limit of the bandpass filter. * \param high The upper limit of the filter. * \param trans_width The width of the transition band from * * This is effectively the public constructor. To avoid accidental use * of raw pointers, rx_filter's constructor is private. * make_rxfilter is the public interface for creating new instances. */ rx_xlating_filter_sptr make_rx_xlating_filter(double sample_rate, double center=0.0, double low=-5000.0, double high=5000.0, double trans_width=1000.0); /*! \brief Frequency translating band-pass filter with complex taps. * \ingroup DSP * * This class encapsulates a frequency translating FIR filter and the code * required to generate complex band pass filter taps. It provides a simple * interface to set the filter offset and limits and takes care of generating * the appropriate taps according to the limits. * * The filter limits are relative to the filter offset and thanks to the complex taps * they can be both positive and negative. * * The user of this class is expected to provide valid parameters and no checks are * performed by the accessors (though the taps generator from gr::filter::firdes does perform * some sanity checks and throws std::out_of_range in case of bad parameter). * * \note In order to have proper LSB/USB, we must exchange low and high and reverse their sign? */ class rx_xlating_filter : public gr::hier_block2 { public: rx_xlating_filter(double sample_rate=96000.0, double center=0.0, double low=-5000.0, double high=5000.0, double trans_width=1000.0); // FIXME: should be private ~rx_xlating_filter(); void set_offset(double center); void set_param(double low, double high, double trans_width); void set_param(double center, double low, double high, double trans_width); private: std::vector d_taps; gr::filter::freq_xlating_fir_filter_ccc::sptr d_bpf; double d_sample_rate; double d_center; double d_low; double d_high; double d_trans_width; }; #endif // RX_FILTER_H gqrx-sdr-2.2.0.74.d97bd7/dsp/rx_meter.cpp000066400000000000000000000101471226036373100176370ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include #include #include #include rx_meter_c_sptr make_rx_meter_c (int detector) { return gnuradio::get_initial_sptr(new rx_meter_c (detector)); } rx_meter_c::rx_meter_c(int detector) : gr::sync_block ("rx_meter_c", gr::io_signature::make(1, 1, sizeof(gr_complex)), gr::io_signature::make(0, 0, 0)), d_detector(detector), d_level(0.0), d_level_db(0.0), d_sum(0.0), d_sumsq(0.0), d_num(0), d_fs(1.0) { } rx_meter_c::~rx_meter_c() { } #define ALPHA 0.4 int rx_meter_c::work (int noutput_items, gr_vector_const_void_star &input_items, gr_vector_void_star &output_items) { (void) output_items; // unused const gr_complex *in = (const gr_complex *) input_items[0]; float pwr = 0.0; int i = 0; if (d_num == 0) { // first sample after a reset d_level = in[0].real()*in[0].real() + in[0].imag()*in[0].imag(); d_sum = d_level; d_sumsq = d_level*d_level; i = 1; } d_num += noutput_items; // processing depends on detector type switch (d_detector) { case DETECTOR_TYPE_SAMPLE: // just take the first sample d_level = in[0].real()*in[0].real() + in[0].imag()*in[0].imag(); break; case DETECTOR_TYPE_MIN: // minimum peak while (i < noutput_items) { pwr = in[i].real()*in[i].real() + in[i].imag()*in[i].imag(); if (pwr < d_level) d_level = pwr; i++; } break; case DETECTOR_TYPE_MAX: // maximum peak while (i < noutput_items) { pwr = in[i].real()*in[i].real() + in[i].imag()*in[i].imag(); if (pwr > d_level) d_level = pwr; i++; } break; case DETECTOR_TYPE_AVG: // mean value while (i < noutput_items) { pwr = in[i].real()*in[i].real() + in[i].imag()*in[i].imag(); d_sum += pwr; i++; } d_level = d_sum / (float)(d_num); break; case DETECTOR_TYPE_RMS: // root mean square while (i < noutput_items) { pwr = in[i].real()*in[i].real() + in[i].imag()*in[i].imag(); d_sumsq += pwr*pwr; i++; } d_level = sqrt(d_sumsq / (float)(d_num)); break; default: std::cout << "Invalid detector type: " << d_detector << std::endl; std::cout << "Fallback to DETECTOR_TYPE_RMS." << std::endl; d_detector = DETECTOR_TYPE_RMS; break; } d_level_db = (float) 10. * log10(d_level / d_fs + 1.0e-20); return noutput_items; } float rx_meter_c::get_level() { float retval = d_level; reset_stats(); return retval; } float rx_meter_c::get_level_db() { float retval = d_level_db; reset_stats(); return retval; } void rx_meter_c::set_detector_type(int detector) { if (d_detector == detector) return; d_detector = detector; reset_stats(); } /*! \brief Reset statistics. */ void rx_meter_c::reset_stats() { //d_level = 0.0; d_level_db = 0.0; d_sum = 0.0; d_sumsq = 0.0; d_num = 0; } gqrx-sdr-2.2.0.74.d97bd7/dsp/rx_meter.h000066400000000000000000000064661226036373100173150ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef RX_METER_H #define RX_METER_H #include enum detector_type_e { DETECTOR_TYPE_NONE = 0, DETECTOR_TYPE_SAMPLE = 1, DETECTOR_TYPE_MIN = 2, DETECTOR_TYPE_MAX = 3, DETECTOR_TYPE_AVG = 4, DETECTOR_TYPE_RMS = 5 }; class rx_meter_c; typedef boost::shared_ptr rx_meter_c_sptr; /*! \brief Return a shared_ptr to a new instance of rx_meter_c. * \param detector Detector type. * * This is effectively the public constructor. To avoid accidental use * of raw pointers, the rx_meter_c constructor is private. * make_rxfilter is the public interface for creating new instances. */ rx_meter_c_sptr make_rx_meter_c(int detector=DETECTOR_TYPE_RMS); /*! \brief Block for measuring signal strength (complex input). * \ingroup DSP * * This block can be used to meausre the received signal strength. * For each group of samples received this block stores the maximum power level, * which then can be retrieved using the get_level() and get_level_db() * methods. */ class rx_meter_c : public gr::sync_block { friend rx_meter_c_sptr make_rx_meter_c(int detector); protected: rx_meter_c(int detector=DETECTOR_TYPE_RMS); public: ~rx_meter_c(); int work(int noutput_items, gr_vector_const_void_star &input_items, gr_vector_void_star &output_items); /*! \brief Get the current signal level. */ float get_level(); /*! \brief Get the current signal level in dBFS. */ float get_level_db(); /*! \brief Enable or disable averaging. * \param detector Detector type. */ void set_detector_type(int detector); /*! \brief Get averaging status * \returns TRUE if averaging is enabled, FALSE if it is disabled. */ int get_detector_type() {return d_detector;} /* In case we add a set_fs(), remember that it must be > 0.0 */ /*! \brief Get full scale value. * \return The current full scale value. */ float get_fs() {return d_fs;} private: int d_detector; /*! Detector type. */ float d_level; /*! The current level in the range 0.0 to 1.0 */ float d_level_db; /*! The current level in dBFS with FS = 1.0 */ float d_sum; /*! Sum of msamples. */ float d_sumsq; /*! Sum of samples squared. */ int d_num; /*! Number of samples in d_sum and d_sumsq. */ float d_fs; /*! Full scale value (default = 1.0). */ void reset_stats(); }; #endif /* RX_METER_H */ gqrx-sdr-2.2.0.74.d97bd7/dsp/rx_noise_blanker_cc.cpp000066400000000000000000000110321226036373100217750ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2012 Alexandru Csete OZ9AEC. * Copyright 2004-2008 by Frank Brickle, AB2KT and Bob McGwier, N4HY * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include #include #include #include "dsp/rx_noise_blanker_cc.h" rx_nb_cc_sptr make_rx_nb_cc(double sample_rate, float thld1, float thld2) { return gnuradio::get_initial_sptr(new rx_nb_cc(sample_rate, thld1, thld2)); } /*! \brief Create noise blanker object. * * Use make_rx_nb_cc() instead. */ rx_nb_cc::rx_nb_cc(double sample_rate, float thld1, float thld2) : gr::sync_block ("rx_nb_cc", gr::io_signature::make(1, 1, sizeof(gr_complex)), gr::io_signature::make(1, 1, sizeof(gr_complex))), d_nb1_on(false), d_nb2_on(false), d_sample_rate(sample_rate), d_thld_nb1(thld1), d_thld_nb2(thld2), d_avgmag_nb1(1.0), d_avgmag_nb2(1.0), d_delidx(2), d_sigidx(0), d_hangtime(0) { memset(d_delay, 0, 8 * sizeof(gr_complex)); } rx_nb_cc::~rx_nb_cc() { } /*! \brief Receiver noise blanker work method. * \param mooutput_items * \param input_items * \param output_items */ int rx_nb_cc::work(int noutput_items, gr_vector_const_void_star &input_items, gr_vector_void_star &output_items) { const gr_complex *in = (const gr_complex *) input_items[0]; gr_complex *out = (gr_complex *) output_items[0]; int i; boost::mutex::scoped_lock lock(d_mutex); // copy data into output buffer then perform the processing on that buffer for (i = 0; i < noutput_items; i++) { out[i] = in[i]; } if (d_nb1_on) { process_nb1(out, noutput_items); } if (d_nb2_on) { process_nb2(out, noutput_items); } return noutput_items; } /*! \brief Perform noise blanker 1 processing. * \param buf The data buffer holding gr_complex samples. * \param num The number of samples in the buffer. * * Noise blanker 1 is the first noise blanker in the processing chain. * It is intended to reduce the effect of impulse type noise. * * FIXME: Needs different constants for higher sample rates? */ void rx_nb_cc::process_nb1(gr_complex *buf, int num) { float cmag; gr_complex zero(0.0, 0.0); for (int i = 0; i < num; i++) { cmag = abs(buf[i]); d_delay[d_sigidx] = buf[i]; d_avgmag_nb1 = 0.999*d_avgmag_nb1 + 0.001*cmag; if ((d_hangtime == 0) && (cmag > (d_thld_nb1*d_avgmag_nb1))) d_hangtime = 7; if (d_hangtime > 0) { buf[i] = zero; d_hangtime--; } else { buf[i] = d_delay[d_delidx]; } d_sigidx = (d_sigidx + 7) & 7; d_delidx = (d_delidx + 7) & 7; } } /*! \brief Perform noise blanker 2 processing. * \param buf The data buffer holding gr_complex samples. * \param num The number of samples in the buffer. * * Noise blanker 2 is the second noise blanker in the processing chain. * It is intended to reduce non-pulse type noise (i.e. longer time constants). * * FIXME: Needs different constants for higher sample rates? */ void rx_nb_cc::process_nb2(gr_complex *buf, int num) { float cmag; gr_complex c1(0.75); gr_complex c2(0.25); for (int i = 0; i < num; i++) { cmag = abs(buf[i]); d_avgsig = c1*d_avgsig + c2*buf[i]; d_avgmag_nb2 = 0.999*d_avgmag_nb2 + 0.001*cmag; if (cmag > d_thld_nb2*d_avgmag_nb2) buf[i] = d_avgsig; } } void rx_nb_cc::set_threshold1(float threshold) { if ((threshold >= 1.0) && (threshold <= 20.0)) d_thld_nb1 = threshold; } void rx_nb_cc::set_threshold2(float threshold) { if ((threshold >= 0.0) && (threshold <= 15.0)) d_thld_nb2 = threshold; } gqrx-sdr-2.2.0.74.d97bd7/dsp/rx_noise_blanker_cc.h000066400000000000000000000064111226036373100214470ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2012 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef RX_NB_CC_H #define RX_NB_CC_H #include #include #include class rx_nb_cc; typedef boost::shared_ptr rx_nb_cc_sptr; /*! \brief Return a shared_ptr to a new instance of rx_nb_cc. * \param sample_rate The samle rate (default = 96000). * \param threshold Noise blanker threshold. Range 0.0 to 1.0 (TBC) * * This is effectively the public constructor for a new noise blanker block. * To avoid accidental use of raw pointers, the rx_nb_cc constructor is private. * make_rx_nb_cc is the public interface for creating new instances. */ rx_nb_cc_sptr make_rx_nb_cc(double sample_rate=96000.0, float thld1=3.3, float thld2=2.5); /*! \brief Noise blanker block. * \ingroup DSP * * This block implements noise blanking filters based on the noise blanker code * from DTTSP. * */ class rx_nb_cc : public gr::sync_block { friend rx_nb_cc_sptr make_rx_nb_cc(double sample_rate, float thld1, float thld2); protected: rx_nb_cc(double sample_rate, float thld1, float thld2); public: ~rx_nb_cc(); int work(int noutput_items, gr_vector_const_void_star &input_items, gr_vector_void_star &output_items); void set_sample_rate(double sample_rate) { d_sample_rate = sample_rate; } void set_nb1_on(bool nb1_on) { d_nb1_on = nb1_on; } void set_nb2_on(bool nb2_on) { d_nb2_on = nb2_on; } bool get_nb1_on() { return d_nb1_on; } bool get_nb2_on() { return d_nb2_on; } void set_threshold1(float threshold); void set_threshold2(float threshold); private: void process_nb1(gr_complex *buf, int num); void process_nb2(gr_complex *buf, int num); private: boost::mutex d_mutex; /*! Used to lock internal data while processing or setting parameters. */ bool d_nb1_on; /*! Current NB1 status (true/false). */ bool d_nb2_on; /*! Current NB2 status (true/false). */ double d_sample_rate; /*! Current sample rate. */ float d_thld_nb1; /*! Current threshold for noise blanker 1 (1.0 to 20.0 TBC). */ float d_thld_nb2; /*! Current threshold for noise blanker 2 (0.0 to 15.0 TBC). */ float d_avgmag_nb1; /*! Average magnitude. */ float d_avgmag_nb2; /*! Average magnitude. */ gr_complex d_avgsig, d_delay[8]; int d_delidx, d_sigidx, d_hangtime; // FIXME: need longer buffer for higher sampel rates? }; #endif /* RX_NB_CC_H */ gqrx-sdr-2.2.0.74.d97bd7/dsp/sniffer_f.cpp000066400000000000000000000072521226036373100177560ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include #include #include /* Return a shared_ptr to a new instance of sniffer_f */ sniffer_f_sptr make_sniffer_f(int buffsize) { return gnuradio::get_initial_sptr(new sniffer_f(buffsize)); } /*! \brief Create a sniffe_fr object. * \param buffsize The internal buffer size. * * When choosing buffer size, the user of this class should take into account: * - The input sample rate. * - How ofter the data will be popped. */ sniffer_f::sniffer_f(int buffsize) : gr::sync_block ("rx_fft_c", gr::io_signature::make(1, 1, sizeof(float)), gr::io_signature::make(0, 0, 0)), d_minsamp(1000) { /* allocate circular buffer */ d_buffer.set_capacity(buffsize); } sniffer_f::~sniffer_f() { } /*! \brief Work method. * \param mooutput_items * \param input_items * \param output_items * * This method does nothing except dumping the incoming samples into the * circular buffer. */ int sniffer_f::work(int noutput_items, gr_vector_const_void_star &input_items, gr_vector_void_star &output_items) { int i; const float *in = (const float *)input_items[0]; (void) output_items; boost::mutex::scoped_lock lock(d_mutex); /* dump new samples into the buffer */ for (i = 0; i < noutput_items; i++) { d_buffer.push_back(in[i]); } return noutput_items; } /*! \brief Get number of samples avaialble for fetching. * \return The number of samples in the buffer. * * This method can be used to read how many samples are currently * stored in the buffer. */ int sniffer_f::samples_available() { boost::mutex::scoped_lock lock(d_mutex); return d_buffer.size(); } /*! \brief Fetch avaialble samples. * \param out Pointer to allocated memory where the samples will be copied. * Should be at least as big as buffer_size(). * \param num The number of sampels returned. */ void sniffer_f::get_samples(float * out, unsigned int &num) { boost::mutex::scoped_lock lock(d_mutex); if (d_buffer.size() < d_minsamp) { /* not enough samples in buffer */ num = 0; return; } num = d_buffer.size(); float *buff = d_buffer.linearize(); memcpy(out, buff, sizeof(float)*num); d_buffer.clear(); } /*! \brief Resize internal buffer. * \param newsize The new size of the buffer (number of samples, not bytes) */ void sniffer_f::set_buffer_size(int newsize) { boost::mutex::scoped_lock lock(d_mutex); //d_buffer.clear(); d_buffer.set_capacity(newsize); } /*! \brief Get current size of the internal buffer. * * This number equals the largest number of samples that can be returned by * get_samples(). */ int sniffer_f::buffer_size() { boost::mutex::scoped_lock lock(d_mutex); return d_buffer.capacity(); } gqrx-sdr-2.2.0.74.d97bd7/dsp/sniffer_f.h000066400000000000000000000054101226036373100174150ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef SNIFFER_F_H #define SNIFFER_F_H #include #include #include class sniffer_f; typedef boost::shared_ptr sniffer_f_sptr; /*! \brief Return a shared_ptr to a new instance of sniffer_f. * \param buffsize The size of the buffer * * This is effectively the public constructor. To avoid accidental use * of raw pointers, the constructor is private. This function is the public * interface for creating new instances. * */ sniffer_f_sptr make_sniffer_f(int buffsize=48000); /*! \brief Simple sink to allow accessing data in the flow graph. * \ingroup DSP * * This block can be used by external objects to access the data stream in the * flow graph. For example, a sniffer can be connected to the output of the demodulator * and used by data decoders. * * The class uses a circular buffer for internal storage and if the received samples * exceed the buffer size, old samples will be overwritten. The collected samples * can be accessed via the get_samples() method. */ class sniffer_f : public gr::sync_block { friend sniffer_f_sptr make_sniffer_f(int buffsize); protected: sniffer_f(int buffsize); public: ~sniffer_f(); int work(int noutput_items, gr_vector_const_void_star &input_items, gr_vector_void_star &output_items); int samples_available(); void get_samples(float * buffer, unsigned int &num); void set_buffer_size(int newsize); int buffer_size(); void set_min_samples(unsigned int num) {d_minsamp = num;} int min_samples() {return d_minsamp;} private: boost::mutex d_mutex; /*! Used to prevent concurrent access to buffer. */ boost::circular_buffer d_buffer; /*! buffer to accumulate samples. */ unsigned int d_minsamp; /*! smallest number of samples we want to return. */ }; #endif /* SNIFFER_F_H */ gqrx-sdr-2.2.0.74.d97bd7/dsp/stereo_demod.cpp000066400000000000000000000116751226036373100204720ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2012 Alexandru Csete OZ9AEC. * FM stereo implementation by Alex Grinkov a.grinkov(at)gmail.com. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include #include #include #include /* Create a new instance of stereo_demod and return a boost shared_ptr. */ stereo_demod_sptr make_stereo_demod(float quad_rate, float audio_rate, bool stereo) { return gnuradio::get_initial_sptr(new stereo_demod(quad_rate, audio_rate, stereo)); } static const int MIN_IN = 1; /* Mininum number of input streams. */ static const int MAX_IN = 1; /* Maximum number of input streams. */ static const int MIN_OUT = 2; /* Minimum number of output streams. */ static const int MAX_OUT = 2; /* Maximum number of output streams. */ #define STEREO_DEMOD_PARANOIC /*! \brief Create stereo demodulator object. * * Use make_stereo_demod() instead. */ stereo_demod::stereo_demod(float input_rate, float audio_rate, bool stereo) : gr::hier_block2("stereo_demod", gr::io_signature::make (MIN_IN, MAX_IN, sizeof (float)), gr::io_signature::make (MIN_OUT, MAX_OUT, sizeof (float))), d_input_rate(input_rate), d_audio_rate(audio_rate), d_stereo(stereo) { lpf0 = make_lpf_ff(d_input_rate, 17e3, 2e3); // FIXME audio_rr0 = make_resampler_ff(d_audio_rate/d_input_rate); if (d_stereo) { lpf1 = make_lpf_ff(d_input_rate, 17e3, 2e3); // FIXME audio_rr1 = make_resampler_ff(d_audio_rate/d_input_rate); d_tone_taps = gr::filter::firdes::complex_band_pass( 1.0, // gain, d_input_rate, // sampling_freq 18800., // low_cutoff_freq 19200., // high_cutoff_freq 300.); // transition_width tone = gr::filter::fir_filter_fcc::make(1, d_tone_taps); pll = gr::analog::pll_refout_cc::make(0.001, // loop_bw FIXME 2*M_PI * 19200 / input_rate, // max_freq 2*M_PI * 18800 / input_rate); // min_freq subtone = gr::blocks::multiply_cc::make(); lo = gr::blocks::complex_to_imag::make(); #ifdef STEREO_DEMOD_PARANOIC d_pll_taps = gr::filter::firdes::band_pass( 1.0, // gain, d_input_rate, // sampling_freq 37600., // low_cutoff_freq 38400., // high_cutoff_freq 400.); // transition_width lo2 = gr::filter::fir_filter_fff::make(1, d_pll_taps); #endif mixer = gr::blocks::multiply_ff::make(); cdp = gr::blocks::multiply_const_ff::make( 2.); // FIXME cdm = gr::blocks::multiply_const_ff::make(-2.); // FIXME add0 = gr::blocks::add_ff::make(); add1 = gr::blocks::add_ff::make(); /* connect block */ connect(self(), 0, tone, 0); connect(tone, 0, pll, 0); connect(pll, 0, subtone, 0); connect(pll, 0, subtone, 1); connect(subtone, 0, lo, 0); #ifdef STEREO_DEMOD_PARANOIC connect(lo, 0, lo2, 0); connect(lo2, 0, mixer, 0); #else connect(lo, 0, mixer, 0); #endif connect(self(), 0, mixer, 1); connect(self(), 0, lpf0, 0); connect(mixer, 0, lpf1, 0); connect(lpf0, 0, audio_rr0, 0); // sum connect(lpf1, 0, audio_rr1, 0); connect(audio_rr1, 0, cdp, 0); // +delta connect(audio_rr1, 0, cdm, 0); // -delta connect(audio_rr0, 0, add0, 0); connect(cdp, 0, add0, 1); connect(add0, 0, self(), 0); // left = sum + delta connect(audio_rr0, 0, add1, 0); connect(cdm, 0, add1, 1); connect(add1, 0, self(), 1); // right = sum + delta } else // if (!d_stereo) { /* connect block */ connect(self(), 0, lpf0, 0); connect(lpf0, 0, audio_rr0, 0); connect(audio_rr0, 0, self(), 0); connect(audio_rr0, 0, self(), 1); } } stereo_demod::~stereo_demod() { } gqrx-sdr-2.2.0.74.d97bd7/dsp/stereo_demod.h000066400000000000000000000075771226036373100201450ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2012 Alexandru Csete OZ9AEC. * FM stereo implementation by Alex Grinkov a.grinkov(at)gmail.com. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef STEREO_DEMOD_H #define STEREO_DEMOD_H #include #include #include #include #include #include #include #include #include #include #include #include "dsp/lpf.h" #include "dsp/resampler_xx.h" class stereo_demod; typedef boost::shared_ptr stereo_demod_sptr; /*! \brief Return a shared_ptr to a new instance of stere_demod. * \param quad_rate The input sample rate. * \param audio_rate The audio rate. * \param stereo On/off stereo mode. * * This is effectively the public constructor. To avoid accidental use * of raw pointers, stereo_demod's constructor is private. * make_stereo_demod is the public interface for creating new instances. */ stereo_demod_sptr make_stereo_demod(float quad_rate=120e3, float audio_rate=48e3, bool stereo=true); /*! \brief FM stereo demodulator. * \ingroup DSP * * This class implements the stereo demodulator for 87.5...108 MHz band. * */ class stereo_demod : public gr::hier_block2 { friend stereo_demod_sptr make_stereo_demod(float input_rate, float audio_rate, bool stereo); protected: stereo_demod(float input_rate, float audio_rate, bool stereo); public: ~stereo_demod(); private: /* GR blocks */ gr::filter::fir_filter_fcc::sptr tone; /*!< Pilot tone BPF. */ gr::analog::pll_refout_cc::sptr pll; /*!< Pilot tone PLL. */ gr::blocks::multiply_cc::sptr subtone; /*!< Stereo subtone. */ gr::blocks::complex_to_imag::sptr lo; /*!< Complex tone imag. */ gr::filter::fir_filter_fff::sptr lo2; /*!< Subtone BPF. */ gr::blocks::multiply_ff::sptr mixer; /*!< Balance mixer. */ lpf_ff_sptr lpf0; /*!< Low-pass filter #0. */ lpf_ff_sptr lpf1; /*!< Low-pass filter #1. */ resampler_ff_sptr audio_rr0; /*!< Audio resampler #0. */ resampler_ff_sptr audio_rr1; /*!< Audio resampler #1. */ gr::blocks::multiply_const_ff::sptr cdp; /*!< Channel delta (plus). */ gr::blocks::multiply_const_ff::sptr cdm; /*!< Channel delta (minus). */ gr::blocks::add_ff::sptr add0; /*!< Left stereo channel. */ gr::blocks::add_ff::sptr add1; /*!< Right stereo channel. */ /* other parameters */ float d_input_rate; /*! Input rate. */ float d_audio_rate; /*! Audio rate. */ bool d_stereo; /*! On/off stereo mode. */ std::vector d_tone_taps; /*! Tone BPF taps. */ std::vector d_pll_taps; /*! Subtone BPF taps. */ }; #endif // STEREO_DEMOD_H gqrx-sdr-2.2.0.74.d97bd7/gqrx.pro000066400000000000000000000141271226036373100162250ustar00rootroot00000000000000#-------------------------------------------------------------------------------- # # Qmake project file for gqrx - http://gqrx.dk # # Common options you may want to passs to qmake: # # CONFIG+=debug Enable debug mode # PREFIX=/some/prefix Installation prefix # BOOST_SUFFIX=-mt To link against libboost-xyz-mt (needed for pybombs) # AUDIO_BACKEND=portaudio Use it on Mac OS X to have FCD Pro and Pro+ support #-------------------------------------------------------------------------------- QT += core gui svg network contains(QT_MAJOR_VERSION,5) { QT += widgets } TEMPLATE = app macx { TARGET = Gqrx ICON = icons/scope.icns DEFINES += GQRX_OS_MACX } else { TARGET = gqrx } unix:!macx { CONFIG += link_pkgconfig packagesExist(libpulse libpulse-simple) { # Comment out to use gr-audio (not recommended with ALSA and Funcube Dongle Pro) AUDIO_BACKEND = pulse message("Gqrx configured with pulseaudio backend") } } RESOURCES += icons.qrc # make clean target QMAKE_CLEAN += gqrx # make install target isEmpty(PREFIX) { message(No prefix given. Using /usr/local) PREFIX=/usr/local } target.path = $$PREFIX/bin INSTALLS += target #CONFIG += debug # disable debug messages in release CONFIG(debug, debug|release) { # Use for valgrind #QMAKE_CFLAGS_DEBUG += '-g -O0' # Define version string (see below for releases) VER = $$system(git describe --abbrev=8) } else { DEFINES += QT_NO_DEBUG DEFINES += QT_NO_DEBUG_OUTPUT VER = $$system(git describe --abbrev=1) # Release binaries with gr bundled # QMAKE_RPATH & co won't work with origin ## QMAKE_LFLAGS += '-Wl,-rpath,\'\$$ORIGIN/lib\'' } # Tip from: http://www.qtcentre.org/wiki/index.php?title=Version_numbering_using_QMake VERSTR = '\\"$${VER}\\"' # place quotes around the version string DEFINES += VERSION=\"$${VERSTR}\" # create a VERSION macro containing the version string SOURCES += \ applications/gqrx/main.cpp \ applications/gqrx/mainwindow.cpp \ applications/gqrx/receiver.cpp \ applications/gqrx/remote_control.cpp \ applications/gqrx/remote_control_settings.cpp \ dsp/afsk1200/cafsk12.cpp \ dsp/afsk1200/costabf.c \ dsp/agc_impl.cpp \ dsp/correct_iq_cc.cpp \ dsp/lpf.cpp \ dsp/resampler_xx.cpp \ dsp/rx_demod_am.cpp \ dsp/rx_demod_fm.cpp \ dsp/rx_fft.cpp \ dsp/rx_filter.cpp \ dsp/rx_meter.cpp \ dsp/rx_agc_xx.cpp \ dsp/rx_noise_blanker_cc.cpp \ dsp/sniffer_f.cpp \ dsp/stereo_demod.cpp \ interfaces/udp_sink_f.cpp \ qtgui/afsk1200win.cpp \ qtgui/agc_options.cpp \ qtgui/audio_options.cpp \ qtgui/demod_options.cpp \ qtgui/dockinputctl.cpp \ qtgui/dockaudio.cpp \ qtgui/dockfft.cpp \ qtgui/dockiqplayer.cpp \ qtgui/dockrxopt.cpp \ qtgui/freqctrl.cpp \ qtgui/ioconfig.cpp \ qtgui/meter.cpp \ qtgui/nb_options.cpp \ qtgui/plotter.cpp \ qtgui/qtcolorpicker.cpp \ receivers/nbrx.cpp \ receivers/receiver_base.cpp \ receivers/wfmrx.cpp HEADERS += \ applications/gqrx/mainwindow.h \ applications/gqrx/receiver.h \ applications/gqrx/gqrx.h \ applications/gqrx/remote_control.h \ applications/gqrx/remote_control_settings.h \ dsp/afsk1200/cafsk12.h \ dsp/afsk1200/filter.h \ dsp/afsk1200/filter-i386.h \ dsp/agc_impl.h \ dsp/correct_iq_cc.h \ dsp/lpf.h \ dsp/resampler_xx.h \ dsp/rx_agc_xx.h \ dsp/rx_demod_am.h \ dsp/rx_demod_fm.h \ dsp/rx_fft.h \ dsp/rx_filter.h \ dsp/rx_meter.h \ dsp/rx_noise_blanker_cc.h \ dsp/sniffer_f.h \ dsp/stereo_demod.h \ interfaces/udp_sink_f.h \ qtgui/afsk1200win.h \ qtgui/agc_options.h \ qtgui/audio_options.h \ qtgui/demod_options.h \ qtgui/dockaudio.h \ qtgui/dockfft.h \ qtgui/dockinputctl.h \ qtgui/dockiqplayer.h \ qtgui/dockrxopt.h \ qtgui/freqctrl.h \ qtgui/ioconfig.h \ qtgui/meter.h \ qtgui/nb_options.h \ qtgui/plotter.h \ qtgui/qtcolorpicker.h \ receivers/nbrx.h \ receivers/receiver_base.h \ receivers/wfmrx.h FORMS += \ applications/gqrx/mainwindow.ui \ applications/gqrx/remote_control_settings.ui \ qtgui/afsk1200win.ui \ qtgui/agc_options.ui \ qtgui/audio_options.ui \ qtgui/demod_options.ui \ qtgui/dockaudio.ui \ qtgui/dockfft.ui \ qtgui/dockiqplayer.ui \ qtgui/dockinputctl.ui \ qtgui/dockrxopt.ui \ qtgui/ioconfig.ui \ qtgui/nb_options.ui # Use pulseaudio (ps: could use equals? undocumented) contains(AUDIO_BACKEND, pulse): { HEADERS += \ pulseaudio/pa_device_list.h \ pulseaudio/pa_sink.h \ pulseaudio/pa_source.h SOURCES += \ pulseaudio/pa_device_list.cc \ pulseaudio/pa_sink.cc \ pulseaudio/pa_source.cc DEFINES += WITH_PULSEAUDIO } # Introduced in 2.2 for FCD support on OS X contains(AUDIO_BACKEND, portaudio): { HEADERS += portaudio/device_list.h SOURCES += portaudio/device_list.cpp DEFINES += WITH_PORTAUDIO } # dependencies via pkg-config # FIXME: check for version? unix:!macx { contains(AUDIO_BACKEND, pulse): { PKGCONFIG += libpulse libpulse-simple } else { PKGCONFIG += gnuradio-audio } PKGCONFIG += gnuradio-analog \ gnuradio-blocks \ gnuradio-filter \ gnuradio-fft \ gnuradio-osmosdr LIBS += -lboost_system$$BOOST_SUFFIX -lboost_program_options$$BOOST_SUFFIX LIBS += -lrt # need to include on some distros } macx { # macports INCLUDEPATH += /opt/local/include # local stuff INCLUDEPATH += /Users/alexc/gqrx/runtime/include LIBS += -L/opt/local/lib -L/Users/alexc/gqrx/runtime/lib LIBS += -lboost_system-mt -lboost_program_options-mt LIBS += -lgnuradio-runtime -lgnuradio-pmt -lgnuradio-audio -lgnuradio-analog LIBS += -lgnuradio-blocks -lgnuradio-filter -lgnuradio-fft -lgnuradio-osmosdr # portaudio contains(AUDIO_BACKEND, portaudio): { LIBS += -lportaudio } } OTHER_FILES += \ README.md \ COPYING \ news.txt gqrx-sdr-2.2.0.74.d97bd7/icons.qrc000066400000000000000000000015571226036373100163470ustar00rootroot00000000000000 icons/play.svg icons/record.svg icons/document.svg icons/floppy.svg icons/folder.svg icons/power-off.svg icons/radio.svg icons/audio-card.svg icons/flash.svg icons/signal.svg icons/fullscreen.svg icons/refresh.svg icons/settings.svg icons/clock.svg icons/terminal.svg icons/clear.svg icons/close.svg icons/info.svg icons/help.svg icons/scope.svg icons/tangeo-network-idle.svg gqrx-sdr-2.2.0.74.d97bd7/icons/000077500000000000000000000000001226036373100156305ustar00rootroot00000000000000gqrx-sdr-2.2.0.74.d97bd7/icons/audio-card.svg000066400000000000000000001446111226036373100203700ustar00rootroot00000000000000 image/svg+xml Jakub Steiner http://jimmac.musichall.cz Audio Card hardware audio card soundcard gqrx-sdr-2.2.0.74.d97bd7/icons/clear.svg000066400000000000000000000460611226036373100174460ustar00rootroot00000000000000 image/svg+xml Edit Clear Andreas Nilsson http://www.tango-project.org clear reset blank edit Jakub Steiner (although minimally ;) gqrx-sdr-2.2.0.74.d97bd7/icons/clock.svg000066400000000000000000000404761226036373100174570ustar00rootroot00000000000000 image/svg+xml Jakub Steiner http://jimmac.musichall.cz New Appointment appointment new meeting rvsp gqrx-sdr-2.2.0.74.d97bd7/icons/close.svg000066400000000000000000000274721226036373100174720ustar00rootroot00000000000000 image/svg+xml gqrx-sdr-2.2.0.74.d97bd7/icons/document.svg000066400000000000000000000563541226036373100202040ustar00rootroot00000000000000 image/svg+xml Folder Drag Accept Jakub Steiner http://jimmac.musichall.cz/ folder directory storage drag accept gqrx-sdr-2.2.0.74.d97bd7/icons/flash.svg000066400000000000000000002724061226036373100174610ustar00rootroot00000000000000 image/svg+xml Jakub Steiner http://jimmac.musichall.cz Flash Memory memory flash gqrx-sdr-2.2.0.74.d97bd7/icons/floppy.svg000066400000000000000000000531511226036373100176670ustar00rootroot00000000000000 image/svg+xml Lapo Calamandrei Floppy 2006-04-14 store floppy media gqrx-sdr-2.2.0.74.d97bd7/icons/folder.svg000066400000000000000000000345161226036373100176350ustar00rootroot00000000000000 image/svg+xml Folder Open Jakub Steiner http://jimmac.musichall.cz/ folder directory storage open active gqrx-sdr-2.2.0.74.d97bd7/icons/fullscreen.svg000066400000000000000000000264321226036373100205220ustar00rootroot00000000000000 image/svg+xml View Fullscreen http://jimmac.musichall.cz Jakub Steiner window maximize fullscreen view gqrx-sdr-2.2.0.74.d97bd7/icons/help.svg000066400000000000000000000317131226036373100173060ustar00rootroot00000000000000 image/svg+xml Help Browser 2005-11-06 Tuomas Kuosmanen help browser documentation docs man info Jakub Steiner, Andreas Nilsson http://tigert.com gqrx-sdr-2.2.0.74.d97bd7/icons/info.svg000066400000000000000000001113151226036373100173060ustar00rootroot00000000000000 image/svg+xml Rodney Dawes Jakub Steiner, Garrett LeSage gqrx-sdr-2.2.0.74.d97bd7/icons/play.svg000066400000000000000000000233641226036373100173260ustar00rootroot00000000000000 image/svg+xml Lapo Calamandrei Play play playback start begin gqrx-sdr-2.2.0.74.d97bd7/icons/power-off.svg000066400000000000000000000235661226036373100202710ustar00rootroot00000000000000 image/svg+xml Shutdown Jakub Steiner http://jimmac.musichall.cz lock key secure gqrx-sdr-2.2.0.74.d97bd7/icons/radio.svg000066400000000000000000013747621226036373100174730ustar00rootroot00000000000000 image/svg+xml gqrx-sdr-2.2.0.74.d97bd7/icons/record.svg000066400000000000000000000244011226036373100176300ustar00rootroot00000000000000 image/svg+xml Lapo Calamandrei Record record media Jakub Steiner gqrx-sdr-2.2.0.74.d97bd7/icons/refresh.svg000066400000000000000000000423101226036373100200070ustar00rootroot00000000000000 image/svg+xml Jakub Steiner http://jimmac.musichall.cz View Refresh reload refresh view Ricardo 'Rick' González gqrx-sdr-2.2.0.74.d97bd7/icons/scope.icns000066400000000000000000022156531226036373100176350ustar00rootroot00000000000000icns TOC hic08~ic10ic13~ic09^ic12zic07HVil32 l8mkic11 is32s8mkic14^ic08~PNG  IHDR\rf$iCCPICC Profile8UoT>oR? XGůUS[IJ*$:7鶪O{7@Hkk?<kktq݋m6nƶد-mR;`zv x#=\% oYRڱ#&?>ҹЪn_;j;$}*}+(}'}/LtY"$].9⦅%{_a݊]hk5'SN{<_ t jM{-4%TńtY۟R6#v\喊x:'HO3^&0::m,L%3:qVE t]~Iv6Wٯ) |ʸ2]G4(6w‹$"AEv m[D;Vh[}چN|3HS:KtxU'D;77;_"e?Yqxl+ pHYs  @IDATximYVw#9V{rʪ**i.cujYFB@d²lܒQc ɖжd-,jJ*3+!"au= ;ܽg}ݝ8hg l?}P8hZxxwAZxxwAZxxwAZxxwAZЭn F'=܇'[?8L {~nṷژs+~Sފß9zGw}-~a2yn7 N'WMqF"?*~ ȓُzC~ g V}50|8asX8֥#OrDx:gj{6g}hɾpBHjAc14|8̜ 7ٺzʐA8hT 0U:΀35 A@AсUAZ Z OX9fC `We{Wfe6$`Yr: vL7~g~??w}ѣ'BB>9zv$wT l6[jüA=޾x3Ν__:a%pWtԵGEgX`W;6__s=+aU]F&x׉5=u?Oo ~H\` J.F'׳yp7TJګ"v_ Q/\q^F2Ռr?''/ p+93}1~g4oN8S {; sZ`xNx 3ٞf. __O~~_i'K;e@m4ʯ?B?\z†>dq}/@\f4-r7nܸ[[*9yY@ .u6NPXxNgzh>?ӟ&Zc.d3,:~z!oY 4K>SN̐#ܜN :t^{__"zSgF5't[ت@:ff#'.y {7~7#GLMYtʕ+RaUOc]g?g3 {X)g'#|Vt) pرɉ't1=_~G~G~<@.2# xYv EFəO?}ҽpe3 ){Ҁ^2_'iUOM,%z>2G5]cH:b`Szg6Sk;$pǏys̙~ggu k;r2F=rPL.w U2xIf"f'8˽2o-, s\{YEM֩%bxv@GmQifaY{#R@ݜ33C?c4N~>R2kqt@:QEDA,͐~GJdրIy44JWTmh"Z5@4KNdBh{tj%]wAo>6 (wr!: ommB!}hK̩{d~k׮5h1rQ|&w,ci-#jjـ|7Qoh۱KeJ{Go%]mu+rL ͣ p f/^8`ɟHL2v+tHsp]k>я~seK0%O1DP#NgNӄ(Qiq6EeJU<&ֿVn^EasV.j /:K ؽrh>Ƶ[:ef fYd-`S't$$dlͽ!qN'DXVkj~c'<O< "닏&ckHۓޤ).y`4mո!q?Jz`{b}"*h0|7ޖ$~$koL<Cas\X5˽,ߝDk% V n5Qz77+~MFVI3_ѫ:A1n/hϙ3OSO=5^~cubLp%P*l{iDzaP[2,n{А `CN[ \Ů{at}9p+~j΋rHb*1`/!OWKwsr[,W{Jgd-0(<&d`bH+) x~fO}ӓ'xB\#'J[V~NAҰU[j!5ę.gowӐ_'?aMV .bi=E.qX2tᢓgy 2@鿒 1Бakcg*T:eȁJtuE˿_t)fܝ@3,A 5~뉔1r|k2),!A{>Y;~a6aЉ뚪'3d)I8t¶q3go(8 gw-' :~"8 ,ϔFbQx*rST5=F`Ͽ0y饗/Mo47u7&_}+k,=5ܒpE_ˏA dzO7F/i9;I5ưV {g;\4Wr O/X{&zhM:5X~G/tnTG_mF\Ӧ0#Ĺ!x:y@ZW+j+g6&o~O8# ^,T+]4DW@y$}SE#U5 {|t\#@+>j׿D cqY1`QDJE̙Grmgr]NP$D毾}:(Is^D`SD\cBkza=c׾5sVvtcs_Mk;Tخ&ԉ,4dMJtٵS f)nI$uoݰr压2w (W)@vcHV&2e cm Xjv x>O&?G}b8Y{gl=񣨣t4h3M:$ӓѮ{1y2knĖFF_Du<ɑCe XЃ94͹@eM$-Lp撜w(ۛqW^`]:l;W\1c?cdO~K|CT݌ʲs:2k3S= ?q65݌KQC㇮UKvG|ƒx3T8wVoRXud.[u􈌊}lh<:wH'G oNI.Žī6v!|Q)My7g@E[Kf0={V'\i3g^<ƈt65M%Y;’Kh4 u)٪g>/~,gsW;f@kppkʴliDWD__Nǖv0]Į;&- e8dL|Ƀٝow{,`(*-|Y@q3MvHVHs}景:@n "?e"jl&OYg-Ͳ!b 7a1=2Ic:WofoF\W)"W5|&%L-8M›I3;hv)'8cG\?/ǀPqM&=@h̰a9tp@ۏQ7| ?'jt-*!;=ktqJV`;U/~ɻ{Y&uCM(kGڒG%eJL8Hbbl3DNg WYޘHdH;yB{@/|gBY>ҺޔS5"f U[Լ%RzfA@~)*Hizyyǘ[;9AR?(-]1o$vv$' l pU?-( 0 )n~~U@`/JzQlg\эw=;$aݏMv/^%|N V;V26Y5= !&\w_f2 AnEJg18B,) 쁎Gv2ϏWuaY[ŒV Ms&[pYߤ4&c' cG?C~_|%MmJ$!DJ`|<$yj3)a`iQA[JݭIz**: kvq^X#FF[%7T>zt1b=1կGu2w*^Jfd|G9CqD.]\ N]l8"<7;1@ pdv^$V`fȖWy_ ~*#aI }Ht<ox%mHc&3HnvV/^a3-9Bh#kpHs &y%N5`hπv7햸cR٥+bdLĆt"2MiKz79J:/ҭ0iNN' Y,@/D*\͘oRt]6m@e&s⥝1"3QceY9u"fZK2(e1 r|[? W Aj󜀍gAL8 4O:hX*,>$er*ꛖtC!wJevĞR\MT <"8hḘVk/+e5_X+E/a5JL~5=@XsqA;5&>ueYkԕKY[EB)ThL(d;?x颏AAxZeۘ7"HHT!)ɼـF!sY鳩:(-vJZm_gzUg s&d Dq/BB5ZpA!-kAjQZ#Z.a^X`ZՊjKђNNP P֨0% qIn<#/_ӿhxz&~pvTA2 A-|mh;9g'uC몾pq G4Gߘ=Q+JKt8K DA{s}Pti"v 1f31|D!)^VVVa^ioi[+e-QT~DU3p!} ?Pi䡵3򭞼=O kwۙA#O`tݖjn-eEvnԿƔoF3@+n- G=B^cڹ{mA(! wIrf3Q%;#ج4G:[:K0DEb7PWAf|ƞ|N [.PK] ȓR $S>:rH Qk4,/x@~ZʛE>y^+cxu^o4u/ HvJGXV׶GwM&uaH~|˒ :^1ie^ An.ݥ*hgCq/ < &$ςͮBS(Un>zjqúx}-eAv&-D޾RւR]>p?CJ_~@ 6"RAg  MYpb{zE2=;zyfqFyv+",_}2(?:bKnoMP_؝<{~ފ_0E%+M.nL^kc싻 ̂ڿҪw !u|u?Ɉ!<`w^ (E@Uƅ}܃'fnBYjPh ,xc:O(&&xLQ[J;3c:<9qIç}t:'t'*o#/ l&B}0yqb;[Pg8{٫k -ӏ<`;&^'NW =?t~E;҇9.v7)ٻW~j@ J;Ib a{QJ&R,/5L9Ҝّ+n0 KG'ň b&kO=YVI}2ÛH8iz@gE#Z:"\ Z5^k%k&o@ Bo?V5(0.]1ppkZdݛb]kQ5%Dk`7q?; ޹Mgh^dxWInMrKtSIvbTtj̈S2e:*g:@>2\ =Ow~>N:3_?mBImnf|ks>0崂”ǐlɐXՒ n K^4)q!hFq ;*JLWbl$!`i}@|S Ԍ䱣@aקEkGe3+ ~uU+RŠb>፰$. ta;H.zdWHgEcIEHsFy{I[XfcFpEzKZbdמb6~z/a5~ηŲ+BT-p/(VЖw饠Õ+zt^ ʱyKĹrA̎cNn?"M39_Y Y[XOcUY_4~_4:O>H>a`r`g)J(#m-v @M\&7EOMiTlG7?U?˓Sy/h!f8 { ?p cI@eN(?VKaɮTmSv;#e x/ /w8 {(^H::$Zc0e" b^;" T>"D8q|srA7/_Am&|@h_p$/ح#3# <WkJѱ `vs*3b3UiN"gM3s-/' GN& fp&/BUMx#>_رߣmnMR٥P5f;j/mL3y=j_a8ۀZDpi,p ȚSk 7ְ^ 9XTSv@GGD.?-;"sAv'IMS{N7ks~`A ޻#c|Vv15I㶘UމH(&Y׎$6g|'0k1 @yg }bjXez4GOȌݤiFe*^?ֈf|6J8W,wKVuK)܋s/x$,X4roRvoN=`lWw+aontrtRl)[{J oc43aku^Q\8u7fS+09XUsO?b)ߜuЌ]"@0(}p Eԇoݦϟn-0YiAiJv|3V Djת3&!76DM)}&=B\ i{Zy?č87$'Ď:,!mi%# KW|[Y|7g9M|FǎR'ANݿ9yCϖo u(.1n.ע|oH?KRllJ׺o9l(K#gws{]Xťayx5nx?w]0c\{Sa抵VQڻKd!0|?ylZ?=;ӣ^U>5m?[2X G@—6{!}L֡}qLZc@mPuxr̞LJw~tK45z3s`BW5] Q;yV9ߣնIu'4wؼ R1}eAZ]f'{>Hpv"4_fdZ9+g=Qm>xa0 #y{IGu}Vj݀ `ѬR;T^+ SSOa\D'*EKN]J`!7l>k?ϿL/U,=fCՄJ˩MWx l(1.y Y>o_Fq٘qqicf?bGsL )c5D=҆D%޷q%͊U}V@Z,Ueqf&95]LRkf [CMur6bi#oݑ[|U?G+qG|:s8{U j3z9řgzѸJ SpQ|Dhlf UXzHOe"6@`\j1Z9t)п 4ŝ)cYDٲ|C@!_ CWFȔfc[_z?J^|!aT٨g%?n,-s:5lmt?vL9($0Sʖ}.Wyp:ጩ'~wZHeC /{Rn6BELMGƛVK lA} > r2< 3 B  9ۦ"Qa\wBIOv0ºF~12J+{nA x966qc[h61Mc'+Igۓ;zEb%߫]'/mO^:muUu2*b7j`]65 gP9/ՍֶC?Wqt<\G5Z=yYlX> ʼnhL4[ܶVvLc٘aV^%l m_%ٴK0%+{ P""~uĢ%䊐_<>:,Yǡ {a ԗy[ԳRAvCf(WSke ~xbwsR#&OԦD,#tFqsewk;u>HhQQ_Eh# p˩wmL8 `h.̙9ax"̷ ArQXM [2לdPxW7lc'p"pڙX~cӁ)8Q_ zǓb~vt%xFIssے*fmlֵoZZ rȳ;Hz_;Kz5ÞknK3:>pj| @j=4x\o=B>SU.ڭ%6U^ KY^TAuMOf)BW1Lun-UMjGNEVS)ItX,hCN5=;7-h>"38{ lq'lN2~Fb% =|`x8Nb̔ţ])TFg;*ci5ץ4UjmA?x>&!$+!͏їgGѐGe#mrmm ,"QƷxtoΛJa}onqn3="2"%~sN9\oG7όx uw:bB/SP)f #Eٽ >q<*' !ޞ+H:8g}$%V貙rxuO|*02VWCsc2 ^2 C,%V#b~ꘗmS=%)3G\)D7F"UpT #`wT?|laT&ľ9O4٢6 2;@-3>Hh5!H/G a@<'ŝ"7b06#4&<|NHRf/<ǶߤCNM̽5 k$TO0K(4a}Txl(̟.@oC];G"e* ‰@IDAT ˿`UCs \V̙@c$€ I|Ng|? z8id2r5X! 1„l; 0XCQ'c:YS8>g>}bUޟ99nVs9T(Rvpv;ʽM7뼐q]dF\ L@F+L:6Y{nnv—d],7c~!Flh!gk$ȸ!'wfck?x5u(;, }Z!%lZ|' l |B*zȝG 3+ׅ8CrBEL{Pȳ,uJ@;?žYᩏYBr@h#8aHw4P$g!)^$ Vȓjw]st\D)ɬxkӑ){h mꕀA#9Q\fmi3E 6'epr ?{%{xYȷ ُkL 2;4Oƃ"Me895(ړJF ^9-̶-X~ہh|wnp1[^iYֳ`װ"4]d.:DIP02%pp-ЮHsR>Sxs)9EQ_ 6-HQݭ獚Ej0ՆK S@vKi^My ^U j+S,,+ְC~ H-> BQ";emw4qV]^{˓?5yy}T"ٝ\B;)uL'Ʊr,|NajhRHQ4| Mc+ 8[: sYbuMY`2natvڗ;RH4"{{P(,?6ԴKns1ϗ֗x׵?kK;`NeƄNV٘ʡ2*#;K="cwffs?>*OQG>eŔß:: w@ՀqUԇggIS#M},PՐcItK֞̕1xaȼfeCG.~=P;?zئZkc\.[>S| K;s/}d ?f 9ƌ7&?7`L|gǫ.}>Vn%޴qoŨs4v7~nJǶ&On#_nW2/-9KAZ7S=VK?k^`tlOM%MKS(]\1c?M~vD?]L[7s} Nn շKYA\3T4G}S",m(GFV6a^1{ݲkq_{2f})tW1aSR Qr? t^ *5|C^Z㪦ӗ/ozO]f`B ._tb)@x_C:4yܣ\㛯BҜB4ep[٨ ) s)0)V}wcGwT1@bX1z,H$mz) O`: |n=jpYr]=nAY> fq>5N4=_Zՠ䨆Cx%}*{gY'7m0XGUzNs-Op v[6..dk΂JJ))'GYt8NepH'd=gG)),)wVc_|qI tLO<"- ,ǁ쏮 $,$lÇ!B% Fg#]"(N"ۘbw-D0E95 7n6"hi!#$l[6l@֯)]Ĉm4r1"rP )cA]I>a /L⢧m{PZ=[^ѣP_#@_v:T<{u~BXK3".9p׵T9S'>1gf_5ev*@uWLqdxT}L 6-e xLBD;]1كa2Dg%҆<x$84žDD[= NR4wR.gO<'#&S4PCZe EuBzӠ\fXPґ͈[('}'Ow]C1/ӢOx# g⢌$gIg\0N2i(ΖZN&/ߘ\t]8/i46:g41‹X_;5P|:!>-_3a-hN K;haU~Q_l~wҒά,cp-jprƎ-SN~T`My5?_}v ޝ< sp%^R_EZ<]qRX|޲DK58]Y:&RCn/sk~$q.#čh >q΂ vJuIc=f @n+$2m"<I2nkboͰXSöUutX[TԠFEKD ݣU`:Nΐ4>&ѾtϕotZ *3x E>zǗP'x]&S8i,X~oKz /oLx)f 'en0L \PF4LBD%t*c<vuĝbU89nNv]#rl+g9b 6Pq7Aö7! Vi8=SIHwz(ep mX]ӰFuv$ʫwY~DDm ەV=$@>5.vp;;(!1U ?Dǵ/Wt4fF%!\.(_hE=ziP.ŗe"/MW,cL?M!7\V&?Z$0ySR[-7^6( v#``F16\93:2A੹&a_}Jr@!oU?3g N96H%=`HcQ{G_9:_.zy|2=4>?EzⰜFi6Mnn[8j#@Wgacyye+i}3\gAp/~5=[xH܎ŴgA~G!!Y61Mu#a^w lBIG聢YؤY1% ,gÊdp-[Ysaڶ!Xp $F#)rH 4-7ouNǀ׳KT7&S ۏҧ8] N o_뇋t&Tטw:>ǃsNs:D:D]8qnGG$[D`S3S{Iz8/_"zp a4)EC}eY+Z m9~y?xQS{`"gR?(Ip Lvb{TIGAN=CiCMtrIS54|ψ6zOK0^H^biD,`RGC=gQ`)sLx,qSi_w+x΅`ɝ 4-HoyҏY *S`H:Tf#A:`eq؋A"|D~\gczv\jЛm X('canbv4^k+00x6LRPI'3 v΀ 7 p3x`8/&=4=rN ^¢^ƘuE2 A!#VN6$|8%tT~bc=M}["]t0?:=n|XznN`8zPdTJ݇Bt3j[4 ?AܹY ?O~};d3PaiMK 8G5L2PI ݈"=H*kNS{sIl1 :`*, I'.pm>#ezF9i\okB\F j`2f<9v˙и҅9nS]O{dx@p-ޤ3#* `%~h! YCb|[qo܉RqMa'9{%i{%Siw3hQ K ;ļ*;뇁ԽU]JL~iˁ*kad龾.Q}zU@Li̓i&)0 T5waR_-ޜ|j&@TzQ,9Ӝ!f >uX_Mꕎ.+CγC U}4PG{}X I9ڏ`C'04;=1ݻK81H ^{qdֲ S;]a(:贖o|..~EŪAUDo&/3(}\eu`*_F5'ξK.@ &@<T$/i% 8s[Jzhs3 P/!pht'Pp^ywA ,vLyUHڋԿ]LQ<-u:5vj17y Wt9|z\A~zp=)f<|MvO amB2]7r]HBnEx@("S40/eEnhz'4# -<4o^՛w/_`C[Zc%4#Sfcฟk͌"%[` ؽ'w=w.i0sF{-SF*2 ݂[Xa/@ c"a@dP <0 ~kR7h'A(R ς+gw}XT P߼f2 -J@xױC c[Q68iOZ_`㍺CK[ km 6lO8Qf@ʏ f(aY`CJE)"4U@o՜WE\ lVྶwxI:>jXKfgykA} 6%5NÁS:kjh#(fz)'S6d .^xD%G~]_CfޱbH%k[!aiidJ:x(kb%"hTk{{N ekh M>[ V<fwN H酴Aυg.yB|Nh͐5Pc4@2QIC,T2tYu:vis&w…0@wEi lC o`Pbvb*6'H"wH31JY`[pQE%#aS ϬFK8rfڳ VHKj{!*NsvZɩ'!D4h[2F |ٰYv0&&Ɔ"Πt 4A+֑qϫjw^fѩ Rgok$dQpK; ~ge&.~Pv@)Y2S;Y4JtNOeg`l5 5X*3!}Ѩ n0D٤ ^ͪiP ~kX]Q) !mɉ]Oq ,Q\ᵬcUI/c"8<忦\ ɲH`pYoCB2)q!yFox&ӳmpȵsʦI 6!R3eEcʁ&QFc+4dM>&J<vF - [n)lOLN%5 82 35f #5D]<8zBZuI%XjO/*A8w>=q_۞qy"8;%4G݈"y&Ea[#E=([<Ȗ-H=Ϣ[8 VOh aGE'U$яdwN`$4.0{N 4W cLhpuXiZRl"0'-F9%<㇆Rsc\(?ʽ*93wǠ7 5|p^K@I8㟅K;tk|М#ɇuI@fL@eЦ*נZ#zVtDWҐ8C klMOՍ YF9!E!2C؏ S'|8G\)4cn0%Z:x{!2IL!~1qMlD様hȺg[bޑ#4Ӝx35^HqL_H,r&JRVC#=]L>?\ l@OPDAy G!pMgz-> )/ >r7n#bakW4x\jr 醎J`I 82 YEO8~ vs=+}]!#Od;ƒ)}3 3gZMf\BPM 6esVz0ꛆSIpMqEgH CU~/'/-6pP Us 2%ʜh3 i#< ~!Bf[ȺuD0rJ4 4B{]v6/ 4aepD$Bv&pTeD~qI)Z2-r]B 5u*i:i͠[WBgftA*-0TâXh;z%2S=2Y`s~-$z^a=|n5- >!9. Qut."'%m!)q^oK-QЭF $c(c1<HCU*vRcRrT~8Cv0cA,@B !4KHݚխ}{uCJww^{{ZgJd @SaYС l! cJg!ɓߎnbH$ƢK9X^u|O_%bUf?$\&oWT`^k%q rAsMoAU ȅthG? u5p;*!ݐS*'9BGQ;ah$__2| IأK-yoo:BU{D)Dž(]mBU*4<_&fpl I3V,{y}T'c&4\`]9axMC泩7ZED jk2U3v@!ofI|lIqU Q@M~5F8kv"66KA PATNz4 M (& |=!&dǪvi,m* '-l Jk/'3aJ~sN/,4eB5F譬S,k<W\d౹*ZeHoB̝# TUP`IDQNRBO=ZWM_!o>͏mf Jd`&rي\:s>S/,-.险;PWF' אLvoe*?r_ ; L6VjF"H,K8Y`k(eL*nةbYRrZ] (U~X/]^dkg:T@vh&R} TmH2KkbIdN"rCj *Qi *j-P[[mτN%L!,ΰvqm>ۼJZ<0jJDeB80j$8 O4F}Ak6K-;"yNO^pU(% M޼bpP;Յ(hcM9"ت[4 t \8RCF&F DYF)m%[(#@,D;bjQ]&-KtOM "SVk #%>@ P ݧG(-4uIժ jeWjr`K(K"l6j[T_BqxK*oP 4IA+ͦ)?7o`KƨկTDof7iK̙`A, :  2L\{~G{n96cdOۡLKp $L"l߲j"3a^\en($>O2B  p&R1JIP j'Qa7ٺ+7 ^;=g͗$Ȁ@|}6PXrϑ=48%DK,)S+5,@h>;kI>GN.X)-P@]dH^Ip*-$g9u'?!"'=(: K|Vfp/䙕8:Y#&P[6a} Drp46WKXj"Yw,֜ %^/r0qb{ë;|ao$<<"/3ӆA+ g#KI9ÀH\S,Y eJur_QG23r"3ڂrC6&ϣ2HIya)*15u\UףOxaNRVE̒Rx4`{rϠm4 G!3Q::s:Yy!y縒J3ך$;$0J\/ 8ʤ$u&Ab0k$Lؔێ%KzQܯ̇J%+r=%RJ  F@VlG* &; Z^̢%hgAAn l łB5CT htCl$~ 6ɑ(;5xf*Z?w8v!蜉_ڵϬ6QoLc|0. Bi ?ׅtQf,Xj1^F4Yhm}\6FO>?e= >>3Ql'g;9?`֚>'MJ*:gG x}`f8V2 hv;Ow @o!gmGX:i$ڢpXH[`C I)-btUr%hO~LŎ>xJIC<2̼M/!Urp C 0(Jm#T8=fڬH&g%d5_9 cZ@7wiNj1 Y1dqrvZ˰cO,H`)ۑ'׫׺xop̂`I.jr `ԓ{)`sn-y ժZxpO68@QD/h %?]}oHc:ࡱ|0٤֏ 6G;g`%@&#|Wfk[dB*0 ;(Ov/xwE's׋1:}jKwlËJ`LMC%>=l|5מ^gy.`VcvIJZmܥ<~cxlM$ 9*tVBd۾0k2E§5@{]wxPK!ʙپvZm k 8#U6 ~l3VepGԚGVg ib*rhf;fK⠛O1Ww^QL::]!0 Tq.~r0˯Ϳ蛳M 6j3JwvۉKA<˻>{ P–g'cOALRrabK\99Ɔ!ۆj\y8~@U#|u|/.(H_tn[-^3^ .-`BJXK0svv׼`6?7+gm1iݾMX |ؽ ̐+G?wv=i Jaq3ALcT2hHpg} GrŹ9NO Dc3y^c+vJ[n.yNPAN8ae]rE'Mun6Yޚ) zަ*+ūp2Wba 5Zqa,"ZVDiV4#8vutL)8&M<1k(\w?m6m~psY.`.\>}F{JOlG=|=ۺ_xwThy`AjԢU-VE+ J\&#`FH݇|_tJƊ*s򶝧;?+p$ nw7gɴҝ Lau9*2-9 #ГFo[ڂxх]wх+^uu#޴ѫ3Wh =O}h+8,K;;hisUyR3V62kRm &cnV|YKrѤXm&O\4LVc0wëcч*?%!W'ZTC8Wq)nu`O$DRnյE/2;? ."G{DW^ A\ qq_yk1'#8L\jxС^-27I^/Bu/mŘ|O p=%."RH:VXo)ߺ)=6gC7l5GYҴ`I3b[%5ye"Ok(r /6qMϏnfco(- 6̇~h]w/.`N*!U_ ޴(0CV.tJ1\9y0d!%Mr+ ȢLH|_8q:*ejߋiawJw.)>b(貵o?YUH~N_efOnu}1{?L<{ γkǽX9z`t]*;LtuD*0w,3'(*{> /v_`!A.(X"0p/0e!uӘ@u`8OuV@'$M3Qr ̣yy4 9n2Q]))V 6$M.a^dEU] 8o/ls۫Ido\OK1\?ن= K{ctyh%;4<<䨛W[1l<]Xx]:Ӛp[׺~y)gV/fz@$Dg\C&g-+#aVT|:T48 6T*gXEjHV&P2!q7܉ i$~"Oo~+V:dU, &'᣿+£/BBwwoiD{բw&%RG0_rECd'l_ȐSr;o1zZ1/V) d.H ֡7w9rL'?<Ǜ_~ૻ3Ws;6e#Js#"!I2zMAҦ$f֤b!_%6C7pE:0D%E**fc?`7;S%S h1v}.RxH> N^C|EgֱU/Oo~#>V^ 'NϞ%%P ň)&aV9')iܑ2xwW?s_ݞ^|q #i@`θ(# !a{,HU'3jZ w0x RO9&<gBEݮc~нJ>bܳ|1E]-;KT^?>mv8خG9{8#ޑen.cl"UYOy~%Ù{k "(Eeva,j42dbn}jXߩMkѰBN9i0ȓCnmS!b+,\_r\}3h I٤ kji/Z+ݫ^#_lvL|Ze  YxNuٯ|!{&Ι[W]嗭v?k_L[}mԾkH"D2`J(t>i/[CFL9Jf"S툑YoרS+ Ф'd|M:|qoD_;|cxWFkֺW]C80!q+-  W,e?r=KZw =^뮺X-%|:5N{K^)1|kRI/+`aJWV|w]/6ºuϜzˆ]K,[t#ZlFz I"I  ŝBb1 'Z+D)plBOX^ %1*p匄ėY ÜOsfۺϺ]Q;w\W0z=ɔնeBa /GaUk̖&el5_0 m¹M_.wB*}z8(|\S[y΅Mg[D7j(zh A]8!o)pKGٔ9rm]:#ܫR_w"qR8Y62JVkQx0s _ C1PǬ;G:_/yćTq |UX@9 ͙xķBw bmw6a` Y;z/R5wL-mg(=7F3Y{<bp&#$?(6YA}&bL)p( X/f\fLfNV~57^;VŨgYU~ø()|-SVZptkG_F b͹`+Ip>*#6 ?SFn:ztjly@6ˣGdk94w R-O \MtYMᢍ\R>ŴA$G1jUAC@*cb.tI+Q%Ŕ .A cPe)O}V)5jc><=`AGnxK_G6G|QΟ0|+rҾƎߦvIW|G@]_Yng *F^Q߇'^++nrFYir4NvD6Ja@e\]aԋ^cxۭ @]uOgr*6{bh7KUp U ,XDw9D](!cTȉw :s'x0B P*ٸҭ7+ e,ǾRv`,$0Ϲ7,d1o_ݝ}نe$=AɃ@$s2Yc"ёDdJGyig&`;%0VS)ib1+Vcs؞`THM3!0DK%a|?4AQ!Ǝ@ 9;pjN}݋&w?&'sUW\pslel13k ҹќm6[7񍴃JGͼy r֍2} +l~h%汱`#SHOo|}\'-Fya9lf[7bW~{wuׁ칼O}}cx9Cij'5Ye}G Kx.8@fhCMLXp1nd>y}k_mˎsrc|"ĎӁtWnϋ`ާYH <cNu|q.ɍ .@f*_oҸ%c|ߣ<"VGSJTK 1 /G8)=z[dKl;s: ?Lo1^|Oy }0h{T8S<]gM',"f]wtߡv;p`?nsMnoj\px&r)Qb|y4T;崒r#ڻwF[q+k)H+plDs}~Ls΃|\ Nz8wIWI., 7@PF'ٛJ4Q˫B`k 68pm=kKcZŞfp>[wqG{eN… ;&^ Ov a%|+]W3TsT3GW'Yud[aLմ-Y+[@"k븼7ɚpkl>[n<3: @L kd>qxy2˹ 1Ӭ6b8K li e|>O܍#~Z|u,nn@@cstfSA5И^]PM1M(fu8NħCDlncl)_sw4Ldf.- ~ķgz'?=7iOYȵU.fl}d d0FZKwk sbӤg9Isy{kӟO>$/" s93֕Su.ޏ`E:P^rs&%=N<r#؎;,2,QGiL6S`IھcKS9vSr93q|"89)({"4qUZip +OcbOB/#Ǜ&.Nن.Kc͝#Zw"U$̡pK;[kfYo7b`>]ɾ~==۱ eycE'W@ͣװsԀ' ГWFUxz`3o%^lP=x&ܟߊU傇zVLc C|+ӖTZ l=_|⛀c߃P{PIk-KY4ϓONZ|_'s saMȱ뮩sF\09ŧ]핗dZx^߉M+v IlL f$hEoD&08NEyz56/Ǧk /D:ggKia[ S-2&/[|[$~΀0k1S|qMu`Aq[Z[af[=E0|nW`~lO΋';|~Gc7 lt}Œ4Ky*ċu#? :v޽mo{۾naO458[/cxNF"ST;fsw ߸ZEu 1vo[~>wnx;%7':'\ P@V e>WL{c~sv)˸.|畯~/ 4i^䧚yu7L&x5s޲+1 `j𓽥e;@mٌPr/Bl<մA`3>!՛o|#z't%'>=# ĥ>}] m y{<-`b n% .öx˿kg10-BL&ϜT>єuC'y|7ſG6Oٙ?/_:!p(hzȜ';K|G= (A+%0h|[x?6]'G{=?W 3@kLP IO^u%҉ʀ XLD<#{NZ N~>D~N~WV8y9qt@A@tvG~5Xe ,[`c-ϋ̕x~ɯ+ \)Z=;m4ps2@ב+^c`ȉ x@Y^7PLX@'3<':/j#>O ya\iCq*IsyMz$A=C@'?W ܖ@̟- #8ˠk|GA~'&8@K\!ps+Fd %^ 8^9: 8y*'|ҧ5Ɨe ,[;_Z_wK=|/y IENDB`ic10PNG  IHDR+$iCCPICC Profile8UoT>oR? XGůUS[IJ*$:7鶪O{7@Hkk?<kktq݋m6nƶد-mR;`zv x#=\% oYRڱ#&?>ҹЪn_;j;$}*}+(}'}/LtY"$].9⦅%{_a݊]hk5'SN{<_ t jM{-4%TńtY۟R6#v\喊x:'HO3^&0::m,L%3:qVE t]~Iv6Wٯ) |ʸ2]G4(6w‹$"AEv m[D;Vh[}چN|3HS:KtxU'D;77;_"e?Yqxl+ pHYs%%IR$@IDATxy/IU/Xwޛ^hV@,ODyO*:D>ÉA322C&eu iAzA{ow}SU*3+s'nrd-y/VH` 0` 0f3C` 0` b ` 0` lX؂AF` 0` `` 0` -` [0"` 0` ,`` 0` 0 `` ]` 0` 0` 0` 0,l ` 0` 0} 0` 00-dt 0` 00` 0.0` 00` 0[`E0` 0X>` 0``  2` 0` ` 0` lX؂AF` 0` `` 0` -` [0"` 0` ,`` 0` 0 `` ]` 0` 0` 0` 0,l ` 0` 0} 0` 00-dt 0` 00` 0.0` 00` 0[`E0` 0X>` 0``  2` 0` ` 0` lX؂AF` 0` `` 0` -` [0"` 0` ,`` 0` 0 `` ]` 0` 0` 0` 0,l ` 0` 0c`IWu(׍^jKwP` 0X|}PuAEKE,Yd7, ?lzwO?xs ` 0(?^=3E,bkyafmHkV|ڻ0` 0 ƽ~?Łz1)1-5tQ[:` 0boPEZE x`QC!OJ} 0` 0 B- %(4VOc0{K̂0` 0^ob yfg[,,pO#_ 0` 0J0@7>ŀ 3sm~5` 0X8N~M0a`&~NLx& ` 0F0`&@S^ C0&GZWډ` 0`@@}}*B `fWK )` 0Mulϙ?z*>"4,L O o9Oe1eӃG];Wd!` 0B0su T{mBEŨo@  b`$yf竿^iEH315Qe` 0` &!T{O>.=}jb1Z `@Iو3Z$J8 0` 0H(Ķk/Ђ܋ǁY|#| yj N"p ` 0` 0 P|Dtw/_ofdY XdvzɈ |y)2` 0``t^#~ml&Z|ԄmdP;gP1jsW` 0` gc;^ mZ@b 4D+VzU: K3 <0` 0V{1|Wζ[*Hd^ XZ*PӎH+RgE0` 0V1@(+P*эKLl_ @K9(HR8gG` 0` fMTW=_kO W>af RA:?@` 0 (7ے @ ķ [`v iG+(d 0` c(FoI1|WڳOBLh;5;J{g]0Kn˞|kCݻ^0[ZLƪmsO)sWy** 0` 3^r!XFzui5u`ɜ3&.8u̙#'O|}ݟ|[{礑!e%:1Oѫ%NR'+=ڈm^6-?_FO6ʯ ~nᆗ^q߲o߾ō[7G7}% A2z,6ZY\:c$={Y?|ooS@NL{Zj,{1d*o K=Ow nNQ~?x/˟in_6]@@*w|ȰQL-`1`c15cǎ}o}Ø|Qd{&.2ZDokLOAt²K܁?/0|K_䷽mo馛^q饗>FhhHX˔ CiÊ ~` ,\ɥ cX]E]t7ޏ-o2NRpN-2S_ `߭H[%~⏞E㥻@wڤ7]y4eL qA 0 c1 ғΟ=%և6 SB>T/g [3c_n߿7/<0U@@܁{@` c ͘dJ(6|{~'~=wP}#^l.-АB?ݕ)ߧj>_[^g"gAp9A ` ۺ8 S֪ȏ=ztqZD܍_;M|gOW?zN?3fX-w w``ؖŁbn6$poF '??.mz"1W3eQa6f`bOwnDw6No~IG+[or.01u?Fvj[@,xbJVX@x1[f_}ӛGxm=05M.,ltDO/;??x__3CZebA3Ϸuccuq-p g ȭc9\cD/\p AӇ{cS k`Z1SO9=7:}So?T-"68bx/>uHWP BbP}: u}N߿e/@/^h/|o#~"pLOA?czQ]s3>= @Q@'ESؗ8kwN>w?c?[o/϶N5tR $;2Qjj,?` ]N=t=府vgg76꣬ c( \}5~])bOo I6.*125wh;M8@ >_ hvr2v,g~B` 0,B3noc&ٝ 6@ͥ7_7ɟɟo<7'(}5;IA^zW T.o8??ַ~) DDͩ9&o8,v & ~̊5U|;;;8 ٕJ\#-e{` 01 WMNuZAlȵp{ "??//,} 'E%cΘE1fYxzTC(h@Ewߤ;oS TPP q#~J݂` e JXx;YiFȅZ87#2d{׻˿kH Z8iUӞ@Gz1?76|_g\O ό")\#?cNx+힝B 0XuK<o%:dN2&WflqbE>Z7=;"Gb1aL6G 3kv8 6/q׾O~;56YΟ?_Gg]_tn4f}8"Տ260`` JY >ƱB.-ink+[n޽Y][ٳgo~GvO&H_$7qاRo[Z1=׾[O ;&InW_/T+(?w\g+Nݕ hgv(AIF2ݟKf`Â% )ZRh 3;.A  @nU҉'yk^wuK8?}041g"Y+` ࣁx "QPР+9a&{?5V.!AэY h 0- Vv5άcqdlg ZsG L?Wn喟@w?3ju3VM+=uX_Θ O|xM7:#A3g; w`jܙ\H\6 kQ8fa@2ƭ MF-A̓ե^W Lgh@H?|z@vSZ dYm`=٠@\# @? wo4r MtرcՑ#GǏo_Ou࿂s_ >B\gs``^#l\/fJ,fWbI>\(T^tw~w^Kxf11)2+!~tK0L;DWU7 e Mtɓ3trӘǘ\P_C~`:dfuR>ؖa4<z$D!5Bu!fk!CO\ve 2I@nG@hߵ>m,U^>OU!Lwy+]RPģDS ٘K026Lw X+Bتv`D).5#4-\~f4A?cw @؄1g_Ͼ%/yst(@=z2?qQq ap%N QA1K9 0\`m]LiH)E)5:.ߌ~ M07O_T|_=-9o*S:9gXN~}cELO>izc4zկ~^hG=0s:th{ q9PȉP@!>,et;` d@zma9X\ mShhm~ofg')oP3g2ϘSݹD7)Ԧ}F!6 4-j0$iH˱J?Ϧ/~t{VpyZXY~,h._Nҟ׉}` ls Z?.=AE fЧ&O` h{{ _"#sĉG6*9N!~6 >)qc)]b`b>7\@n#7'MUM61/vq=+$v*/tPC\O~[[%ħw<{׫^^q0]yIs4ҟ&-6$Yc>W&o/T/:V&|bCOM?ʂt c`e1]UgsFfdt8P]uU|Ӈ/xIZ{g+r 60=xLpE9%FV_2ݡ.J*p$3R)ѝU Bs*sΈj$!E#wa%ϪWW_}uEN_׼R H(b4WΚEs J,ُyD{]etD}xSz'>yxnE?fp{iQM~_1,oMΧ|Z>wН\ Gvk߷/@ ut_Oy{~tO_4S?ȊN0B; Hm?cpmLva3PJR"Ȁ+%m 5ZcyS ;RTl_xp 2k']ey)6dٹ}{Ģ'-LE51,-SeK_0+?"O;@dC׼o~ʗJN[+:i)݉FS'i2)YpjNIfq` 0 # ɱY{+5!|g?5,p*-Ljmz4tz:bxk{n L1ŤyrQm'rIJhj'~b}ϚQ$D{L;cڧJp˓ FSГ}}D  m+r VoXk_%`o&%K͚]9o%:sȰ!ɾC ԑI;Q8k+EP%e]vW(`16F.ŵK)Z4cy> ~gh;5OmRJlRI;ut7|t?ކd&1mO2Sf#m` 0鵅Xo-3$ ͼg} eRYe?Zh)*{MJo)FĜ-m@/)]B?(Nzֳ'=fBDv\Z$lv"F iC)_|Y}k| bX 4jx /6-^9?i4"PY@Ϛ|]wyWu*h.y_{u9gr音\j; 騲Hc϶#!mq>2_Cal#Yf0VQ٪8?0lvvk)L>VK&~?3?Hn?(Ӣq =" _MO<%/yK}T]Ť@,H\[}* [c;0"p QkN``M˸J}}|Lp lU "ǃ}h?&f֭o|ti0l ֯X=aibV' i@N `:$… -IHsHM 0>cu/81?Ep60we%z԰(-m54*Z``\leՇeA6nO!uJ dN3O?H1֔t>o_x=~DmܪSvUm _0+DƮf%:OlhVS j/夭yO u`g?x[GkYn *6ę?v(00 L@x_{H#CaHFr /h=: -LI-oOGwbJ7匼&vݙJ;bie[oJ^cr<-hR8ȋ^k|Yz\}{[-B;$&I `@ŀ7PnpZ6;ܡr||G)o.'e+%S\t*ٳgWcyݿ4}]COHSJ1nJjXٳz3fv_M G+4XG']wuO#1V>92I|l[!O/%*ʧl3@1\ ƄI^0Ku #XstZ>TRҁ?>J~lFuX3 Si^lɗ/l_e;f,͐*? D<8WL-^?ŀGݡؒn0kCVIE@?4dc@cvs{$Oy7<N8QJy&{$F}eg1bz>g')'lHxH4W_gqvKA/nsKݶlM=];Kw>?UmIA0/x@Uv%QͨasET(֢+4^yz~__kj)I' tS{i՗+_茌h6l=="ws#y_1\=}ɓ9n0B3"z4dhUت|̮<هъ lI꣛hg:ҫXbSByeɆG9!$1MP>ɐQejRt4ԩSk^ߌ'>#GM NȞ 0vQ+N\*"Iϙ"Fm|EP` :5ChY(]#KM+kؠS+%bhʩs-dbɦ?&> V;^o6Ok\ubFa>h0Y:]k 4H{t''kS;_)>r[20B>O UbD>5Fyq^´_!.?v^CԟC$C?z c|s.a>S sWO㾤gEVe-%_x EX}pbSɶr (~ߍpcQ*cošv~GfObΉ PbN͍jML!E ~i:O.KZvQ 祑X?Y'* ?u#wrmsx+"!"wA}lR1N* u.:a0֖u>O]S\ KrsގGčj +NO%{4|^reF? =S'= WZ&f!/.ĈU5v=iOKnܻIaw€;`6fGFb~`1Rs2ym2ph@d}e3pRrmֱF)_ESI G )/XsKLNĮ?-rV_i)J g<㩞F1^}HQ'HmbfCR,lDDkUsfGE0ԬIaRc+(U–>XѬvlk C1~R^CT@OR<h'#bdp`FsԸ>v2;$[b;F&}xom/Pb1Ӆ7pC(̟d`hKHA8 ơQ̻ &2R >$!M ڧ]YUe8>l oЖ(=Yu϶<ۢcc'6ka۷UR/V 0@a$7A_9 T%|_%~%.uc2XbaZSvm1__!(&W4Oh%A|')=c$IK2.?eWADj:sg+~Q6;Ȗ!_Px}!J +"U^DW%*lBRi! ٟquqgY. b%[Ij-=D<B>K 4_ݣx kJ]4Kmm S')foHŢq\A{%%ef]0yK:6j}ބl]a*<ⴛٳg+؁cz|dĭ}[ "UsX~m;ڼ=z61mzsc!ҩ`( 'BS3_8*hb-d[pg_5۲\r0-F-_c~hS;qE lߞLo@!5 XlQ+rltڧr]Z/I\> C>IGV_ ޗW٫6ΉTEB=9E2fs FZ alvҫq~C 0@ pйT ]#H`}P~1։b]湗4fw6z(r4z*R?rwMpT#iٕY&4RYb .ˮԑ\F/H UOcJ@[ö1_/W٨4DB#)WM"|}I4a KY~ Xgu z .GE>!ߦoφ9 m${4vГC|Dnq/OLέE} @&;T^%n^yZ~{QrHom_)cǎU'N?^/2P 2D$ԢY谔N4Y8Ls ٲ^$+6lm_` `zs^ɜNvYF^3YC݋9VOnnmT;{w}{U%\R+W^ye]oCts[ρEi@@sa'?,ws3Jy,]ĤdPff[h̾j”~|, nww%(z|af۝CC~E|@աC#GT'O ~#?{gZZVfp3\F"#νD5/``^c8M]$_6cЧ@7n{_CT&hf/^&}lƦ44AdΟ˫k뮻ywiX)4> -/Pjb10B=s}p"N4 9|zU@*ê/  kFD!ž?dyGU7i5 N`OWYˊnIH$jeef <0j 0_.n?k&S\d> NfY='V$,#sB2(U9`zX=iO3=X]EwޛZ6oe92(p!O 0I Q&] i# q@E-iw.y{XJ{A!֧C1'oT<-fTnQV-_ܬh6`&o5qvN`uS-r4 8uwwQv)RZ(Mlۻvi(oh=`l瘢@-Z)OyJ>CVNBY|uhhQ۶=?趩AC@$ Zj.+Քs`l[=Gb{>G;R}OUW\~eu7WOӫ]cToLZL"_b4Z6Jyb,IcSRм^3mf>|~YCaJI2F۴ Oڃ?X}/W͗'v>L Uc"-Z5R]+IK!;%%>$eZUV6F0XsSpfN86t Sݳ=M^mցkF)C`or!由Ǫsw]=o*+U>st =eL1d žG s4tPoVB5싶W_//RY {؝#ed&4ĥU.1muUe` 0  6ӷ5d)n~vц5J 5{%iv[ӍOSu^Jpz#w.oTs6 pbIbTwe\ sg< MһWt`Jge@IDATkRa8|/߼D_,\GB?_ge ~\s|RP3ڐ"` ,e|84N||ie/L1mcʺіhG bt;I2@kSYw7؇^yM-{^w_.Lkz2XSPo;Rb5%-SYvkAYR|9>TPY#^&G#V;/vړ8r< SYg?[M`} ͞c֟JO4F"z1?}G86k8(0XWk[,paJz:Gĸ~tpn1V4x2XM3I` LNw4[]ͱ~C?\}{uW۽CNEDLq-nU)nj{j,LĨ&eO hMm|CVBfuvM{4lAm?|ڽ(y%" #~G"4bC@U-$?Dk|ܩx= YƓl8V| gQqQvWNw2&Na_cƴ Ne7rkkJ<۾\tiٸp">UϻYCs*jx~x0Cy\ყ)s8kKꛆ.;C?jrɝ1d_nUHQ jl}_Sb6쾘>ĺU_-Gc;+/ӻ⥙x_1/V:cH8gm[.ғS h66dX mKp6PlࠢK`` (qUZ+'k+ܱw+?+JuoB\ǎ"qN#A`ٵmzݤ"8?k6axΝn~wT]w+딈Kߎ 4'.7MO|:+#4*Y8 )>zy@OwomO | THl8:ˈ-}_;B'tmO]d QuDYQ\q6m?"` Q>:KqyTk#yr*dj )$7ytS[8`: NtZ ]f4$jdQ(z d+TFV3!V>샰y1$1wwOL~Nn|G,BP!=6m:2R}dC*'.;NQFd(g r;q,₨rXñpn`ZHlB|Z υ,w9{{ F惕^_ȦmkNa|$)0uQpr]aä9g̍类ꪡ|WC< |UitoDʼ')M5jd5>,Mor uAԩSTE u'˱F bG]n(- HU!P_-A @HR )˰lb񂳛5p7P ֚M:hl܈,j\qF .fZ[Ocs繑=e: cA@b ٷΕ=̳g֯?K/ik߲Ez sJldR) 5j FVjһ~3Q9'?<_/XeRXZѬAVU 5 s1)[EWHg<3бmDmX=P` Lb`簜Lgr ,7Թn;]Xu7YE$4[,9i6ߖ/2/T l@ nѓ4G'qmnORMݸ'4Did7jOv) )Mm__*d'D/ٶ0% XHu֟Oߵn}@p|h+$4208)ж:H~DG=)Sl &ä˝ 5t_ ns3þ\(s'{K>Ͻ = `q캍MJ3e;dUzӀ_җŖE> *-Msj0~V^;v>\OVo|~]]>{lTJ(}rYEVo-}+b.:.Ҳs 绫T)\9w܉Tle_uCKGLq>SuZ~?.D>mSr Ar p@k3杽{V8Xꪫ뮯jgǛӺM'1#cn<̯ۿUO|>!:Uh@d`#'ٿ=/elɓ1p=G3Tgϝip9>vT}2Sat tMA3G YYj1V~ͥ_YZ(7۶  cdac1WaX4E 35N5wڜK x˅7 =T*<`ԧ=g?u_GkQ~ʲ"|W_]]~q??k@dF~b,0{-12,>avm>9sB?53V?]ĉ՗ѦG=P)^ۼ w:NҔd(GVZ0 Z0PmoAFAvqk)K5<ޠ\6?dc1&{\h:9@u}zC/ R}W݋Ň c/SCeF/XU\S>*,$v1kyv>>YB 0 ~7$ɠI>"$`whbӼ^:S8њKL"Yρ1OzG>1D|9vga7sUX'=0*)TESв}ڼS{Zz pfzl!O؝xש{7/l`({=`F%.#o%kzy(0j20漯S22@OIP4vjS-X3w*n2WNX>bEuؒh:o *y䒝J=iXJ$&c1€c0HR&ů7-xLtgJ4q%'yu|W^WT/!sIs|+ͯ$%D}DIFV7*,ֹf&h̶ky!()D:Nք`v `̥n @ʀ0͉O'>1ҟ@ܾ^ '30a]_9~.̈96fL[? 064&?91SvҬy%DR1iNq 9Fk(1O(دZɱjaMnyi\SoVH%2yϟUNY?ͷ+B=iцT X{J% 풭Vβca@_~}㙏 ]790'ڽu"a ?;D&Q aB#^Q koc>}PIYe` 0$s_>$Ь[jRhN,nڼK=W+);C&(;ZjXc+ZGs4G_kC+}4WRHno|ℌꪜ B0TgC,,?6jЧw+-HSƨ dU*tK$p#mVJ\tX9uPZrÊ漶 <. ~,ڇ59πtGUJVvg(n%yj *|e*iX%[vF_n>분CoW+XzAo Br 75hc̶I֏?ba'n#~[#UW2Z @YFA" *UpFZ &ŒCR0 r5jk9巯Jc.<aqg  B;dijx:ĭ.Z QF﷝rctXӫL^ p| HkWy-W\7({aĉc?V=' 箱@Q޹}r_pI $'MME:2cx PJ4n?VgOD%_NJ#jZF^*l,u]&A[_ zIdcPlJVȕ]< 4ΐF<'sAB\ή*R))3jFE)>a/`KC-sF7C \sb\KIv+Yj;[-ÄȐZN 0XqRP;!EF")S?eka&'k F-8 y7*9Nքrܶ :~,Y!N~ڽpn5Z9u( BuX ՑHM,@ט`)#g,[tۄ_4TN 0@@%򅹜T52m#EPjk9Oضib(FVuɮ<-Stq fA5qsv_0s]m_ /L4lMV¶4@iF%x/+X>hڂn[T/%#>yC>#OߴHqj` U6_VZ$l "JxӾWt:,(v[i.Oz) 6{bK865]v~s(q91j2 RZ/Z'kxj 4濮9avr@S \4A|+^ÆP 9%y}p|8!7u]"K10 $\I|ܹ)>P,ΚS=7GL 7JIF95jmݘvqRup^[ەNDz.:-?#Be@(1g_-3wax%/JY\F9x'9p}|gM"mξd\s"!gjLmn@L,p[|˯pkwyGx + O8Y]uU{uEMBT[Fd B?90ytE҉'"-K׾5j֥=9SM*\=HP̅"yPQ)) &6+Y)"`_rjNL^#@` l'nNc0'\(|i@)k6b@@t2)DeJ=OL֘f)0wJT`5Or۲U d]8~J/-^"Xh ht?38MW!Z }%t͏dl[Jڍx2-3d;iP1t>)2QiE$n uQ` a@z-\NM>ĉm^h}|&BMm 5&}I(r&oQHXK'e:47=+F3~x WA`_,HqƸR%Jm!!0jr ` v?ם#@J{/3Uu3\qs Ժڍ,ڒ|Ы'6+mo (zQ~iL/&lxy} b#U5Ƙ,j4D HnbdBL@Mfa3WM4@t!2Y `D.Db+ $e:.SPi1,&)ܗ^p Y79{zǪk2ћ6pH&V XR;ȖٳgB%:p8lZzdmWNH >F ^'-!hQX)6KϦimv&8 lq:E(= L 3ѤlojхK 47sIq͉R-09P4 *F &umYѯ zE L 4߈9&: `SVs! K?V:GaF(xim9ʱq0TC濠 4#+b=W5rPD_xsھ]W<K?aK =q1_4XqCɶE?ҥ;zTt=wiT@1)$2R:X F &Ɉ`׎#8Ns` /t~L#M>Qrw5\}Y,]C/J>`^`]ϴƪ[BE0V7Kd+%J-SD-7p,dζWbwǎ-li^@go [dr"Mvx6cHG0)DWKB',p&h~X&8XLvss~-l%xBa4葢MhS*_+_@l^oDҠuP18`` xp4]1FBVi[83f=Ū[B]FWKyMݶD$ddG)SU_o-3goj2VCPs >w\H ^N%颸,0v0MɶuB䳥Y#A;w.n٦G$׈t qEc:`9׉hS!BJc@J6]mqP` 0P3]'KR=H hђGkW|6cHǪ=1ӡbT<xmD+UD@qdi^;M,ٶMU9L.D11ߋ7PZ#!= #$ʹYxzӪ)dspD*79զPPW:-S/H cr͕,Ƹ"vI2Q?GzGB3ebT0?n4fblaT!`XMS3OT({/+7-9 C'[+ ,fi,o\#jХ>s/'urW3κwGazuo`f (@{"eSDEH˜7"8M3d"BqF RK6n7!l׍@ZT'D%:ڗQRpm B~Tp" {vDב5IIVFfSLe*PVv7d[Y=G)$U_y"EW/ۜnbXb^YV "sP?V:=X́*c5 NK€ M TƦaF3e[50Y`Rʧԙv:5i\H&,,){ˠfE1 ٵ)."ž"Cdm EO6 ն %ޔD!~U싻m=8H|pRx('ڬvgU Z6u 6+P!:+5y  qc0-`l(0`g?6̜%;m9>g0fzT5i:yB> 0EVP01s" d:@ںi8~U 4vyDPN]o\,$Y&{a(AH:%#MCmͰ!`I6{}p7XYcHs:F}Jvx?^ 3 9N`sX!{gu\+G{ Ƃ2FDi$," cZ U[ESY˿ٺ2nu`(ՇׅFV@$S[],1%B;PT$o#jJmÚ~F*kyHM(յᦐx[*8?INZ^2A թC .@]2E(,~8` dzN2gmn;8[_ ѧDUdXѬ[ -D}C"@)S甜B䛪@7 } W a`2X`&nSS}ҥȽ=PgvjXN!?}6ZClG\`n #TkCVs}1ti ` H Յd8kqdd}FW"@qjAﻥͺPXSh;/N"m8۰6,P:'&Dn`c\om+k ,xyj?[yfł`o{:ml~a!0X<kLr1Newr &<@UϜ2_w8KQBS>F20Vz=r>XSc`:tp(@eP[Y_ZI\|bI {}:dՃAbAYq%;I0 q|́4 d '%%y0^@<U#V/XQ<@5U[kdvp@z_RLϛS߭[29Z PjՃAEgD"8r+_zlykcͩ1$`0 8_+:>5Ża @A"DUHW$@ՙa"@T7rfRt1Eu#oЗ*pLShNS܁bq]T 3ubQaj`1 OVt-Bwk‹]S`+mԬ+R -Qi̶yܬfPR10fW[!Q= jaɹNy^T5Y /.tq2D.RĂr R p2CF C0D_C}n [ NH߳Pe`1jaCo+Ke2͖LȄ[ ]5) TCr"bf9JkpjiYjZ!196j~I$CD}J] `ITF K^*hjs'v/ZԜqT:9ͩyu7]s$B4*ycf Q2DA,nW1㖏VWt3\:o] ]]ypAfleٝԡtzR @)A~j}M^Kua.ۮ1k LBI=%7"T*duMUA'^1q5]cuztEҌw#1&64gТcZ qFu-WT*DB.|2BQU3mi+ۅr&* Hg`qdUփ:( hSϭK8\dS3@ k%iK2P$aKѵ. Dll%#IqG&v} B̷Zy2zBN/\,ZC @h4y"jE'4Mԝ6YٶFld(A5vmhC&X TR&IJ9BjI5swkh9|%v0 12%"su}/O\ ˦&?O+rXܔE'mG8ƄFNȩzG ^@#1g+PU*%XF݉ӆڨE8:Kt69flVom O_p.^?Y@Yc8B/S$7 49&E``(q>N/"HC2&Y~"fzǨČh'IE|u04gf8rr(4 r++)|jdeGӚb`ʀё4-0{< +`u4up Q`CT5ITJL"ά_M/œc0=-?X:HT[r$LCO9D3S h1SG2,I+˰*|"%#qsQ"Rj;H x ],Җcx @C/I$'$pqb=@T] 6+]dZٶYY2Lok4 ΠBV*+H͖/nmԙ2y_"mbef4^hx(0fa`sa΁Kh#ŁN|Pud=04.9p1!Ѵ$2oV"@/"KEOnMͽb6FgR%6bj|]]hنg-4q5~|O b<S0״"  Cm7Ɯv@uߓJKf"nTwEaP:(hlL߆ֵX3||Dѭu&D-jؕ:) 0fYFՃ|g*b mVڇ $'anoPi+v̮cvviMao#k`޵%9~6#F@X3B i> 2`,xf̴۸nw}}9}/YYjuλ+3.ODFTU^V]`Q`-6?9PpM#az*1cY)&8[q_ q"hM e@ mɭ8еIN=0AJF V J%.N15p 202bEUi?}L8h]X[JW] 'tK_"+lRKTDI];vf3=^N8@iHzX */g&z(z@)RBAE3hȯɴ{x "gjFw]'Epi)ҶlH)5~(tc- TzP #^ ӹ ^`ڹAxum%zKi ۀmW<.#sb Jz.ţ2υk&P\ Q:dd@͠U,}j)RNXpyNфEfQYL ?vҤ1r pTΆ qxBa0Lj2'G9>`Ps ٧)Ŋ3 8E& Sy[;h PBb77Գ.0v(tx{v]^\i4#L$:f5 kdw4 Ѐa1‘ @|?(#)0&`YV ,"LHanbq.pm;U-*}2î@dc^%Uˆ~{A3B/U;Q vuD2 pI03=7 I0(A/nE)@T%:^ $`+I: *Y(,|>4`ArUDyFBy6D6"oΙO+ bZ<)+67 0 ώ◚rFmEt#E_l Ԉ^\Q0Ou2@s0?YgP}snME֖:jr&g \"@ȜL`";PN a5"t0VD G6@Ri վu]1z@/?8 ) T~)\@"/dI19!/d͘~]d6[gf޻w]wΦ hak>BM|oߝ޶Cxb=>Oڛ\X;WKB ch*O-$<b$UQ*/@畔@)vT[,Xk7q|LnQ}_a;cHī ܲaErP( y(N7 ^w9Y4 (K /3u9n; `/E$o WɿAE$ A_u$N$0n_d];mGL߁B 条l}LA/b */ߺoQ2ArPɈX= E`FU'{(^֤͜w~١]P<.Ćv&.H4L-3uĭ@2'WQ=K"S0%RBP5{ xVʊp(1ܾG4;ƄX l hSYsu6)uf> ܚvnsc:cs~me/g?#_yvO=_5ˑv$z] 3  "HyyTFU/ŕgw<R~  RfT Kw^n+atz {j##R=R1,V'TUa<8awRFG@|(*Tfa a`%7s1 Ʌ'3|DJ(җwEr@~u6jT쐀C7N毞u?g_jxVJ)b@Yk7 *1d s,PaRUD[7.c-Bu&7T *JȲ$ecg;F9[VVD 41@thvw6&|7> 踯H~ōbLvnƝšz qwk^57x!x52s{ ;T^ ,zN=W5d bթXi ډTq l {-wd,DNR~i;?~+7(h?ȐT^"ρhP{ɒ-NЯq9'L 'DS? {prU]/@!2u+"CтcI'q}(Cgg7DP=*K{Ks>yݿAY ͽ{\@;/0#+5#ڔ`Φ`.bi1"c(O,?QXDP<_uFYt~L%Au?0—2d"I$Hj@$2A>)I/$0 vpSwtF*anEp4uc1޳e/@NDVD .;0qt2d/j*D^jxaG.cL̯&:@fIN3y$ sg]V0.;z}0-x\ F$@* D $xDAۮ6/>3x ZJOjBU8W vCE `ЧdCcxcodK^*Cn%O@&|#s33ue-HEGٗ9bJw0[ƩxrE뿽 }JCWw.f9YFṔ&;A8Y"5TP|YgEy鈣}unK'w7E3$z{AMDԛpTx屎́1A3`65OM$6i2>A=EJUCUAy'u;+`_7}/=kK+W.y4Ѩ$y41o H`?$3.3g0ί9m[|27֡@)p~}+ȜqELc? XVo#~P *\ Ae$&SCcJŊQ^@u2_x~88a~|Ger@x МMJKF7 5BzV$bR#,8_2|pq!8.0GF06 A (X?zC}0C-p.O|o#w'%-[ DA$p eXV2axUfXoaNA)/gȰTw~^OVC7r?Bz?pAο7hmY/Dbn2ZQR"5{ ڦy0 bpN)'6 M*Zh/lN 9i-NVџb:;0 EDBm!Hs`{1\kjC 8H*Caj%~[k6gam9^g 4?> BRP`k'\1Ezgɬrȸsuo&fҎ/Dˮ|~NA:Ą|൹J3p:l0F*kfB"j+qw*jٰIGނw.go+YUk1 Tv=^Nna6"Px%T-.6b-PGU87^b'B# v!anM8A5АouqhMpsɓՕE2j)NGQ5r7r -1BLq,, D*r b뽣2lP0{gw6{oA܀ަ{덮oſ aw,еÈ=ڇkAj;P}TE9dDt,C)kGOQ}>st ^\DUY 3^dZA$ezd4 '!j|3APTT# 39{D%F ~.f[oĊx!z_HQ(g<+0urii*g L[&yy\=AEE@<{4uP(4p<1a;EWÉ/8q8N N!_}RĒ+`$7)B I͊m=팑 9g~'덯e R:Q"@+=&t_)2)JE0'ss"ܨJ8G{WЌ%\Xuޖh3s.tKW]^ UȆ(~ďtFzBE}#\!$`V͊ pU?FЊ1mF 5oͣp@/uス1Oр{fA'<߲-wc;r8Ii+iQsDz Zi߫OBċ@OCg9:³:E!P ۷/}:T3!t#)%\)DF?C4+APYyQ&)LPMpv7/vppta-6Jh~i\g*Y9g{T,'p!!5PgH$=GoH_g0CrұWD oOkR`nʥȽvIBλ{uY~6|V]%*b:!(7 Ȩ;xC"Q_}gpjNN](-o6* 4 L1`رXrPq}tu+9eXg0ٽ2-DAk\F Cq љ %hw68~{`n@1Ý^~A},'P+ݕ">W9rvw ;6$IJ#Z:fuo%W,#jJ &H%22k} ;#*l`?'=^!M'u01Uє|%-&l`$"@l "D#V*`4>-r0_/& ҋ*I˶ep!NwĒߛ/[Wk?K`MXrAś3QȔ>)& /!D*5~$\3.3FXZ+ 2,_"eľpς/pikt!jJ6rh5LsPߗE5DY!,$`&7tǦ2`?fpcX1۽)Ho?7q'RXŊf#3Y`RLn F kXT"_Q2m~~uB@nי mOqZkh0ò2U.W ~/wv?u|Ś|_/s+ T(gh0G~DՈ( e(tz{፲'jQO]%9~BZL"~T %@+SWT*Ӟ}ol}aG =@ zZ$I3cȾq.CEXTuqx᫺1# = xiă km2-;sJB?n_x}vt&.2^ y|]d` ii&{: [P xIbIJ Q' 0>Sȧy<tܽ,̻n5'Z]7҂ ~=7C[Utf>PJ=[#8(ā qSV[@8P*A2q+z<E,]wWbe]^Y DB<{ui^zKA~_ أ+oG.n:\hB֠M[bERZ2l|"^ ]2ȹP',1bQ-*y3`k!)mHiIp?OV`ovmݮ{+/l1e8CE-/6-1KH&ി g/_m6I泷J4>HTԱ!CбoX!0"L676YJ,w0Ԃ;`Nz pFgU>R<¢}gfd^^l(|:8$J/>J =0YN&OmdoЊ|r`䐅{kT|4\@'@L! 7 梍<9Qc'P/s,@۷ⓞJٲmJ7hž΢}m]3I T8> #9AW b_!&~r,I6Yj>ҙ7G+(.e[$hk|*˕kmf))ħ^x"8|N`F y;16p$.]a=Lbept㡎tA=ZT?JG'bC=U#A6m6 7W_c62Mo{$tUh~>Y^;2iT,B?aox"@ft/ý.EPQ۪1xalXrcD?c +ű[)6 ѽn{wL*Z'NuX]#+n%OҢ/[1EFtr2B )F"""vm^_-*GzH53 &lP'<=hDa@&],0 OC֘ Jn7e~i?u%h׷sBښTh&eaT@<8z#87JC-Y;Dy!,K1m{G,>uz$/5>Pں'ڼclF h@xdž(ui࢘B٘7KC"Df>YBR#?dT@?pGw<@cr^hhl>"ѵ7(QZu {*" H(ɴ?(=cwha2 iF|@c!DXII B!0  1"7Wk""8 ށGSm- sUNVf ;a Fl0~‡׺l ՘"E2^<Ԋ$#&T-\q |@C~dqowv;=T#֕b\߆}dWł&Cɦ{w0J=x2b1 T/n`pG<^ezPSP`&@]`n`7 =جՃstrH{h^~97E)P(jطP||cC8]p;5$e6n|([+Ǥ@,c^:SxXНdgu@JtfQ5d8)(b^&¯8 gn}珺|N0AC2[.t-yxa/;omK}7!L3lO\uY&ge 0A~A9C>b;V k+P>`qǷhאr/c0qޱ|x&"r :x X@[imQu`p@ w4ʆ2I፺}L/t<im}1rdWm)@ڃ i=ktR.˨;¸pVl4 R%WF50zqʔ-)hJ g 4Xm 3T?JE<$b_3]W{+I0ɯD:}g:[p,8as%W /ˉ Xoku:Wu)Cn_p!`]2D=%/3ڌaQ*;pZ-ǀfR inA%k^ȭ ub*Opc!ccn n2γ"s,OM_`"Q4ʃ^$iXCdۜh$x0- f#.6^*g]s7[cLScX>J,J5<Qb}gHoЊވD FPi qLY?&!'em{Ǔ1X0GhډqFͣ9|۳܉ ‹n=/[E|zS+㴄zaj3SD[84`_@{Uy  3BB}4 KQmm#ڣjC{;t6TF("%->eK(oS,zb/S DE?y~l[奬;zåoY€"|Fs~_ꕔ=F@ a@_OBB)GPV0H@מ_J|+Y ѯυ00th?GP q]HƛI }mx[)ڸD= /m"8f2Vɱrtq@b]V,_`ĉ24mwƆ_|Qe#v{Hu+e/(.[ {Gd^0vT"ztɰv&q"-|#E+&IyÛOo}nLX!Lf9;bL{E 8ᶡ#dؤY:zň ,"勹>xf[ˎYi9ܪJ10)LIP~r16 -X,mtiE3E&&?8AuC}hwDatM/F {ex1_u§ bCB"! eZG2Dݯ:\=L(HΞ#iO1?2 ~ R@BH2T >L)π Q/2sh'*qs\󕖍pxW'\&|iOo$:K$?O2"p78+EAȶ r茯A_+\0Fwmn5_ AYOm۾ѡ2Έ/o uYO|I;M͞q`?"½ *R1Dߑ%2柋ʺhJdVihzk=䎏4Cg ӾQi52@* 2l9h6"rkՎ,.Kcuh(Ps|-gsV;;12k#SxZ~I#qʱLC898-j7-AÈ̍@lHQ#QzN[L:d H;`YӅ>NȘ n/.D"Wl]q;wCZХw@ޢfe/HA(_?~TUDR@*1ɏa !9E Mէ)er²b!X; `f3&*)A3v==< 3BO a@֔MYh;5t":Oyf{3T@@ k~)8?/|`sFזBiC'4B~iROeC St?HҡЛ\W_PbI }%4Q=]1i)#:|j(ՊZy4X cPr/X98@u c44" @Wch$㓃Seʠr95X9T[9[гx@e(#jBKas/ yU'c&{}$D )Jpvاv"خ6eq[h} 4,YU*4S$^ˆrzNjdRM 03H6Emrat0L\ =F 4? zs5 cbhOB r|ÆH]V? a=6poRGLC؆92vMp6ѫe rzzRbe#-`-*{ma DG҈7C ,6#!m8aՌ.\0,c 1^5Y0"`^%h14*L`H/b5,?;<u\& /n۳m$9yc =~L {Pp4@XS4m!19hS|NBH'k'*V9#jmv1p@ڀ20?#ia}`T vY& p"5\XtQau+=kRN DU9,XM&p!tp%P/ ̘$G tہ64 jఏ.Ki|y! $8ID@]o PNЁƤoK8 @3u б[Y.D OqrVS VrL tx[8/b\T3Yh谀U V/1S_؝MKx`YڭpFy#X.Ce~o*Xv1_w?p|[ogj5TsZs#"eH5 ʗKg_v~tŧ]}uX-/Lny὾vk"yyLD/gS)389%jʰDOX{T(/1#\:BG-ri`p-W`+1vyokw"Ka2bcyRIM0 + 2C;t"p㦏}4O;tpFTZO TR6?Ӈp`F{ H`e~m.i?,l`1RksyMbWS!y׿7+I^[x!^$`t6׻d[ؼ^]oѷ7͝tBʯ\}mvHRaF"ZKLO ̽Ln;%^umмyK|U"W)5@IDATEºc N0chUδMў7iTF)O~ RS}N$fE. ssL\^FP`( 閲p>!'C|=Wh]je]C9~%|}$ՊE.10 = 9$}kz9<'/~o_ٮbcO<;y+ўV\놤g7pLp*!O XL1Gp qs5~NOGYx18 /B(fy<'TRQl`/k<07}ohW,b=, c0Q-%ŊF@-c^#!9Nvu|XƍK i݃|Swxy3Nm?Ert[L|lVO<̳iwgGK'm W,q{w80~1C-&IGމr#wT&FB2N[|F#4 5`Í|{m™R8(h6 \wDTQa/U Ţ=qډ{#Qcmอ0%Y_/vz@vY|z_&_/wkзT '=+{(JkND= ÕHa/~"l\E–u)THRJ\l]˷Dy1+!bxeHcQ939:2 ' u .MMMuc :#3?K{uf|U]1awѬre-x/y_b\6!N(3OŸ fePR̊ ]%2@W`#@5(H}`up`%w{]qDz͋z|q_\މ #E ^0 k8KғϢ+qh5xe.O/3nsb؆ *r0@JMS'2cb㋀l{|mk0 :_s?fR  #EB l/>+KP,~zG"B N}0ww}yl)m$aNqYGAbp:J[iLi0FAbp:JYj;?gG+%'%sQ^#w55mnwԳ"TJ~1sP`m~ CG7F"wpy_ /s87ߜ~}MLCnZ 5Zo'61 mα :#_g^vWn:ٯ7C*+:E`< nlzgZ|?t+l2y^^I ]y48XWDyPC=w٦Qy `ڭ>МacU@Cօ}|3);6VE= [>N#s>Uy!2 Hx6LLF)$@ÿҿ@*zH"q {(mn/XD# _vBed>(У_4:o]0>[ߟSrՆ(T S'oJ"ϸ#_8+3D}ݏKŝ*b9 E{L Eӿx7ՠ$2df-y_X$=1*|Z{~ZUgUהkYƼسg큊0bMnlaqOnJ<7@C|EM \|S/5[9;;:{ tczgtm1$.M̛3N"0WIՈ\y ;7A%kp?c}Ġ~T86 CZ$oFV̸+W'gGXfGZ:[P*;O^q"ݥ{,$LKD}Jgcx^/ 'z/_v~f#nȝyyIǁ;ZOW̙ &5G,~iן%<<;7ϙ]x##̿I~}G!iwv/LlCq򜺡$`P{w}JXKFYfpl6 /^Ȅ&VYnڒl?ew$^PPhwwl/uE ?E~nS"s"?魍~cɵykm֟8KZywy$|T6/omsG8<R4d,#s6'd~.BPag\qF30+8Xmbb=݀{e_7KswMwjc~՗5Um]G#@s^YoP @H;g4ϻo PWv5A~ӽxUDj~˪C .+bG74_9\.dJJ "^TZ98"#XT鮪d&=I d ڃ[@~$vi^L(]Kw#.93I^N GO9&5;qmm?t?l=uw$Ӎ@Ozc~|Ћ|ynwno|O-b8s!ћ[W7©F@N5n숌υ":4ua,&H@2~xe{f?eJ`ta6xY ;Q`+ML*Xw&$q[18Amn7vեLമax {ɌO͝pׯnWw=7wX7`w,Tv숚@0yUE4_S nfL3KbZMc:krNjR/dmO@/(|) Jx]Y(@GkG,ے c ]@;s6|4р/3㊼8c|*s| {:" 3TL4a7 77^ʌ 1$/,)A(o1ќ9ū'di'Wؒy[;-t{4< 1ZOkIoӇ[_c@p-esCuG ̝ٚ;1=$Dp dA$i-O  xvZ gx r@Z K@."^.@Z;5lHk.)JVup5A;_Ӑg7ަkǚb<zysuX8H#@_P?wD ^g7A/2uStG('۫Y] WЪ9 \TZhmЃCu (?_^%h ^S@+{D,C\yn<Άo*57x~gFT)ejʊ+l@q+Lu"*#ʨ;<(ԯǴMҭŸ^G#eَ@TeU?n?@^|xp_?,fs*y)=F KCӚ{j?3y톱[IC"C$*.Y ơ^ 5GZdkXGO(vD 20h ׃:gNPv *Ė-q6?yhP`&/rJT ri^8> HKkam_[G\l˿92ٞR_8L.>y%$` Py{r-uH µ êSqHfeYUC5Rc3*s"'iv mm֮A]tsu￳~cWKmmRrخE."2o1Ĺ'Wa3!>`2 9-Bثqlb}i$e Kz0Ϩjc,g\4ݡ;75ݹuS'q=,̧3x4f0$__7tL/ pk?|=v{?rڵ.ﰟ00Z>h4q3) 1Tv ncG٨x  7ȱ/2z/ _jX*tEZ>8o,P%o\/h<37V@٘-yAh - h4UE@;R!e\[x<>ihtlFλƵ帵d:_FYuCl\{,nt|qk~żeޱ lLh4IDGxmIdd886P0/vY4l-ânF@#`y\m#%<8u̖+IjAih] N|cZ؏zghy x`\sVF@#Sz˙ܫ2η(FFh&#p̏V/v0L=YkOIsU!@]M:0x\B`Ai;$A ^s @ceF@#'0YQ? )e);Oh@P\ajf:Ur09\Ji4>#0W6H Gol@͖ +=U1p|9Do%^.KctrX;Ue6qDvk~E?o=H;R[ߕ~>:]нF@#@޸مKngl iش <^t^w/, 0[ *BJ:q+j63+XuU!+#0yqX<p Zm}|"jO@OvZhdh43F=s?vw;3/%8>Mws:dߺ~G҂F@?Rc@2?:y2F19G6kr$[{h˓ Nh{m ӫʧ Z\V0"sZDoҶ'mSӘ޽_xcκom{tӽ|9i:p8$L 8{\0Dw8?“|MQϲfz쓾pkdh\|pٓI/H_1k7^#Bz~7r??u~w~s۽Ʈ-05st!fр8ltr[v8n* 5\Q<~D{Kz1N[%@ĆCrB%pm72t{{ z%KuBwPܦ?a,F@#8MH" t$m?7kdyAϐҷ@(F7-C16o7 &My]IW)IDՆ24NЦ\!Xy+qcƌGk-}#EzX+|"キ.ʾ Wz|Kђ5aEO;Cܦn;om<:iQ5;.18?>/u,VRaF@3o^ /LBF*~l0h_/yG[<B&uZE5AGࠟ!i8,L0Fq|Ăkl4"ˏG8*#0v2~-AS ' AʿHh/чCbhD`7#)n>3>ǟ720;ڹZ+ FjC6™yӅ_vZp!|4.{z{F@#Pyuq_58#e\w5fM\/ 4ڕOc-}E vqƫvkih4+@c;;[7/9CCn4߃/LZ6w-X"` cKz#￵ww=j|@z'4.F} #{F@#p0mC{ U@k8q98㐢 WJ*Y>jH!$ LH7PIdh}*h4@Kkb{1tԆC5Lg޳ʬSékx_p>y[?|Wt!(GGk4:" gzh+a - J8)YB;y]̩Hv4|,QH0q,r!#εbWhZͣ/H?Ժ\iA#i|q b%3Ʒb6C ;v ƨE"`59D`\;o^ʷ+ޓch E*h:x݃2۟GڰxMHT[ǑkY}8r'Z.4kn/}sB<q i bMhj4'?OBW7lbh3'ϛkX"ZS`Vp-Ha۴mQwvmmJ|ζᏓu~6;CqXT%4i4YOv7dj.zcoM9'AT֙g ɆK0#%zL"2AH7+j4VfdYYQg[C6MXpDZ&mѓCH~ #{F@#0.oo|kZ~sv/_r% ek1-1GmNL p`M"}O ^:=\2x{ZxAL.i ڰӺSΒr5G?ׯtl?~~=gvkfjU#D@¡@oE :}+L=MuL;|Z)@x`Vlh4{l'fyi[otݝe[eDA Ͼ~;v#g SF# m}KK/w}ȄI@|g(F@#hy ~m@/R SQiD@8<`,ˆ l@/%.c|9Th;^9js Up [* f]^/~SY/|q.[-/SF`5o5-5x*~):+EgoT<%F`e'/noٞ5ކ߂-my@ӗg%dbd*h4#]Dݩl<%G쏏&gY[xwpSڻm6F$m%9qs@×BH 5F9h n@m~ IhGmD4\>E =6f+ Vwo9УN0"uw  %5@_ yZd`!{ N@ۨt :hw(%?1L/RZ!1x8NS 倭׳ pzp?Y ;{[477}ɃvHADwސ_n/.dNIK,2*h7n*b 'vwG/F`͉5՛ƫ6JoG/`n0 AËZp>PH42r%͵͉5Ot'M7RE ar-Zr68ْL̃"?#;ya)IɕY䭉oPy{(Ý`kXqL6L]bb}.?rr@8KlMCU50VW4{ Mz9Sޠv ;/ G |}a/8![mK PXWQ劰ZE0E>9ʡ-H|ʞ\ҵ]Dh5;Gn;حr>Y+| @*h4Fp" LT`Zh ]b~qN#ͥj{J|H6aKDv Z޴Y4k?{D mxKb_ BzrO4:lqCJz/X9.k.X]c5G:Hov&ixks}>ӟo%t9{ '>nunMUʟ#_w~Nt׍?kb^ xGGޟ]`c Ag|?p:ƻ6!UT _3 9O =}ж(qexy`04YFUع3| [ ۹Vs!Xݟb Nz0B (O#[s!W7hFp͚ L#m`_A~#7`EvMcbї/̟P@"?D-gflkH;#bX^DSi'&m, J:]DS-&Oh0پlZ=[]"?!>s{AwcVn݃T@/Мr ,3)TD朖[k[#ysZnaڳM/ܶ{hî:Ϋeiٗi-ܵmu7ݟmZema̴1ƴhV!ۣ&X㲣̆a/#%B%jևHӦ'j0C-}nFk8 ;x|= 0m%@@,O'bhy~κg+qxARCp ?>FXXT@Yiܛv pS5z;RH4#҇`PkMk:ܼzN%_p>Ev~53K'p0vWk|~Φκ}d@#phxqB[hwǁ6FӿO.^Ke'+^x%GK!Fnendj?5Ưo;'SIW Gm?֯c0Ҡ|@kN,l^%ڽ8FvrЩ#jIͶ9.#{뽴=n~`)_}fUd" {sM_){K tS*_StAuhS/jy(t:2-X5_77ՒfL}V37i(d?bp!iZVaDJI`f+ Qfڞt J A9p=tZ;uz )Ӭq{5ͿBhJ'o۵1P0Gw>:<%(QUv̿XA(QU_u F b_5Om84-(쿃 !z{Oe7Y7U?4K9.;j_#tpK] 65A9JtXcAȵ| KR 87 | Zvc*790Tprhhy,cm^{hhí1v^u+*乄,9ybKm[E F@0knw͓  ׂAsڢkoA>߂l.E54뺇Z0bk3zC? !Wk朖~kZ}9գ1'(X)_# KGx(>^BGb&t@|o<#~,}ae|?zrQ]jҘ"\̡?bp"yǽpr7wk1t41A2kmG3.Ǻg1]҂NA#Vq褯8'j5^_[h ^#[sgÍ~= mng?0!Cϗ`ЂI%\(RH%$s}%M ,ᠥrjiǺڀcdC"#kET,̓-ʳ,Qpf~n_[?zg}/AMUY8 UEO(QuYφ專ڽ P%A-F0#⊟J/ ȋ;!mԁ1sX6UwH7x@d& s)*G̴J!$gXdK QVuD l<$*\ryuyhꑢ(:kDC+Gnﱊ+{tAM;O׬;L}*,0d@/䉄#Y}JO_1򣆗X?Mzg@ "#c|@G;d=d3,%<! adZ+unqoG5ZiޭÃ4 'XYm ]Yq¨g.lMEjCp,W Vx,/MeK8\sL{ 4R%# SתxV| s1((\K'I/zΦ{m2(oa}, }W~ Ц! ˁ& $>iAE&ߴ_5p[' oTthpkf6rm[wg'ٹ]rZu}EO$g_X"`b!Xt 60zw9sÁ iR(WϠm<(jh e~?O[ węR~y9T?͊n\Rkej7 _4H*քU`%|[D)wiMߺ3iI~y5$Սb5[>+o8BysRLC9>#<,UZR, qi kK?p)ZyÕ<ӓ;O NOxtRt}J݀;LG6D.}",slϘՊw*'Kq+;!;nCHB]^JO#,. +2 ksMfu"u|ˋG_7ͳU.O\0pPexT$?~0t1z,}M;ujT `@IDATay 1)#(P{0uR[gsKhmXƵGQ ajmOݨOϛsG44o,ꛨJ;XX>w8We8J I)ݺЇ漠k~4?h?h3^zBԀ9)qv&g TDxA}j<~dȂs V9tc5]+tvvj/|q<yctBAhK scZs8LxܡѠL9 Q|֥9 Vg;mAJ3C4k -^ 68Jz{ՋUbEvF7~lu/tDQܑ%W"Hq%,}&Z~iܿ5 ƭ§"\7ѹGo`49ȟ5_=i7kuq6 ,8n;PL/FJh+ RKh#hͣayAH{I7.AxGhc]e}A+e#w1:a{ Q[dMEkKo\mVFm?{G"Ò?y4m*pBqwtrpF[L.2~u܎-2ӝD |1RtzQ`m"v̋K8B++԰zL9=;-&5X'ELF|B dXQiRwR܍šY=0Prbꁤx':AvN.w]TOBte\."zgc1Y$>0wUa8rfSwok (&O@r E;GӍ#\˺0`Ŏ|Lcv1dRBeTH\G@]pIg:bncI4/Fs$k;H\G!.31qg9M۟'̥V'/1IӓPm,uxx5OiLJ(nM"‘XiqZx]ӫVu`fp@lt+aA](:~?,:N "X.vm NKH _}ɴ#ZW_b \:εq_-Z b ]a;t[[7EGMNGڊ ` {~1-2`Ey>"gL&nx}  KyQ %2#-,TarD4o= N7D=5HĔDQӳ]dMب5oUekxIfK\Z+$t Z'wD? @0Ks ](Ǧ7>̾iuLlSf 40j JtMۢ^+ɘ ,(/ g uEtYOT~Z)3ιI6bVaoK .\4iC+"D+Lulkڏ=?cb53ͷi.g~b I.Q eqaҹjʕD?ctHsy =imL=CKf 1.YxhbRW'UB]WlvZ/ѕoѪNN7p:JtDcVЉE[A+O"&x$䶥0lY4)0wSXuU ^]B4Ai, .2nF| )ӫE#)t֝)r,,SW O0m:Zw'Xܜk&DMTk'7[ : $9m_XB9!nQ@B- 8SfmRk^xR_`ྛcn?gu08 \T\ѧÛ%>oV`(`_+*. \tX⁵sme:uccڰo`j{N-5sf,^iCZ]!\uuT@X<<0T<{ |ceR˦}H[a#9gWoִAY>{`m}n鶯?݇1s>h-efvCmsUSw;Pk΂ϊkʸG@G.51v_fd|*(%;yǟЎpܷ`g$Ug)a)m%&.$lnZg]Kၵ/E3'+5 5 4JȌe\d+]hP O`Zqkx+ecj@xewCB1rNԏQ.ibzn6l\R8btwv[i~1صy[-SWGEr;6DK޹E (S|,৿h|m^w*_ck #p^CV-PÒk@|p㞳=AOו:&iӳ0k+  =  zX-nj#;51m2ryߤ|633Ÿ[|Rͤ&_ǾcuK6㳴/s|g;8=oF D; 9 ,q ՜dzsB^͵z9wǒHu;{w4@$N[T<|\* ɘUy*  ɤgh!q'@qN5+֙zr|(*xIڀi% 2$m6s]>9.p]uxqDG)* MGݴDkbVmn}Rte=si / Z{j̎Iژ/E[y(iuwYYIt}{E_R^IA~>]]:aCdlgളb֘3v%!R*fJnULu0VYK^'vH?y܄}uQ3aq EbWuRw@e촷_G>ߦ.9"JWT__'ɒo/ǘ(c9AC,^|H $S77 ΃k7{ YMuygQ*k/:qpGB6ju9utqʁ,e{=.I~#aB#<"A};EUIWOUf3Ќ2> $,8ic\œ9P(]sTW|7Zf}*3*۽d%Xl{ qI¼êv\Y:dځy8#;%_lW $vrG#'cO$#^0Jq!Nk_V$@:(E$vt$cXۺ_רļ $qZY.ckr^v4H00j#ڼ+5!%se4b!'d]dNs L3\Rب7C[4ŹaQ|N@Va_ 8?n)d| հllWs?W&ԣK2ΈrN1u!aƈeBX8 {%p?.Uұh }8;{e$ܐv0H ڌ!?JI+ wlGW\!3SJ&|txi~浗&*TnO ې>0؍ @Lɻ ܠ3 6/6 *+Ͱl(vBd9Ե#UJ֋\DcHWߝded3 kT o<Ji: z]^( ,"V'@Ukd9Or2T 0D.2`ATSKO;勘.Zs(,Cٲe*+}pͥ> -|qqh;v,CU'Z>sjv_ڧNyʸ/Z ½>\QWKMszXry{@zQ3.=l0Lp~(,n2]H?̜5D33Dt}#Ka|83ʼn*RۛTnRc(I ģ9/{|u l@l[(\"6Gε3ES SqUy_/_+Daրe|#""@@bOBO5Μ b2@ڟ~j|"_8:VܝXif v;wb4ҒۮzogZ~WҕΕ??? T;_p,DI2}؅YnO ?ZSoҗ_\utNUv9^ȵ=1~;.g, Xe3K$%! 5~JI'˕ s$:p&tY'O\̼RntdP҉fnZ%CKBD \.K(n־a6Fs<{\pX-p++ 4DbZQQ\:W XphIIl`#3S?q;g\Ϊ^ r^; du]T_N'/_Y(Ax9]ֆK[Px e`i '$PT La<<}]9v7}~<ǵ+B;cP"vNR޴,GI1VsV>;'g>kjӔE#Zľ/FsOgvg_翆҆~.D?RN9t}Ͽs?;no5o4׮qh rN@}]s#𯖅nĈ!{\&6b*E < ]"P9 $QW/Rc`a{*oX'Ŗ K +F=4.!eaкL@Ղ&yRA-Tog0Y)E^#}Er 4 _NWJ/Zϭ?<ڿ\\h#ŵ3ҎT4x]1Qy B'\>eT(f&͆ȱ>O8mu'Q}̖mv{u{Ϳ/7?}pyd6dݠU{`&Z \QUX(.X!U1r<%v GI9e"xlwk#wog}Wͯ7w_Y'ϒmֵ?ꃱ tiuP1@k[f[0mc*کٮ)C1B+/7N1}IرQ7 !Us; Ƴ[}ο@OټK'͟*:m84u\~h-?ޘ髢jl`pVmۨrh[ 3E^dN+o~ԜY-?0|-/.W9&8q>Fvo_5nbu1dRm1Q.ޢyaaʇ۸M#$?ۭfQՠ7ҠIP]\?ãO t$嗘_I s>.ļX.E3krkOC'y)c_OEa@r1e(L%1S(xMlc 1,e椸뎏.rOBse(g [A430$~X<ؘh~yO[T/?Pduj|vcL@AgѮhWpYs{R2D 6w͘uŭI |BD9<9XbluNL߇P2h4"t̃+^;G\7VџSݲ.W:Y}*tHg6ts*J\<0*]+aOw BJeAFB=`Bk1?ilcU7:\1xб%h_ݿ9 O]ѣO U Q~7͛ ^qvW lNnPO6V eH*BHUWx>SN|m}4\w1.W01pv%yawSnK瑊>1€FjYoQbEw!dv÷O怦؎grt9=N\vzw CѦ3 S2n 2qQ3t 2B5w'z} 8*C_~eK'Ie $LbK$1nZt9('t⦟JmIʓ1?MPa%+n_ìJ7aLgtĹm>A1cv~Vƭ8h28K8 _d=/>hQX7dч%ݷ5/pS/B_vLnGNJZ"~ X Jsqs(9>j9Ȯ1y"Sl:Tl ;sL@e)>u^27@e;+$%N ]r6}UwkS/@1+}ft Y885V>!3pJZiVDX0ۦo8j?g~qj< H)we+O`?nKVQݓ!gwF3_Ǔ\e8r'A_OHaQV\("t;X;Coܤt|>iå1JXLmrkvY 'Zl{W1vΗeC:'=U / vD\-Sݮn:ROōћ7}jǽ>5NRm{-KC8q+ٙl\߱cހzć HLfJ5 k-ڳz`1.p^=p=0ianèn@r0_;"y 9=Vqr1NMSo?V~‹wDkgڻ+clϜ>S7]r;n]j2$6)\" /s\(puٖ(J` DAbֶp~d Z| h>ZV,(sI+V(@lATjn Jr89ͱ3;1|MsڄQ3xǝ`abw_KZk=:]W ߶&_KmbtL;WIXriG2Ȱ$@$xbZNn~F'zO?qҞ ܠm'mP~1Uvz'Mna7t(BPaGfu%^ϻn=+qƨ29_ !O܈e3Kl7g=ZP`?y79Ɯxr)m' 9]-wuz#"jZVĴ{I?qZA ٶ|͈U%aD 9Y_I:sç(-xrǢpLږy#+$q5TkU';a#Nzi]eܷ=x1ђ =,eFӇ壵6<M&}F_;pK.59-_#3ys~cÃK_gq7/UK̫5s 1+ړo=71(M9_=_\b\(>&Ex†^<5 y]. e@0BĈgk{ĽVK7?7 3w0 bjF܀٤# f2GS6RO|8J>n ?˕߹{ _ⷲu'L4]^_rs~-8@. V} b0nq~n =n5,pdLTaEþyszM8Nkea00BĈou{֏ dC+m]$@ͩ$?lg2\z`|_i֐lP85ZEpJ[ 눎[T{lpK{c%RA"mR>/?|M/IeB&-Ϳ"B8ZBKuiKV\č lQf[7{g{eg7@~tSmxi&ѧ?U ~R޴h7gq!fM~Ap05kpWB8c9;ag-vː ؇|/';t"{J370hl}%"yUGg#Y7T&_iy__'ͭ7R{@{QVM6FRmǟ\4?~;MIQCl*z`_=Nk؍~g\ʵ2mu8!"f"b8zS _JѴKY-[q6PszxNBZЦ3 Q$[fHcߜO)O0Qtnq UÞy4 P)yVJ=KK}MH!Y/HT2 J Nhy_/G6UZO>_W>Q|z< _7g_\4qyOOBKsެjZX'&؎KB ^h뚳x4iDVaef_wx*<3 G}ڸoPwJc';S}uv9mϚr\8tbՆ҈#Z)rq~"t@i8Ѿ_M Btx: 1r\jj 8#[BzH5`W%(X!R`\LC__4ϞJ0'cw ~b%ÀeOf=䐱@ fR-c=L:r<'&0Je”"3ctZݜlQwOwDv[DD^cZ$'|>tj)_`<`ܿY>KԄ&]M InjC)atC'[U{ē\';z$n%(yhrpm4n1n/5c HdZ< vX09)n}AD4=}Nm:PѯA7\Xq7:U_$YC>.j^K?p}wђpG< v1ݛB{ (>}j27t2N`W^:n&T1Mn]}d4?Nv*h쌌P'h@v+Pq6Q /5uĽMn1@2@<ޑD&mJ0k9`-=Dn QGcw~!YsZz=p8RjInQjс#(B k3vr8ad#OkW|㶜f_L|?%Ɖ;o' =v1h247kBDFɂDdY,x::.7!NuƯ[*wwX )VHObSy 55*wPT}C.q[`ݔca +w(X572<]WEOxYbAVGiZS*x%D<0P(za anud@{E8^qԼ }e qMN5KN3z4n@=l3 X"/3"x%oلފXY}IG~qtG,8iz{)胵qwԜptc1 ([å:0p'V: hj'L`2wIp;/jNU촹oAdndr%Kܡ r!_|0r>M7vE3/( ɩJ >֭N?Uy\^7ȧ*5`%f>MEJhK,u|&R rFNdojOE261y&V*t\3^̳`WUJS7};)㺊|OY+'X!tHPPm)2ė+3kdRCL\m_osZ:uZ-w7[i}9g:rwcaFr nY(k71Sgt H=ΓιA4&., 5Ԝch߷i7lVmX'5P+k~lbʹ-R-_Lm տOTJT:rDH59VdL2V<00)m-xSg.}Ѿ]|w ul@cZGuYdKw/-+jB cw@#|MFˣ_zRy}c㕗h?"1CxVܠ,vWq[2_CJF_ Iz+@uuUK@099FUŖml S *feZY}5Ykz8< )iݼFX>[hk/߮ yQI\?+^MSĔiOazˈ!NdUST%^'qZ'oƫ68^"mP$0-9{1J= M=^)lװ}cIK:h%ػy[һfju| {S[ᩘZD zhݣt-wa>(L}I伩`-%2/fp2N<ˤ٪8ֵ\q^*UfߌޗnT79 \{ aǭ'x%JmK7 fdFZKJ2UTy2 G^i7jevzGɼs xp'O^4wnx^A`W@ A( n׆)N5 v9Mr3DK4yrnJCjg]D!2)F#Qʶz`y|"}^]<=Ѐ㎊؆j*E]H]cY5` #=}v|~Ǽż^+x㕑ۨ'Sb,v>'} VB!!jwY2sv:S%j[Wܣԙ~XYJ++<#"$@_HH5_ZM"=UMU)3'4٦N~SN6Xu q,W;?cqR >wןҳҷ!^ٛ;J;<`] x/&Af/ d .c bxV+*`c$LzD_w:a-}ϾF}5X@¼@ص3&vl6"6pW`2[2V{=֧Y]S:^4OȶcL~C+C|ɨrT/5nMĪR3ˇi WV>B!%K)t0pH Q[KaM3X_rźL=}+^7!Jcɯ 5)\mqVPHV15q9'|T(*,m̳BTQFj۳*E%& iJ%o 4rP_8%ru9s_EY2VXyy#Fhy1\7>σ!f7`~㕦b|ĩOdaxZ8@&}3TKi VX7;c Z6w1+ Wii,vECixh kZ%%MTiLRN&QɈ6>0mß{bRS.Wwʉ1y U?@F{rAd_%fP^/,?lLpg s+$ȁ~lpV aܥ},FgFj>@wt>yJY/nS7ׯ͟qy 0ZH>$L1n:t%NK Y-ǺJ.XZ9^BG"D`}te,ܑѝWUӅܛLU׊<f' vbex/~7 ty6n|a L@IDAT 0(`bK͗6LԄSkǚ}k> YckolW3Pu|ܱPŗ*W͝WiO s ֫)e*?ݧux:cFjPn:EjVIݬ $K}vŭʽz`r+\V5='|cjܯz1K//&x3kl hG@\ ʾq ˻-Xkq(TĵCT*Z{D]%*{1o׌+ +nCfICa.x175q<7~u"^*pwǺFJ`I[ T*J|5ߜmk`Ր2*^_'1NnWn@tŲ't ]LB,WOd`zRIل&bJD{@!EN)Lq/y7{tr+oVK`&3 7hX吽1F!IoJ$=,\W<쇧ty-9ρav5j T3l[KN\fQɭF{ ",5!9 ~g`3oLD%*@eNa(s eStY%V_j?ZhOߕO1**zխ+)WUJOxτz4&1)=boN_|*hi_>,O^X8[G 7[i@_7^*d7K2ky<(LݠՄ*~0i# w)-N/dsyA5)~dSSJ~ֽbM!ݹIh5xc$D x^czaF&S.ĵOZF+t-mudxݓ族KO;Bv)ϋ ;61^W/ K8>ۚcۚsk?p߭:K}01?vDoe|NxfвA Yp̛܄:F 룹mߊz{Zz`*l:/p6:~6MXT7t*/w3N~+z?Y]OeKj%6"𾭭L7{%+GiwtMmW; ;jU#X_K} 9--CKP Deiq֗^2Z 4NO7onZz0=0Q+Bi%j%Xww/6*/l.a(@$`K!f +?}g}a? 3< h1W?UE7˱~W^1wLȊ*W!%ۥ*}jN ~vFKkyo4T」0m + E~?yhY:Qi*AL߄.֡[%[<Rh4}4ֿ$Xz^1\CCX]Jkce.MTN Z8`({ T[GTWp;rL;)ZgDO)V nc5`%7 }b馹ًE7 ŝqŪ^ˁb?TM ;̗LO^Z'-LuE$2);;m2VjNCaԯX m xr$UGϗEf3B`M̶WU U`Z;gLОkOio;mhT25c+~~~<>׍OP?Ħ'U}0*Ht,>>zd"@׊? =ޥfl@ $2xD;a/8jNl{~Z)ve_47y /4Xiy _e!Q1Z@0Yi%0vTr=Xpʗ-eg,c{X!ɓ(Q6d ̃{5y^nBw~Ykz =P~'h`# yISl%oW/O=27+l?[23ϾV;}DEx A&?!9.b(;ŕ!?Vyz%5hA5*0@8/Y@iNdhwNgbk\@WӷAwL&=Z=CF'I@e?ƌA,\l+=^7ֽ(aM`qYU7>#g[sbW͙JQ-DtA }gyX rj)];j~ZF_xyNn݋!1,O$؅NX_ BKL@09gF51:T .`3BhIZ3=@nrnƒw<5vY

76 ׯolXc8ޔ#4#[$ٸnP! TsNg/JYUOgNtӁ/+Bɡw]}@_z`<@@`$~d}U{Z%Ff{Urxf-O`Iv@:-i,OYuyl *}Ӯ2/@8>BMVci. 1%{l<0 @0B$M1%e`֣jh0 ,[siq0`@m_oDf0kx󵣦b}߭PП^eȊ-<#p#r 9v$֥mЗ2N6U~Υ+V/%`mwhBb4qBb(Q\^a!RlqdZ8ͬH u:Rc))Y[%tz5كCljPTXWl],\ծї䪉uS&Sh-ѱ㏐nr;ܛST<ƩO={N#(~+P;%@>t,- QB UYʊ2$߸Om=^*M0`7hmEMNO22ֺ2D:ƪew',+Gܻ!BqszUlʕr(1b))ܪ) `BFz-Z=z@^CZi{#zlaq^FnW1Ә 0'Ji6rpZ )kw+nlk[ @<}%v@5V(#|Fi-;܉^*Rƌيb8?XX֐}^}UCJFo x{߲낆`#od\ UD1TlaPq/)UmY<ԫS{Լhn*1fFCqD<8scA3v {Q:#C]W1 11W&ϥ>[#JGZN yyz*Jeƥ2""+3#5z`,t8n7M<=% ^\9*(Nv˚9|mp+P`bSA堟2`3'WXME(y,*T`^ǟ]44 dTˢΎgU0 1hFn2cPG97,Sű*k֛`7LHkc W3_PzJꓛNKɋ 6|yz!5tP{&(Pl׳!NioTNi<6< T\E$XU 8ɣ:+kIg5wY%:ID $ }28kuz.1{.U3-` W@r}R\Vrb|O\(+Û8>'+:''5yxQ;el]c (@"ɔӀf{_|y#x^'5?㏱1oaXW) U%B)VhB# `I! v.;VSC[n>P !x@ՈCmknꁹW@j,\\'@p&!'[tǀ~yDoghNq8Zyqn^PL 9-vK3v'LHrxY|k =y{jI1 s"&atjmmAjLX!̯cሤTzZZ^ӴԶNzrRsٰvE݆vwګtNs m^[-an}!`vz20bX_VD8wo7˨ =S|gt@wnr;9eh7tio)ux;@*e i4^:1sð8 x娹C_zquuXc毎JrHo0#Ep j`GاZxFAG]K\yjՔ:qZz>n*/fAFN$ٶJct1Z Z9z!X!( 3~@,óŸCڌp>h'׬rT b%yiLv(+H|j|MM.x =+/-HPd AIdk@40AB X&! ''ĢCJ_fOP帄<`B?v3ޕ/ENh=wd5jQd CʬQ쒉0[0fbhixAu|:ؖ}NQ<æ9K5;Üޫ7Dž-\o9p#\c.yw)Vn|BZᲓJdGMN&%:ͬ>_c[SNDkW$\Ѯ,2xr$dSZ%ҙؠ CX1ĂH?k0.Av1Ii )GC4-1Ari4Yޥm`π/LZ΄va›/Xˬ<FþCֆkdذ_.:jg C<+J3Qq)šSsel@bbm t_ lmɜj-m>)+'dv ]ģϭv'l_)5۩'58?,z~ڭe4 ^K$ZBf%qMȼ9446ĀsQR YU "Sk(6TB:yPz<#Oր]S Y= 7jpz{Wei.X|b5_Gp *RxgY7# `\Aٙ/`AW @2㕁>:|-o:J2gs ʃxNx ГοntMpI+]~Hw _`ʼ؇iJH)9 s۰b# R8>* ǰid m-9Җؐ2N@3x:Bea*`)٥=ޫ=N}r@Ey9d-~*('^vQkP:s3;/ XCj<>h fo2k.3N#1j QT9N%Oz肹0Ipv\WW~e5Ri*%W:m 6t:86EYyEB D6i )}Y9lPpvv]*B^Na75X=U,;jVC }Pሊ^3F"HAl^`@{@LbC%Z/M^D|V FU7tĴiV.ŹJgT1I (- /цo 1%QpvUFF0e JU-W8܄(ʖk9S hAa`0( $G?[1BXۯ/_UsD|tI^uk/& ^8G+z}y.irڋ46b<TqgXᄋ4|DlHߠU m~rNZ/]?F~lib)e_˨RQ1W*;mGƼիί[gxuV ѭ 2_З88\c܄̈ۏ:YRc5T$ҔB#EccT^F0 /Id;*lGY(f,سdBVԥ"AJ|d 1$;J1a39ʲq :bkI|)v%]Q)6Nн M0˗=`>|>uԜ%*h2#~7߀D\"?`y~2d|;: XӁ/}0\oɇO)m[͕Gb,4NGv+**`riJڛ}ws8AVB3kI$ v2+W7|c{lJ0T&!^0>t ҤkZYu.`ǵF:h cIbA=dm5z9֗\;O\^s)IY77ѧ#7~UZ~p7[k<>9mƶqÅd~LEe,Sua&hS`HxQ,'hp]'i_82Xsʉr&$Tqlߦ~ иpaoh[~叏Λs4>HqP;Vf#̺q/<pod;`$5~j{66ך%e=MJag֡si86 6s]0uqB9+H0!2/ :4W0/}*6dsMy5z`KMy]|KtS=m*Hyt J,#@&Vh1&t7O|W;: j1.OΥ_w_xy vK.Z]B0'%z)`uZqpn&迵̺i]P<܌A+hf$ξqCY x㳴5 5@'6޻yܼRFKůDU+Zn(X+_йڭ!׆.%R1MӞC1@C(WHsi-B@dTX#6-|nmDp4O((Q ^"RNiFN^G =ËR%KMT ll* =y#G}2krP 3JT+*&TqZkg_‡'vap ;3o|a]{iܗ^L ψSv+tS$>Z t0 R=Ha ܊־mx)ZCQ[?iň{{j6ъ 0 ld)/6Va0c1_H`QOutc hAFX=,w9gP0FLZ"t!h%{(\,~) !!)s>!HO.\i(diqj H&3@;>wƠ7{a1Hf윊 ?ogO|t*mB8N:D]ґalIޚy~=d|/쏘K>[l_lY:T!䩊u1u.७l =aîƒv놁/W7ߏOO~~#W5\vsTtǠ;J_|@l*uIdERsZas$.1'S": x`b.` C'Xd%+Ca~Hһ1D ǔbBI:-Ep:?؂]Aa֦KaB͐b^@ w?νg-*Z(Y8nh^D3+$57_?n޹l8o4$lAuBSI{/O{qr0vػ3h>xC:Tw,!68׀U+1ȱL"Va,fmNl%0f.B)2dH L9ދrʬx9^Oq&@0*%ЄyQsP,S@F+Et!A& hyZŀ26tX{W;NZxD7/MK /h֡fQgH܍7T6Vt!qcZ*+gXAi^uݸSs-l/ P\E{fLjVmso;xL4&&S V,(l6Tdvuc#.ƭ hBZc\}X?9G;Ռ@#_%x&  `pDv4f]XVM NLQaR&HSl)pwJjK yk¿1%s%!I`yҿph :>R\q7yHL% L;&KUg3 (1BmȀտ,辑ptS8<э{ ^@a;l_оď/&4*byA@L! c͉vw -*`,ݹPcq &p7ߊ;8nǾD:S_ cftv{2T]fe-9~),&X+dIUq?hE)fX,d3ǚrm$QguZ l"qbRE!a*)\ -j<ݏJJ:)Ņ)`w{f-F:_ sPP,y)ŕ?٭'XZ>U,α';0lެْH;ډv H$Hdm$ll7ezSLI&yѨI$HdjFl+<#<"=s5܈sȌ\" E DxZ?>Y/Ɛrm-V7g~'7n7S! C  .tyDPÓvƁVz_|W?v7{tǾ\k~e,.^wBXEL`mq`l2Pj*fݤAєv]ŭ,gFjK߄l VzE.1ŴkJ8)@o[>̌abb21CoY&rr1d;-o]okSegz/,dȘHv0e" dyNWY䦁%C4 heM&-\o_e7$vzzo /h`oV ?PHw caZ{Ivﺦ Gu],dL0,L>y2K䜿B}&#<yFh_ pgOu&Jl;lB<¾ Ԣn@zJ x8TȪ8e&dS nK{AKѲMD0\ )+BZaWsI a.8kn!'6BZѧCWł>2c uQ CɰgDwuN9)fx$x q73z7%Pr_bs'Rp!=A/]wSM)V{p~,cC۹UtTǦ!JxmIt ;-GO/nW[e`eIdėn)ځ2]qta ܑ:IwD0/ii޽-iT}>WUmdždYu- Ƅ;Q!6}{E3=0TbP.ܡ;cit}Ÿ:{F6h]v'q#; ul ^t2ۻ+x|˽d!cI^d~&vU]|8l7=LhЦ*$`!d?+KM0{\'Df0{UU3"| &m^n }JT1`4us@,ϹRk<-wʲeZ mɶloYf4Uٻ `6l\ e!]ܙ\1$X#Go7TҚTk^*W/)iJw[u?~3+R=Wt@.?!g帧=–u^h{-/nLm[%0.]t1z LSIcp$SGAdn?v^񣔇\ }s7@IDAT/YHӚ:rdhRdP¯&A/K rfmӕGGaiމZRV(80 6V˥sZ!+xW_&9#OVP{w*ﳆcwf(>dj[&.y%zSs#XW&/Q 7\;dkrmaO>m k.#tLF:#(kB\]ɺhgˌe՚f/!@ k6Ga6rFDz= )T$M ( wNO|L D`áxT Sn%w6łzLN<; \.$'obrЅK|+IF0;o"RzTcÑ -܇Iz^hf޳żs:czH_;qklHH:/xɵn*̈+y^w?IiO<~\ruGGǦܥ/H6)s>HBy@l0UA ПUbENϒpHf,[9$OwJTyCp/ w&f8o TzOk`ZMl-fl}"0vx4q$G7U*ӵ2t)bkYA&?s@(=dar^n>+?ۄ2O'!c_V-zS&ݐ5 N`⇌rٖeͿ$_sCt3RnT_ʹtNŅv3$̷R<=I۪T{3!7D I37u,  ("'KurgǩeՖvO悓[L@Sq=m. =AGc91twC|y}w`@X 'I$|f,+ Zx 㧙y>W7w"a_Qaz cPhbB@V_}#^=Z I?o 4GL%7t> H9p^_K!x7a%uuuʞw?5wGn4V+ |`K4R09+e@2Ŷ8[L6Cϸ*)_܊xB)c|PenNjln[ MJ C[,=C8[:̩Hӧ­"4jr "V+hlk/=ztJ?9'lb/Ԍ@]N@ZR^) 5po޺~kuj]GYO@%xYmlB V2h5SKn+] I x+ŀARZ TYK0JҦ\(]K6`Xng70 6/ω-לwȼ;{L &ii6 f+^"&$]"~ OWt9ܣ~0& RU1|un#RY %uw.;Ib$,b+;˶b'] ZHIQ'tCn63L|I6&aWp?"[{=קb9_GX`?R"zĒTGI:\<8Q!E8_23ĒStl;ՔJտ?VyuGKW3܉8628~/TUz֭Ƃ(yGAp\3B1;8 qBh/\/AȡBmg,{t$kEF- q͓R=*jM>|/Ճ}C(+Ny3ΛD,۰RX %*M'A2x$:"S _PYѧw@pNLj‰R.#,cp "F1E͖.^pS0 c0q&$]DOJdqI=UL0ұ&\>WjNT0V1B*uLq=R|V"P"B*98iD.be/\,uıWA+._ ^I`&Bh mg.m*rhpEڈr 2_Is_Lh7e%V.E3d>oܩŀմ2D|b|;V ou`Lb?C?ɱLt2~LfO8Dpqz:*-'n6yL̘ LePZ%.wA"aN"CJ߿?"=_^ qz-R:5A2^/gn;UN4?Awu&Ri oA.au!KGh&suOdq*941e3:j=j.4"RU9AK7x+E`1*پ.m3'm9/LCEMz._OL+F5 !|'d)1bCpybPr\^Jr J$.@)b 䚺Aĩ;f=)KD`0-$Zp,V#)aV  9;h%}O3!XRaNS_EЅǓJ+ܠGD6N>_/C839pwRis /&1M%bӃg[is^4 ))bu bi K&aC=Ϩ;nu8)cA0~$΂2+ ڮ&΄mL\ 4$NŐfKt~>vFK]]m)& a9zՁlբyrQA >Y~bK3<44I#wdK&DUް;+7s4w*PEifęH.P: *n̦??x"rzR'}G@͓"X9/h qБD@hi8*mu-,N|n=HrD0 N&f)6b@Uq0LHOs[ʿ rac4pCy)'CBbcC6ꏸ7:#Z^OD5}bJNNj}GeL*Z)Ƿɣjh aʻ-w **xn.wËZ al/(N{7I_@MlAÇdExr0Cu>^f48 Q[ʟ\u?_м yFϾ O iY4tF L~R3a^(+MA#b$Ҷk(f|ӁD9`#L @ MH, G9=+F>Ħ9/yP-mAEl}(s7< 'ҁՇP7VPddu|qRc _%][TK/֫/9@&[:8dd*HJ` [ldm}gO.̆c']ue  ʔWRS:ȵ  N0:`k/r9&>nKF ~GИ@$=&nmM1dFO~hke1~奝Wd턙NޜH #Eq@<^_;WoL?r-3vʙгɐ-!:g589 V%;t "Zڈ?֟ۺBHbCD L-\'kPƠ=S9Lh[4/smRAί(YDXB93x 576Ѳv^ =эT~(%-Ge\ż]rb.tIGL0`x?ܶ9eAC5xlj"*݂n4~bL_';Bhղl_S=b`ˏ́VdMll2G,+RE4!h NK%aNy~8e>yIx(4?/rUO\GD zXE]zYӶzz^`¬:Г JɿKt"Ept'kAc9=wD!gmoBZ;ruS7wVWg~$31k?[^Owݶjis@iQ!v]>F(U >Q)?=DZ^+?؈EQJD,Eӊ4g;afNShr5СDpV@dG 295T@gpՐ=YYmk 3WIJl?ڨ#gGזZWʶj4U{rcUVé@^*6,u1@p,"n@IxP|r?i||O&.I'RQX~{b8#!.)IW%H8Y w] H7ge{ l '}H'?/\0<9 ,袏bzHJk#5ΧrfG3PF8pg|l/3@ 7hI R 'E3pE*YRBzHu%%;]A"a}#Z뭂 Y6hTC($E Z YMY>GK&> 9 2|li mܟh#Pt` Z]_<]46(C*L8R*ت)Ո1zivmQ/%]-nHlsK1T±TPmY8+[RC'CT<5e躞*z(B'UR1Kйi]0|}07 &\q ATTN$)Eɦi7Hs=) 4@gN1>>03ysg|:'ɫ-='Y0~_pvȪj vRX~JDŀxQ%vO#W5Ygrǔib@EB=$1m3 ?'H1]=]ATZ|VJ2I+>X$XJvugu ln!/՟l vE=| x҆ţ/H|lF8r=ݎc#iwwEq G\t[ΉWk,Ac킘Z` kC%JqV= >} w wOp,TW3Z^B5'\-H% 89bu[@xO|[#B())d(Q1OdDn**,]emq)x\DuBяtS5L}˪6XE?wK@}HV/ӻs?Y3N9'^xۙ <]DlwyB<$w5@ vDVて,}袒ZMB:& &P%wUF9A˟h|AsB>Y=DS;Wo66d-LX]8Pt?l4 jg̅SV$#RGW'; Twspcf 6QٵY@>v#1:4źEVL Y%=lN+ځU+oNqBur lhԋk٪Ѥ]d╂e}p`5GX*0MU DnKuh{&0 39kff>B4&_ +2ҒR0bFv& ;U(2R#mtn\_)3ms՜18iXn' A[V{1NS(KSSrQX+{9LE6be:^ږMIyq,s삈l4Y`_(Rp`LZykh!`E רoBtT/x;po ObyN? j۬=ϧ)tG/<@~3z*F k? Qw;tD/-52·.*<&(5L4h)Z9L$181HkfVXhiVmMzKyɋr1mɩQdU&.@EA)# [.릌EL9~>$x 0Y:n% *>\Mu 4 jhZKrRH_wq^>o?:xȞ:_#*q}y{DCռszՕ+`]}/V<2QΛc#;]XŽ>z`ˇpzg 2Y,81e8C<ٯ/tZ#!rmf:wh&GDhBHD\i]JW, (]_Ė%lG ,9"ĨT>=Q LAe%V92LWaxqZ#0v=QXڵƬ։V}mmzU 6 WՍUyxW~rx 䔹H_KC֟һ_ 5$X~NmnŅObaD%y@{'Zu&/(Y$ҧLT(JUह8U:5m\ƚUAGmK[j{ZMG3U@w@\Y +?e+ѐ 01u 'ODpd]s4 vR1wa1IԯDUŚl_p±7E3ק a.-DAj@LC!} ?րT&+m1~NK]B:a,• o0@-M1<~VGuY?  ku{$_"@oú:xJ'bR%rN"1qtBQ.MZxM:v=EݜOw6Ƕ~S[KNht[T(* yaw])KC ON HsQ|w:X-:F\w"/*s-k[B 'B4&|:aB4""=7|9 WqRz%Bp"9`2O{ΉתpVy-Э*]CwVg+z/ []Hb:ľ=[W* 2.Hْ;lZoe4}­RJbah d(g$Q)Gp` 2Q*Bպ'BBbMD,@w鮷:, K7m >QubO? Z[öU4Y*Fh~X0@` T` eX.m A%&'@ a`;hz\ۀ[5҇;k:NpmMPY!LfbxS ╁deY{:@cc' \C\cZp2R]I$ )N  Bc;GG > Y#5'GknW "`/O^Nq=ҧo|_R!I{ F(tm#B_ۨg%ઞsFDxW&e"Zh܄ChKj2]r[ $A.?п-84C-SLHGx Lft'Vk2f ESiQkRW&ɺVpyb+g$eʉFW>Cr4ApffRFlu+GddmV1ɼV|2d\TBˠELVmO c-PX.`,JqUG D?tb C;?Lz[ozw@urB8h8L!;}ZR0Q𥠒uHy63Q#ˡ '@RkkEa5Axx5M⫃ӧ7 ĖB×:B(K0rP|" 0dU\+R 2rI/lQjSG;۱.=Ѱ}rZ nުM9r\V51eF|1DO*EӲ)9K6"()i: w,ToYc^ 75rO)׮/ |ZNwĉX\'"U='u@ɠ(dfHUT_l7IxW,%/~X Jrշ|~~t [,o3mF`T.C'G=g@#%d(Nt$DCj R-c'B !W61`':ͳIS֫@~1Ծz>כz14o_[# ﬞ?C#F-D{\.$_T(s~%xK*yZ t k-b%q$*#)HOA ҬˈvE-ui%[)h;]u(޺`mbO._j=F8- L7,S(nryLvhO=k#N:HRP9J(d`HvMu"0!$un,rfIY{Mpev\GUAWy;!f!X7/<~T$x yʌDRT"P//Md* bi 7%(Y0LL&V7$.JtէJegYNC?/ *YKN0șIUq*Q& FBpͳT伸%,>hף븓y_CLou|!o<_=}O}_y"MII6p'$ďCKoz%ޒ??V?9X[c7Jy~_G _;I[^Y Wp -?jrfGev$ߕyuP;_#VOQ֖m7q'B:7V6RNn_7,v.`ϾB?+K1h1̥ n& n%OC5CEqNv!%Gk {ze j2tPSh seY& g؛d,;( (J^#iЕUsD ":OhAG ._b)%y"dm[Ay#b[u6{d̟#B"!FW9'* `2V!+.C?ߛDwI ,*/+f~'tSI8.Z.%엽Lj"FЊ"Dp,A =B V%4THi Ykf}U;Թ贲r m.l>h|wۗq7c72&nj 0u=h JD7hP:ni8ǭ?sF`3g49JRA `dk 7k; 'L#8|3Ɲ^Xe~㪶d6,{8veWyS[]/zX[n/r}:&k}OcR/}K{]uبEodG$6ʙk2շnӧ%>m9KougRʭjm$ok#w ]-_h4막O(%VMf]޶ !ikU I K4HxE7롱L+1ULe:j}u"D"0tx;4CK{ FX䈆& r&c^=w\+A+)u26b~hV< ̈f 'bK1s3?Xi;UHrz耩#'oq9xZ D;$ȥwR8HlNud2}{&/S.9Z +HQFl$חJ~Iiп3RWa &w9n5M&)q C2#Y-Ȉ᷂l$Zezx=&&N{B-.$,-%X;%8Nc=L&)u"ԁ@Ebd 8";]|rw!o1Wdn^@LkJIdPRWaEѰϾ#=Jk+v:|Æc\j4lJkFP5N2ZХ%?N #ȅ%\gKk[& >/KS~w$nKK41'Bi1e|L?B.Nl%IZ>1& ,Uy}[ ںqo:,y'"v|#L;57l(j{8oҐ~J87P2su [2L#(GK="'̴QW*?F5LYF=?Ӕyoj.r ЅsMU4u[K1ȃUm+NѸ;lv>ߥ<}Agk *a KtC%c,B_/م=.P´A)'t%:=m61'B ]O#j2^3fu)[N ?,FΟ :ZZ^\ݼVY)ʑQ) :9vLyo؜xA$6iɵp8h\,*i"ҰZ6OZ֦jcXнť#EsDxJB(EX*hê;XbGީ"'9HW_ޥdoiEجfpiW(RYO|N_CJ>E4M_m;)ӷwg_:a;[Z0_>;p!ÿ A3?{:MBF'S'K.֐lX-IFB_ 'Kj/*}z)ifEC]fpݪk 4!$ܵ+D*&尥ɑT6NW S&6}LЎFlbs]"aa aS3NL K>O6֖BTHVHZ&ڨ☑!=iKU9DSrDD . j|Lw4]!Rq-76*ɿ@fj܅'k %8\|unyVyhqC @{>s|Iq.N 貆3*HF_'z|ﮧ[;{H\_PpFa w,f0N>aJ@H&W*hೈbY.x&4^ ܔb1$Ypt ^ȱ(I%T.~"T6 ܝ0G`r8ɰ$1ބ0U(ɡ!WK}FRメQ,9hBF^uW`h?ŧ}:`;g zPW{)Gġr,_``sy(; W廙l_}d_cn]͈KXEpg=~}a»?Y]z ] ޶؝1sKj'USYW bfMfEG-8رQb-AjR(JU"(JRZUʋZ4{sRݰ^BP.Ŧ"G"s E%fZ&tsB/˰C2.M";@Ar0pphrK("NlbW\ұ%7x/MR,s84Q%D֢ rD(rTqs%7uGkfS|H9ޝdHqA69*[?>7{ M{-T[F޻T?ϰ Q aY|$y@] { AqI2)}RwiQ-i&JRX!ll٠u4Zy8j8V(tӄT䍋Ҵ2=JA.DckG˝i@4E,RiIhuy- IjCK1\D؇PˤTUJhZ# yxS "p*jy6.FpIpj9nX[T*eP,KMtfTHPհYPexRH Dוol{1ACp)]1$MŲ59KO ZWh7ųz?k~qS*wH$b=K:;ȯtW{y#?7cOT$Vm`^>TWRmtLO |UޒD,TR~Ba-끔_N}/z(8l RI% (e\vh+". m+ (;AC HNj$p'c,dAČ%'`JP4}lQ6E%qR)^m`?! |,HV?b[Vڱ::DڣToTմq ˛q/kzeQ<wKq_R [Hw% 4)zp/}w{S AW),#쯾{Vӓ.Vzud$]uze: Ƞ H>Z$.IJEsyȌ$hDWv=bܡ_Í? @_\ƹR?(-T}='m>ZMa]ڿO\Z}] }VVuHh0&qMGiCz"269(c͢2nx6;lJ)*ɤٍ+Ga - x$κ #UHPv,mt>uLT*nW AOYJ_rj=ES~ñ4GkHI%PKwx_41p c5(23GTPŹ;Lwu-8J[W3L;O-T'EB#E= ]绊nVo't<#}D:H?wl,Dnqإ巊Hh%w4N`ns%2\ycK̢KSJKcz[gm ڙnZTIE=I}:""XdD9-se4θEc@_Ǝ}"090ɀTR96(@l xC6X;[+\9q9i#"ODi-L}Z}I+&ߒZr# %p>h0vYqRuhiscyGx'kY8K~"ѝ߹ | xQo¶{Bt/ϒ9l&D>727fi?Xߣyҡ]p(W&Vw\6x* i$/=A-DIWssrOle,X/Ʃz@ɌoJCR TtN`mVxyM#d}xŧҞ/xw@~YK#op{56kq>a鳏s vV?|cg!}7ka+E.PK[-m[iz_!E  ~%VuSH3Xog);f_NNj4a8|yEy"R"H=R*ށ;)1T>5[[Ԯl`"9N␊ҒOA+K:0n0ılx.zFYhLAE.8 Grb(hE/+|_rIS)/'+_h:XLK%ޟ Wn'ogΫmDdھr|hzۙq$}$ ? O6^RBPݝ,Hv $kQ GB)JcL>^i릔p#Tv^xu4.GL$F)R[JLSO R\"Qf>mˢ|J#pxY3WLPTMrQHnÏ /Hعc ӮnBpx' s}w*}dǮsUr*80K-KWWHOKq-Q8G߱m$?>K \SE ^0SNVH\P Y=עR⛃qk)ogrDjBEbxTa&LGɈSiWDF$!b?Eҳ$vAf@U[1FiBa\%6`#A_ jU57PnK,Pa{)tD}N֠Њ exr\u!&?Q#N#W1}!}ϲ'lgT(lBt.|z ,ߪ粃>*C,-a/ |,oU./g2ս7_f.}^.+H5}Lڍܔ?F<'˷u&R ("_'im#'<ľB`>w[k]v&| ǙA$͚5 GLաexݒߗu(3KՋ lTsC`†O6 몔%W7b%^3J$Ȓ:mC#ξEQ;gv|N3+&FؠTqD9VbɆ䐒䁠!YoG~W;t~_\@%tk _]hrQknthH^Zџc "@O*E68'Qk!zW.@TqO̝|?K4@x Gtx?W.n_8jQF1N|ts"G].)+a*L7'yjlO:>\ yLw.k߸UU7sw+``rq"!YsG|wNj{#y?jf̯ G :S\0yh ը0~ r[&eqXe+ޣ TGڔh=q&lsH"XnFB z1}SLyii8i"`?"y#sJ,:Yarñy0ч*9>Obɲ^ȓĊɡy6gO{-Ȼ%@k~#-͛v^E,)JQn^lM#!},bIiBoDI'Fƶ]T}NӴPf$'$H3Mf8 6P]ϢaPJr.0.Op9)EÊHU%]ѲXz.6tsv}]-N,# F`p;}kMߚ rnҬsnΕ6KIEZ@u} _r9֑-Mכ5y>IA*űFGկ$+*ϥr^!qEד&z/`(&D/J,R. FQNj69~kvӏ|M<.Q*xAX{%X|GZmy3YZXFY QLSrQ_zJgX[XKPwMS)лb$ze՚}@==2>VobܠU2%Jݶ cXk&Z2CT z mgk'zW GMi+[F>B) @*4C>9Ɗk,j[AI%{W_cߣ}> &>}I)k{lQ?~kg3☑_<'JV!/r!Vh3U%&$)Tb(Kemɶ zHXQqDuLeLj-fxdȜ\Ԁ/͠Q.DLrD*Z% Tjy*g0 6,+}U4\%i):v/=.AOp.d ctB5WjZT'EpQ 6kU ʼneE { G${$zONz}_}{<7wP~Eai=ܾ#5&l&2~Ҳ]ړ'՗_.J:&ʺ`R7L GҕpnI7ؚVޒeC;a9&-$+eDYw6NxM͵!eF~,(li#|@WOq~_q 6ė.s*M厵sKe-~ygfa%i_, *>hw•';Ĉ$ᖈ<^JʁZ(-i2T+ 4IAjM.k,'>Oh!HuY~tI+nzA"UC=GG^#Yo)-C&Η>ᄧ܃&Aj ˙)%. SsW<ԗD`X.l#g1b8Kᨌ0.0XK\Ir$LvL@ſz dX(Qd b ~q*D*Z^W5%F:2X@{S&3\ӶV}Osy?LrB5wzNMYOՃvUPs~dW_ @w-WcrJ!PK};=oiů[u> 6ͻnN?fЛn_"XtvnM6!k)!ہ!R5&̍=7P U ;b}\(8_׉/maEP.1PzDIU:DRaRD@vHDN7Mi]QXg4jL[TY.BI0JΒ_jonbҷn{ 'cQZ wuf qO "diGctC:˷i!t|U pm#=rfTif6sྣ;i{O?IxOwm"ѩo=T9??_xow}Ζ&>tY`ihb+~45-4qk QVN͝8ohKZ",ㆉ8"M@,^0 QxZ,}YFm-GqWEAĞt@z&ǽ΅oy!*k&δZm}%!Hd1Ie4o+oIto>66i/ޥ/쬞u?߉@Mk=P?ԥ60zO"6zZh.sٖG&;KtZ + >ލg\m\@g"@93r dFxF`#ډ=4"ۃW>dk!*)DNC.Ebi`Fuv{˩rP[C׭%,(4s`dDb!W*;+|'xK^Ͻ7pWV9p2mjuSвy#^{^(]zw6| d_o 8׫*>U!bX[O \a { Yp#&IL,ݪn5 q:nס (+Xn%45uk*oS!x^Ik_d9Oƒ1~GMIa>9n|;nGwۂۖ$:v[kc[ֆj5ռ նbMwSHCZ(°C"x0 U\OE W#cdĉұ4C{2;Y5PCQJ,*2?p:ѪܓRJd݉DUU/`-n`rM[vQߣ3ʵWvVIGKW4ֺ_M$_u]MO#MOe|LJSD zE.k Jtu-z.r`$Z#pb̗13]8#'-ٜIPYzs6W=zo A#RSC 7cj=u6^oE\Ѹv+C@_`tGGў{+v 7-lzϙ˸)أX:M!&1Cu?d pB˓4!$I9UzDh-T-jEF"YC$XKfvfws-@b=M6 ۳ƀޥRvl+lQ3ЖoV_ѯ+L-,$ZRZLpX%'δ,f'A|}(is[u"h Xҗ|6G֤I_}Zy0Ӄ8/ e"y?l{] N:[qmt8=ؚZdiHfU@(fz5:RfHQwaBo.{ 4_8V^#0y&=0Am(j@Vb$3=-W&7,J_IxPP鞨V5w,{*}V|s^B}Fi 1-&GԳB/IWr;s5zŶ^ۇ@ʅT϶\F>ֹ: Z 8n75E-a13W3ԊL7#NWVi%ێI c7a LTSȴ&2<=-^Lȹ/MI h+Dv% x Ow%r<0Չ$v(29K_*X…Z-|>ސt?.#i'tۚZ5Hk?Jxw~OW_]};Ȗ:M]I(κ"Ey0~{: z&r䯝&Tu\#yPciX7G4!]NTLu_kgXDHc+#` %EVr1)v^h9X`YɅ/5ҶͰ'qrooށ?S.05xcɟ?YZ90W=qIɞEPf<|Wۇ x1Xt2|,c?{ 2hxYl|ʞn>eR-Qp){,([8mȟCW>=ar izeQ.5dJa<m?lQ!!eL0'ă2'T%L>1(̣0pL@w ~ဂ8˜ ?NGhQcH4 ZhYωf l\govkE`GI;~ 9Ojp@ [QFRy G)"ϞMyP^gA޾1?)Z;^H{h C7,_<T`?EW_Yᓖ<}6?su PhLe}Sd!} ,zNی8 s/إmC# 8\xu'NīxyfsK|/Hg/(aX41u:rTquocV8cflʂJ%7T޾9M4*HOA+:\ Otǧh'*q{OLD}._B_A3Cr/+mi/ YJ.H}W`+lD}$`T3}Wlywׯ}dx`1^7}806UI$WIV^h 1М8XV"̜1Mɟ//z8D۹sc$>!a(d ՒnQΘnї{<ڋm&. -T 4Wfz &4P(̄iIN/,S.E)ylQ$x`}".Ƥ<1`JD|d~6]xioeL+zcwwhT6y6@cAک1rYI0aCLAA~LPCyеhi"'ȃ/2 ]O&:"i(:d6\. yK5 rK:]0Fj ۂ|k:|TwRzʄ LjZŸ;`=p'J"ueu^KzX.ӧڏr<S:5"-3* Lb8|z穴y=;.WC?+lDVι.8]O-5p~Ȗ(,]zѹݰK*xoWo78bk,@keㆻڡ.kZN}֏jb v!pJk=؜\QZK I!J y^8؆ІBfo4>ܰNaS)HjhȈ"&AV׫g=ީI^,dp5A$Yw˲dxRuo]PW W;ڥmd8 ?)DDx^I>5Oj "=>CZ =&l9f7 nAȋ~wV/

  • 0k7&5Dk9<"u=GӼ xnޓ6D곿70A?w~g[ԉҏ:77r,Hd:M}\ܣ}e}U+Nflɴ p5y`ES" -K֋@/'Zkz\skq!lߠQ9QG+>q%Gx<{v5wy&X[n |+B1vs'JGɫoܬ;\A^&{|n6~T?+]o3y2as_X*JXgCgJ/MX`3XQe2  d{!EP,lG7ZԏZ,緿9wlGqJFTYKtiІ.6[QQ z=Tn'XJ\sdb=Oo$xr sn6\.@ }XZ>Mx*BtzHepuc;Lk=Q_ӑ>s=ks~=j>onkuЅl ,[P`kӳ&/hW·r \$J ~d +pU ©YWc 'h2 sS`,w8Iźc$B6'L|98n1hl,ZzX:&E!{Q^mY"úO~Ѽ{tCuRD-)^>ۿSʵ:oKJ?˒>|̖9zwnlwkCޏM}qt-m"PRS\:M>ŽLBS{ yΊTU1UG^9{mɿxVlk%Irv33;ٕ 02dH2If4QQAA OfJ~<#<"="=歺U}VF?Ƿ)xk-`[ΑV8֬S.Q : 8,^^|iSݬW{Y=AR:[f2[.c Ww6g6}hϾ{_޷6oW *CZJL|F9knrwYZEF?^&MJTC}i+v̡P/Y3LɎ(=> r|LO#on>hݫ}D~h |='*=5QFrQ :;5$#k8|l[vD'Ϳ\w9??konLتQ ߯{=qM;AASwG(x\zsVӞ d3l%|O$%Si @0lm!2݁{>  ۆCdoRdqmnQ[3lw=iGT_yj 1d6S>:p:|pWRYi~omL|m+=fH^iei0JXvμ@ ?e-vq㚺 <^yzŮܔ' sDM@C|۷& ?ۭrю,V_('FOiťMXA[19y!ҌsU*~C.yԮ%o!UQpLawȇGPnFLG`׫cH8  Wf^a3)n~^nă/D;dIA@_ -SL2cvB˧'tµ迦Mwd7A]9>/BYߎ}I656۶WwuXsa jl]RrR=Rm^rG?0HSX.t̀f1ZZk[pj62PEe GzL3-s'&tth"w ~02ه#o&D Z:ח}|2jo'u k{CSnV &OT4r,e6mnʩu?"Y!o楺rkۂͬbqɶ3c]Ú`qhUHL^^d[نS8a;io|UIcoz<1x qhXnݺOFu+!< J.4wKHxrTţB[\D,!>3PF= bS6!{9BZ+OJi;h8rU'VxGڔ*6 Z#囧[7toL)\~l0j^|N/gzCzEcR-Gf xoXUzcpbb=A}9Nt3+ռn$Eו:x3M>PM/q>W(5XnZ'ZF{l]6!j44spNVȾ0 5i(s]8RE²¼䟯36\8( 2K_(kRȑ Ƀ,q2 xE.>HP VC}H=qsB.# )W-ɕ>HEy&,W{F( lr Y ^&o^dseXa<|>r# N6kH(NYdֶ+!0o5ApsМ*MtGw{][-K<뼤d>މweHb42r~u'p*mrAD犒CqQ^` NLL 3+ue߮|Va>BSBZ4Kj1Nrlm_lRl>m7*y*|SO=-t G qPr`ܲUH+ +֡[͗]Z:\:z1S̪ L~1`J= @'Zȓg'x?8OAV8rBLߺ}y]j!7r$xt 'bn1 Wׁu05mNΧl8O+d_ /YJӅg \G8YΖHOYHUX<6.eҊFqjvP^oo~΍mppw!X:,vp |,Q\RZ킳z[spU+ZZiykŁ[G&m F' }Ѩ<ʝf9V4 y~AUGe,#1n)Dz|11n9ހ7)NJeIj00ؒcskE&s`y+=S%v/^n^㳙$e*Z4D|h[NOޣ\'.+3 %l U^s'Ze3rr&@R*\@wߦ;e.E|%?=gԂCn&Ca*I嵚MD(sЈAEn}`ZɺfR ͉H^h@IDATT6tRt^w.ohkzlcE|wZC/Y8KZo u-D3$Nج;Srӣlamk䶣ܶ(ce/ ҾB4Io-LHUYıȈOiEtCЃ z86C|eJszWU"uAx6Ԣys z.,@#{~(V䷸G:v|%^8>;v1ǝR>Q C=S9sEW4ڮSs/x %y _n~Jwפ>$(<e[ޤ;[3m{n@u@VS45TGFD?bsrxpsY2p8pYzǹz`@@r 6m] mYF+Ⱥ/|;[Jդfn PRz |Az=pa)+[|P3S(,HYޓAn$a*I_U%]qd>82wP=Wr`%WK-r;aͶo8-3Z!z3ͱ. 6}mc Kx؞u) 9d`2p8o=bs9v¥m\}϶vi[Ay`7 ̇xcUt *bʾgI 5ˇ Lw6|O<8e % ͑gnd.}/_bXO^-߾ݧQ?S/G+aBPJ'É6*u1[roQ%M;%q٤T~&/{3 fiaop4L'kkԍŗ0CH7}6b! hZFO_ޛn?,¥{Ao8;[ zh9x9>ŝN99~͝+J8={*"̟=l~;LkʍNIyU|/rd7uBuNLZ7/ʱ3} 74}MU ̴(+gJp[ f"R L 6{ȦAuv?ʩ ?Y)"_C>SO=;cުp6wة[{1^yuc~mAvqdCFyW,vcYCNɬzofIrh_9$hWM,;a7C`$kry:Amo[S5;p:e"Lhɠ0D.>`ssW! M'qQ5qSԈe+тQ[@>F9"ohi8@;|h;r6 ;gj^w[X#VP9k[ٚĕՅhkWoPCS a"فr :qFe曝}od5;0Wp{ #@F,->:s@ ?-&ikz)g͎,`ë?p) ޾#={=J7gtO<"1w { aԝz;]Tkǹ755eⴱG392\wӴ4V,5O= í=~TvF۪ b^mWMפT-97qОx&Tc3kZ-}ڊBcug]#R~hR*ylTxOgP]} S?{Йq/ީq %oU-02)޲1@E^N@gT,o(`}4Y@M[ 7u9un6Rt`ڂfS" EWX.Є+hƬRu1Ř 5_E5zB\{56FNxa`1=g5İВ#ͧ[˫ez?ˁ5"Ӫƭ5em/?^|}K˒CPKKvu4F3vZSNt&Vc \Xr+vDV'xYh b$L'ӣϽ 5>U a˲j%|2SO3AZDľHq*C񐁋mm/Ok6`Pϲ% :A*Y"YO0 ¦ɞk]b.rIHhG/ktD(#r'=dLG8)10cW>aɁ Ӹ ؁j1]=mi|dH- z]m@gLbҖ\\wLNdJ.,b*6wާ;c ( Q7p)DYŗֻu!p@'P/ #G-/IH}M/;}|BѬRg9C u[_ӟA!/*-$mZb9POY*cU J ?&}(fĘlPp'";wsW8PӹGjGΣEEByO(&kU29X eױ׌%Z cy>7E2??|J8O<*?~J9P +FG&cqచ@uG(e9 .ȅ*-4kjd]eNAKܘs>̤D6+!Eļ>Ū aQI([Ի)\4"XwM6]OEy Fy]Č;X%brzalpŨ57}`iZ[nXi%f̥0$oPs_w,-=te>6 ;*y'0X _7mSu8{L~Dd%vU<@\^7i˺UL9:Mi8qhBɏ$@*K.t̍r" ߢ QnAL՜ʦ/>m4X/o۾c/P6dۙ,V}p٦ ĥ$ose\>"cjѲƗ?_\~Z"b5߭|Q{SU7D=AoZhS4^a sI.dZMx@0*zJ۶qP( Vln3wt:p|6>VVhҧX֟Pl)Olԍ6 C^p`z' !y2RHo]}aH<ڼ-WCwHAÓW߄VGjʧI֓8Q!iHQcQZLc&qw 2NMZ`tmNFK4fДk 7ծmafv{8p4dgŪ`5w@GZh,2,u=;ip^x_~y~`=k4;.P>MaP.C5+.:C>'3k`ŭذ\ȂE= Fg-pyA+OZ +`aFW_ kqՋXې0⾬2ix@;_8d9^ GI0_)@>`$+lHSXX+vukl5p*m3iVl,lOZʹpS! \ q.E)#U^;Vimn ]o61eHRi80vmIlӖ!HVl.HÌjەuVBRNN)& Dl91ҁ1e[[~wGNpHVfl~}tϾ3~ۦG#⛼[?N/RsN ߶q]EsCs^xD{su{1gG?;UdK?d^D82=L+=a5S !sԒH XQLv2vqG^U4EfPHqh PLܟ8lZVaM2$寖c1VjF a:ȐF&d:MHPg,NȠ(zM[Ehu_‹@gж(^IUQ;h5dR^:O {I N^$.=jV:sz9q qǸm~pBރ\՗~ݒ&Rܹ?ve,zGc #o;I! >dS5asC:!?bòY1bcU;,Ia$H--K&hخyGFSq] nBm/ҿCg]B:+"M4ӐωL03ͨ3%lJ}>|m N}Y:z^@; z̚oni髩VśܣPV ; u&*6`[+ѹ|2*uіϻ s#Ǟw]Lf=ɿe],˃w%[Gz 'X#tl=^ȪA :'_n~}g/no.[:U%*=j. I Ȍu lԎkl=@,yD9JM(k.=eؒiUu6De2}u(GumY!6oTtp)=FH]V|acGX$notwHNҿ HH>&lB5rb}o(~Ql[}ޯ6xp= :0>º.aF\h5"^Ke _Wu Z9F8Iw5ܸa(-bk_і$%W?Sx3}鼶1QdL3+anRA&4 'Xnh`]> ߦlXxo!)c#%T",$J˟H̴[F6DP>d`2p8Wݱک[( ŀ>GM?W9wnAT;Ҧ?n=PUTb$ LWa&2c:́?Ikv[K@YeOdeUz>\X;W;myՂ 6KC42sƈ̢)FlgL#pTƀ5& ;쫂XNeƤ;GqF-1eZm 46 vإ6XDg2;+FkcUOv``.m,+jέe) 0`]om""B wmXk~OϠ/@9U#xvZ9lSyD7[6p?栂#{}'V{hRS[ۊz߄AiNx; # <4XaKYy+O$O[2V9FM"Fuˁu Oj~Hei X? ;Ht-WK]Y IR!xbcm*e^N< ,Ls)!{ =76 jw !_WIz`N8É=lB `M󞐾5.Kh ܥpIU*&$$j8@94 '{|8H5k":_5er,I/,sY51J5EUet`dzpAXĖoE?Zޞ0H|Ao/ζ)/{xt5i'@D:Y*W`XGUFH(T",TXKsa!mvu5Mmm`7P$1@ͤ+5gYFR֒m*dfd:4l!W"6 A*|O@0^oxkCl&`|46iFX0D]mM_ME6Զfn嬹SW>%`[Pk-z\V?bٹ*8k6 '"mnCs+ 7F0 LŕWIZ0C+}(OR 4ܬ7n9mgزz`+o*ْO%J<["cj&!dZ~8LA0]ݩ8j&răm6U_Z(ApB~urZ/ {Ф'Šf& X&K*^"upPWԋJ Eu݌J ^`Z jSA2@ȃ䐁=@ndEȠg=d2-w3۶mMĠ&WxN[k~׌_¯P@}Z\{mm>h{{S Z/|@Վ<>N8?j[=zɏ C4*pHxN1clmn"cZ}mhvIҺѩWU?̅Xk;!֜풲%QXgMp<3x ф,9C<x$*c]~1@݁\^)MKl@vVشt඾m'H!lp;z*uZxi㵍q-I0~+:]-ZO%)bg[- }x'Pүk7%򁜎ڎUr^vL5g{5ߜҁV}]*x=E?}L˗LgYVI C=?׉1ٹJtȕ@ѮzF),\USTx.r9w h#C.YcgU6ٛ`j9k c+YHeaIm8דPHlޡUd@@tmNdrU%6X266}5UsU#Om궆ۇ6Fy60$wT?ɏldΔ0:L h" ;-+ ߰.;Pʩ7ܵ=Nt+^DZz@! qK-Йuag#˵ɧcuaJ.\|fв2pÐ1RvxAq4C>fSCjkG 8y[7$ #@GW#|B]؉C-p^0(0TJ@Ё.}{>w2r?2v#h#jtµ\!R\Lc6DbJ֘XN\LB5`S(+mq3:'.8"6Q[wsPaؗ&J@z#)6ԍZM&օ1!ް Na~嚻7xzS؊MGV$O[IMRv~9e~yB]WXX~YHyBW輟kz4܂X7UJ}EjYʀ_מ>hs wqю>x jXʕU5I0 3WzeZ@dGw9@:o4W^.@f| ࠱ ZR!VzCT3eRGy}}hb @ lCP/EU$ %SiWֺ|&4%9kCeztg~ć GbctE.tj &cc9K9Z H2 -he|xџHR$=o&J2ޤ+@i /K| _w9HSEizƠ1Kѷ7|JvowBi^xݍ9  MDx V[gqߡ/\Ry5$Bɏ٦QrSpM^=aNƐJ#,.c-kNd7ߌ:W*a:d`8EV;@c9|^z[' E͋mj|_M%҆[16%R$zslp/+"@B- wӟ,xYkG$S\u弳?s/ C?BFy LJ_ ټAxOĿ~uq"Tӳ?lĎ渊gK,f$Ĭ&^4UధD2*J!sxҦe2V2D@4,5i.V SB=`~XH~U$ɀI%f@Bòbq"gJ&ܘkhdٓ3j :d`2 EvNwN\7i<ϐ10+pkS+Rb_o5WZ(#@:N:6p<o:(DN5Xy!63y2[,/v˽"626s2/ÄL6y|&e cRs4itg9P*vfjKȅ ˙&R qC`9hY202 g堪hŸveai* f[bdc;jXa->Ku*^Db{j*l*įd!:jvWa` T9>:-?kL;88fxT{qh_1@2cJ# y5[՟ƋN;x r$bn*$1GXoKT6<*Nm|)S: wт43Iӭ_l{rpnOnz֑2R)G2  ᴤXAodBWHE,S9zLMoLj}#}fkG@9u۶(kX$ERa@!y d:ie,k(]k]N6P R?R,VlLWw~ E'v՝pbY-^Vqin\K2RDMav .L~ɞvQiivdNd1OJOEhI*sdTY:m~{he8Tî'nuf뤏> Ds/V(UB`BTU%t8mStPUίP >^<מ9Eêwtu䬳]\RS3 JJæ"࿀~ATL6.Ҷ ;s³d>%H =їSO#զ;^9lw/J⼋]E +ZUu%٣+wp%~ĢĢoOOyjs/ڠ$8؝qp']>u qn\L4H R%?2HNVr^µ,KN<5шbd`IG:l;֤'t;\>9"L"4 B}H9i/?-z, PT a=̶b R-qĉ9>Jkhlq$!2.4?$7XD]V[M}O%e|(2~'~3z!FZIo<I$AK5{M,A_#cر ̈́3Fն;k[p@d`{x旊iA'/7Ǐ;O>SkOM.OJdN ;mdFw Ks򤄴Hٚgm(!;\^O~>~uo\G?ʸ>WpM1d-M2c.e^2(tt`IhX-#&SS;.VzsN}3'kWL0SAex`]K"Es)3p8p) W l"FKz54!X|髩ȶL!(EKN(NؾX?8DYcNUL@Pk0AP!G{7Q[lc~%Y27²h~NdR+r Ymͩ />sಂ=&}4=xaĂq.CR[rRCT5,v(1ɠ*`kN8dJj"H fI^hX$Xv7B fRXa$&,f0ȇjYamy;X+kecd)xjf6D%~Ngd,PT꽴 gE,sPhB))' DES6X=ݹv>84\ܹgYLInlf&o[z{zӇ--oTqU_[=i`A_bI}9~:| _<334,T|"  %]s !5D-N-3f'H~RE.s9|pI\$ iv^v{M>܆oQJq*;z@̸h OC(EeM l;` q#g'ˋkY(~zSkoޤʹ^v$Wr5Z&JVMq$+B  rħʜ([xlo rR桺@(|6, vXR@ $UM&s^&R@%xUM7X<^v˴PDPK U&WA]o/#\ɣo64X} ]]池B[)ס.xkm BU5 R! QZ?oӂlf/7|4߼N Ys2BRpu3xu  N/_krL̃ +lcSu&9ݍ͍F'n3&7*;xD"LR5x2D [3L3s$ɩskN($+jKRLx-S5H!+@eb%H&)Rv_|.%IqhT ]aF)TR4HyKf E-7 c2& W9dq2;KTVSE<`Oi6v3q!@VџX TeQO&ܲU$E*qhisÓ7O"1??DʳO?Fj WD-ˍ+q8PE%π Z'7$[q!6)M *a˫:+(/!F)C'^X"<\o}csr~p!u L#PGKWi/aK:S:HyN}pr pZ(>[o Q;[,ּdR;=^TS" y&ec̕?E=,T_d .շeNZ0*KNRRsc #%kqpv 3x@Py s9ke ] mȺuKќrr*úqB &QtU l}S-br)""Vڟ\@SZPuCYsI!X0A$aZ(8 Xj.2vH"$ġu1;AP ^s*PQ4D-:C:1 toûMK)jh>QRWMR\}2;O.*̙>Fv֓xN[#lbľYM TJ-2djWbp5!TNBDc|9cKD2_ulm=prAٴ,j R՚i =e~hͧ$X{l>7m.T-, 6 q`݃ ;Q\~(" 4พ. t :|(qů}t)CLȝavWyG*Mc*I1/(^Db DbN+B|eJaXPJs嬌Vڰ((3u'ˤd'\ĭqE cqV%r Z[U]-`1R,t/wsqGWIUtW"7^_4ctK3`h]op`bC%~m-|Emizl "nکF6$pA{VA.bn@ۡtɻŒ2 m#A:eN詼դ▢/KkjvHHItGڑSm3#ڷwVW}c̥,0cׄDߚnHt*rU"6^"fIN N(PPPpf8+3II'67J7 @@3Yŝ/6ЅHg%c2c%:`u0wĤ8͵=vg?m@,wm@IDATW(?+4V#"٣; AHcf|s݈>rO artl@D U%>yJǘ̂-D5KȘPT +UbDFCF ,&X<"Rd?܁J`KJ @ q)*kL 9,ӂk;tQת5.*}]}o!ˀ1^f2&T)-Tuj\)ucݔ)l@_ ^a.2kKw?i%yHqx5gXJku>F {E|Uc\sY7= mkCz;&Nn\,*+1d;<1r\TyYHcE֚޼ʨC 묐FWx?ߠNp-:cHdLOG`M˟:ӿ=*GǛuO!I[@Z׹($>*8P ?^)ɣ|Q'ZEUBRnSk$tjЋ ,]`'TٶA2^F+R}i(As#Ӛj^jK?u(2py2>F}o'1m`9TwY] ǤLQ?hɲ6;tG!&<`rSQ/~..y8P@XJL Vc[e\y ܈_|5fScS3,ct2*q^ɭ 4R晊 ;):xthɴEKQ|DmRhF[2ŐNcLsv!?*ڧ pO&?Z`E@39GXA&Y1Ўڗ2;RTU9Z8-) FwhJ_y5"}@M>7Vs}pf@(0DZaw 50F|"ykM) |.0~Bʵv}rA  52M'ˤTptIF W0S Zyy@e{oNT1Y.VޤO,Z6jie_|VW Nlxn[7Mxȿ+WIrhJz[EϓBBI?p+У3B9V+rP%B]'iWFRObPяJd}w> 1%gQQ nn l PhBDcN1["a=%\,`&?„9t<1Jw}'6,YB67DY(4 !W*<_sy2G!"oC*jYi'ՉGH g&@1;R1:msg͍E젟DئBYtK6r%3|+  l-j'+@==Wk ? qHNs\u/)m.T/pN~J|j-\#.HeC0*V-eх$AZO=E-JYe< H'9@kzE_ Sg"/9&q+M萉Td(Z- L zq e!,9AтM(Eo#jpƫC@̲I8 3P?MeSu?w(A V~"ELlmk>诬ZaRk4,6-#;8 NsDwg!+mM;k;R׫)VZ~l),і81F3̚u4(҉9FB'Ɵieh<L)l*,fRiy@ĥUGPW ~Yw4?ѷ70:Z%%6LkvJݦ ȶXoA^TPH[ y` Ҥ DX~:.<_晜L[5A乬 (Z/u`ޛ\o$^4UioM{ x"h#>hج)* VQRqrR`;6ٖn_:(r0)TH~YBj z{0rr]jP* |bSeX.՘m&){־A{ N,HdiWЩIKSԚO)dgfn$%Ϋdк}d7wzNJEQUxZspjب }.͜ _<=ۼ2>f_)}+ř>h,üke aX{wkb``:p~H4 pȝ7w~gWBhsS0D7ܵҠy(<`*$zɏ]; Xa脫&p֋ܦ9xoCBO u "cGf&ѓqfYat1&0;T B98 `-pyC ڊa#r%!QkKv0R a&y*i), o4n Th}"p_5C @RNOݽ4o6% !Rqv͚h0& ~j.eZΐ"̎,tl(댃P E3Eqc\M8h9QC]h鶑I;/N":vnZW|x+8*Yl(e-d#HH^r PM15lٴȦ;; Yn!HjË5 rZo?=epuU 8<vӇ) oS*L[ Ym{kQچ*UܪXF*|W@ӧ XjG㧠=ܧLx@#Z\[L8+'<羢.' zv/鎐shӿ5HpWm֠ݣWJ#qWN3LژDU$@NK/r<i J]d--dhj"Mܢd=s9&ʺ¥ۚ6k`f%쎝6=0ʁ*6mL)wm6;EېvŶn&ix?@>(K'Iipm ,DDa>L YLlw9 N@I/}ON7K 2-A=8,$ޝ Sd۫5#{u%lg~.Y)i**I}9bU.2h|@Hu #yNHFbf0 G5,Zߔv<u+N C4 !jW n^@uX\,g΋?kȒ"_V}P_;MyocX6?33Y?@Uml)Qf{:&gyc >W bۖ +ɮnmPx'd+UaGiu@ Hܚz[/#ק*EX(RLY78h"`DuW:вm;b䂠~g>Y8/ܽ9NJXm*Jls%sy;"#L|BRiz;y`)V?Ean`8 }lR^~$0X4qTlnn`Lo< y|eeK&ʢş(iUp.DFqYcQlһx3C+VNyRGWh/W`7VS:@U;HYgs)<謒x|Kv&N bh^; ugOM"5'g;~_=/ .N5 : ȹǎ}w W'ϨwYv1z}T|aůózј3 ??HƋ&iuq3^QI\'U=)>*shR1'A O[ 'RNdI.q[vQ>l"QeZSc:XXyfd*"Bg]ֲ7fY5^gp`2hgdcz7cFdgjI2*/SngP_gя r+y8R#`(aYTI=RkLr]ri}<<G]Ёqk *v6bSLR1RVdFIk>{ H"赶,`Vk*[3Lf|^`ÚH_" Yy%-,/r(w?wf+ٔ9IQ笭dArUaj6 HT2潉(2G˺p@7d{LP3:iBM+ 0TSEs=fy0}L¬(/%RF {@SGհ(ŰOEmUKJIjQm͘Stҵn6Hб^(r\?bmEGs{JPlͼT&mu s] ;Aρzo3p8]slix|z#Uo[!ԡ~/p@= &aSxc|^x G؊4@&YK#6$C=¾x#ALO(>ro1p2ϚX"űq jOΕٜT#j⦩)U(t 6-˷F,q8=&Yg^ԟ@;'''y5Wd֝[AuDU`!YeV"BM3Ȩ<"CsAbғֲcRa1;L}A6EKW}WStV@Ty;-Ș7@B"!I"zeZQs~P\f źdd \ \^^P[梪D39Pixh%]S|,#ѩyJiE=4S!?`b*ЦȀߨL3jӛ=L-5Ą FTQ9\J@MbfP,QBDv$'DȂ )vTήRcf8"Ex AJ"=׷FæׯC'z@s=_$y$'x&K$2gGMG;L-ЈNxNezM Ρ) U9O܅ 9W`!fV#s)TO)-XF{*Dٙox@204'#)FV ~oz΋ YA60x_)U;L)Lj)ڭ5l 'pxlAlxTQĥQEvEytƊ}Q4kmꗆAo)g-=[Z$_znK9\j3 KYYNq7 pM\bG:*aoO: ؃+B2N2BL q&{'+OWNI [&΃MC Y-/dT?ThH77\HN !^YɁ D2TR*r!FDQUEM6)_K#/rٳ 8խw>0O}Jrȟҁ7Հ yy^:Vx ~J~r;/wv;|) o^4X>iexCsjc<IFc="K*{MbH$qԽC| 7Ӥ,T1hfPFKKdEnpV%SK>ҘPW""̾zzet: |@"H8NSL(,?|h1*2^I)z khmU#ԇ鐁3f$|etGzKrE=ӽN fu4D*`D,s@eJv$(LgpJLsf/ږs&K^-"ywpjy%,az,Xwba䪩p[_'"A,XΏsc!}}"V^ W[ /{B"Nyn ؔp{#?C"1KW(J bRbYR$sm N`4]t5aV䱓1 qU8ˬ?`4BP)Wֵrq.v>nxоz|O\.]2"^>3Ψ\jӇ)˂^iIPgoN@jZ1wrh.P=oA93yйrAUT,k"Ȭ}K| ;|<ښXNCdZfջWŠDo/!:yLcDžT80п4k_Umݝhޤlmf ŵXE@1ުOBM*vPbaYL%ުM"םKCfwܲsG,ē|zl{ev,pxr¸8KnN ; Mw$l};Ǧqw&|l>72~ =x=5֧!>ψEp'"E,~_`X*;aMZC =vX&f':05@m '_2roaY,$8mhE}6ttI\,r@/\.ބ[3;nQZl0%A*4&Ur Hbd*)l\C Ϛv\dGW`Q P)QR w/ZI;!̔,篫+-dLA㓪j/(ItV9 ׋衯'}'<5N{rݲ[2ʚ]B2DZˆiD#U4L Kx.(#U,15aisG7NF|8Vt|a!*O AiCz!DTZψi7>k~RCimP65~ ~OinN !I_I,4@5EVf5QﳘZSg FfFI=*`S]=ٶNpaӪԂrXgtԽ@Q xz;z+: zHnޗWu RͷiqBOEuܛ(;gNbSL'cϸ>"t2-%D0Ka٤blrlT`\N&9ae HK̒Ȝ ?*+4Vd( mO< w$|`Ms9e!}u΃Lի}83 s3]h&b؛S̨3pŒ )--bI؀lULҼ S4Ee25kX:A-䥔H%k ÜT$=¨oD۵!x׎O &@\}k^/-.;!dPЭx!9T/S{8UI=vXXw/:@,u T(+^HuIbJLm[Ch7f'oPQmQL{JY2:?4 'vָg`lO;YeNzTʸQԜHti"om3qI(]SPA>) \/v*Y+,hmRs%\8Mwhp93 9U#WF.jL{Fb8rGz WfP BX Y(psKG'F"ꜝ8 Jw1)㯛SIAN>*ؓЀE]D +fȕE{*#UX1D6x3 1"%G,f5K@Y& Tp"+@5Ҷe8NZ&Fn͒Y0!2p8,oW K4K3NuL:i⭣)jVҤ-oE!L;YKvBԛ%MTָL&,ȥ*;XY.ΓER أnʁnO(Nq; @ؚlX堝V:5IPcb?gf<[7#yiùmyD؟wڨܸ2^lDP[dGb2UM%(@edT*N`C"l_|͓9fȼ0odTl_" !6,,u}\xE+%[qp~'ct%ueC;ܙfK곘ZS9Uvֻ~i5mQbtFt[Gȭf49Q!=AwiHteƒKeUuUlWH@m[ @Ќy#hDQn(Se5?ػ|Qj|+qNdCW dI#;a H"cKfa*EA#5TOrPUT!#njHVCĔ9wr+#4XZeb8K?gGs9\ ɶzt7Ɩ7ppO~=?]788z==pƍ©]P:+y-lY &/ mDH\7ȞxDXZ`Ns7ɳPh8A ޏ}Tۧ# syp(b]HHz/H9ȥ.A͏#UK &y;:'Z&CĪ-9 .@Qyxfex*쉁#&/[q/,f .TŎS 1 kp |I= ^+.߄337g|⏧H_]@(t), zSzY`V[?z%ihN.uov<s~!i`c?f12 ci43>nS|vGF1 nF:ׇl~RFpko)/긳`;HO`K">G=<&K SYS,V x=>-jxH\t-<&^0Xrc7zE;E.RoM_WouŝcoP).43ڑ0=WD$x:D to$ M+Fne6p ^)lP4H]C|V.Wl _?{s*}NBfc&x~K|U^ɛc!jfIEA @oS1LcIR ,u{ó3]PFϣp*UΙ$J ,PNd2K;~e6yW'xo=o-iUڄpWЫWو"MƲ>-h*6A*>@B0y;)iw .X:E:h0pGlb⧜އğ]#MF&Vܽ&\mrbWd@3f!4Jc; dI3~!Rnr#Bb#iD\FjQSsa-&jj; MkD؊7f|!)'fѹkT:eUmoH״#-ux18?sa0Lfr,*,&uQBnOm*NIYLvA4'j 50UF*tE"@ĉ<>_|îQ%\w)oo!uՆUVOr9&|_)bʢ ^px&3NZ t' !%^8:[i)?⡵9'Qx_| ]w&cd7#OI(֭xt7Pe?0TudL ϗcS{Ov0'e>U&RJ;1yQ?.vnJ IKTG4N$sAxɔ%/5~.-& ca;%'kY'Wknr6:_ ikQ5Ɛ^}O*曵O|ES`7p#nX^BlcSv@u)7ȇŠeg-&Zqs@kvX,˭iGi_c]z{䬮N҉*Be8 ۽;[/ /vAWbBye\xtR0:pO5q4sK>YHA4 *?f-@Q%#CG~,} wC@x~ǹ狯 oW; {mJ S˾L%ڨo-} 6~"p<%!l\>oI,jf}gP P.YF񢝵1%`R 2i_qG|P-Y=aɯ2,ITcrm,bk`E%cԕx&j(~EؿUV~r$s e1E=)esJ6 CA#hqDH'#gEAC蚠QF1?N]Ȅ)Jfǣ+Gn&lqeᆢL,Qj ĶP u ԾjTi>7Uŋu:%sO0\krQQPg:n'vDotY{g&T`y2"RYN4DՌ)Cl7H*5NJ0d:7+oRu5^w*pp""%pd}>K N]^*p#`˜ѹpf$QPlMAy hƿׁ=E X-xa6ѵQ'qzNcMi vB +wpPvdH5g#Z_ ͓NO3|tJ80^;*jv 8@. 1,B:J mh^ v!?xys+0.ڨrog >զVQM"_Zet-|qàΑ'<';7(ՕR4;utxCة .i<CxfPOp!)\7[xPh" 薇:VPFhzLn9.s`b04( ~bmnW=&Pxv%2;:$qYym`g"C~H,LAb{NzN9y`S?{t>߰X<ɍKr7ҽ>@e݊N$*#\P*cXU/X\~?>6xT7Iop/ZL[ 4n>#M!Դ.T C5٧Q>{jpw^LL  &k,уz>\VgMe*G*DWD`xW5C)W t-. ,tUP+-*P+>|C?_xo6$ojX+YkІ E=R>z o, {Pgp3OtNq>)Ĥ:43 9Q~H랳2ÿfZLr̕ H,` -NM5 p#'pf{@)EԴuyosoGL:L(#vs^^ #k ;E@Ӣ}%:6@IDATM:FY?c:[xHH袞HdB6 {|3._44~ 7<O=?8H`Bi#b<3l?q63q\LUĞs9i+"2+ɷ0k*>>[3#q&&1%&O}deczZL|ä́/7(Nol> Arr,zY+Qf:NvܭϪHcDzM19'E{@p>`+F5P*12ǗBkߐ+?wqÓ8 e|Jwv_UJz{S#Xqan 37%P5:MボܠЫрNSԶ&DR q92BC%yԫoYh/k1GT%pu#iMi)N=r*`P1ֺvQYbKo{n!}Iq{ա(0Sě:0% AS<E XLe'}*qvAc-6d{|9/9| ˑ`Q><z_p|_ΗV:/6t5WͶNmح̬z.$sD"Y< Xu M32h9oDBz ZҀc)wЭw8APSOa4'-o/4?>\p+rn 9iWf/57p:sH:}twMNsxf8K['XrDSId*Kl5vqEJc*}Y<0Ͼ*/I׼ @1\ZL@hh?"dnF=6WOҟ{P bA,PDpr OoΜ82@^ 'xfbi6 ake`ç 24)ބr%mon~y6cu)f&3ro8v2fw*;EN.f#t Ɗi{ X%еQs@~ĊRՍ$q4 0HQiAվCa4T,ڻ 20pOI5O_fQ_f%ZS!p]㇉Lyby֌@0 @mT ?l$9~Hj_P*$ʵ#B8`x&;)~ռp~wZ 61R{JT # #2TgE{@N,~X)O4C-/'c膷Q,XB6~1/>8Nn;QzR[MQ5^ l.==;0; vqiĆ+^eb:GL 7o8/7 56>o ŷૅ38 jG-b@&Ns2 SF J1 2_x*Ї{%u x}J* LsG 3{ڦ6Ԅ\Q tDH-d!X!5*y.ڣg2>kAڛ ċ Wi5qpQ~Ŀ"kv/ a K-J3FWHTh};.#2fA~HTS@|\F\,]ОyP2gw#.W6N)hnl*SLV,Bkm5|"{qMR<;ʲ5ڨ}dhj*ĐS/_M<-3(da_'.>Vn 38Z8*; x93񼶞#jַ Υa: @mTi# Geabc#{Iw;Srûq?M?-QP:~̵YO2!:]LкO՜'86 .]b 6Tl7[V٤͐K 9'ЭNAsG@a8?S1!gݵ < o, y]! ^q1:XOy |cHmTӰ$%QجD3xVu+ (?.L f%kPFl"ү>[EF6]hrľ|JGMvfTh>E$[UדI_%=gRXv8a2bkfNr1Q #R՚.% 8p]J"8;3ϴ\ϯ[/.hgRLQ3i LMGƩ"t~̽ڨR/[āTf-&ѫڽUJ?B| O|=E~8&2zS]PV`o ihv3$uWV=n>,mzlpl n.|n2OE`;ٲmU)C3^K:(n;yh '- F+e$ʞׅ0/._zx9㉐^PiVV+LַI255&QCRSz/Щn<%|D8 @mTIN/c'iD .oa9O>hyFO$JջWNBJ!ۆ {o>Rh~]ڱ\pɪ/q5D*Ql2.k1hb.n`u1%';>A(kiN0#+` i ]B܍ 0~B0^8OC22@Ӷ0iFU_L)""WZEؼl9ѫ&fvnIvqͱ`LlG߉opo;q귝G#<'>7 8Ѳc65#&z3E0s1*IOnܛ: S6:7ȸD+Յ f#5plS@A :0֝^ǫKyoOܨK} L=ꥬPvO!-]r;0k[V$ȭtP1vp2^&Fo[;.|"xQ/ )ǏKzcǪkXU{mFo7WkZy,+rWl'K"ڨ[\e-&n(x)_ؕsޑKaY0ᦲD24J;x'}o+<}z,QgT9rb o_BW֔٩!/?=~wŌ0,?agc}=J_{bhF4z#+Al:2mA(T(G4Em$n%8kJ+deeܒJ$&UQT[gm*wVW@ܲzzqVUiq_t5o|J3}`,}o%X4S"" JG&M+EB8eu2 @j{qYjԟ[tavƅ@A'x'`ȦO*.@/q P3,6-=|Bӄیf Psɀ vE Qd55y_;) 6 yn3Z?`n4w*cd{?Yя?%`8≺n>%W܌xo$U̟%ֱF"양 |۸{fuoϬ߷Нv {y,Lc*òOzg|0e W@!YNS{!R4@'ZmD/>4uBS5^^#} ^O"lB̪'+{`h`} ^./Ɔ֋ gS PɦѴ1.-6-qq4>Cu[5^Ei\%^6dO U/ Tx_`Fӷ>BMF {8Lʸh3q0l Ԁ|`lIL>Y%F4ZL4pqsB &X w_OOq{8H>thӦk9ja7)Pc>YdIND+/h̴~9W{~୾1yEV jfI1Y,N@`iﴣ1'?zFmr|~Gg+`+BЗQ@Q}5vs] S#_)}atl syĨ\1/L$Bf#kXmTi[-3Ifbx7@(9tc_Vr/r9a1JnW1*pU_rc9 d?I3P4Go@J +SC p,{e0q: E"d4?8@ohęr/|Ȑ>xO@/M)Z w}Tx;*.Jk.\7V"J,bNL'+5kZ3EbQm[32fnIe\n`r|z1-IYno|##xX/v- K)_uS 8j9Ap70g:Eo 0z ~hFdѫ< N~˧>U.ם:>!I>č1 raн1,9+v,s6HA9T^'tЇIv2]xxi)LmU*Ft{ƬRsE:_5NxGo-g;PFonC%9ԋxc ">8Y͋}'=P|} Rrj /gv_G']Zw߿ox9pk6sj*u'mi 3.1.J5bb^ w'^ '>.xRDE͍LA~Ì;8ȵ9@]Ud^aˉ'1tID(2+FzQGW5䇃 oQ$_&p0% Ej+眂N"y'stMٝI^F#WdyaNL\A$UUL=>THr4)gŵŴ.9Nl4r #)}ۅ?2w ǯ[ zW9ogb}Bg&f<5@C(j!S6x0)TNSqӄ~HI4ROݏ\>|I{Ka0ᝰ3.D`§V?g̓Y)\^Cȧ.lnL`s+} lΑ3.*5(dܒ&\ ^WAYN0ǫr^X7 7Y[Bڦj`&/_}ݼWt6g.BM.܌3z2 L2h;58!!T2n6.`b1sK*' ~BDU% ݷZ$Of]V O Fd7'ʉ֌M6vM$u#LwAh}]g4U.}0<.VN-3oERc:^2rnIe85*{;eV(l%|q)p $6:w4ۮ=\|>ehcH~HAP~.>i-#T(LDC>[Lw|de̕*χcv?E߼řgBڻkdTma$?a@az+ {Wyz_U(^zS:Akb)LtBhNЅt#>37BdLu?6J bP&D{`P7P֊ũ-%*Uu0 ~ˏl]Z:ፌv9{sĸcD<3jQf[^Y8y&$i2Ѝ/`6G bȬ-'ZD&-s0WB24?0vx/1__H?!iAd, UiV)M4b_GG6񠍃Z Blj^N R7p"< Q6]t7tE훮97C-y2?sIǃysi?)'YPnOGR֞ՒN +¯D܀=Oo_g %l Cp}(@lZ68!Z+CR&D+/n4"NB--́kT7yIgBe|jdFn*.<խGomc`ei<[`avܝ8t-w:>a]XCD݉L+XyʚE.JἌGe)c"p5^;:0^# TI,Gu`v2}$9}``&P`Ѻ`kDverμ<d;kͩv"쐥;l7D۩v$%f:NhfV%fv[W5^A!Ό0X,_ʴgjdmfBtǀXv)] o\O,PorxnuDSќ9`S Tᦴa&)T錼Dpj]sz/H"m)R@W< OSXԧ"JuG] ;"ջu܅z˅ 5۶"#0&J̤uҊdjJCoӸolȽssL 8y3 rD{t̼~ o<|3?;}]HJ3t`,IBj=xT rcIHE4R nT-.WN_xU {@k}GY7ׯ 7fӽFu#9&lHdq@$s`LZ[!HF4n#ͥk0яαWCP8;Z^>:;߽ |/Dw Un)oL ѐTwSB; fkkߋQ@#Q\F/O&ӳ) [R)nTDfһ`MZ@9\` 2Jot*+%l1|a_4[~ ZI\{:unop}A$8WNVr$`I +%fM@$Be4+nPҰL,-٢* E%3Rr;[]"h/Ω˹:?^ ,sG> X3ڽ;K-<ǁICv s?'M+ ;z# !rSKE-gEؖODF![7xadѿ!9XZ!E z;j-]`Q$GfIȞW8HJsC2: n࿽c‘R30!m{F2*xfT6Ixaxd2>be7집Kg9>?v75}<) eQϕ讉窢OvPm(v֙L|( *ڂky1"PhY;L`@=i>OԳoYG3gd~aY6SK^S͓59궗DOi1HFHGċE,He``&VC2OiK2E`gM)

    T@n>!^WvDǚ:JEjfw[#5~֤D^X|rc\8\ۄhk`S,ȶ\=?[ZAxgޫ&ݚ,š@791=uIZqDO/^=nkߌo§+Wz jO3I?EA=*2Q,(V=z{jq:^4Coth+9J4Û`ʇ7yOjP j?QvC$"v6]"`F3cѱ } ņϓ҄-UrY.M8o8 <Ӥ?vϕ>& :WgA}mՎ2&^9P 锕"xP([uȚ' C~ F>9p]5J~'ipńeJ} 5ߴZե]lsQN0Ph1n .]W %~V3>֠U%Sj%L6Ku# TIH8ܽԡdVQM瓄륐#LG|+׫׾xJcb)3:8̬3J9b̫ 7?o4E$QeL#}CDSW@ o½*8%Ѽk 9_'?"U?(,>/m|Y-e;)B&;Dڒ3,Ґ;oq P;zC히E(SIƵƟ3Ɔph{f#M:UM/ň+ѝjC22 f1 Q|~ ૯% X?,zmH"T4TBo1='w2wUt,۰oGGwlrHz\!ۙF˻[o̧N?A̤uuh{Ǧ&d#G̺ 5B#1=S.Y˄r϶Ow΅]][–;eaơHci_DfcGS:rݨ e=\ QF\e n1^LhEs]T(8VS8xum֣Lᒊelxsp8<(hhu}A[1.&|{Iqˠ1XQ´Tԅ6G$|(H$0զ9".KVur"Pe ^1t t n_u1 ;`ґ @~Y@y8/VSp˵|&s371Jiɺ2:- qS99C>#,"&u`6%KRa@73q?p[1Y(xs֖~WWN$K';vɛqwZ123/*ޝ+EnL!Ve蛓\ -x0xgn*O 3 UE3>+*"կc5, 7u8zŹ:ꍜTɁvW= 8NW}oV6Ͼ js'N`,@dڗ<#-y Q놓rNȇSLV."p- ~f}ގv{.­aFM[< ߇DB/'[y^xeUS_p,gkzr"ʁr*i\-nzaT!ްt'{Z ĊD fQ>dAo&U\ #. %T3 KlC%j]޼)u_r?#~<{sItD ^!_v a˜B9R'VBDLԊ'pM~5GWZ>MV'g9WھG"GpIvf+7[]aYmCz>o)仲߯`͔пobŔ`!%FTo'eY"(ZD Pf!>n>EX㿿}/TğF5ewxnk7GFxls,ј;b/@eαu3_f?qDhx 02dֲ1F60T7mAE<7iK>ޥӿߩ@ݘ5A̜!6DѢV׈T rq0|>}r]SOe]G&H|B`pw kX|blLX^ $-/OO xԌk#޼o-,6/?[Rx}F.ҧOHy Ua, g\{ġe㿇"]Q|1rl#3fC2ccė 5T10X_ʀLpĊ`ӹҐ4@uyzX#Vi9hق>,I578&JXn04jlw9 [Bm5*!a g8o3>Z;8!lXf0|E't0sn` 5p#geѲu`k h220\@F^SXӁ![5˺ < |Un YDt6uAY,o;XR+ CAN<9Cn)*ʟ߇w; cfcŅ.z&sJx q=N&0TZF3Ca4TMhKB ufS٥ Kb:.a7Ck Á:η6i_lfpk`x]W+#y^"Nƻ 1lGxxCtoԝQ؉`ݵ9Fo$bGeE+߽'5NjV򡂟^Wo\~Wm,cS{5|k׿|7obaK F( Ьqd ^CwºM{V3nj׀/AqoSjtrrd^r2$&Y'L2F/HZ%"l o$qݥ øo߬Ǭ x{>Y;;ucwVs ]z*;7OL@sZ!x<<0IJ+wfJҳ{}Fč!p:Xṗ|q;ƀwYLeB L ĝhp0֟?~FtMzx=@IDATOQ^^TMRG]!>$/pPzNlzWp)ʄHb1QZ]x]:aAʴWx4ژfɰ3KOdXZsE!M'=ږ 1J?1wLmuP`WD;߇t6t{L9,abʊrpoz25ڎGϛX^:;v^kI[N|Y!CN2[\ 9xb/*LmӨp֝4SهڈK%J*?:28߿38@7ғD̨@rt٠pVY'MX肕̔#_/P?CUdq9$XP~X_cqqdbi/9HƏ$Y^L6f UKjhx sqԀ6*HQs:;DvZ~+1Q=3$Z'Co󽁔;W0xh{*0D2Cmd6럘@i JM[ T*q}( 6uvxqf^]7sтs$ U!E%Z39d=:89a쉱?p+yXCrMcurdr['3Fi4&p%nQӡ1g I3ѧm)w b:ϜpAiT9eTTfrA-D\y%y{w'xꜶ;t6ߗX ]hE|{xs>(U}<ºmT&qIu]+2I@sOl"[ydA@tO-ŵ ԋfI ^9 Ex/{)@y';c ch#6>PrA?8.{<%Dk33_\UMep8#73VS\ y` kiYf_sN&W ^*!}'G ncPI?]{_!W|V5Xpz5$Uko* ͊Vl|UJ#~A SX֋xڏp5 q)yzItiORZ fNzf,= #MKXrz]y>|^1 膀 vOk_Y?,7@̕[Q7+5jA/ D`DBC1k֟$Ya2U7'P3lZ 8vN5B#,ȗ6D| )@r4Ur8\V3f+Oo6{bU QFxsԈٌdh0?%Na@^v+Loc}9q;Cw\d?'}(itwT/6Ћz5uOoj<;xyҁ 3~AcU#uRAA1yjg.w!^n"h19m&rZy"43wREw<|[j]6Rcn*YNAY Ǭᱱ'/M) [A9]H\~ϋ$Y/r@CGthx 3e`*Qph990~'۳!}o^'.4jo ÛqAm-3"~|AF:8gEGmSo!""6-k2Qۙ17M9Lo&'Ę; Ėcf:cN4@—B7~rd#.rGZ/7(?'6ICAn 7ㆫQx80g B?aq,G}&v`ϽT{"(jNasOaʍ0vuc$|Y31'RlԔûG;q$`ƼcJJ&&[CYrܥpAnѕ1iÆ_ '˛6ިڨ!|>FȄljQR 6j)QϺh`ąK k'zqJ~GOm.<"~O3jt1fa4TB`@BGǬXO09"n)d|wCF>MN 'vNվS7#qVcs i(IawXа"H(l)L N)KJ hUJiN'L^cL wqA:~q{O 4b!`J"|.lʮH5[!LC}KvIDT7w(snDFƎEyȖy}O QZ 2QE4CX1agUY}6!g z Wڱc97-YiWF[|ȗ,R^%_*yo dvO^\E8+b,AH~)%:&}\SRdR]_3J:T Lj *ov#"\r|\FQjʔ^ܡTq˻JqO-TG$D1VV7*\ y[ڂ_ZKVa@Q%{F~1%ϡg9.ɷNM(_p+7 Wۂ{ۚ̈́4il=c֝U#-#m7?=t928xY tB QJVƽXLc%o#>4D# l]Q_ 1ǭHq ]|+:`(n#oiC޴vfʺiS1Le \աk *ppP!o;lfb/#Owv@̉Nz0?/g2'R ٻJ]Ep$\w|O| eP[G|t/8%d'~"ѣOy={h[~&m7[;:kv) g"hu Mk+U."qVv;1 *ޠҰ/7Ǚrp;4tI٭o# v5ȾCa4T" #{χ]\kbݦ:d/p-4ЧѤgTf.U1HL+ppbǔ:^|1!DV$ KtQt]yc+Dsr1IC-|e!eQTS8ҍn}j0DUF NgO Si>pOqheOyj?āANZ؎=O8uyHk3dm[ۡحֵM8 X"`yro;j@tVvDnFZ S3᝘%eX"Xe2)p٣Wf..i|S> }=y?,9P!]$%%q# :^; ѼiTRhFyfjJgG^fGd5*wޟ3/~5/=q]NhfW0rSCK<<z-RNXf z~9OkDo7|I`)Anˀm`Ca4T_uGY>%~6kMخ"IM9~:_.ﱾğ3GkS'oFbk뮦U]9"`1?aXэ;XknrDs5wG-Y].TE)\<$'Q1{H>b[~+~*P&[d#Т{|[_?I"{bmݿʮ$͓h%Vq(hD!te{jW|,ρ5 4p\o |csv`+#SOvT.5l~ݑxueFg0EdЙA |~ g0_.#,i™MbΫՒV6đfMX//pB=%`z *Cc(6˶=Êd kGCٙ.B{+HF(c.#,5&ݰ AQ-~ЉCFmh&bZj.Bf @9/&#:;9ɁV2ᄵ#RE' Q2ӢɶbB=_jSVЭb/C{{՜"K|@&n<2~mҋ xF3$ m(cLB_蓥1dVyˆbXTo/Sf!pf>?#C˔1~,I}G8ɫxgA Ә3[M1V2,ApJՍQLSp@+"(\X-ngBYi=( _e&妎̊҉u.E2ɡ˅yyIQvKF1P#nj醜3&& 9TG k쏂hcwd>w=S/hCo8w ̜ka̋ˇ+k,JǷ.Eצz(a=j@jlrYWn0{ h!>rsLf4QPudeKkA μ>^p`x>@]8a=V䛅4oK2FH2{"P}^aJT9ԉQKIq04AZ~?-o4C-{-/Hvs}džFL_dF_$rs_62XMs-Y6 ÐL`˱M%,t@0TX$ P?2F{h*o6N  NKhCrWS+1SGwj].QSEZgCzv#ɰ?<yϾ0` ՚Jb3Irr coB \?6!BuLy?'+yL7…ȂQWEx/]zc_~v2Sgs3`lRW\S#0baIzŸ.E%gKcSSb'Í=JI:$biY.KG$38Sz+X52-?PsF aݬ0Hn^ }~n|?p+m(󾊛*AN 4Ė8&S_]+;)&O= >,P {i-\i f8ؓ;7ᓆQe{7|pcI@*J*1@r=Xؤ)[N #թRw8yALJ.$h_.sߜAɋ =hX)!RN7>a'?yc"/!kv 00ĸPsn n=+R݉nK# 1F!:Da]͔{LOLԃ-*<~Bq8jq ;O+/YU_4UKkʨ_}:ԩ"t|uԞ}'')pO.\!1Oã3$jh B9`J+bG/bceO dpXafҸjNA`z 9$?|pQp"p^G'”zkN!jx Q1Pb|ͭ>Ea%pB}^ ǟ 5S\uDws $l>" ±Z&ptH/tJ;iyԡĈA5ܻ#tV^͊($vJ&!qVITʹg>R3@GA@?p= >/Z G@XćƎM`_H<[;r,D> % P!ghҒso9i ?=[kU]}ɒeWuB6[՟ -+9pL\4k0uGhj.uƃ0D8?q*XPނz)'3NNj˱NxaBNi}0W-ctrd LߣZf Sۓt ϱf8Fn4!k#~U\1`Y[{u"NL?`8'}RS!XyjuIh9QD^ƘR\ݾ-%0瓇'ب\3P~*,*ǓsoEm5 w01垅m@NI!R8;(YO3u 4+ :wiU d$Sl)J1*-i᫹m޾cw;co*;(43aAU<ܘYbr0_nLs4x\|=uzG 7w \v*\_U(|ǂɃYW޶[o~òzmv}s9xūvbpnIXjg 6tEkiCZ/1 (+i{[.K.:X'~޽uCgb󟏜y˞bx,4XJx>x0LU`W`7qGbL& h516l]S'rM_am"jF똫5r|mɛ>BkBîc -hNZlHU n{D +Gc ɱ0Vylk|q8[a’쏣0Mn':NiNm>VuxeKV^A[ ꠇqڎwUUuWSuP,FA2W_>wƬNUƵ5ԗq{"4 ~ S] !u!3c5rm8 DGC>ԡM 8 wX{ͫ6AH2}*XjUG]?S_R"/,I5]MU@31ձJ=XwJUv\̨E'a-3v`ܱ JLͫ3VG[c0fUaR*&u5:u+'.Π~Lb (G,B)al?v,Ue mŏgYn931,Y,mFéØN_}CTJ֋SoKG;ic5Z[(;NeU_Ux vڢz 곭IktaM$ik?iQcg\^8|TJghrPlijO2iZp\-`L*,&CChwh$E`;Ua K2捪#;؍>"鐾9e齁kʊT_蓅|U4'$%0 0IM8d ɓ9ehXqIfyK]??[ APL0_'2h^x'RqJS\?Ww&"EXKW [ݮڍY0:ڨ24RpQ9jfHzLOؔהT<{ ;W*GYPRI"ֱI(c=`[5+xAe#ty*q!t9NI|7Ic.sXԄ̦i,9֙\2$-e4(ʤ)*^ jㇻu.06(Ib7Z7xy n߸WBhz^x6_KһFN@؛5gsLH2hZKW_ YiLa@j4.MbRDt9ɚqISuER\XiP4^cXm6b-=?q W5CL~] @d(ӄ_Zwb|cM=BՑ((@3tC[xo~:#k/М\xu`OX}S,Ä\gJ.bST8./066N< wo'K@ 9G7>VۤGge͓&^4b%F:RJ*o< Z.dErB*e+WC\C_;.]#]P W^tb(g$n8_Ԥs=3$bMlk. 4[lSPN&$HT)H!QhWcrn{EE.ֽ`u ?wj-MS {! .(HX^ҏSZ@TF'4^^H,x@ w=}m^;}A)| 3ܧSK ?!+ۊt*/:sPڮ#`+"e9ϐI^[ɩmc~a%Z73'lc,w( !]og5e3{^Fe/s^x?|^D]%Þt2]CzhsKs\/l˞ 2 jk N>Uo;J ›ʾ V/svvnJ@W=x: +:S v!YXS Kl(:y*Gkf|4LܳXOQ[AFN8a8A<L 'MYl|2 >V@ X́ĭ Qc|յ[K w}v*,xRzkUsc3RI١\l*|c*(;l:EAE:IHB!uuG׊]k0c!4yP? aȨ l);4f_I*h$8lfTzcfE01Xչ 6ǿMB;@{>_@(S|j38CG;9 tg@4$VXZm-L]ăS1,yʽevdTq Q; &ջ0hm h%ҩ!"7o-׹?rBߍw< U%p~ oꞾk?kUm+c^x,%5NJ*Oe ("Pt (\J).p֐Xus mt_V q&'csw`c<8q# +KIYEtR)}x$k&“ARَ [_9U_DYn05A n6n1p3q Ec<*!YכI4Z,3[cΦJO3v+Ck >x;>">(=F14[3VC#ӗTo0缐-= q#L~ІPqvg5},aFqr IW*#t羹qRv5 :>\%]z>\A`a_U NDkO_ҷ70߇\ː=pec}o_QSOtC|E;r= X7faցW30`tRŋ^nI*yS= Chh{.'hFcPܘ3zB)H$fo;o|~U"ѕn <4?ٲi6MN^UggK3ڮ cD, ]k#Ts::#,䢺j^O:DZ~-;Ygؗ8(Z^sE En:Uy% _I=81V+\'vzػ; Z"5y{իw# M@nKvO")qLk?Bt ĺ4]>I6i8ސ-D r2ԓu3f !Ȏڙ\G%:  n\`]sv'鰯 7_wew';whT&LO~1<:/!|'ԑ8LgAG**P̽%QWf^tϱ/ygӅQ0ЦԽ%_݂%iO~k,)\-M*4؂ R] Y7.:a6:3Ymbq,A~_ 0AlA]Z7>3[B, vx~]\w٪lNI5O}18'#rtߣ Zs%xԚa4p-|$Cn"2AmTY͛+hc?!CrT>dTdM`o3 %Lоr ȍ 5)s@mm_\T?/> 44.F!, ėnV  ?Oė!h mH1Bc6و޴Y$s%eBhײL{( 3_lZC DE뉄֐Q::W4i";AuT\Vz:p¬s`8|΂ Qա|qߒ:|pe446!";RZy(1mEj|Di(ʙـd7ofq9v{\$X7rNr1()R"-u]V#YmS<~t56? /ԗUp_Slѐ(N?(z)qڲz*Z .x5u⩂{M??J]{.:#W]rT}Gqu]L]%=Xs=p ?  u':/C}ƙshG}'O݉ ,+^ \rxDn_"9|dE?3No<_ Pe-s'Ĺ `XgZwx~\3% |'S{+6!wamz~vp]t,XEL/ f:ϔyW4"P`-65,?ER mPuԖ )Ҡe@UtpSp*n ] 77v>'peΌM=8Upnχ m}1.4<Y" C&Yl\ iq.A!|0^=ey?%gLV΃w虣;}߽Ck|v.H s?8v+X 1CJz4ČlQc o[ibak2 fF .ԨPFmTu^='c0mTW[ cA|U ㋄契q͐`1wiaBgRiیT/Ki`99gPG!64ՂNa3o\h!/=Ԁ6j}1 3bBV vXpw6]BЂHxa\^Ҡd0ȇ }TlfI%%C"LYLD&*IgMB,.'|̼1<® qcRw o+ח\^E#T| -]T$@73ol4pg^3v1u^w|.na3=qajC";z8<9m_2 Ņ['j3̪xG!<9 W|~z~X~[ošyI(H@NcI ~ҮSgt w*)?@0xqځp8]clZFQD aFőUbd;tQ2q*MԤ㚆^^ˈ賦@ +EЮ[jw:F\}rSL ,c"jPUt m)G_w$S4wn!^f$#ln=tyK$7'%Ye@d=Ld"I:Srӄ O8+ 7?\Mm $2$ wwv8G:/qu[k e+Vcz&IU'u@j"qƽ;B S/Fs<peؖx P/tx_d^>遃|&Ɠ?yיK!p' (Fڎ6%z܍Fn}skbX9982 kRĶw6NRspA{=* zԓ֬u]UۯgdFxL@ B.,aHi_;=쨀jQW`2jtm[s!J}5UnEH .0 q>@RbF[z߇g^2q#kU6r:Ic uemt lEW" ߥve|kiT\p'Y#CN^'=}沍COkY^[qZ+>K&(@,E#T_5+'HVCEt% 1._42ԯ,&1hxƆ\f?1g"3NtZ/ d.N+Tz(*  &KVM G;*Q;]m~^(J_/;/1ٍ%x+tg' wBlΩW[=ϙ*|Tt 5yRjua<kw?Zny|C>8 :z'ӷc)?6cE XCTay m pةq_8t g'~|l4DXL[ks`D]G:XԲm2㛫*R΋@t/UNZX@!6{_a.% @M& ZOAN-u9x> =?9CWYe-rf"Ï Ki`3к:<4XerHxyڊ9Ge"ҡxTƥ3Ș&L]?{b /~=hˆV!1Irí==oO>QU}6:;WoVU~D]rv<;tЏ3Z;#B[T?`|x̭Y U.E.;<~~s-y[|6N 9νUX6c[Z]jM?,TΠxls0:t?PUXab\Qg:Q/@a4EEq[:k|և~0_7}1!B`ǐh67OWӬЂJ4Q|lK8y7ޞq4;+o`b69P:6.+UE2GHRtȓ8Pv.1w:,p(E>Gvy:Zqp~u68!V<7Z4C%d$}ɂtũ9#x&?C"@<@YXCGD#B4t\'ٮ:h=9fxuDP[FdV,Aɭ,4r$unFڸANp cpXTQ~iL7+'ꦠɋ! J|@B^r؂Vd .n_;i@TE$>ɬ[o8o`_D|AETI|)y6(2c@-p" s ix;|lqC; $w&?u?Tm5Rw>;Žonsg4ѭ >U(y T1nG>$s9¥2 !iP1BOj>ǙYg:F323(q+s򘀓:i%m*T"F&D?–F} D)iÊ:?4"lUd$Z;~!/-= g M9$)/m3nPY4I+aoe@ ߞt4qqB"  os{-= '% UgZc' s>ԡ{\OxI)j93|,VFdbBVW^X)N|e8k:i$C croKA,_~GgDs4#0vL#U$yt$kF]ZďJn`KXa\l30}[ŜK7L~XBCGj42p\47@Kgj^4Q8(8 z '\q ) x$[B |76Fxk&yo_bYqA6Ary#(ŷg~ ,vߤYRST|#ު$q3㟚63*+&>´@WgSEn`~A v^o,h}B]Sz<Ļ\).*VW'p*^'-Uuv] {c=y>xC}cGtB7+"EB;5eW^ ΋EWnEe"sj" WWj̫'傼'ԃ\KvfQ>xjmiNT^t-nlɬ/kd$[cG*i9hv ݣ䮆M]]A8gٺlcsJ:̗g7F&&yzfK L^bn.. zmcƌdj͔,90AUX’&p>nN4mwՈ1GۚH+M{?;=nBAQf=O \%epWpk3 W}# h1ȏB3l1y?G,/:GKёLfI#8pX0kg{yxp9D}+e,v<')^NxQy{!tTN{3sji#}iHsgRtp|DJQH_-oyuHZ0T?>T;8ඤSN ·g׶S,aZ")Dli:Kv!""."%eϧʻ&4Z.vJd/]/߾/u8,tBmGH`\lJO\bsMl5o+m1{  89k-cqBsj @mBfgXEWb@l-_Yuk+EC"]&mKA{YUC~%kc!="lP¬r)** ϚsonLt'Y/[vvu!%9zk+D #mwY A'(9A΄&ԢZ;h.xT wg?J4U B}҉*jT!Iv{oiH5BdH8=l6QvR^f)%A4-MÄIbdf=?OF6ZK sF̧cLx;s/hfsW5*,yK3&);!ev]9pĬ,-8 ڎb2asJnjאxG( pukuB xX0l5NBh'͒ GRq`^fTaq\ x~/*VDB-J|۽t@]Ӯ8$5r#ƶטGf'%1l ,DXD2ƃ8o#fX)|G~E 'e\T)o(ѷOյWؚ mDλ%Tm8R' O&8ziqʩƉں`JyOާ^(-VHO+ (F\`ZPbZloc7N NύOsifm냇z,F) M~d qM"osN~Yʴ#ְd;x~Aq{A% K67LYdK|qЏZzLqI̬Q(/ɗ;9ζ*\[X٩ʕrT ewV =ԣM)SMC!C%D@4O:)|TOi-`)a` /V GI"Ђ\N*A$Ҝ;Jwcv u8r(jp>eꠙ^ aMzuӎpo15TIN% k&!ضX[aF8z bk /Xp۹wXGق`}'~+ ؼt_B' "B(L;<4;3-ccm{}cĠ,kr E!ӳ}iVJrf*aHH`ѲpKS7]C~pR?.ǀR/g ,\Ɂ:c#,Td1lߚf`xܫO?9ޏQMz^o'e笪W| *]rJUI5-<@u?V㟝x : I" C#`G82w%=WՒM`"{K[ q 1Hy?Dp' *\z"a?}_(t/ZlmwCG-H;oG)\H@6m)IYcT]%Lfc 4p:!%&O7e^ǥ[gh i2DjJ,Ȏp!Vpk:ǔ$]R|;JV᯶ r,nEM s0οJ[+=yBa"ϑ+bh^F*IoU=h!ƅbV?#BmxK.)^8#;lEzUdkIɂyI>{\bX=U)啥|4a[ q0|4[:&$ njVv* T@|Lk8W'hv)/;6|G޾4qۂګLJl P/ |$Vޚ3lZ7P \-Y!V][z/#8XgTEn]wL~ɶJ9- ƴ'.v@' %0ʃ[u3۩L+^1O9}ÔI*@3X+3* ևko<×vc2DenЊ) }݉lYM ĚJX@Xg^J |ZsLIbM֮B,8Ǚ1Iz.kٮ45cZas)ǀ':0yyS V'8T:B;&Liu8kYQ\0(t{Lws1Ex!+$u*<0.9oвyD]<NE]"3/X%WNzMXbɷ9@Xी7Uzb?sɈD,{fHaJ#(;p}JQ~y Z$@2)J ao0r "FL9f`@dyW{i߳ 5z8~;mbI V"Upt&@c9x3^}j"vpbcZ鋬qQ-]E*bHr,錬va~GbM)Jkz-cR#uHEx  OS̓ cܡ6倞Ӑ}/.Rg{AR.<]:Axi&u. *?5#J}r{SNԞ}pm3jź"^2_^6I>?m^RzdI%P.( 4iҤM|byAXdN'͚RˊL tL4mK${02!HX?9sA~iϙ5vq}]H؟Ƿ09qkrw7U~!]2-wP|ϼ-&q[>ӝC ~#_:R%YF0}ݴ > _]L~|?l&<'x8>ޗEfs" qLqUR+ʕgOԯ wMރ J9 EE.r^Wׇ`'D 7~H+zczR,d㊷d<[~~C˛%E]PP*l'׎%m(\웪;PI)Pf6x)^4Ui^`c[I6͊K}± ;[Y|ULtYPGt?og)M}x5+}o[4(?ž^G  6?o]3~pݭ'gprfٻ|2k֡N_!G1 Z,hMIYyHR8kSOu)Cg,o`ۛ{ GذRO*ĸ Dm! ?k\f׹r8Vȍ6Cˊ%|O8*e>L'~ǟvtH'Jfp$uyf>rWc \ƕNqL"W*vtyMxvuxqȱD ϘDsVvkNn6N.E>*Ќ?Z!G_4]ڟO]Pj걄F1O  ًfPnJ+0Q ygnL[]73 NT"BUA퀙Z;oAyT]siZ&aϺa\!,KY }Z+queew$Ot`d@#cm_qw]XuqD `?<-qޔD}‸PFxȲ 7tjf6NOٮi;ΏJ]StR[DHLqm췀wHXs0sI $BvRѯݘ?կjyߍY8Ϗe+P+9mS\K+V{-% æR$c`YEAgLBbkVNbʌN JK蜱s4y pkll8-mn{['D~QŗQQv{ߢ`֛j]X=qctKːKcH^K-Xzhg![QߩHL,"ƒv#^Q#rDL0x)cg oJoɯYPZa;DZ/3TbXMn͑873C6i^&t47#/4dT.&nͅdV3r2yKÍI$vJݴ{}olw=:d]o|#hi¶Yh4:G\C?P;y\;e<彯?䲋)࿠ч*^we,663p+NH^ƷV,Qbn5d#OkMpK+gԡ#&I7ǟqj) uRG+7@0]^J悢&H*"hmG51j8B^6/IHS\bǾ_bo'yFv"HBɄƷ[0L5w@Kߡ,N^-6dEN,/]3EE IA3k,.rVQf#nykGs6&ߺ K&|r'qpc:_s\Bb9o7,:W\n˜e6т]g8OF;w jǠF$zh(UK߿~OVղT{_?Q[%`)9kw>O%PRІ!$e/Eq>ƭFЙ!-(t_yIn=om'cוroqO*Pyj:|"OA,WNp򙛶_[]EBdơh! cFƃ&@蠿h=mQ(GSuߣ% *%v(H YiQY1+7hg݆NWIXh: [9N3st u; o,ˤĘҧ .)5' #E;.j9VA"X&6Ac`ma[X H e/^FB{F'N܍73"gin OF>+-Ctmlkg- L%!Yz}Q[H?nu#q@`zʉpICFZ䓆y#17'go0Kt>OT7gAmXaw::Yj]Ѵ`nyT8 yn/@hz֩L? ^#=x)[(g ([Fւvu;NWyl 9m&Ld" #_ǦfY5Grŏ'fq"ώpkZ[,;Dȧtg}׃ܺP jYmljġe8Èj|;sT qw~y2GhSZF) ?v?;<208ÙQ3rԳ;N&cY2uVz-Rj\r+UlAɨf5Ԟgt"!6|P8(G\H>懗wkK5K|(22* P6b1cEf8[ƻvg_ZTˉe7}~n]Ӂz$4? !Px&k ֪ &ۛIcQke9=4ÙA1w2ICc] ( N(=&}+Nӭۮ-ޏYZC:bhml6TWpσ*F#q! WX.w2U$~YZnԶH{EXz+qhfdH<b1!GblqMj1EB[CDC/(QsxTa42`&<RnV<_Gx0@< )3\r/ڒ_0@IDATC}X׹Oki;I4/Gcm$$kiţOW`b./zE\>*#a0V f8nwʫ^Kx ^'ߘsF%SXTW=6[4vdѲj2<0یyP4KWN[''!.I1p2ޑuq!bazٟa]- Zl)/-}Hn 1,S5ykՕ#G]?oAX<:D=di.)!w%LOOf3ffݡ&*c C>Aݑ>UޅvZM}/* x`|;N<&1>pCuA94(^iikǔrtYO9gVAsv~ 2)-M-z&qv*XG™:[e56FYNKc+YxV)@4@_}DʧJmi0k9Jux:F:&NH21dTΩD:ѫc,L5'KW^Uo~N2!ݟoR]nK.:_=Ip~`R_qQ}?#Y%a:}g!Zv>w=CFɯ}7=oCP,ao@,IzvM̨<.lYUB2Cb~6Ljw`v'nNDÜcHٕqj5L[vu-vnC=8}[1>O|bOݱ>68Z~jZ^v}U_e؁gKG=m6!+_ nunj IB{a/)EKy Ru%Uzͫn z ٨M:,s~|ܛ^1TD7|(ܠLsJ1K9j>[OĘaʁiz8 .%vA%RC+U]rp"ӄäu?~`.k_ )܊aP44sZK&;K?0O]?ч(|$k!ʬW|ݲ:Mj9;Vb'w1x^~Ͼ$]QkRڍt)GU8^B~>pU4# K"n yv] UlR"q1)i2 yO./R+&fK/}e5BAZPJaDd =-kCjm~b `U6NTi-a4QKذbQuzjZ]G#!|Jiuz ᐨ#YЌwxe _BY6dsޜGPB ىصqMj\ @2{pao~ê_܃V Wil~]lp5߷`*~=#\y>.K2H.t20 ړTc j3_H Smf5?҉B& qԳ؏*t/o H]ޮ24Kl/1 - VûZ"훷C@:<n+߿tǝYlׯb!l}~V@$B_*!XnbNQޱO٥puDW JKV G^v&|8 +(?vT-,_y>GVmfbpFu~sp@ ggVnaM*}4@7h&_a,6PcZ;8U~@Vä#q7Nj1&Vl6Io,1(w@رM:zt0wU#iȨ~D}h"UoC7)O=wN#cUAyJNP *\Z/ax:? \X*^p~U1c8> 2q`I]v7\6n0ɂE}#Ћ\ Uel$mYؼFZSXrEzlЁdͨ$&,٦DD}O8MxnJ9L*A$TLqqȝw/fԨD[ʚIesDpTszAl'o\sJ'?o Lh}]Ӄ; ~9R>#qyIg?0\ nϋKU]B#AF`Ú[jp^]:d XV:耿7#&\8#W΅䔴3wNt-|2󨮧.WĸEaE_巢<u5 Z? - ^Ң:_Fxo\P߅f8mxGcS/52J wK.~E9 jSw#YșQ Tz ªC3f"[k)}+8n8Eqe#_բk&MN/dT^D5!T:yAz*?kutה[g̢_g$PI~[1)IUwhTsxI׏amfӇLԿEn_ΟL_|ߕE[r68O^5D==Q Ljāӑ_|MGŎxki&||cM]3KѺ@ȇ`u?}`X1(Rs]8NJz׵/s^Nx1%/d:>obqPj4-ۙVY­`!* ^6эz8(uyP_!}FZUBjEw5ۨ (4+DIWc?Q>#g'?8Ń?4+SsK? +q߂2|vA1gw|1 Sx3+<Cn>Tn=ZX)l!sT_RO?G 0El"L:Iql$qAД,ƕ> ˸l\ɫ -k,1.-c,OM|evp!ڐͶf{ݹ`l6.RrScZ>m˔ MTNgaI-Q!!d<A`_pPY2BHKd}>xWչ=q ܮ=ւLƐ\GudVv{{pN8!Dַ#b<̺9hm>ϟ6U<_=:ܷeaQa?~lh=Ŏ :6N{@qy}uKO5q5|ԵژeFr KᆆI;KeaU YrJ8/ˣ#+IДir;X2-**Pc1dzW]_XPS,'X2r ܚ$g۸ԄkApH[&4R_BJ'"7 ^d+TP-Ҩ=Iiڸ$Ff2j λZ_Q"9,yJ{kRfs*Wuc]$,5vva\'!?}ۢ}Sѷ3tiueC,,CGZ,/,.|c|g9x?p‹[շ 9Vm /q$(z-jvxL;~4UwݧԾO_aD<u$ۿ-S@n?.EC >Vc\{uZ0.7[UC(N -.IMϖB29:b HDZ 1B!O zӵpN}wbqgwĚ2$6,&C:;KU%f-NW}=>Qԫ3oO]pצXG&L'^QtJjqf:둟/_ꍯVꝯSjq۳OܩSK]/7 tuQ1 ,L f0ON<(S8᩺Qp}NZWm0YM Eds{K =-f~ESJ`IoM*Ŝ`ؚȘ.aۼ&XevU, 2b]reB!jgTV«&̖}uYSߛyji[n|}rssVok:Шۛ^֜/ssf[R¤8`\55mowgC(b?<63^;SS qmpy0=cj0Ŧ9,gԫ'Ψj`z C~W?s]kIj #Ĝd.Os(D] 83\oK+;Bf Q[-E^/}nZх)B#^5!VB="!tbΆnY Eww5W_3!#{EuS{yb KTvHࡣM檽on~mMsþ;kf{AY5tcoma5yAxڑ+8+:k^ .+XiSSoRsMmj~r﬚Qq^q~5Ѹ]n^UwYfLc7bTҪZFSR"W]݅z[f]o1lv¥fH9SY9/|p .1gU(tcvWMgbɶq8%ws4+[{"ng+k3.ԋv 8ЭeEkU{ VOU`BJ遀~peePehpשʫ1Rv4/<=^SYу) +sHxD]=^y@VKUV{J4S7jўꋶs4aeԓd|_H%=uScKаk)`tOη>k.4{N~8J լQ8ɧ%nv)ֳhVx tƃ<*@(7S/t醳BC\4/Xp,E#e" lQQoBi$pg 坕mdf`ծY䉝՚m3&, ~ZHPdٌ~n12M7{4_ Y ^CMP[ I `kɂ/rЩ5:¶ &*+2!<$㩬T֝jYWnḹ"2p*nJ }"pZYE-5NCT2V,oңVʏҘi4,jƲbjF[6Uβ S6=X8xRx ]f~`IԘ[ruZ82$)dOO=m$N$SьI kgG-n&dxu}`~~m~O8]_u[3/6C3VÓKdmQP3};QG 2ZؑȀPm]tbX*0|/-8s-8ܫ]Ye&=w'm?uSu<8[LӓbNҸxցiRVG͸| ;=9fSv`PrxG=JNH|^'IGw`GO1":$aiNi_ D?MeQ{ip.ܾٮlީ=%6;~l]h_W݃@磎 y" ZfNgkG-R.t{O[Yݶփ`TYq2:j\UM.^NRZ U:9u;;ndeFP ,j9T?]dKA?OL="1F?l\IG%$#&H܍m$@A28 i(vRg!pT$`:#˽Y +Ӕ(VeIYօ(q{^ަ3T٪ce8췶_b0C ~Ruf iii'M߭_RjhSYgnj:dtYQ:}ٰ ؔesF&<# ZK CX I G|.'b* %nj͕x$Yyw8g3*YDX^(v 㪍.ˏzAAsT+EiUr`' `llXy 5X\lP-G~JkE0>06\]lR cYoL:fLܶ w=|Sn[Θ7AžD:( H 8'qpgB~ki^$hEٹjܬKJ~_X #`# Js "dA&EgYYl,nUWչL@( ]Tnk/PrFIla:L9`,fgZlS[f{!KYԴrmFڱRDu6ɮ[ {.v+sӭ_ Ubu:`fΑ|xe'oKM\CXGZη}F c."] (.b+fnm~6Kx VbB&;Q1[ZKb#n.=+S- ܋i kREwV@STG)\)€X(J0#/|[xI͒W "0PxOj/Q&f"JEkkshB}kv}e [)ܖvhm1 my*Y7ZtZ=]}`N+鉩[?٭ni lSŗ\(aEj3nR6jvғ(εlUq~ hYdB~3*]΢Ĩ06R`kp!\WvR-5iY@,m˪rOvYPɢKٖRfr$)(2JV[pYx MSQ(hvYR9y8Z~y*Zk޽W؊e&F ftk>DBQ"Z8{a8W]yUGLّot["IF'furazGe"߼7 9=߁?<s"0v|lp2etGt(.{tt  ki^eֈ>gmy+SYa`9nHembVs's]9XޟߕNqCq84ϟkdw?Y駙:PBꄥkz*B5`wKS-:B h$ [F.\ aKwsKL .*r:00a'n`W6goXNZ'_0lc]m h]Ԋs&kQ̅M e #^=4326yP"m>w579c8LdZ%8y9yفN>n9 -٩λ{6gΜ ~f^uw4ysste  Tr^oP/k| T, tSdX583|@Z@𤘨F _۴WH0Pyg:YU7K;cmwUVj9$d0 [tW66\&[m: DGӃQ|aS%!W9Qӝ;ygkܼwwP3o3c<3DY,Gr8'p}Rjq@=!ӎ YokR6\iv .] @ukC+p0C69-UŊl6(9yvYɢWTtV9+rcڮ(mxhsҠ%7PXxuANV 1[K[Sx{j!Pi;E19cqSo͓.?`7qIRnk( X`qYB]J0@մN11mjY[:iJoi oVٍBVYL #+ T~,NKtH~[lKȩ]|BRj.Be߷%zOg'<q>ylޟ5z%=-GAD8x$ ]!^VD;Ȗ@yZ~7;wvv"Zk. 0q\itJTE4! ŷKe;u#BeD'gPiBtz|L5C-"!?7ɷrt5-EzaԽFueć0?,{^ypu;i{?[[BZG}կ 7꘭B{jA+-[UyRǏo`R#lt~#/_& *":_dHj'/l~L-ZX yf*Ў7:Z\gd;9ŭ-U*(NolB/kL"L$0*M=~qWj%30Ξ41jT/ !cs ˪enY+W a%{M_vVx2֦AFi%m0{]~u1V ΞhdųrcM1jAu<ʑF,z߄~4L%U~<{J+ } R3-n- U3s0:FS=m$͙8=~uf D|s^94ҕ'"6WF#|VEK<>,nUvWkF6geErS:0V _knj>wv|c?,3PZjYr|Xc5 iwmUlCU#M"z#/ oGs6}xu*̄܀]p-(k֊&ng7!Ah!ܰ喑oڇ}:R Xé6 Bu ޠweCX̠WBpL s}$eb! iB&6W NOz'6XSズET:e?77NYgüT7mpz3|A(حDc*aLdt[u[ ܜ;wNI&i/@[4;3>>hUqؙ˺+5 + Y=;dGauKn̂!ڣ^ ֧ٴZGi FbCc5&'[yNe;Q`+ ye"TZ`M vK=\R2ܜ믻nϾx`ht kA-lt!adNSY:U+aW3,j08ă ެZ.,>@4Au;}[em|Kdՠ'Qn4%F\ |~]_?7\mNlO@ֲ .A`EɾCӖP՛o^8>]uդp<~j@#f!RC7+3ŀЃ5 < Tm -ꥊBP_^+*@TX foT^,PEcۮ%AmsH__p{20 uzW_|CUf@fjsVN/UDF* ?ߛjk3qr5`'^J.<^&FQ ەܺ. g*c8Kޡbeqa#veJhʾ-vG (Bge;e"4]æDWd+@Ie,V:=ðw gT Vg?r?t]w6o[U5)zK/_!_rZBEq,u@;LJ%v%Oqp-VSԯ?Fs 7n,+W~'j*RlB[Ϧ١ؾQ (RyDouu%f"cۓVdB޿Lj#Na0cZ]bti~\n;1Tc$E2P9-&Sa',pTս%5}tٶ}zMjۯ// ǎ>`$ðSAcK_`X͕Lv]y wpf>"f hP3L7>7g$88/,fAe| %8 ĩzKY`1 ꂨ5z*]zUٱ^h+wΜW?{ 11LE16F8JgQpYLEVg&M湀z7VRWuiSuZ]SpF;ᤃn8pPjUY>I^V*f^wӟtsq4KesXnP1";a$0afpBopٷopjIzٟ?3KrDICp{ۖ,1sk/.AW P4[/s6(cn &Ad Ei2qP8'{H*ndDPo,/Tty48~T˪'J_7QTױa/'?ÏY3@[8d,E*CTao޳gOϟm)٧lP-[}E!Wbέ~P8;=sz"iP`6iA&黔f +'ꊎf@/ocGa ?"r€Y+S P_8ux|qb+`P$H]]{i~_As_RbpYFNeU%@LZ?86]ǎS?򦦙DhԷۚ_knMZkI>%6X>KȽn0K#ˑ|flz_5:7ٯ6 5]BDNFvW#%@IDATQHG4$f*'v$mޢy׻ovܗ=ĽlH l7.[^D}1r6{tSYjn|:?N0ifW/ oſs:g-`LI6>[iM"?TXӰYUoQEv'Q 6Ij8zFb`hN- /sΌVf3b]"J߰g~7ٟ(Uh+믿hɆ[85^Ce`t aASO5.wv1`hOǛ;ӓom"t;#(,^.lG\k^r++6em*<(BРt0V =OvC 8,+-uB_,pWe/(SkV#OceS$P mٳg _\Ϛ/|OMs+I2jmFr϶XH ({`W~ zjq5S7V&G,M>dCb3PEV*Hc#K]嘭#7ƕct(i K"]h/>]q/~m*M؋W=}'WؔHƿ0~T/MA]Bz饗ok D@/QƛnnTo6k>yA¦sYwm Uc=3:ʄ\gx+" b98ؓ]¢pUj`ZZ$EJHK痹+aLbYΔq}6G\ sX?_f.o4검#!ǠȌ̀\<~4;vK" Ʌr:~oٲ?yFsFP$ ,G-J6ӱhOp7Tچsk&ݵXM4e$ӱ0@4n[tGQ1Ų]Ks{~77޼؀+eGy4e:\oQGt:2kӧ{>`tS"  *@w,ŭ^j?_ xn·z9/ZglG`YuVhf#cضLIڳشy0+K#,.wHNe**ʥ++v9 `prs$qvIA:a Νen\[83W[9g0P"aDRNܢx3ϧ^As~ S^cZewVj+Bv -c#h ́*Z["zi"a ^Qk[o{﹯;cUiɣbd>c ^e'?f1[D"z4387c8nN?|swMI$?3 1ᮻ-0UXܰvlLX^VFlam-a@a`,#{kR-'0k]\C˙n=Ç_[r&\!R?%JO]@駟nnQ?g_&d=Rݠm md\3aM5s`0}۸l7=Jf>*€0 X ,mwڽkwh~=.8׼v(&0 7Orml{6Хܛ=9elYGbVg1ɵ?ny~[ᇿ֜:u"JHT I05l0lv|&$[T)>[] Ip R& €00ӵʶ._k/%%\m/\XCLHm*}3mRĉW`v2ۿ/+'rekVA.~VlΜ9mJ%$?*d&,AY6 6I 3m3u5WXMr͛vǖn26{P|"/ €0 Z=][fku o;F)t4ץ8Z <*|)ˇcsm(܋k_S߾h^Cfg71ҍ1ɗv$@|`䡇n~J% H噰c]l0l쳄gMpKgԚ71خ\7gr H,p5 €0 <\;7BHԀG4-ZlrZE&xHm5}3i^=8œ?%m,Mz!<_x Q|5T G*/KP5 S^XBsd-M(a*°v !INc;+ @*ҩH`Wޥ1*#K)ɓc D㪲PĮ x?ܥ\'C.+ƀVÊ8)$\G?|_i뻾ٻwŵPESoTȻꦸLdL} 幾pCXqVkf38PDDh*2Љ\Gt@Z.|&1toõ\n pwN>5{̙3MxMBa"1m$0;ڴ/;h?8 a&W5;k֐KQtNd5N٦5]CR /-6o*ff"pmVgn3"vbQ^V2qxMJ.k \.:EJ!?vClCm|I,#$ˮ{Iz\ǩpwqetU%T I"}1 \w>DPNX/]I.F]j|'$&cYu8 1Ʉd^@`bęܪEK1ѽQH'SM[T̶ |믿Zr~Ȑsi\# Dp8qk,_P3hW3t:e ttm].NA$]bRcuÞd2&f9\଺ܪS$o(rظ]!#wgǿBvm@dwύ;wyGRW-,I0' €0@蚕 \BѼb=F>p8oʘh/@3O<Ѽ+d6awA$W F? %N6s0Çw͝wܩ#Bdƻv+ō3/1욜PyhMgْ`Axa@֐̤o^S3%bp8>2 E};K/6O>d܇dD€=QEpi|o<4 p5,Xut2LŠótչ3g`MӘ@$]kgN#So$p0RYa@ 2z (B=(u9ǚ~C*]>q|eQ/Cv#y?<+IɄ-sG(mX-a d8yF\2Gmٷow6o Zg!}O'^?*]9i:f0A6 $;7dN @U8Vm5}WIK86Ŕ[Q/vvX}dzMmlDpXw9 v@.Izx@lM7l "/94M{a ^2Ά&Us}ٝ صis ynLu6`ݛmeJVשqU\:n]n/qh&)$^?boRVd9,Is9rd~_7-wcim+\ ǕbFSKSwvܹ[;]n,C/N޶ 쇼ZhM?ft Ġ$9Zo.n$uguoO6׵$-= ;Ӿ~2w_ziMӻؙ2-:[q|4I'cioKڷ]ۛoyfmIz_Bl8}\ x2u?QlS\v\a@5[;ۆ~_㶻L #'ޚ=嗛z/S51" H`L )Xa&7O4'ƬhϞ+na @@+E) \*ؼ2dFkWߺlJ^oMͅMYʄa@ Bר hR >Y.*~k>? O>ݼvpg;wϣ:< id@\wN!@,}@g ʟ;wyWg v1 \}ճ5~M`Ϟ=ͮ]ݻw^%m'r*s1t1 WZo]>'s‰*&€0 k@ueHlbX!W7 A{GVr… xAɓ'gHzw?*cʘjYSd52'n`50hR5sɶ4*ΊfYpM&l$iza@X1ъ޺;Ƞ.XA2g8|v1X;fM)pqp% OP1#?:H*%6.@W9 8-o(^αK|F}Eb~9m|z&M,!GuH €0d`D ު\3:,VUo[0㷧 nGX0vFL=P N*z5 ' khp:()kΩVt1oema]9 ޯkf,3ˬ_}x-mXi6\y ;abuP[/L:8Lx^4n;DSf(ǭ4\0n vz+ 36 Cd*ĔSKuP;&w7$?̙3vsɸ+->I8:Z]V{E@=rFP|2u.8(1|qDFa`uR7ѡTi*4RLzZSutرpix>8uEv?:9 B: 5EVa2xqAvl&@ɫks숮0 @R>DJl rȵ= Znfw] Wr^K ! xv@hpD'cA*o뾷O*BAI!l:Mb4z\oc70*綵u@rxPySfwhgI;~NNjJΜ ysdo]ljm2A7s[6i0 #1p_+혵fҫ6cdԵˠ €0 )חJ,yͯv@\oaif% 0KIc[Sz2sRw[+T`⤗vFRQM D6y+.ue v91>Na@y ^N5\ 5W 1:K:5u ^{sv:| ҥKSi>D 2Ƣ]̼}.q¨M.5kdeN a@%ϻhQoW-]N @ y4UB` P܊|w'5dqzcH&{Swy"7Mbm{\ƉXd1 q"FqЪEɪAOB\{#lhHW :Π{H^4'YI7s/Ԧq:pW'qB׬ \%~VMsӜG9oW\p(jL}mM&ܢhٶx(Dvf;7cЇno=رx1lut| fvZ%$؋& Vq0Ul, @5f[湛)U{`6sOY{&cB?&3krQ c1,΋1{=G{79 2_{Ҡ^u5V=mǶP2€0 b@K.C,&i}*?8Ufc?Γ)1F5,bd]8c8wPYDV8Lঙ3};}-zun^mq8xq6&(. X͢@gϞз}۷§蓯^^pYN'O}pHS~q:F9),]+Hh^XY Fl b`ݏI-Tg@cyJTiV%:g 9wO \9X"?V^֭Mܪq6.a&~/@r5vP,Ч/ut2AYrG3B|]0(KF&tw0 yiUDHf:U3x[B3Zޗ>Q!_[k͙N2b#3eO4MHdu<+R;񗼡u2AJK0 €0 ]i:B=+%W_m*>1[l-1z ]$(_<9ȉq{Hy̵MVʄa@a ̀yMf+A*\f"^ďyoI岮@CN볺O(ǹN>V̘7Fg+S347Xǹ1 3NѦ >'I ]̓yTY8lyJ렂Mmk €0 ĀHe <* FP67$%XssQgn"M1cӘ1oXLnܩh`TʛC=tH3[1wi‚ mm'&P֫t|:- ,€0 D]D7u=7ڄ&Wtlz39x5/xG^3p:c=܌y36`wf 2 @ ny_ ^+%H\$n;'g^{O" к1a@"]>%JP%;򉵀I3@1?|cX I+Qa!G A1N 3PlS:fO}ݻwl߾;\X-7EĀt}⁐ 'TVۨE.9rU>.\ b9| Vv&R[#_z߿/]p=eiBL [1 `g-{cF b^"wZyC-\@#_{'j$D@/l^!ܽYu+S@SC;Nʆ0  u ٍlFmT_ۏqXO,k[{@^bGG/=zԜ)q#w?3n (Ōu3&յ1Ȣć08bR/}ǹ;ܦi0HEv%)rL:fZ!gd6v'3 "]f[Y &zWecگ?9r l :&f'XV;| )hX̠nL 07h={g?jPvrlcRm'b@g)'—3颻➝R]J ,OrQwvK*4vXܹs 1q1c4?f3vTtt ļوx^b7AfH \E.&XO6e60  L. 1,\Ӆ+P9ˏ=I?tcrb|hc\쭜Csddqb^ I+hSR!fINAI 1@B00:p|w~wl?/% ڵk TLD#|0'8& €0 Ek?s? /A?&CԘImAq̫#<я-wP\Y!z= 8Ӑ)(x>b૎Y?!,~pI0`niuq2&MzrC€0 €0]i2m׽xne90sOd_A5k۶1\̘ Xa,1ýp1$n?v0xU#Kz@OrwnF!H "t1y+P~%"N €0 ] 6k*.c,A 1֘ʃoo?c= z cOεߓۦ K}7*LJ>,nS͑饽{o뮻֫,ѿǴkz>$qI`SVO)al87|`oYR,6,C_gm]9kӰm^wHAl2,6p,屦i.׿\}s7~kgڭiȋj ٘3J>6p\qj2[& jG y'kܕLYݥ{ x> K\sͮ[nkׁf[ ;Earo . ?\<}O#~l0"a6*z\J:Ȕ5`Vkt2%n V ;%0|ƇKe𱶍4|ۈݛi6xK$wgā튵&W j\ovrsyp|q.3lCO`;w: 3TVf)`Ĕ|`p2w=42 /=j >:t$lcJ 7;1Ssg6r@$)=HvfB'PL};//< /TnkECu :1)h x;M c2MW|H:htA30%fqḟ1ƌy?cXY.SnN$q&i0_1BǕL'}~J]}Չ5cdOn/S9%8)>*}58)t{#jL5;$1S1 `n11C.4gܠ5 pvzn=fcU|ѫ~?|衇b"K$ ?HNAb P*?+dbe %0beh{S%0B a#4K+uilGLt$a ~7~s΅ݘur)|?f*?f`M8:bưcd.~ܠbw9d=n^ Vr{j9[N{~{޽o sm#'>xf 1g1{ȳcDrm|C]my}vU7[r?+ҐǾۙfeUV Pj߽ۨڏ˗lE&_KKOܹ6nG{$ƇS p˳O[[__r~ OͿ8=ҘaP|,PpW`PM znj 5[UKLBI-oO~z#{csRPF.~@J{E62~p,;*rmꇼۊB-I߶-6s䍫!ݡژR滹Ks锰Sm-`/_*Q"; 0_sv~ƹ3?zqmw,o_ǎ=y'^>v|/f6 Ϫ%pt(e+[j ,d U~#3F;eoD>pٳЇnroru_\6r72`tVp'$y )׹}Eoշ{,KX sM쬟2ח7K\}÷Ɔj󜋟Ǩ.w8)mA9mH|@]/ _x-Ξ g3?^T ^& bf Tll^\Yhj9> ߭OJtޮ=φ]w]ZCn~Or K3 ӾVk#W_sŚH/,я%08-Ǘ7K ;%0ۺ6>l A~}q6Cmo–\} d kzg~~?|dWWtZ"s6rݞkgNoZ^ @<{np~ a9&/ESF a{P\\}>Fu ` Fs9jۘz ~韾|O`lz^)Zt1N+.0+a4fsL̀"#ub  _G}Qz7ў\H0"(0#j}~r>O\%E&&k3N>6$0A1G muߦkPlmߏyßg?~OpvO1Iw X̻8z8-F -h3; Õw%f%.~@̾;p+21;;|[n|D7\簳YG>*wsopbH9I{ECP0ĽM\}mmH P}"}-K PU׶!c>xGco%'r ̄#oj_=oA=w9fpl 2 Awb-JX%%obokル\0t-VR>Pb?jvdJ耞j?_ ;gVǮmվGrmsحmvm/8f,`+>A|[\\}mH |A crcL{ZbCOa Jpp2ॺ>)Syn Aʝ"^;Hm?ww}87a/׆94ve^aN|rFU?mDFeP7%K`_>RgHJ=k?./]Zy.~3[ۆx1駟>/~_y^[txO16>l26\* `~ %|_>Lx_ZV@`w1r|+o+A6վYڐ@>V {czp,sb~J3%0|jlר}C^yeC L3][k쐬 %̙3z'<U[wjyV/' <\~6p޻O&p=7tӕokピ\0sQ|GmP0Ƈ|``mYѲ7%F~ko`MغE>*ூYkv'8{gZӓ̋ bg `Z?'ٶ Oc)+kpZrIBbU*^ ؍ouwu57|*HЙ!լ ?p mU0cڰ &=g@x\}t[5l˕}_Fm4Y#"Q> *yO:z^xyO u8` d]5s&<f0zLL<Տy Zbg pZb_=P*mF們J@p W0;@b}WPˮ{nWL %܌.Ko9 H`p`6 :mZ d-?W?k0gԾg6rPIDAT/E'c/_y`!cw356rΩU_}uJ??v#ēma@a@a@t+OMZRO&Vbi5V@ v$h `@5{W0 &a}Zb?&H6=jKg 1+!#:a@a@a@ 1ƓOq@-q2Kx+j B.|`K ST)0}?:` rx;3>qKAL=a@a@ax~ZR_'pīܘ.<)a@a@a$ ciK+,|<]@@)<ŏX5/́ޞhY«Pt~|G 6=ؾ[-?߀Hbi|)Ia@a@x 9 3y;5>=k{SUz pL 9=z;OM߭ k%̎/X I€0 €0 €0 `O1Oű _+aZR/'%%0C?&o6`8f`Z?1 Ւ2mDbp~Zj?/+6uJە$a@a@a@`0S1kLǀ?e0 |Q-;00OIh;f5Ķ3)eDTAk ۋIx* @$ oSKop|B1 /C@0 €0 €0 lxb,2 _V |I2Rl#xjVuV)*1t)D|-@Ka@a@a`e~ c?g6WԒ:>K0?vƀR>LԨYc@; `KU,<$ €0 €0 &2j;7a?^UHIo`Џ1c *&%6x@vZR~]0h J>6ZaZa@a@Ucmm_RK{ A?OtGI+K*ܥ 1AFpZH(^ fJP! €0 €0 @m05ZNty * wb5GW*d x2>7`О]` yZr&l €0 €0 €00tr uzwLG0SS6V6 ^c?"z+j桋5$V ` pPa@a@a@2A0;x"3~ ™0LyW_Ŭ)0MRtcmU_QA :@ʠ A|Nm|7 >Pa@a@a@23>z)cP^I8ݯ rf0cډ'+=G"0kH^wJLǬ0 % Ԓ^ibceH?9I€0 €0 €0 `XD֘ҟνRJO1O S!o_l{m`D SSx^0ec'%hLT~na@a@r `p-lR= ~^) )ހj{u*TSsޑ>fƏf>$ €0 €0 2W 1sOS🪯ԧ. SrJWwS$Ha@a@a@1'GՂ@Y N_i:k@O1MOn>>B*Ia@a@e`6V'~~Ʉ@B 1sS%Rkm`@0x%uC_2|P` J'X€0 €0 €0 dO3eȵmf`&@t~` A{D!0n €0 €0 f3'򘒏& ,`?`~Mn{`\t~|Os& @>~^=+$ €0 €0 J1)P x:_#x0kk6"@=x$ &~Z4oUta@a@a@0'|,:lϨq?6"mT=  b^;!@~c,m|a@a@a@X}?هcA>w0 *yןRR?;H^o\c}_ ˶@ %c%e€0 €0 €0 >gGB} KE{K>"72BTeJ<zcCP?Z"I€0 €0 €0a $y xŽ5cA?vbwc`[0pǠ=Cxo_3J0 €0 €0  `o |XD !ASPu@W? 2-lHa@a@a]RO遃F' ,JTCaFf )a@a@a@ 5LwJ>7NnS6IbQc^*Q 3JJ'8€0 €0 €0 5@,9򏀂 xj/N h_ivOa@a@>WpY, HB^ ~)@ @P@~A0 €0 €0 *k &S6ʮ%:t}0;YXおI€0 €0 €0 >x?ēxƫ z"S0(}صX@^f($ €0 €0  ? 5wIL$$!@. YX a@a@a 0ǚ_$Bo@9xMA_:ʫlea@a@a`3c61Uţ$]%HTQWEi 0 €0 €0  ϨȟL$Ib@ 4˶0 €0 €0 >?^9T "҄YӁt{ a@a@a^3GKL- bҁ#`fHa@a@a@X2^ ҫ5I`NU| " {1% €0 €0 b~%  fB} $e€0 €0 €0N hЏ40pWo؈2a@a@a@X%0A jv<$0rU0`-Ia@a@U`O1ZXI`bbP0 @^`%€0 €0 €004x>ֳA|o.'8&! |C {%qBa@a@X >z6Vϼ/Us2xR) €0 €0 kX07d^}/Oi0 €0 €0 €0`e@[iBa@a@a@a`zFa@a@a@2 +-R( €0 €0 €0 X€0 €0 €0 V$`E a@a@a@b@՟a@a@a@aʀH0 €0 €0 €0^ H`SZ# €0 €0 €0 X)a@a@a@֋+m)IENDB`ic13~PNG  IHDR\rf$iCCPICC Profile8UoT>oR? XGůUS[IJ*$:7鶪O{7@Hkk?<kktq݋m6nƶد-mR;`zv x#=\% oYRڱ#&?>ҹЪn_;j;$}*}+(}'}/LtY"$].9⦅%{_a݊]hk5'SN{<_ t jM{-4%TńtY۟R6#v\喊x:'HO3^&0::m,L%3:qVE t]~Iv6Wٯ) |ʸ2]G4(6w‹$"AEv m[D;Vh[}چN|3HS:KtxU'D;77;_"e?Yqxl+ pHYs%%IR$@IDATximYVw#9V{rʪ**i.cujYFB@d²lܒQc ɖжd-,jJ*3+!"au= ;ܽg}ݝ8hg l?}P8hZxxwAZxxwAZxxwAZxxwAZЭn F'=܇'[?8L {~nṷژs+~Sފß9zGw}-~a2yn7 N'WMqF"?*~ ȓُzC~ g V}50|8asX8֥#OrDx:gj{6g}hɾpBHjAc14|8̜ 7ٺzʐA8hT 0U:΀35 A@AсUAZ Z OX9fC `We{Wfe6$`Yr: vL7~g~??w}ѣ'BB>9zv$wT l6[jüA=޾x3Ν__:a%pWtԵGEgX`W;6__s=+aU]F&x׉5=u?Oo ~H\` J.F'׳yp7TJګ"v_ Q/\q^F2Ռr?''/ p+93}1~g4oN8S {; sZ`xNx 3ٞf. __O~~_i'K;e@m4ʯ?B?\z†>dq}/@\f4-r7nܸ[[*9yY@ .u6NPXxNgzh>?ӟ&Zc.d3,:~z!oY 4K>SN̐#ܜN :t^{__"zSgF5't[ت@:ff#'.y {7~7#GLMYtʕ+RaUOc]g?g3 {X)g'#|Vt) pرɉ't1=_~G~G~<@.2# xYv EFəO?}ҽpe3 ){Ҁ^2_'iUOM,%z>2G5]cH:b`Szg6Sk;$pǏys̙~ggu k;r2F=rPL.w U2xIf"f'8˽2o-, s\{YEM֩%bxv@GmQifaY{#R@ݜ33C?c4N~>R2kqt@:QEDA,͐~GJdրIy44JWTmh"Z5@4KNdBh{tj%]wAo>6 (wr!: ommB!}hK̩{d~k׮5h1rQ|&w,ci-#jjـ|7Qoh۱KeJ{Go%]mu+rL ͣ p f/^8`ɟHL2v+tHsp]k>я~seK0%O1DP#NgNӄ(Qiq6EeJU<&ֿVn^EasV.j /:K ؽrh>Ƶ[:ef fYd-`S't$$dlͽ!qN'DXVkj~c'<O< "닏&ckHۓޤ).y`4mո!q?Jz`{b}"*h0|7ޖ$~$koL<Cas\X5˽,ߝDk% V n5Qz77+~MFVI3_ѫ:A1n/hϙ3OSO=5^~cubLp%P*l{iDzaP[2,n{А `CN[ \Ů{at}9p+~j΋rHb*1`/!OWKwsr[,W{Jgd-0(<&d`bH+) x~fO}ӓ'xB\#'J[V~NAҰU[j!5ę.gowӐ_'?aMV .bi=E.qX2tᢓgy 2@鿒 1Бakcg*T:eȁJtuE˿_t)fܝ@3,A 5~뉔1r|k2),!A{>Y;~a6aЉ뚪'3d)I8t¶q3go(8 gw-' :~"8 ,ϔFbQx*rST5=F`Ͽ0y饗/Mo47u7&_}+k,=5ܒpE_ˏA dzO7F/i9;I5ưV {g;\4Wr O/X{&zhM:5X~G/tnTG_mF\Ӧ0#Ĺ!x:y@ZW+j+g6&o~O8# ^,T+]4DW@y$}SE#U5 {|t\#@+>j׿D cqY1`QDJE̙Grmgr]NP$D毾}:(Is^D`SD\cBkza=c׾5sVvtcs_Mk;Tخ&ԉ,4dMJtٵS f)nI$uoݰr压2w (W)@vcHV&2e cm Xjv x>O&?G}b8Y{gl=񣨣t4h3M:$ӓѮ{1y2knĖFF_Du<ɑCe XЃ94͹@eM$-Lp撜w(ۛqW^`]:l;W\1c?cdO~K|CT݌ʲs:2k3S= ?q65݌KQC㇮UKvG|ƒx3T8wVoRXud.[u􈌊}lh<:wH'G oNI.Žī6v!|Q)My7g@E[Kf0={V'\i3g^<ƈt65M%Y;’Kh4 u)٪g>/~,gsW;f@kppkʴliDWD__Nǖv0]Į;&- e8dL|Ƀٝow{,`(*-|Y@q3MvHVHs}景:@n "?e"jl&OYg-Ͳ!b 7a1=2Ic:WofoF\W)"W5|&%L-8M›I3;hv)'8cG\?/ǀPqM&=@h̰a9tp@ۏQ7| ?'jt-*!;=ktqJV`;U/~ɻ{Y&uCM(kGڒG%eJL8Hbbl3DNg WYޘHdH;yB{@/|gBY>ҺޔS5"f U[Լ%RzfA@~)*Hizyyǘ[;9AR?(-]1o$vv$' l pU?-( 0 )n~~U@`/JzQlg\эw=;$aݏMv/^%|N V;V26Y5= !&\w_f2 AnEJg18B,) 쁎Gv2ϏWuaY[ŒV Ms&[pYߤ4&c' cG?C~_|%MmJ$!DJ`|<$yj3)a`iQA[JݭIz**: kvq^X#FF[%7T>zt1b=1կGu2w*^Jfd|G9CqD.]\ N]l8"<7;1@ pdv^$V`fȖWy_ ~*#aI }Ht<ox%mHc&3HnvV/^a3-9Bh#kpHs &y%N5`hπv7햸cR٥+bdLĆt"2MiKz79J:/ҭ0iNN' Y,@/D*\͘oRt]6m@e&s⥝1"3QceY9u"fZK2(e1 r|[? W Aj󜀍gAL8 4O:hX*,>$er*ꛖtC!wJevĞR\MT <"8hḘVk/+e5_X+E/a5JL~5=@XsqA;5&>ueYkԕKY[EB)ThL(d;?x颏AAxZeۘ7"HHT!)ɼـF!sY鳩:(-vJZm_gzUg s&d Dq/BB5ZpA!-kAjQZ#Z.a^X`ZՊjKђNNP P֨0% qIn<#/_ӿhxz&~pvTA2 A-|mh;9g'uC몾pq G4Gߘ=Q+JKt8K DA{s}Pti"v 1f31|D!)^VVVa^ioi[+e-QT~DU3p!} ?Pi䡵3򭞼=O kwۙA#O`tݖjn-eEvnԿƔoF3@+n- G=B^cڹ{mA(! wIrf3Q%;#ج4G:[:K0DEb7PWAf|ƞ|N [.PK] ȓR $S>:rH Qk4,/x@~ZʛE>y^+cxu^o4u/ HvJGXV׶GwM&uaH~|˒ :^1ie^ An.ݥ*hgCq/ < &$ςͮBS(Un>zjqúx}-eAv&-D޾RւR]>p?CJ_~@ 6"RAg  MYpb{zE2=;zyfqFyv+",_}2(?:bKnoMP_؝<{~ފ_0E%+M.nL^kc싻 ̂ڿҪw !u|u?Ɉ!<`w^ (E@Uƅ}܃'fnBYjPh ,xc:O(&&xLQ[J;3c:<9qIç}t:'t'*o#/ l&B}0yqb;[Pg8{٫k -ӏ<`;&^'NW =?t~E;҇9.v7)ٻW~j@ J;Ib a{QJ&R,/5L9Ҝّ+n0 KG'ň b&kO=YVI}2ÛH8iz@gE#Z:"\ Z5^k%k&o@ Bo?V5(0.]1ppkZdݛb]kQ5%Dk`7q?; ޹Mgh^dxWInMrKtSIvbTtj̈S2e:*g:@>2\ =Ow~>N:3_?mBImnf|ks>0崂”ǐlɐXՒ n K^4)q!hFq ;*JLWbl$!`i}@|S Ԍ䱣@aקEkGe3+ ~uU+RŠb>፰$. ta;H.zdWHgEcIEHsFy{I[XfcFpEzKZbdמb6~z/a5~ηŲ+BT-p/(VЖw饠Õ+zt^ ʱyKĹrA̎cNn?"M39_Y Y[XOcUY_4~_4:O>H>a`r`g)J(#m-v @M\&7EOMiTlG7?U?˓Sy/h!f8 { ?p cI@eN(?VKaɮTmSv;#e x/ /w8 {(^H::$Zc0e" b^;" T>"D8q|srA7/_Am&|@h_p$/ح#3# <WkJѱ `vs*3b3UiN"gM3s-/' GN& fp&/BUMx#>_رߣmnMR٥P5f;j/mL3y=j_a8ۀZDpi,p ȚSk 7ְ^ 9XTSv@GGD.?-;"sAv'IMS{N7ks~`A ޻#c|Vv15I㶘UމH(&Y׎$6g|'0k1 @yg }bjXez4GOȌݤiFe*^?ֈf|6J8W,wKVuK)܋s/x$,X4roRvoN=`lWw+aontrtRl)[{J oc43aku^Q\8u7fS+09XUsO?b)ߜuЌ]"@0(}p Eԇoݦϟn-0YiAiJv|3V Djת3&!76DM)}&=B\ i{Zy?č87$'Ď:,!mi%# KW|[Y|7g9M|FǎR'ANݿ9yCϖo u(.1n.ע|oH?KRllJ׺o9l(K#gws{]Xťayx5nx?w]0c\{Sa抵VQڻKd!0|?ylZ?=;ӣ^U>5m?[2X G@—6{!}L֡}qLZc@mPuxr̞LJw~tK45z3s`BW5] Q;yV9ߣնIu'4wؼ R1}eAZ]f'{>Hpv"4_fdZ9+g=Qm>xa0 #y{IGu}Vj݀ `ѬR;T^+ SSOa\D'*EKN]J`!7l>k?ϿL/U,=fCՄJ˩MWx l(1.y Y>o_Fq٘qqicf?bGsL )c5D=҆D%޷q%͊U}V@Z,Ueqf&95]LRkf [CMur6bi#oݑ[|U?G+qG|:s8{U j3z9řgzѸJ SpQ|Dhlf UXzHOe"6@`\j1Z9t)п 4ŝ)cYDٲ|C@!_ CWFȔfc[_z?J^|!aT٨g%?n,-s:5lmt?vL9($0Sʖ}.Wyp:ጩ'~wZHeC /{Rn6BELMGƛVK lA} > r2< 3 B  9ۦ"Qa\wBIOv0ºF~12J+{nA x966qc[h61Mc'+Igۓ;zEb%߫]'/mO^:muUu2*b7j`]65 gP9/ՍֶC?Wqt<\G5Z=yYlX> ʼnhL4[ܶVvLc٘aV^%l m_%ٴK0%+{ P""~uĢ%䊐_<>:,Yǡ {a ԗy[ԳRAvCf(WSke ~xbwsR#&OԦD,#tFqsewk;u>HhQQ_Eh# p˩wmL8 `h.̙9ax"̷ ArQXM [2לdPxW7lc'p"pڙX~cӁ)8Q_ zǓb~vt%xFIssے*fmlֵoZZ rȳ;Hz_;Kz5ÞknK3:>pj| @j=4x\o=B>SU.ڭ%6U^ KY^TAuMOf)BW1Lun-UMjGNEVS)ItX,hCN5=;7-h>"38{ lq'lN2~Fb% =|`x8Nb̔ţ])TFg;*ci5ץ4UjmA?x>&!$+!͏їgGѐGe#mrmm ,"QƷxtoΛJa}onqn3="2"%~sN9\oG7όx uw:bB/SP)f #Eٽ >q<*' !ޞ+H:8g}$%V貙rxuO|*02VWCsc2 ^2 C,%V#b~ꘗmS=%)3G\)D7F"UpT #`wT?|laT&ľ9O4٢6 2;@-3>Hh5!H/G a@<'ŝ"7b06#4&<|NHRf/<ǶߤCNM̽5 k$TO0K(4a}Txl(̟.@oC];G"e* ‰@IDAT ˿`UCs \V̙@c$€ I|Ng|? z8id2r5X! 1„l; 0XCQ'c:YS8>g>}bUޟ99nVs9T(Rvpv;ʽM7뼐q]dF\ L@F+L:6Y{nnv—d],7c~!Flh!gk$ȸ!'wfck?x5u(;, }Z!%lZ|' l |B*zȝG 3+ׅ8CrBEL{Pȳ,uJ@;?žYᩏYBr@h#8aHw4P$g!)^$ Vȓjw]st\D)ɬxkӑ){h mꕀA#9Q\fmi3E 6'epr ?{%{xYȷ ُkL 2;4Oƃ"Me895(ړJF ^9-̶-X~ہh|wnp1[^iYֳ`װ"4]d.:DIP02%pp-ЮHsR>Sxs)9EQ_ 6-HQݭ獚Ej0ՆK S@vKi^My ^U j+S,,+ְC~ H-> BQ";emw4qV]^{˓?5yy}T"ٝ\B;)uL'Ʊr,|NajhRHQ4| Mc+ 8[: sYbuMY`2natvڗ;RH4"{{P(,?6ԴKns1ϗ֗x׵?kK;`NeƄNV٘ʡ2*#;K="cwffs?>*OQG>eŔß:: w@ՀqUԇggIS#M},PՐcItK֞̕1xaȼfeCG.~=P;?zئZkc\.[>S| K;s/}d ?f 9ƌ7&?7`L|gǫ.}>Vn%޴qoŨs4v7~nJǶ&On#_nW2/-9KAZ7S=VK?k^`tlOM%MKS(]\1c?M~vD?]L[7s} Nn շKYA\3T4G}S",m(GFV6a^1{ݲkq_{2f})tW1aSR Qr? t^ *5|C^Z㪦ӗ/ozO]f`B ._tb)@x_C:4yܣ\㛯BҜB4ep[٨ ) s)0)V}wcGwT1@bX1z,H$mz) O`: |n=jpYr]=nAY> fq>5N4=_Zՠ䨆Cx%}*{gY'7m0XGUzNs-Op v[6..dk΂JJ))'GYt8NepH'd=gG)),)wVc_|qI tLO<"- ,ǁ쏮 $,$lÇ!B% Fg#]"(N"ۘbw-D0E95 7n6"hi!#$l[6l@֯)]Ĉm4r1"rP )cA]I>a /L⢧m{PZ=[^ѣP_#@_v:T<{u~BXK3".9p׵T9S'>1gf_5ev*@uWLqdxT}L 6-e xLBD;]1كa2Dg%҆<x$84žDD[= NR4wR.gO<'#&S4PCZe EuBzӠ\fXPґ͈[('}'Ow]C1/ӢOx# g⢌$gIg\0N2i(ΖZN&/ߘ\t]8/i46:g41‹X_;5P|:!>-_3a-hN K;haU~Q_l~wҒά,cp-jprƎ-SN~T`My5?_}v ޝ< sp%^R_EZ<]qRX|޲DK58]Y:&RCn/sk~$q.#čh >q΂ vJuIc=f @n+$2m"<I2nkboͰXSöUutX[TԠFEKD ݣU`:Nΐ4>&ѾtϕotZ *3x E>zǗP'x]&S8i,X~oKz /oLx)f 'en0L \PF4LBD%t*c<vuĝbU89nNv]#rl+g9b 6Pq7Aö7! Vi8=SIHwz(ep mX]ӰFuv$ʫwY~DDm ەV=$@>5.vp;;(!1U ?Dǵ/Wt4fF%!\.(_hE=ziP.ŗe"/MW,cL?M!7\V&?Z$0ySR[-7^6( v#``F16\93:2A੹&a_}Jr@!oU?3g N96H%=`HcQ{G_9:_.zy|2=4>?EzⰜFi6Mnn[8j#@Wgacyye+i}3\gAp/~5=[xH܎ŴgA~G!!Y61Mu#a^w lBIG聢YؤY1% ,gÊdp-[Ysaڶ!Xp $F#)rH 4-7ouNǀ׳KT7&S ۏҧ8] N o_뇋t&Tטw:>ǃsNs:D:D]8qnGG$[D`S3S{Iz8/_"zp a4)EC}eY+Z m9~y?xQS{`"gR?(Ip Lvb{TIGAN=CiCMtrIS54|ψ6zOK0^H^biD,`RGC=gQ`)sLx,qSi_w+x΅`ɝ 4-HoyҏY *S`H:Tf#A:`eq؋A"|D~\gczv\jЛm X('canbv4^k+00x6LRPI'3 v΀ 7 p3x`8/&=4=rN ^¢^ƘuE2 A!#VN6$|8%tT~bc=M}["]t0?:=n|XznN`8zPdTJ݇Bt3j[4 ?AܹY ?O~};d3PaiMK 8G5L2PI ݈"=H*kNS{sIl1 :`*, I'.pm>#ezF9i\okB\F j`2f<9v˙и҅9nS]O{dx@p-ޤ3#* `%~h! YCb|[qo܉RqMa'9{%i{%Siw3hQ K ;ļ*;뇁ԽU]JL~iˁ*kad龾.Q}zU@Li̓i&)0 T5waR_-ޜ|j&@TzQ,9Ӝ!f >uX_Mꕎ.+CγC U}4PG{}X I9ڏ`C'04;=1ݻK81H ^{qdֲ S;]a(:贖o|..~EŪAUDo&/3(}\eu`*_F5'ξK.@ &@<T$/i% 8s[Jzhs3 P/!pht'Pp^ywA ,vLyUHڋԿ]LQ<-u:5vj17y Wt9|z\A~zp=)f<|MvO amB2]7r]HBnEx@("S40/eEnhz'4# -<4o^՛w/_`C[Zc%4#Sfcฟk͌"%[` ؽ'w=w.i0sF{-SF*2 ݂[Xa/@ c"a@dP <0 ~kR7h'A(R ς+gw}XT P߼f2 -J@xױC c[Q68iOZ_`㍺CK[ km 6lO8Qf@ʏ f(aY`CJE)"4U@o՜WE\ lVྶwxI:>jXKfgykA} 6%5NÁS:kjh#(fz)'S6d .^xD%G~]_CfޱbH%k[!aiidJ:x(kb%"hTk{{N ekh M>[ V<fwN H酴Aυg.yB|Nh͐5Pc4@2QIC,T2tYu:vis&w…0@wEi lC o`Pbvb*6'H"wH31JY`[pQE%#aS ϬFK8rfڳ VHKj{!*NsvZɩ'!D4h[2F |ٰYv0&&Ɔ"Πt 4A+֑qϫjw^fѩ Rgok$dQpK; ~ge&.~Pv@)Y2S;Y4JtNOeg`l5 5X*3!}Ѩ n0D٤ ^ͪiP ~kX]Q) !mɉ]Oq ,Q\ᵬcUI/c"8<忦\ ɲH`pYoCB2)q!yFox&ӳmpȵsʦI 6!R3eEcʁ&QFc+4dM>&J<vF - [n)lOLN%5 82 35f #5D]<8zBZuI%XjO/*A8w>=q_۞qy"8;%4G݈"y&Ea[#E=([<Ȗ-H=Ϣ[8 VOh aGE'U$яdwN`$4.0{N 4W cLhpuXiZRl"0'-F9%<㇆Rsc\(?ʽ*93wǠ7 5|p^K@I8㟅K;tk|М#ɇuI@fL@eЦ*נZ#zVtDWҐ8C klMOՍ YF9!E!2C؏ S'|8G\)4cn0%Z:x{!2IL!~1qMlD様hȺg[bޑ#4Ӝx35^HqL_H,r&JRVC#=]L>?\ l@OPDAy G!pMgz-> )/ >r7n#bakW4x\jr 醎J`I 82 YEO8~ vs=+}]!#Od;ƒ)}3 3gZMf\BPM 6esVz0ꛆSIpMqEgH CU~/'/-6pP Us 2%ʜh3 i#< ~!Bf[ȺuD0rJ4 4B{]v6/ 4aepD$Bv&pTeD~qI)Z2-r]B 5u*i:i͠[WBgftA*-0TâXh;z%2S=2Y`s~-$z^a=|n5- >!9. Qut."'%m!)q^oK-QЭF $c(c1<HCU*vRcRrT~8Cv0cA,@B !4KHݚխ}{uCJww^{{ZgJd @SaYС l! cJg!ɓߎnbH$ƢK9X^u|O_%bUf?$\&oWT`^k%q rAsMoAU ȅthG? u5p;*!ݐS*'9BGQ;ah$__2| IأK-yoo:BU{D)Dž(]mBU*4<_&fpl I3V,{y}T'c&4\`]9axMC泩7ZED jk2U3v@!ofI|lIqU Q@M~5F8kv"66KA PATNz4 M (& |=!&dǪvi,m* '-l Jk/'3aJ~sN/,4eB5F譬S,k<W\d౹*ZeHoB̝# TUP`IDQNRBO=ZWM_!o>͏mf Jd`&rي\:s>S/,-.险;PWF' אLvoe*?r_ ; L6VjF"H,K8Y`k(eL*nةbYRrZ] (U~X/]^dkg:T@vh&R} TmH2KkbIdN"rCj *Qi *j-P[[mτN%L!,ΰvqm>ۼJZ<0jJDeB80j$8 O4F}Ak6K-;"yNO^pU(% M޼bpP;Յ(hcM9"ت[4 t \8RCF&F DYF)m%[(#@,D;bjQ]&-KtOM "SVk #%>@ P ݧG(-4uIժ jeWjr`K(K"l6j[T_BqxK*oP 4IA+ͦ)?7o`KƨկTDof7iK̙`A, :  2L\{~G{n96cdOۡLKp $L"l߲j"3a^\en($>O2B  p&R1JIP j'Qa7ٺ+7 ^;=g͗$Ȁ@|}6PXrϑ=48%DK,)S+5,@h>;kI>GN.X)-P@]dH^Ip*-$g9u'?!"'=(: K|Vfp/䙕8:Y#&P[6a} Drp46WKXj"Yw,֜ %^/r0qb{ë;|ao$<<"/3ӆA+ g#KI9ÀH\S,Y eJur_QG23r"3ڂrC6&ϣ2HIya)*15u\UףOxaNRVE̒Rx4`{rϠm4 G!3Q::s:Yy!y縒J3ך$;$0J\/ 8ʤ$u&Ab0k$Lؔێ%KzQܯ̇J%+r=%RJ  F@VlG* &; Z^̢%hgAAn l łB5CT htCl$~ 6ɑ(;5xf*Z?w8v!蜉_ڵϬ6QoLc|0. Bi ?ׅtQf,Xj1^F4Yhm}\6FO>?e= >>3Ql'g;9?`֚>'MJ*:gG x}`f8V2 hv;Ow @o!gmGX:i$ڢpXH[`C I)-btUr%hO~LŎ>xJIC<2̼M/!Urp C 0(Jm#T8=fڬH&g%d5_9 cZ@7wiNj1 Y1dqrvZ˰cO,H`)ۑ'׫׺xop̂`I.jr `ԓ{)`sn-y ժZxpO68@QD/h %?]}oHc:ࡱ|0٤֏ 6G;g`%@&#|Wfk[dB*0 ;(Ov/xwE's׋1:}jKwlËJ`LMC%>=l|5מ^gy.`VcvIJZmܥ<~cxlM$ 9*tVBd۾0k2E§5@{]wxPK!ʙپvZm k 8#U6 ~l3VepGԚGVg ib*rhf;fK⠛O1Ww^QL::]!0 Tq.~r0˯Ϳ蛳M 6j3JwvۉKA<˻>{ P–g'cOALRrabK\99Ɔ!ۆj\y8~@U#|u|/.(H_tn[-^3^ .-`BJXK0svv׼`6?7+gm1iݾMX |ؽ ̐+G?wv=i Jaq3ALcT2hHpg} GrŹ9NO Dc3y^c+vJ[n.yNPAN8ae]rE'Mun6Yޚ) zަ*+ūp2Wba 5Zqa,"ZVDiV4#8vutL)8&M<1k(\w?m6m~psY.`.\>}F{JOlG=|=ۺ_xwThy`AjԢU-VE+ J\&#`FH݇|_tJƊ*s򶝧;?+p$ nw7gɴҝ Lau9*2-9 #ГFo[ڂxх]wх+^uu#޴ѫ3Wh =O}h+8,K;;hisUyR3V62kRm &cnV|YKrѤXm&O\4LVc0wëcч*?%!W'ZTC8Wq)nu`O$DRnյE/2;? ."G{DW^ A\ qq_yk1'#8L\jxС^-27I^/Bu/mŘ|O p=%."RH:VXo)ߺ)=6gC7l5GYҴ`I3b[%5ye"Ok(r /6qMϏnfco(- 6̇~h]w/.`N*!U_ ޴(0CV.tJ1\9y0d!%Mr+ ȢLH|_8q:*ejߋiawJw.)>b(貵o?YUH~N_efOnu}1{?L<{ γkǽX9z`t]*;LtuD*0w,3'(*{> /v_`!A.(X"0p/0e!uӘ@u`8OuV@'$M3Qr ̣yy4 9n2Q]))V 6$M.a^dEU] 8o/ls۫Ido\OK1\?ن= K{ctyh%;4<<䨛W[1l<]Xx]:Ӛp[׺~y)gV/fz@$Dg\C&g-+#aVT|:T48 6T*gXEjHV&P2!q7܉ i$~"Oo~+V:dU, &'᣿+£/BBwwoiD{բw&%RG0_rECd'l_ȐSr;o1zZ1/V) d.H ֡7w9rL'?<Ǜ_~ૻ3Ws;6e#Js#"!I2zMAҦ$f֤b!_%6C7pE:0D%E**fc?`7;S%S h1v}.RxH> N^C|EgֱU/Oo~#>V^ 'NϞ%%P ň)&aV9')iܑ2xwW?s_ݞ^|q #i@`θ(# !a{,HU'3jZ w0x RO9&<gBEݮc~нJ>bܳ|1E]-;KT^?>mv8خG9{8#ޑen.cl"UYOy~%Ù{k "(Eeva,j42dbn}jXߩMkѰBN9i0ȓCnmS!b+,\_r\}3h I٤ kji/Z+ݫ^#_lvL|Ze  YxNuٯ|!{&Ι[W]嗭v?k_L[}mԾkH"D2`J(t>i/[CFL9Jf"S툑YoרS+ Ф'd|M:|qoD_;|cxWFkֺW]C80!q+-  W,e?r=KZw =^뮺X-%|:5N{K^)1|kRI/+`aJWV|w]/6ºuϜzˆ]K,[t#ZlFz I"I  ŝBb1 'Z+D)plBOX^ %1*p匄ėY ÜOsfۺϺ]Q;w\W0z=ɔնeBa /GaUk̖&el5_0 m¹M_.wB*}z8(|\S[y΅Mg[D7j(zh A]8!o)pKGٔ9rm]:#ܫR_w"qR8Y62JVkQx0s _ C1PǬ;G:_/yćTq |UX@9 ͙xķBw bmw6a` Y;z/R5wL-mg(=7F3Y{<bp&#$?(6YA}&bL)p( X/f\fLfNV~57^;VŨgYU~ø()|-SVZptkG_F b͹`+Ip>*#6 ?SFn:ztjly@6ˣGdk94w R-O \MtYMᢍ\R>ŴA$G1jUAC@*cb.tI+Q%Ŕ .A cPe)O}V)5jc><=`AGnxK_G6G|QΟ0|+rҾƎߦvIW|G@]_Yng *F^Q߇'^++nrFYir4NvD6Ja@e\]aԋ^cxۭ @]uOgr*6{bh7KUp U ,XDw9D](!cTȉw :s'x0B P*ٸҭ7+ e,ǾRv`,$0Ϲ7,d1o_ݝ}نe$=AɃ@$s2Yc"ёDdJGyig&`;%0VS)ib1+Vcs؞`THM3!0DK%a|?4AQ!Ǝ@ 9;pjN}݋&w?&'sUW\pslel13k ҹќm6[7񍴃JGͼy r֍2} +l~h%汱`#SHOo|}\'-Fya9lf[7bW~{wuׁ칼O}}cx9Cij'5Ye}G Kx.8@fhCMLXp1nd>y}k_mˎsrc|"ĎӁtWnϋ`ާYH <cNu|q.ɍ .@f*_oҸ%c|ߣ<"VGSJTK 1 /G8)=z[dKl;s: ?Lo1^|Oy }0h{T8S<]gM',"f]wtߡv;p`?nsMnoj\px&r)Qb|y4T;崒r#ڻwF[q+k)H+plDs}~Ls΃|\ Nz8wIWI., 7@PF'ٛJ4Q˫B`k 68pm=kKcZŞfp>[wqG{eN… ;&^ Ov a%|+]W3TsT3GW'Yud[aLմ-Y+[@"k븼7ɚpkl>[n<3: @L kd>qxy2˹ 1Ӭ6b8K li e|>O܍#~Z|u,nn@@cstfSA5И^]PM1M(fu8NħCDlncl)_sw4Ldf.- ~ķgz'?=7iOYȵU.fl}d d0FZKwk sbӤg9Isy{kӟO>$/" s93֕Su.ޏ`E:P^rs&%=N<r#؎;,2,QGiL6S`IھcKS9vSr93q|"89)({"4qUZip +OcbOB/#Ǜ&.Nن.Kc͝#Zw"U$̡pK;[kfYo7b`>]ɾ~==۱ eycE'W@ͣװsԀ' ГWFUxz`3o%^lP=x&ܟߊU傇zVLc C|+ӖTZ l=_|⛀c߃P{PIk-KY4ϓONZ|_'s saMȱ뮩sF\09ŧ]핗dZx^߉M+v IlL f$hEoD&08NEyz56/Ǧk /D:ggKia[ S-2&/[|[$~΀0k1S|qMu`Aq[Z[af[=E0|nW`~lO΋';|~Gc7 lt}Œ4Ky*ċu#? :v޽mo{۾naO458[/cxNF"ST;fsw ߸ZEu 1vo[~>wnx;%7':'\ P@V e>WL{c~sv)˸.|畯~/ 4i^䧚yu7L&x5s޲+1 `j𓽥e;@mٌPr/Bl<մA`3>!՛o|#z't%'>=# ĥ>}] m y{<-`b n% .öx˿kg10-BL&ϜT>єuC'y|7ſG6Oٙ?/_:!p(hzȜ';K|G= (A+%0h|[x?6]'G{=?W 3@kLP IO^u%҉ʀ XLD<#{NZ N~>D~N~WV8y9qt@A@tvG~5Xe ,[`c-ϋ̕x~ɯ+ \)Z=;m4ps2@ב+^c`ȉ x@Y^7PLX@'3<':/j#>O ya\iCq*IsyMz$A=C@'?W ܖ@̟- #8ˠk|GA~'&8@K\!ps+Fd %^ 8^9: 8y*'|ҧ5Ɨe ,[;_Z_wK=|/y IENDB`ic09^PNG  IHDRx$iCCPICC Profile8UoT>oR? XGůUS[IJ*$:7鶪O{7@Hkk?<kktq݋m6nƶد-mR;`zv x#=\% oYRڱ#&?>ҹЪn_;j;$}*}+(}'}/LtY"$].9⦅%{_a݊]hk5'SN{<_ t jM{-4%TńtY۟R6#v\喊x:'HO3^&0::m,L%3:qVE t]~Iv6Wٯ) |ʸ2]G4(6w‹$"AEv m[D;Vh[}چN|3HS:KtxU'D;77;_"e?Yqxl+ pHYs  @IDATxYnupM6I2-@I ˂B/Rdɖ 6"$0!'Kr[C$  і&mR"EIn6{}sUVj[uv՚תqמNOOZZZZZZnZ6o[ZZZZZhEEEEE@[܂\nhhhhh ZZZZZn-XhmEEEEE@[܂\nhhhhh ZZZZZn-XhmEEEEE@[܂\nhhhhh ZZZZZn-XhmEEEEE@[܂\nhhhhh ZZZZZn-XhmEEEEE@[܂\nhhhhh ZZZZZne(X#\F G3~Ǿ҇~-""""snǿxq`!I,Rbsfjč;O?㋗0ѴvCsGVN'4"&;3^P>`o3(-4r29$܃,n{n&}>zZiFrA!A[ |plEEEEE`"QMh®uq/(]v_Vlk :RNg޹o{RDZEEE`# `YMr}_n[n-c!$n]3?*~}2Oo8[oLEEEE`'#@c:&v's,pDzY(s1#vuG`05g~Ȥ?ZjhhhhE@<4 EwW٩ Az Aco`-2O1Wa7 k?w8\NXij~\sji,aҟC0&[ZZZZD'3|:^PX \݅݀/&#в;0e0T """""> ĉ,8˯/9 $0` 2`}$,n(au5uw9 я~x'ǰA蕔meJڄWy>{gn\qwy_~'xs݀L[YФhlR"D& +g;gi {3i-,7oݸQx,lo޼&-|~ I}Σ} AċZ $Uf6:&xl?~/ME>&&wE`v:"iw^zSտ_!?pvKX<h0E^MlFq{-BN9ǥ;/{&lKgmb?yإ^Lҗ/__/Py~S.L "`Ruc ?&tlΧzZzH.MKU˼ ~ݪib[/'i~ר`!H{>h [ld0aD`(=s5İ8=ÑM-quo|LwS @ek&4īWkk_;cg$0X"` -v/l뇶1I;}w8e75%B撞'{"yM뮷{VފMO^z۴oēgy~~D7"7֞xgu7KcN(^ғE_`mvL[Ɯ"ԆJ28-_}FO`r2q_n&ѡbJk]\%^7#=kgn)?DD~͈@9.(OlHQ7i9}ChXx}R`$($q"؈J4G}?_Ї>]k\~~C)6AT p,vf\pfh<8O=mҥKl$W^K Զy^R'ZZVtdLL?m{W\y~~n=J%K+OkY3b5/wy9:;][I@v'!ƒֵ҉tAaEZ1""xYZ؞:'aқo{gx KG/a X8 0x &?k <?-/2ܯ_~V'=Ѯ}- Hۡ(--{=jk´ y-f!;& uf̢ ~R Y(g8][+]P}_{K ~w~?iN}=gAO+dgO3Dh| |0i,zBgTNk1O=; … H/[ .L"g>rNlut4gu9jMoo0vZG+.5ݘ1.=ነ 6'DJbӲǕՄ8˶o_Y Ѥ D (&魋Zo6)ַ:1sw|w//ü^}^]BGTj'ўQv,x0@ Ҷ?g?1Zq?- U$szΑôN-"fY(^HbڔJe˶=SϹFlس}ny"bΙg?yF ]@a倗]oO h39}@@EY3Y0/8r\+TShp?3p G9{-d@Zj[[ aƷ)2? 𒈯l *r*!^1 ֡Q,h^{hc XH2ȯ"i{D葲{F9 tM(:c:џ~XX# idN..} "XD,&h. ?w}mmep=[[Mҡ<g6"@r!t-*uYfnFKɆؑhZ4%cTD2Fe[d&nQ{tI_4] ]xGw?] 8*UV"@5+# ,ܖ)h`'r+,2娉D>jRۑ9N#>([:pּ`=(qsKFeZax;l1D'?S?'\ /xl!8W# O>y V ꙿnlƕhY־3,yY.RI(4v瘞}sS 0ĺٵ=*K׌umTÿ"v5b!>?% Xv7Gk+, z< z}SY\)dp[*fM "B,;DVf%f(/A#JGh6;܄9j,=OP7V@O,`0\ ~%J!z0TN˓BfSA>i.}LsYyd" y|/iˠ\uUб9.FXs?s_SXljG"}ahN*j nm1z2@ݎd?ҙ|4HlvKr?+D4(,;Ǟ Hp)%?=KHl-|\X` d"x=2|oʳ5\$"*$UMw?VTEzӠ{r=-=Ik,ٵfݍu UYD/tk2%7 zPТS"  s-Qs0Sn.O</"X^s7'pQ^2p]|TA -RJFiDowI } Lcqb`L8+iS 6X QU8 ` DgMD8pr]9܇} x X-hj>8]Y[i ,L<=HK^5^Og$vІ:5(Ց\AiYӓ-h]rțb]0/^d- ̏ 07hoVȀɗ.LGL,hAv֧2 _d,z</ Ai"G0t8{7n^a?w?xMGh)b^"`ƍ}̩vٟUh$@mAUFpZSs[6#`Z?%yH|Ķ İ\׾rXjDJI%jߋt$S:“2[dA_'i#}3- 7t^PI_]Uk~<0",74ڨWL1WP`܉F.Gܸ"Xn$ *rz }|'xYRl=sdG@<Ȉ:8DȜ+>=#˗yWN#[p[քXh[C} d3AFiVɸVm4Y=Y/쑦$i3Nt$( pEXƥ.`ǸF""m=D !2qW1{aH=*B%ԅŧH1]@7h,$G#oK~ӟ#QL[<" OF?=̈́Vj9 .թqSL} /MDϙL `rD C'@]FoLo|xzxgOl%[c*]%REm sЍ7nN[9K&if3hjqo?dS $}m4W36haYVM`\T qRomwygwys_!:t/R_r`1?"@1n"[NHAZ9 s=[[0XNCIT{ -~}+Ow/ǝ*yʲ8%4~rǥd&Ⱥ~㤻zޗ@3,ČV!I?w?$hpV2 H@ѡ_o)sw{}Ô7ȋtxw d4g̓٥Z:e==uDߴreh't~3_>w_{:!YBCLfJ2~2@;A:_F9VwVҎ K G*Q4 (Ib*xeNe5"`ܷBW}_{=Ou?c{B Lc1J$Y?OcKB8"؂oty^PQ[p%hSCXLWhx }ZʼngO?ōUQ2D%.2,pE!@hS%-n)[E`w#v|0mxũPX !d'phO fn L:>{G?Od&tUE0C9ـ~&mLiđ1΢ETؑ[sA#ANߣ>{IU:fttc^"q$ 4\KYD3 d$C1Ә ha[P"cE:}Ev_8/x%65b#yyg2Ґ" /<}ӿ+|$!ݐƊhb C~\czI&~HZq4I Er!C# !"g5rSm>UJHjC2 '%apba9ƒb>MnƓX7#TIvG#8wc:P c|ꫯRv*4"1AF8zʑlzD4c_p⪃+@EAO@fCAC TM T闣=T1J% xTkv2rXN$ʒ [ `ʶ$dȤb`}?KO%%76?T\1+cEo@ W|EvϚӄiǭ/')ФTE_[dgpؚ↙I =2CE@(*x yB^*| L$-߆}{]ȮItQUBϒHlQ+98J91B UP&H!UtNĔ_{kt_eAZ '~b h1 1qA]n~g Ig87)R]. X"P233*Zc& #;‡ MDB_:֕hX_yjq\D5)Gjm/!b>Ԛ+;ѥLV!SB&S`d 38KNdBugPY v&j ]H#,,˄_!sEhYd H\(nG":kF%-xIzJhQ+*ɀo򅤀J :믿n0C\AdžGS!R6eX` d3T+(K0c35w2= 5 ׿3XQi=6. EAKЈH(p,0(Fb!@Op`S@yĐpIhΪNHyt+/4D643'5-`e* ZmA]Ÿ)c&Td8k.kfXF\@UZXBAF[sO:FhjeX;TNx= IgiNiT:LYH. D3®ѹ! '2 !V%=d(10\\PCLSE@dM$wW2'j M__ M/" 0N\[0%EY=Tؤs>2 :VY=~ 'I`h*L Vdj64N"TPCZjOkcuu3l 7C0  ](H'Odw_S-F0qV4J%j8ьAHp20 O1"3FӠq $yx"S#4˒qu &}&,Nyp>U+Ŋ3lZ -!EnMTvL7^KѾ鵖{"lw\,)xE֐5HrW4C vlcS3H|3Av.?߮12#CYU3ԌT8My+J0vh;1Qc,Iweؠ3C@IYQȲ!/9>QppӃd͖,? T 2U3rS E&YkTHl| QWѦv@]K-g(u5T̎D?IР1{ `r[;ehfbaaX@`VqE`mx?̉k,KD4dz7PNIo8 ok":}qj(؆[CT5xKJBZ9"5`R1lKhI] c#l)T`Gy(`LO; LaBShQAv(ܰ1T`2$ g0rN$i,4+͝0ƤsI a<ʄ>j;54Sv& ?եTO)ڮX֩q<#[&"D6b h(nla'h,Ȣ Bezc*sL$7Y:׼c6hZ;hp lMWeN.H|Ar+8Z7n5gT1OuX  }" R2r6iD,%V/[aS ԝ)xdHl-;=Q#RlE!R'&Yب :'})#JRNdd v((c@T,ԓ`R.mV  ׷Gc F&4"HRŨ<eYhG`mbFLXP;W9.v5mwr1MbP;s!9*eh>+kE{9E6L1S.Y*N]ҤON'Q=«yO6ҬC,a<YDDf_0-`"<0N_=iȢk51_!O'.&`9ʮS阫O}UZz ./# PIQ a.@4,L9ᒻ@U䗟 q+)l}PbAuqJjҵF=8% \Ts'iaw_@Ӏznǒ4a 8OSx< ;odNl SWQhm_oh㯴U* 7!\/ F0[JHP" &r-+9M% c+(q 5#%%"s٘wwQ)i`; GdT+f:&llШ64 _B'`9j3#I&,4Ё_A>ox}ɋCv8 W_d_(:^_4U(2 XkȳdA,ƐMRZySϙrZ4%)7*B˦%#e/˿ 9IiL)Cp%S9̊$r5'^e%)leI30:.g ZM(PE>㧉K`U@?|ƍAIH9#(?9|zdG,(?4TEYUX %TEK+$ ;JYAT4.h1I/NTвr?3\ |ӛ'ԮT^$0&!.- U gHh֗m\Hf̗8Jyd&M5A¦)|Y-=>@ p)ߋt;C 9?WBUky L2(wg@x]_B"΅ar"!#AyP`Y(!Q-8KÄhFߚ^H{D3i m`f%M.\WzcDe>OLh ]POXI!Ŗ8(M~9Xdy䦙]Ln< h<cI+vkiC{TÀ9T"I(7,rgf]UttНǵF(:l#_r}[pО7W8z{jBkC9}W : cu:NIYqS99: JBⅭ/7]qٿgGjR7( MYs>x 4SeGdDb(r,Дy:ECȲCfv .js<ȠǼ5O7P+t޻L6(9@NNBcL@&s=tW^;>oO_&"_ 1DIHO4'$S\`QƜw{ 2 Tc8 9L,nO#u4)7(;@ή\DA BVʅM& @_M@s49#QepnN|};G-pO@P+9b\y jJ>tJɩIvr9+ %u?zt>v}?٧ovϾ mCJjdU(65Q/"tYV 0auk'߽`l_ʐmc4WD1 )3p퀂 o S?,2Hzz?g e"|a|44.hRl9J?aWĘ8>ZQn6@hDQ+ L"N&w ރ ߒGnCh,7AJ-hk!SA(2<Ј^9%9. "oלw^݁4$k; ^cnML?^s|jK}"&W6x , q 6_ ~=Dۋ[ ::GOɋHwKDn"&'(Sp $)`'+XmNOݤjPFhmQHx-9pqfiL/mONJD>R&pֆR*}D ` ^Cd]0Qw@ivgVfҏO i<#iSpZHH44a1XMБ,2 "\H$:hǤPdv_:O.:%zI19|="'@3w.(u< `J:Oo]W=H#]וkI *Q~? EhWSB%. _8 Th8 .ZuohlXdp U7Q<*?Ex&G!!siFi8^{^޾B7CD@k۠ ]2?_+B!ro}uĒu?/:ʘ ڿ2Q:0w=RKle ɵ=Xn#gv%#X%Δ՞3/FM08~ uZD6R :qGuøM] Y5|lsoҤ٨G;8:?#Ow M?p ,o\n'޹VF(2'8Wqo"Dqf IuErU dOoF}c,EHb~pS>A]za /`?sD4ho/@' ! XJx^4%x0pג )}l5GF OѨvHg,[}>HAX0,YP#G3Pz sLTRݥ eOeQm@a;k> 58X cHvψ{a?Ȧ߅Hx9M`dzѥi94Sh?Wft!5&wX F"eK3k'VXte'U ( }v$ސ=}N,KT0r#iż4[9s5gIwUAzKD]q׷WH  Ft-q,' M7pF/ :ODi6-C ~##vX`pֿ;ݨtԏoRKqNu((ɳ EO"QJY[u:Xry&4^>ɪF2&w#6Gڰ^mR^]X + d9uƂH 4 !oDRM. \0 XbCQ~2q=^'{ tǓ9 X(HFS9\"[ e)m(7uwW龆&ۖu-21I_Dy,"KBQV:@dm,K㛑)POq!(e,4Pa~#p2znF)x !b,K%9r\.摁`Λ;X"Rh` `s M쯗W>v7iVHd3W`i!8N>F3JΔ#xD';9Hḏ]xz-GaamcH7\cY`(G _F* E02si)2B][[6o 5\t4 {Ƀ Hʠg2ݛW'@P"{|=q+KRʂ3WK$OK0.hQpEp-Xr:}^URɐ#: زkDd1p$U+7^DKfA x݀~' ^3 ..^ZqooQy].׽oڛתrLuoa4$\qB+8Os>CaRƤ*Cp(DB:-v"x^A6Hp? c.|ӌ\@b2f!2ȱqwĨl*P79ii<Ĺӡ + SA%^u) =LejQF.W2II@:K UnH=Aii!M6i}In#{a45˵%n;51_q/}}{?q{þH`}*8qG(ok:_PŖW ,X*G-U *GsXEJg5ac"!a}L0&ֻdWyN$ x޳O>?WZZoSF)峚ۭғZw̓ yh C#P]`tcQY/,b]DLm؊1m^!َtƍ?DVΰǪX(`U+ˎI.lԱR_#јa}{O ^9ˀ6y2AIyQro?8HOy˿6:.NlNT'Wtwt|a4EmxWG2?e,P+ ZG_@ Z b:? =!UԔ+˪,jSsqQ&9=G W?zcє~Z|mtǝ2 \~"\B(aM{B}%֏ݏ`kyooIUtz/5_m)`ݽe!QL-L0Xi\IWCٌ)F4lt{2~XeT@h9  ycqMi-:b:f(I KKԔRd)Jw8ںw۾" :|o(0hvwD@ ͹r3gT4lnz@:@@ae Q {ZԵ9pgs 7];8&k &'eҋgM@n$ӥ݃hQ0ŔN•T  TAqm_tf 鵌 @|lFv;,W sYQɊ G)dîڧDƑL517dnPp_ј*Lӄ_CM1TiWq}ˊ;񥌱dzQKhxMh `b $n N\te3tOuԐaF4?NM=#6jR',ZؠrCxhQ} !luբ_{{^0X\p]y1-Y)7v.6Ly3&EE@4Iki1|Mwt@U z\G D˪}su{]k| Y~+6kڍ3&C%'d!~z۷z!\_@KQ`ʅHe>Tl.)'O!h:c-l`-3wS[T K-,ʄIYQ-ӗXl͡ql}x/@1{#]xHSm-p/FqRu4Vp;G_ @_-=<Ώu߭m8sp̃T+(s&X(T@vPO4Nfeڲ,'dV<ћk 'n=ї>EZ^S ><@p}֏-\z* EHDq#!ưEٲ!iT;8t\  A"yK\- PzLzZڪ彮]=s:|+.0Е.~Y0l5i[Z6 "nFq;?!~FoVۑ9RyδGKviM.9R,"F;ݰPdx_ eʝ ю--;cDd* ePܥn5hY`mY7įK䎄ě,s6=Bɰվ Bہڂj-E`KcYv!8fbR(*S[Ûqm37=F }ZI_t+vlhrxϿFx kgEz,F-xh[Dymvz+/zW 2M3aZEE`~=pq͉ p*Jd6RcQ" k ilTG?q Ά_-+{ ,J:T{-˱V݁UF_]"Mq{ͮ{/_̧xo~@mqkq80767ȜT *}]iDz]VTX_a!?XFA%`CiP>  hq<䏳}{N{`( Qc@i\}SmFQ4*up޺^ݳh/V-f-.268'[xԀ$@톿Z_lJ'c_ڥm\Lܔ⳿=:;V;fN3sǛw=v2.MAS[XVn8}w|-[U36wlU? R*dFXK kmp|;;yxPn=+;M"[^; s#7yE-|RE@m2Syl %fY|1gp˵nY'>q Z2nDa> ʓ(MWoC;؅d]=xs {6 y2 xkSoB,=>Zyq[2j헫0:NUHm2v= ֛+2 {3|LtY7f)aA/oe*1Pbv@= ĦaF7H"Dn^̨3Kf~/xꋹ db"Ŧ4D=\dSHq9- NdY-'dֵXƦƻ&Hkj/ ptF/8AeCP.\pz9ZtpV> "m%EYRJL_VeYeҜ>6h zl&Xdu*~Uaƫy#_uKWdy4O<ٛ;|,(Y0yhʳE,RMGX~4#gk]g8pʳs=w=}}9xK]?.?-m_jwΩ_AE`vjl}KҞglu6[bΛNŁpm_i/{ n=+=9oۭ?Kǻ=FtsM2Vx5Ĩ[he75^| ql_,ڛ\9B2i/PM yV?ar< pDN-i'8ox-S?c ?t ~e,y3v m5y[mG+_\]!nk}Eꐬا/NvbIL1Wy_GL Lr:u~8&UMu>1zWM$R.dT{(P~o)E|F姼"An1W:MQnoiég NS}]]>ɞ,8A27(MzBg]&q0 oP;~j ibܐh%NKMm!<؞߽8*֙}(o|nNpCYDذMݪ# Vqƿk-c -19lw3OaK7hJHMx 4t@죿$Sb#QF7NΧOok­M^'"x_w-] ùw@/ *=2(⌟p16z uH"tt2 ğ20fi01#+?ɠ|\@*usGP(= .]DTHe;=N;ڗ/"'>^V {n?ѵ} B@Sz)銵J MH/MJޗ)#]?W]/~fw[HBcn[2`_Lة=JcKMa(EC3𛳪20!)Rk$eho0qw.N MJ6d}iqN:r?mh;)w=5hs#[ kov~sӔG>.Ow:K{Np~B ?M2xF)-p  2XjԀ?(p:iw2pt4Y>*'=f (Na04qm毶6 UVMΰRrZ,DXw)*l. ejYRyom,E!ZGֹ)\ ^"FO.Gw ͖uGr > JRRB!ŋt=֟7ayt _]3MI0Te ~c32E$<NsZ!)%% Z֜up"jY#$$n=@[P%~*'jPD67O,HLEE_+o/ypJ4&Xya\F)j_Oʯh ֠dvRQPfp5uE`= laSDG tmno Ȳqt{z-E^$]wPFt4ʵt&\3yzPҋ/jQ.VܳyÈmw~A'ejw%⒗K406L^p8FK_uf]d"ٲ>/]3X?Qzp- Z7f<.pyl†"-;Tje+ i[^Pa#!'Ot?oĆKg(‚2JreGJ?塿|%Oߜy߇g`Y ^C (%xs8ſX ۾ P5Uǃd#pZsN 4(o:M"1KA쿌ಮ:BBOļi:N0 1w8&2)i u H4ƻԚsc/6S[ZJmLSm|.WD2}d [YT7d <0-Fx@t#IrzݐGe]l\`"T& BX裡I.&vM<&PAʵko`?.^XP:&n+5O7bg;޾Z~\/@=gxWxҽ Eī0`޼{L"?P"ߥýIXRZ ljW&ۉT,c#ˤ?P?#GaG! l B9[(/{:iv6 QØ/k׃Wecu׳9 ޢe;bQ-{`i}_1&\`yPve?>6Ųv3ԴeG=Q5@sEAỸ`?)/'i \ @( 9 8фQ!;t7yl4L 7l̒'z7W˳Qt|$1to\_MC( M\|=p_#z0Q# . M`ZJ  `mlU4㇑8x~;=R:5_T2d{ r"ǙN˿6j])? -ZI:41@7l(Mh-GmP5NQ)ҵm"Mj2e:(Y^8u,ԇ8ufy`r-e9z(/oN wXZ H S_"\:LE=11 929^D؄"́W1vi x z+q^/32ni<>+퀭IK򹔠e! $x@2` .o],b?#y,[9GCoM̶hMȶdH!ңVq CWoUo뀗'&wKJ'8ŭ' T#V:P_M\E)qL$Jg3Q3tV?gV8xX[Tu o|'t8Df1Iv)rsɶyP ׄA&RuGD|~2eR!xfrǭMb֋F:Jg1< FW't**Z8}E>Ͻ8,ia׶[{eG=bV)]MVUǧȧi@&4@GD O)i07lL,ZHkc7~a@`+ձ/7FaoI0r_ k5tb>˴ӻWrS'AN{"DN(u]ŬLNZD:Ir˷*zbFqc0{dT7Qb/[f?"5H$ j~S%nC.Rlot֚E[ZnxyN4n^dwuz Obm%͖='ñ6 >Y+ο|!`fNN[38rك1Ǚ|x=*q/[oK?΂YX"c%kI Үϼz=YzIʔ;8O[9 YW5D[Ȱ4;WٮX4oJҍ".:9.WL`xGOTZ>8#?^,|aMٚ';NOzI2v\nQsAvܽΟq $GZvwp JJc>?kfMhÒ蕵uZ>W@IDATpXCb?ߝx{\ᐛmQ56ogP._ y qlKuE6B,bE:k1 `NuQٜexI"GEQrOMskAGRh1M@&H> M{Zd1.[${~Fn}'AJ Yy4p%2lC^ JW']mGb\tyFEr6C.Wښ;={4DkhYݍ}Ghx￘yM0ފz`ׁ%r9E-*c)_@G<rPN$\zOaL [Lkt%phRfg3۳L*vs@Ҕs@h9%Z]+Ŝ-+0v) X`.*a.o@U{a 6 ]aߖmҼ)34[&%՘Z{#S<ƾ+86$G8Ȫ&d:zr4[Gu'p[`k:Sϝ!҆_S*q`! i'(-YmK!2:Y:m ~k>Nk!(-!*6xmn(JV8dxaeYH#c@ V._@ZSc]S}:5rsɓ1,ǚGL+>$,C !Ȩ7eeu&LkJ ix5;;iM5̯cBŗa*9 az1.O-;x=7)G-s/a.9> őޝƬG WD]c ,(Ë:˿\>%0B NBCA:ik$?.i_u=oHHq#5(~'a{Y>7<!w{n}-rT}z<W:E.J˭th//"} U3.JTøH?r3!=iOGph;Vj4,-e UEݲS1HA!a[3qٰMktqƹ9# `s}.y,J\")YU /.:Exn(n^!1>'|5q^#kahYx7ÉМhFɇά3۞j8~(k$lySު%0%R~,{᫃齕u-tUbȣBCdxxsg֛_ʇTH !Md :;`ai:k"'p`c}| (P$[f- Ti*8=L#:lӜeaڙV{s ~W׾4S&fCs"6M?X@ХaX'jH>8d&CM0dڐ~ 2Y?ԳA?9;MƓk(}kQ[0 <)!'D}8Bӿ;IG}|£.:|_c?;'cU>ܞj_ǗwOlu~VߦL *K+Ieٜ2OB;o(("F<6c!hƬ[EZ,F|3\ ]@"{&>m-7VP%+"n1EoP@\h*+wYR vO逖{~ 5|镭74 W>}"@~NŵWk<ŏɂ$MzN#vi}I 5^ɽtA=)P?O΁_P#֒ $7ܞ-aqƻ K/#gm<[hј?p]ejrStNf|I;G Ke}A8{U!obL*BWfu^T 5g#eEmT. Pmz\o;qzО @`0:jӘ"J`]MĽKVKg4ءn s桡U*HZ ږEdeZ'r eⲽQ1r:9}yUL*]hqbxŬd6vi@P*34m?5՜ʏܣތo}E^5$}zE0b\hU eGu N]O3?OEy>|'lvQ5I\0m&huv4$m,d$(Sg@QRK "yo:E].uqڷ< ٫=Dv?@uܧ{ Hve`G֌~i`ߠ%1Vz/;w{9ȇ@D~5;/YW^2p`+fzyzJ7 :Z66`1h h:B_B$]D_(VنnhE!c#:~ ވnB TMw%`Ff2F>k| ݻ 9/liGCxȂ6MF6ؔuQeesT9Pc72oRi>ŻLcXxydv3Xldzӌ@n/ D9sѮe:|ֲCRqB_C]ExO5FK=j]߳ߌem3V.M/J׃|dd`V"ZHںPMSJi|xTeF4xGgkTiOL|пӳ$n MVLe OnwMu`?)}aa*h 7T<"ܘamء6 db'BG2<8%/絍}Iqk M,ƅ*N'tM1h٢)tz Lq\xmVqfv[&8+\jkz5 .CPL[}la/ {6FCy8bߠƧ LlɓZ'  MÃ@KNݟPgcU vM 2Aؠ66D[DH϶k]gsL$ A4}Ze=ͥgkI/k#P@E>O N{C(.T -r$Dc4ErZ_l|K އx~Sw<ѷP6cU2|%t~DH&D=Dv*ZCNShK44ڼ) k(Sd}ncԓ`Oz~з9U$[zu6$F.;/SW|97:wj\d'@&JI~5>+Hm&߰[G\ga FsϺD@e+*~`[j@QWԐr7A O1Fget-Zcaͧ* g|$uɖ6m'kAScIk7/W)-u"Wpbj0~ y%MSU|}7DoiQn Qbg82/`[+Ŭر0mVUȁ i 3.=rR݇8@uYg 8ÜZ jpƝ@8lqۨ%[6b mjMSF)_E4X>=y _ R76k-5Ic7OZGӋun >qNU7T0DOv^Hf>(}0*Oz"c+xߙv|mk C8fTq c"XP% T^YQ5 h>E$S$t.:"BNi˕p-=|jvj9ϗE ņh,pU(pK~Lx 5䶇0s|sL {xΐȂ{xtBNh:խ(&W'JZw:P)W;  4 ǯ{kt@"tO{ierA:ȩ( *E *%PH@F:#j:evݘM n.ZT\443#)D ?b/61LbSIM7c?ٔ{Cdop4zW:(& _I[5.\XF :OXui+z'^x܋`CB[u  448$Z ґ`Ǣ֌CBc!b{K(9ρgc}n2FֱLx+V8hz9N$8lW ,vӣ#7.5B ]P-r j3{?O*xwn7S:] @lӌܯ?XV١ ' _ďg~\(2zF~̩Ɖ=0rbc6 hDDP w^g'V* DFP؍Elð1%/x`5{2ga; n21ˮ2.NJǒnޖ&%WEGru}BɯSDzr! WVI2ŮFR+GXַ*l}@^/[[ byZ$VЂ1'~AD:!w>p6'x(r[۳@$vs9'h)kY v|vt=GL[>(3N_O1b@F39(M<|I24rST*k@OYG\vDq)"\Q`d"9h E%.I< HK^71܄,3mbf{q79gOO598kgg}FoC.0PO]|hW"^~"BI*K/0-1 }^9ү@iOOՁєx$艻7sLGbE_EYFx ~)S?[<%4n.KejaR/TF?UlO~ L7_k0zR[P!jfəgĨ~N~(u|zR ?*d-qQs }ؕjyide a`@ Ŭ8 eCҴ8wne-Jbz>G]˝b7uj铜-.ӴK `ǰbȩ)\ݭ^ݖ* =_ş<Ÿi&ze*nGTD@Tqφɟ$7+gh'B._B!:] >(O'mS,#M?" :rԯ7MdVI;N.S['.c;1Up*:p/,v/$)D*|i>P 0]Ge)/}:k$ՠ6G.D0 i L2蠉7vj6:(sUѹI#tɔ 9`hH`0䕐? {( Ƭ#}Su~vkN>FP`vM v (>Gr݆3ϵBN>s(ŸD?Piν6nsq`}`%D])E(߷H$8G} GM,@R(\n4QVV`Y^t;*T*{IFFԊgX͞CI>˅Hi9yKXb3$VhA`aeawY8Ude嗋ՊzkagS*&Nu:U_6bx1|f>H^nʉ \4{'O8Ez{sS@ZGseH8_$lPN|Ayռ iab 3v@:$!pՐ xd >|~T@ eZxUሦR/ZJ|mkߡ;löj~fZ DSlkln"J`eV1DB!L nIx g+ nݫ-UjS >V+[T@6qA1lG!*OEPWVNv:w7W?[mpl ~O;[G{,mR+>MZ"au SgRbQrX*B&3u@R 8]шZ@RdP-RaW9Ԇ1'dŀGٶ)ypd:eC)Pew-JW(ee 0462&اbU -pevvW;qB .c'8&(B%^<7߂7t#4S2hv1Jo\.j{IgѿN2\+~dޱύ~翱:0Z-`j%XY nJCP+M: oX KdSyuTBx{fb-2[BSFiSef +G#rcIk`G)xhz˴:h 2lM(p2YW8FK# a2q(s E`(!;4ٺf߱~0=ŋC񅡷ĝ^w|ZeFËiiO9qnOs>wlF-ԾwO F=_ŜJyC_C`bӱʼnJH%E)X  u\s \.\ԭByuJ<dƉ! 8 tf {%ih! ,D0P{cNr%~]022.^r5M54YD^eMBCc&mg'3'f/DM7Gѻ# pM$?;x4}2R^KB)wIw 21o?՞q]ɯh>smT=|^=^{B7m SWc*'*Jh@ӂq")/6e̗ZpMVbXFlӡ(sO1J͍e =r#L(YO/qt%r3"uzI^9ǭ~̔/i.rj߻<'6'n~՛@Pz+?I?sWĩ :{MK-5dvN Q {/X"-?y duCoCCOG,<z5-V%:#Z邐5(3* YPu,12 `fR'(.Gör$588QL~p,hZ.dwo2zn QPY^pzScc $W,?^YdC=I|K5zz!x(ŭYxqy{v4mN u@1]r;jyc Or.Kbl8-9&Xy'ʬ>Ca,n/0 'wܻ^ oQ2;ѺȖ$Ť4 FҝM @]4`RP'} tVJo  ;I3JA.jm%Y&i4-Q\%.ipNk'Q#:PHi|yBEU`U+cFIwK%.>Q' Vg Oڀt V1iXK5B䑰tRRtJӎuROgxKe#q/OWtq?Cd&_t4iG (Zئ_a~(%6v HA=4p~<"ٹ̱qI{Ff80'@gmߧ 7y iS_?ţSϜ?Tƕ-|yit?ŕ;<ɝU9u? &Ƅ7'N9%Jd_d)^ "X&8v)t+B 2i!y+O]s 6ցͿ adՐa `]|Ls`6սF iԳS E|@qcSt%κ@wX+o@2R%c `'}b,bͶHaV=) >+0EPF2!p*! y:҃ҧ0/YJa[ȳdb9h ylX@"Cb:TA(a&1H8щ76;a DnbS5/bQ`Ltp$()g`'/EFD$T&E^C&,c=XSE r>R|m)cԧ~lDym[^ ]Pߖ_ůJ+<`V&8O` X_S*< HiAP /Da@nj#@'PN@`FnƋ#u&L"~2%ߡ :6BG\߂CDFjr6l dpNߣX =}50tG9>  N⎻o a)|vJ"/._y18xٔPM8S'D4찛sBt1߁_=}qsk'xAכxcgO"q:RzZzLb͓6Mj0J'83LIL]4>Lt_|#D&`-T4Y,EVuD-D2 KEr$ 15RCʑ&$k_2u{JXUʇVuDքRQ9z-!awP]]Bf:. sUg ^ߟz o)Y"T{L)^E,>%NGD#zUktN^@V@JZ% mh$2”%r1"[>HynQ#vɪT ;)x>/CiЌ(t$\InH<2$)\{d׆v !RHSpdFES\*D8_?'y+#[t?h6~j͞c;T6^ёwOВx+sED010 ^u%F Q hkv[_hs<5 ]4Xi,{qʁhVYhQ@>O*frA$x$9ChD֧63}.i#(V}?p,!hcQGQk/$} &M*FP8 |ՕU3m!b$j(<؁)̠*j\8\wT4Yk P])#UڹXEZ0_WWCgD Z&?TGSyE D~2C~7_X];J07&xZRC#!؞_aR\55_%t(/ZeFhʤV 8JFF+ mbUѺ.P(yH PРp NiIv|p`LΕb d6|(Gߔm&PE^ ƴQ;EZ"@'e#Ƹ8+&H֗1& e|x,?!G}JjZق5R[->ǚa QC z1JTfˣaˊ"}ZsV88Mp [ X#g;x%KjHaƒJxMtT W3/5T6ac*D| S'L_f6Ӿg4EtyZ252ג<^ZK52eEF8 νQ ǹy>;$H;xRz$tEʒ OZGXOaݼ2x8m8%1t GT:Іyiw>+JI& y̐wTw&~:~h0ضN(&RKhur0U&vyןm?meb<w*>]I[SD]R75pV]զ !/b懶.wBȨ(D3~{#PUCStP" M1y~HtB5]lath`jnJE9қ f[=QhP`.e3R$ +NpO}hCZʏb-˭)FkL%P%%LplS N0BYp?2H]bLn 'ܣ6.泶PoSeQ+F1qٿ嶠 1W/*s99W}@qo==ENNk9-~>\h ~^q SvZE+{-K/&Ydv/HxʀFuM@ 6K_fİ-H!"%0}g* 3 3BǾ<_m> gr{2; &8LJ&*|~BL1)H4L6_|k2>'᝟/5=jZe ?u4.?bXՙXA;ϟJ?+UՏI7Gz_kC@W~;\C^GSc}fjkjdO-ˆ q㟎)9e`6 /Ya0sgA10AclQ(8 %&xm:|{EAcǯgx,3lI&{5t3ϦCy̪'rnnb; 2I=0n9Z|܆ :r8/w65JSy(y"j(0ve6ſ h(@iu'PFmTvy30O-d9a\'-DT_HXGt4,ӌ@(}f!LXQX T [ER>!c\@"6q}q; A{%j>zG2)g_40 ?2}T$,?{֥GT|sPz~nhzŗ0gt/^/-򈖼~ir}Ý=3Jt ~g{7)u_@dwX{n%!rhgU%ee 4~ MRHqZCS-P5A,TUG0rP!V\RhkH(2#s*41Ub@σ :3<#6%&+?CgݖOc}YL&֙_>yc ^#Lxd'qĨI9%JP-!7=G\H$wAiܓί~aXNA,y$ɂݹ? (jK=O^،<1$ͼqbz4JUi"EP@IDAT~(>+6;VnayehGwiDȚraixC ^PI7tcXb3H#4* 1~^LqQpR E5m*DITR<-nUԁey߉TH XF5]0Q$.tiP5MDJ J"ʱm=QaU_aP F 5-U;re0jGL9IyͩYD!,[-1a6sw5V(8%t:8< pbp ,"ʝ L%<RZ15CS.NQGLF5rJ7B9׶pz"n= 赴a++#?#X\Kq1(Ԯ*76dbטPU _c!P)[ӏ4J}x&f'AX%n|UHF\t  O9h{i rO1=o}^UY):v94|^?5NW&TSkaCh`t՗8|ӗԹP|)$–ؑ,h _"D!00W(N2拚ƥ2(i`\ ^TU* .ѐ@!(} jZ GK#3l*-3+3\iq5r\%6@>En;]Ƿ~r^xi{&;p\HqLJv^yfw{]~O3>ٟfݙGiw43 /-kK=dN wo*T:vȷ:r:?|Zx6+P %'1G;@ vi*?N#`i[lJOJkZ%UUDYӥb0?J{*0 3U08iH΁ `,`Fdpx^IRRP2x]t,( Op.*X!rcǸ+ `Osnr͑]w7I,͝ԅzVS HZhIi`N&:NId@/ǟeMf<j:;uػ/XRkqJbS<0^P̞bmdi@\6v():WIrb8hdA;.H4Q\5W_b 4C{ |uFtS~4 ?)/fqԭ r4x\`D^txd l{b绗_ܚ5,2 >qDtmdR&ɱzr{|_F#KzJ+w}kxMi" lKc:I'o5F}`M6ߪ}BGlп_>J/ 27.x; ZncB:pǷ&7-y|1H&rJ*1#,P [-h_ss\3.,p?QiZuG%dʅ6?@-qHo4 lXxw̟y@"ޜR!`,qZ߉[L /Js_Fk 16 @b-kɈLpVMܸ˲Z E18߷6jۅw!+;SbtW{CI, eRлM_K|*`8Zɀ6*jmKNmg*]fL3ز  rk_F3)2 zv5`,4FVeU&Xx4+i"0 31hfI $\q9*+2 SY)2RY~.TLsrod>s}dAdMɁCp]4Gce}-1 >ccN4!//>@O8,40i؛@$:x%3y5=aKHռЀ&O.m`FZhYh _\OҢ_~~ bۺȳ]NFЅ63T/mPm\*,Rc$e< 6 /Ayxs])oMNDQޚB>0/ڑ Y`̷ Xyr^*giUB{`i<dD Dx2' cXDfrnh Vهw6j2}\dzѳ+#J78h,xTa~ ͅΞвulaPp&oצ5:GnFd WF,+23O##; UYYFIF1@.AA 5JӈQ \B9a:Gkٙv;¶axf[KOаO6oӔk}JZ bSB钜7 9C`,_'pm.&6:oq`ɗ(R~yLHG Lt#HwՊho8O{8 #8E4u;V @NrcePyV,4 Bxڻ0``pAʫϯst:h"Oof-NK\B ~ t2htdeB _Am>)t*=qP.&Φ5<2$eTdywH2t[WZPP;>R_LO2t#@^eX <.g 6n8@.)%6IwӢ,qw]a!1~>g (-EmБfRapa RI,!:ITщ8}>Py;%ۈʥ~0b+x.>(qßt`C xOrԜָCDrBeX7}^2\Az k7`NNv+] Lj6q==a2 F= e4ٙ N2џtLu\x(YrRNA18J֎ #ؙ1BE noBn(D6:ASA@$Ύ@P~B%<\Όe{M:Q"$3'^?CG]<삗{S'OnK:$HN{Ay=Ĉޣ2H>`WZ :˴g$biVqfO0[N)DvO)4=PBj6g"&X8KR4 U}3s5OWpĆOqceGt`q XؠueC ґ7P/=;beLy>1@Гs6Ðȥ~8 s/ɢO w ̖qn'>ĶT]+ +d|{ ˄$9tUi&q~r&kjRHn \ zd7Ʉ*]pp^Ll͆.1C8ʟp- JQ5 ;+v[0i^iHN^D^ܾ貱1ck/31Faٿ3,3a@v'?#=ӟ#y¬=ʶ/|}> +9eu`ܞ@V򇝦U=8i qfeZya#̣yM:\n!q˹$mu4DK 'Q }I2&*LcaLB=oJO2F" kc0)eVQ^*)ҁwKÚ l yAz^.q2O= ]YX-}·Npʶ?AlC"Su_~v6,{%`+'J 3OJ$@"|rP2HL֦A@!)S%}`s#VEUc-:J! ea.d*RtCm;#; Ex}gi 'M:J/]TgeSX z5/ӈ́U_D!DCE5cs ]{M?-b$:67"m\˘ a0>=% S@3Gֵ~>6d&˓1"G ;6gW''_OY), {ߣXa_d9ϱ s<tXqD(Oks%E)PkRPCi"ua|0,^́!@L(4#L S˅[F蠹ÀlBO+)A9-El6`sGvcɪ0 1QEifAf9Wږ)fIK]T$Pez<g*kSdM-h6Tƴ=X[ zLz]ŝc꺒wCHYЩ<0KN+m^4_H8v_S/ؓ#%>IGF0q<Žy: ]kac>>|g{] +yoQs\̣9l%2G<;EFbylp;y=Ilzߗdc6;7'Q="jI&W.zk`5XCOl;LوxݷJ 6GSMfj5*'}`(o"8S4W*^ TXizp+vk1'〢>8tvդE,p):Peo00V*#aU\EYWBw WZd0a%쌫+XggnD- uh'[8h, W#i)C(3!JM̚C -S\Lb~V#_DO9RO<%"8zgcON*|i0:y݃G\n҈\tBaA :sUB ҤxԼ C'3.)+vLryED’l<+y`QeZt$Y'y+.zLp9d(UfA!Lb`_wem#|j|n2a̬M.(,|MRǓ4;¬㤣0+ ΅*4%3$:@4*-x>ZV`VVf͵rjTٓYW5N;<-qT\a nW, D%T@n?>8w |t:\GY%0Yk$O򲖀 wt"'C6O@E bP [[d,MG1M&E (9I~<&Q#Bl Q%x> 簯 (iUDٓS w0`9 %ph)r e pƼDF*t(m|hX93$'B 4G3 wf'wc[SMK)uj˦#r/sEł=73-W[:EE0=t꧘D0s} "໠9́CTpHؗ.jCHY2Xyuə ?7J{9hkPUxdsD3ATCZB[\,=?ж}I@-m,acrnz;Hք Zrm:zaϻh\2. rzǫw\Xa 1|f2(_k[[Cd LMB0d)!3-gBiJuZA3ArzO!N&ƨ? :U?cOv }j׸k~nXu+& 0]l;߁Gboc!dt:tj04Rg4nH7>?2{T9?ďhEjwv2*#tKb)H)Hc"E}Z2F.8_=FÍc1aYRfM [MLlZyG:vyIJE<ve)TR  op$SZub'~TyI2`3u,ːlUP."˽rVgs C2 tkiEgsP/+XUC)ye PעS=Yڮѐn% J 4L 5.Kfd.aV) 6σḱlBWŅ>-Y'#pE<#xcM۲M80ڦK?@9E9,mшgy3p'2;J~D>p&tK|wD(N! j瑍=)Iʛ_ʙ2 0 aoq'DW>cd0v{HwQY+ڕEB6bqDgx,pgWP6g2\L_qifo0Yth 5_ʊ֩=,Fܜ4DUd&p. evÕ|G@f5 A^<+r$oqwPWY$WpC@ʵ%N<fėtvQ@6bUE7 .!Dau8ؤs}N5pimKgi2Іlpex2(>D!շ/ʣ;ۀ~OꌽCG XӫhiڪD4pgwj["Q _I)sNhKP7o/gԖcS|51oe>9rbPl;'?۟c6Uq6iQ" Ihfi$K`юif$XrP2I4#F3d%̿tV#vUya OTAʸDN xf`OJ0uG+3YK^ԻaY @3%C8DA5qsKLzY*-Ciԁh/?<0.oi#PxV-H|_GvBڣ>&qkCړYtz 4r}Ͼy`X@NnZZt7\ MsV n#F "7:%A"qaaȥrG#J M 4csa.c?:qdv75XX:9`.ٶƅcD\kAl ϫTBT&ryE/@_ 1s64ҷ.3КӥS<1q'/nNc;Zh (ݮ G2ib:>| М N47  < #  H3H)'5~o@*UWapFyG"˩o[UAUOt eSP%.ZDOc%!nѦѶEY ?Tl44ե i0Yd+)5a?)?dD@Fphhs}6ԁKF_t̆yoV!>n5p ǦxiM 'bcfRGr ]2CQp$)_=2WoƼIO9L^MA8XXB @겼3T?0q-PZ)vOtW"M _eQd% 53J!} S&A,l@ \h.Q#˚H""_(J冗q:腬D_$IC} X!Ada\ћe,4 ^Di!z c'fO"y]7~Dy&dc(@NQo>F!0rt* ;hIQ_1>FPd4? d3~d)i@ʉKB5G֘SyBC rVpe )0UsbRN9"P1K o[$IaOÊSs`,ԂOM ^M}mL Hc-gB?j]Z(RŠgp.t~r*Q_٨kyrNE߶'Lb~3j$ ;vr( U!C\4PcX AcQ?_>:ӫZps` w'!Nh j h&cq/_#c gellI${`Q6P{l찁08Q]PO+7x]4J]~PKvヨt_:"?6Z]B*Si3fao"~В23)Q,C^$S2eL<Ҷ;q{ hb@sMP}ju"۴(WKRg|G ݅}=@^qlOOjo*.C)=OÝ",w1e_JڧyR( f]0.)+dYk`Ȁ%pҫ/MQ%ce4UJĭ e٦+mOkR՟:z6,V6͢S'j/G*jX8|zHR2JLQfC;x*܅-9wlZ.OonSfXY[~ad jqcG?YL/A, yCnU1tl> :K#XlgV1%@oBb· V~[) x 9Ni镧aDr ؠ>mGA=6~Q֧>*X+tB!F83aS[;~^$ɗ@0]&CUSDdӆY`YKpӠ{Go7fn:ڦ^ELpKdGe^̋i_'X azykAme퀿\_cƠ< ALI VB߈e fH= 㔵 1i"5E5/c|BJfOVzO7m4YedI la `C$ER{R!IU*B,dk[Kd}ԫW{pϹ=גn#&2xm)6tU]Ʃ8"ΠA< SD'$3%Lk\5q7:"^;n'*T{:"AYXʸ,%00-a'¸?K2PLgC1Љ30 2I5* D656E)I-" coi.tUnLːuS\vZAHژ @oѯ@ҫ{o*x(n/al RރeU2ql*+puIނg~&]Ct*0%It8hY@$杈%p<$x1#Fe]X)(&+ҦZD3 gBh""}Yc^P2ZFxVA3AKՏ./̝$NcǸ\#`c@~?$<6Yčjb6 TY|3'n~/K,'%^Zro\ yx5;J?f 2 ůϞDK0Lᖪ3g|PB̢7)X T fڮE*Pqz><p=Ғ:^VV৏$?%һƀ%ĽIIFmRt__t|ڹ|}(Jv2#iiLD2-P 1񏱲1e(,(Ow&mO֯ Ȳ׼SFKpS/: ~2Y*a{D.,b(t,!W[fp JAbK ? yPz ]g(!Op0FhǑ#14R%=G̥L?ZQНj`AY FIZL2@fY ϱD wU`eҀǨ9NЭuCFTňvp Ň11&G7ٲ4Ugۧ+ȶfmCΊ'˅"bU*6heI:Yyx+ qآfh /]%9N^?^GlB?˹N8AHeS M,mqd=B c(3KlYِQ  ,(\6 ذVYq}vHq@DѶ k\Mg{!7Zk`a"Hu/VH$0@1l\=(F=1szĺs)jPR7tOԳW:П- g#yKtch{ Oudk6;I_PL16  ӟ `/6d-rH?OL%0Ӡ&H+X*Ď>NYC)Ⱦ+G>`'7Rod2|+MNZP_.;βQF 50UXdǏxzgH1Ʃ;!΄N\Jɴhg?-UI]U*I[ͨ5ݐw /4tg#$NjG߰ 8$WM7y+,r3$AN2kqS܌"{$fcwUqFK7}*ׄ>^n[ daI賿FfAp$o 뻖/c=!WC bE`ҟa"+̛e*2iI`d!xILp6&ΈMvDyCgNwlSvkV~F7YӅ'ҫ/.kyPЮ땫_\ӧQ^ 7x ys1zYN/)M co T`g{a%/heqG|7;oh5zQV]mev8{Z($r&Ax3:KuE s`U8{[d0-<% $zM"=+hY}@~'V:3=1(˾mHS!jJE:0jxĠ獘aL;ka;@d%}t )1u;X+'=AWlҠ@IDAT ž9qхeqrFP^֟4"ko}WA݂ۂÀPg_| PCovvl^|ơT ~2"쌹d2`] `\>EqƑ ~0.Po ˛JTٛGDFJ [b3N=O4{ܹCԩ63:ѣe/vQEי@?c+_H}P3[h<rfuYIKƓr<-uv^9 IV. vV!겊BUp5QJiUF''5s;]g cyH+<@ \Tp~ Gj. !v3@[@h1VR%i23rp'3z)EŢvh$$WDaZBRyR"!|>C$@{܇#N8:bΜJop^lӎr={܌kYh|79jIj+ &nF]Yˆ<TĨ;jK T@L7|,83N%s)~e2^7ޕ2"x낊DɎs5Uq_1`]C>\I >Iwoݖ=SCފ4U_v_*.nlnt#XܔcÿmFPj|z a&$}l˳ɉ]`2*}}-q b>Q)^ɃMp󫁘D99;X4[Q2|b>~\K K7.8/?!@hJ2Tu ^zTzU7 Q{϶s/mǝ5*XqVb{C#qM8uYqZQt<1U@ !l&RSX7&̅rf &ئ$"iv(`N0,Vu(#P0l& VUnF;Zۃ 4E{ 5)TDӔygnVWl󊫽l^11cMLwC-4Q4ţ\:@l8!t 퉦 세X2赘XHDQzcwzwAC5XblmyG*qSn_ YD,u$r05#G;+N8s8;DgĮ8(T}*HW}0$YT` '(XW .t)7PbRǕFJOoФOMm.uV(Ϫɡyn%HtARpJ~P3[U:c KѸl z40@˨j u*a1&=C~6XWhxx. ^6×7#58~q~"Qj1RJCI)tFݝE2Λ7zu?l@ہ2)M8vj^oЬֶ+]Wx U9'2s+b;qQHd'IY':".@7'GGyRANiWZ% Y8ɃO`88g)^QG/liO[U\؁'륦 W,}|'M[#tLRi2{4(%atf_+3Npue]k 5Ъxkg>JtT(3Ƽ-u @rY-PHW's)9Mǫ,?dȭnfg%Ͱ~nKE^a ^ARǩXoٰs-IQ6)A| 4ts}~\nSη@K:[Sxv?[f 0;랈lm"1Ѵ֋$VWlk6zuUu(ObO`ď_I~>Hiu+m%{/6ATP VAk*gkG6.Q/p-p68GovU@.HM<5"UXՓ <(MpXq @<ϥ9?%7aAǕ'?3}zn<#Sqfo`uAM\T95:_| oқ IW Q[-| Jv^[Ϗ-6HUadtSFq\K<XSFl4Zwqč:ӛgCe_0U`6OccOA;j[F̄ 1&/*gje˰%& AJJ)_SvPq%/Zv7̡s) ZʁPJl-{J≤ó*xLX: 6BQ['M,p a@J>]`J "EsFǕ@?lxP~醌uԨIdB2"HN95fReXaʊ6 35ؑ e ukNN=q#w^eWco׹ Ns}$a]fb|ΞgtpPG}r fSN)KD,jCISeNbAx"U1>{ cA|gbLO>^3;w"گZ:Ȑl(d,[߱YbM_ DjQ,e6(aJQBj@2CVi -&'Sk?X'Tpɍ"ڝij+^|9:.Ox}G#+1Gbӄ^ ݣK1&o!1#ˣMSu${,Sxf0XZrxcu8߭;?LIgv /b@WdKpo?+Ňuya bb}uL[2.үHU [ д8+:zzXc~>Ia %1gӟ4ӂk}vV{:j8b:[0S6Hle֔ 9!$bwXF0Sd,ɬ;0ŠzP"{xPx /Vw}-_/~V{dnW=nxD-9"ڰEŇÂƕ9g8k`O"5Nt8M 5cu95oYN.|_m6k鄍~Z0!Pܫ fI'U)"9kmmBUGz,g0/bk>ٽn$Э;m%"$9E&~4hvq/V$pkM_\|ژ5tt}"+D"c}GmRqcoiQ:LN|t t9i^]؆B[}> Ou1nl'DAȫ LA" m IZy!!<5Tfgۦfx|ZuXK#ʄ11۴AfLM']3JqAj\b|PZqn7Xٝ0 ~O%ӹŀψY*TEk6tWa̳бjbYb#*bC.2̬3 4>(&FOv-㵻.0P?^qc)@Kx :lg/Dz$m8VRD &$[ҵy&mhW$}#a(Qr71WH^#E_C׼O cѾi!]Im^'y;Qkp;so9>6cfOĒNepnd@5.61wĐi: U:ftU%}]OAuSq?S,ъ-{3oAd`k6inI&XX+@WnuMMQ{ݯǞdKMR(iݬ̈́Qʢl4SY{ (m@k+&Umi82?[O *eu.DQL0%['5Dtzxdc- +cA; b2! $Y}G8U%%j,As]/Ll$ KFM>c&}TNU/=w kΒ.#AmӦQOC>jʍ9`5͹N=Ē<297Ͻ,_r.kCdنTnT/f` AW %mk&>;Y8BAt'l̮Ո4+mk:@p eEXR;)9K?IpF:w1wgc~Q>65v/$h혐܇;W9/4)L&Ài3e"6y ~waIbrcG)Ja CW.j d#,G|XWT]%m2m,TI"o#`c&Y11ǎL@dW|o[^ 7o\tC񖨢AtURL1-l,uCYg{#Kd4 ;2{,W BG`1B;U<£ZDI ? ] +i{g\loQ}kԡ JFRh1l SpR;1vR,L_HlDҖge1Kقh·]P ;_e P"nc3~(Ғk)j>k*cP$nZ]*ca(]rr6HZpC{ĕc0֝lQ5%bFRtNrz.zr~]zk 6 Ч0sZ!l@o Y \.j8N(#-c. ۱ve߅gЉo@ߙ1d%k3N`f7lǻF~# r$ht4[}jGĎezy˭؉#g:Y55+R"&t_~ ɚc/aVe_4]PXyǥ `12ZYT23!7h[)_qҟјG #L Xn=AT6g>@>N75zK&S h+7ljhCmyʰZ6P^V |~:kG:{;AZgR!*>z9by='Xljz]{F훤k2}Ь^m^Վ`vkD- 7L`#< q%!NA螕DE 2OI-f)3r7~e#O?ܝ@A OaLaz10,0;&LOb gG:?bJ|8>Yxf(s/bvZW2姶(5?|;;sv~&4pPJ= 1r&t%GeF\0p?!kbؐHJnV`vIj8 |QFB-XYQU*@ 2*CW N*?O@z~U\.%9#v9􃃕$f5*Oߍ lFyZѧ?#t z"?QN/+ʙHV*-D/0?|e|Av y]Րmb{Gmfv/cD+ ]zvX3:uڙd񉎁cl𢜄pXsSs/U]&d;7SfԔ&Vvoe>AmLݕ7f3ؕ?MyM6VASE573H_ө4ڹFHLM`oj Tv]Ba{erv=Nރݓ\ekt'>/*c5 Ź}n\% (|# ;lmqP-0*v4v0857@Ͽ,/(-d{@-y+ڬ8nV4k;2Xt.= B 50ށxP$|BAR6 ` `A" ٥PJ0AMm |=82#¸Oe[6lF8^ь*_[O~Nt|ڎQuy^՗Hq`gRV^w>~Ω]BT)Ĩf %T`Š̈́pu8EGGƯ9y3\777rB^d Jbc8h# L|u:M Q@Vk9;HIIʪME$H!Q~ 792xa_x Y7^綄;;AaCa`sy)%^z1S3Nڸ%-3Շ$;jaf <^l#dE/<\H:ae _g~mܟ7,N{< x_T&ǚJTpȁLy:&k2]U]13-aH [;I$6Pn? W<tO/bR"Kn ÎԾgvBBA !n"K[xC!m3Aù& 6adlΞr2qMmN^z*-,\y+溭bѓDf%8,kgJ§3`g )-Flo1Ӿqf F-˛(@o @8\hnM(l.S]WTDp %v}e@@C,װ(ư#LAfEP-X^TR:'DkhgXpPPddm'Bl Hq-2UC7[ڲlPC7VNY?B,}ӟNPS2d.|v52it raSxSAZ&dvWj5$'񠚓:H!zET*V jD] ~(pA3B x6{iC>ݯ*nX+T˺M"5,f_PL*cY7he9aA6CڂU:e5ޤ+g s" ~1yۢЕ:%yd1߿J<# &F (h! (:^P0O:ĩHUĚeF0ye.~ƃGx)Q񧄓i, iz^sm=sF=$pk>a =@EĠ{({uBLHA)-0 tm1GBP)h`c b8vxpV-aĴ fBHc&b|!46 Κd8e)'R۝|ct$13&^ * @R+6,Ƹotl xHh~ ? ) k̾`ɴt'Ik l|P:5^̀/W7^n* \xR KG_[S  [:AeL['hR `RԺfcLq91rf p"cSD5u{bo7$e7*l/˪^93=ޯ7{ ZzrH]Ia (!"\ND.WO4ܥJ0ߡ\ O?wdDkUC6E勺Lp R=b3?9oL.mtO tLĘ̞>ɄMYd_${qCl"lEı)]zcF) {qpͅ;_@BNNң0W{gZHjXy5Gj`/BHeW-=,GDŒILRC֭kBψgҬՕj1 %f"/ЫLz(P1ٙ718vBXK,PFRznTj93?qyu4)]F%Avey_~ Yl(e« I-s@*z(g_9G"zQCב FMR_F?  )%UMڿHYY%/)߆HS fbX~C!ik("F^є.xޘ+?~ODn0sTJ@Phi|[r[?\W`YOTL CyJc7ie3Bڬeyd(siV(qE4b'?k6-wxM|е:x╛GK/-ӦI3LtmTXSN@L I緽7-##/|eyeApD1sX}PH'ZҎǭ %|yj tlWSxJoH:! wn`h9-SQVntrd/EZ>`qE:LSʅ7BAmH-˰"4-t$g9G)q mkd kP.:tb}{G ( l AW_b|ů-KWg*BBtG`]oЎU*"Op0 t$+AC[rK|r>a| \C 7oxy~bqzc1p֣k}(b$Ʉ` CԠVSmJ-7Vxf{chtǎgЦa8|pMODS5D(b,').X2&Xfnvb"0MGxWꇧ]J"`^b*KG#SP 7m* '?J~T4R[:6gR7w,?NTՀ+5]c&VG #1GB.2ψ{e#MzER9W 1_,vu7ʍ=}@_ ъNT/.O˒;L@&fdf6k!SL^ecw x=8C p#u=[,4[>(CILDVƪĈ|w )o$3ԦsxD4QWLDd}ﺽďBy{jM˟/)&„ウ?IN2~.{ge:[!i} v8Zcv;>sRMGƼ!;`4JaUf)~\e+{u@IDAT5{ ½6-˟;?G/i䃚-óQOseqpaXOu.|Z#eS N"uLD,#C7t ~(O G QHӑt ' Ց9KH~ B+ Yzd |?Ih0 l<6 d97̪1-6UڴёYུ{,߳ȆG.O&W6nJb25".;|'-=Qcz_ C̕]ˡ0 ^R ]!FczW.Qݛ;\јAv]mOCخڕ =T7:)_= Hz*bQv":?uXTgy3ڇKϴ.CD4h<ʑ/ɭ (&6ɠ yaolCoD%G819#×"ӆ2F0_臭[a9oKΤ& &,y6|îwOCs[+8ş/L Mc?b cwLNWnoP?򧫭+F&Wv+YQs` w{JOμ e yƱFVZ I\\MpDwH$g!N3g'Eh^lR`D kՐ$>&z%?P[em15mzZghW8M!˔hb6/f]p2 Rd{|d"P+crB JDG+ņW0b@+q#+bB$1O#W ܪ4x^~Esįi;s|܇o[i#J[G73wh\*g!\xE|Q'&;چcfhyMnvJ$Uq-&(6v*4e*08iɹ, W@gjFw,\w\au8Ϩ@ۢ8xk,A9}=O*GZ@>,uD=HxTq8_ԑ\}pTuJR\rrG2A9tXGRLSH<ܠN72(C˗~W]ߦÿ\]˿xsy ? Co<@Qa-|w' ! 2} 4\H`?Iy9Y*(;E"}!8k4?&16' T?U(6!Sϊ"EiFM/5R_ ݯֲCW,vɄf}4k݈A"_M6׻; B2@$a9 ﵃V{d;~=KbGQC-zpdoup`@; $$MAY\-ne&X. 29&9B'JLg2Q+7ʊz„Y!(wΆn^!IyYW[~ot^}A@gXˏ4E?sRiǔ~ŶHc"YÂ|7Ci aE 4E鑌WgQo.lY3("lt`RȌsz\;H2E ez^-w4]ìH+=~%j"l i,?\w`L@N ;K&S,|ujiGn> bV^<] 0"%1}1G`ƕUϵFG <| Q;%q_|['0Pw6;>6d?3̲)چc2=t̽绮SecQM[Ʋغo|~A5!Z|幗FG>T)W}hؑ4¯/!ˍ펿)1¨j;idO?wkZeE^{۲|ǛD<rS3KpeuiՔ^ yj1}ű ? м‹M_K^(#e?z=r{yN1C|y- |w6>%Q%co: Cɼa˵8EAA~5"^+b$hbưGlk8pBNG6sDIk~K`?/.V 5|\ E3u\OW=Cx<*5D֞ @dArSS mwChf%Sp< feSӽa C)Cϸi{Qlʶ`}[ǵv+[KwXxR5;,/?]ot_mĀ6_)&`O5M,+["ck-.~F+75㎬)0jbN55Fk}m^4_54=$ب?:0;֢=>fe%ge:-× uxRb ,&YU"u25{S#_6&A9C/t3[O:6*cw 4>/0 ؆J>V^˱+ΜkD\%QDŇ(q./{i𥯪XH@UbM@,@!;ٟ/%ox7҆凿Y[) _ͥWf^A} ,n 6$$+v %HR: %\Ϳ`$ga,Y(:ȿǖOlUo-Y㹂 FTGPL.x5tԘAo7V-~$fF d^o@g `2xpM`o(ܚ._ *R{$(9`)dꀐM_!gAVr2{qa HoQfα>`0@%C͇ulGS\ΰ &<~y"jQkuXԓVWY5nIRprjO_Os 'XpP]yǑ; Զx}^ \~~ēCto 3 07'ÅJ˚0N+? ^_S|lɔŁM`}9ד]K[$Ptga\#Z 39,X,Zh`,u8tQѹ~ ks'/FqgUED!KzcIQHG [3wXb ;E:61wENЁjŗPQ 3˳٬d&CAtR&pT_swuk()xzYGkr΄ZeIBq~3GzB&=^.Q*YyiI]0d)_CȜ"‡h3#r?͈C,$#3Ikxfo4 .O7'Gh+JFIc+*P9<#:qupdf2}H r!k=8B~_|)lyv0EC "tOӻxi`L ]|73,oGlE1⏂m"_;^?4ʔT!ב`7|(`7 wJw`֯V;SvwBE3iGFL&10C?@q,Rry:cM}%U,s5bœ ^y~&p>kPK 9F:ip' 31R)&-x7\*B6}2*Ceվ?Lx ?ϯțD~1n-ܺ5Yh7`t]T/ %󘏃GeF(}`-.8=FycU-!.aےj]570T!6Eʬ?q)tXq?j;򅯐ZR񾷒3Ymi 0[|i^| 0bY? (ǿz5C*}xv%qS$[2#I/LpD+FD;-}$%J34x]ڝF!#]r?2-f/ wba]Txlf/MgK9@0BGp"YBT0=S:?(+Wr;TAj@k>̤+M.WJ@{=7~2<2=sX/zjc̕ݚ0 il:bmϴj!=_ ,+GxoeGPl!񹤹ȣ q++,y}FFC >kXM]-R.{AUr;q)7ɣnϬy:˞P 9#!v75( -9;o嘺0婰RQˮs;8w^UwYjdʄ)~mU!]P 2wC1~Fx[_:d̄X~|}ﺻ<^޸_sW З# "7XZPYftIT=‡ق _Jdh{%JpmwƼ ,6́i!hnү=8^Yo8Z~7;d'7nuqt_ ^eկΜt)7\0U8>+1ݩINCc[ҥ]uv'1B @y89HYFeԋ'apͱEbT(OW?A atIϜՊ,tK$5OȝIyYV }h(c>]'WUI-,~j7 TAP 0J#kfmwɔ a]Qso%.N'f}&eQx{``mU룰+V^ӛ=Z?kĥW6-:+,(K_$-oz!SLxwԯ'Z6ꠉiݑZ.朙P56{?Aa+mLATvSf}aPNe(O8$PN7ʊ9 >t~ M}p Y)+3&LC~ܴ'Ognd( @J3OHz&ccy:O U(sY.P)63?/.2~./Z4fq߮Г2jL}YIgQha6"cn^1sٸev59M(UL% ?}S}*dYkN֩b}h m>os9n<ϳl`7U/6 'gDY$RwS=S]s{oc:c^UpDړY6D%<@d=vjƻtkyGzBoܿ'9)u?/9ӔMk'sE2nKN =;3(:&4UHg6ˆFs,HsE?p;k`Tz6k-.I _jB.Pk0@CR?s|ÿ́RbKEserzA1%qGYUۓ0l韼9/:$O4[;6_pNSӚ=n7 vP g;%5_|+$,ӻԞ'ɂn0.pٵ'Ie" |iH%A j^N' bN0@ccL_Qt 1 k -H2T3'< Jf-S!WTV5M8Q֙ȲHfi_1 e$uP"Ah aQ6>? _IX *OhEԂ>~'3/[=NS8D52`ȣD,'],zkaM{>K1' {^CɉjC_ԻB1YV#L,K9Eoc7%b(yт݋5JJbgRZ >jUWv{cnoWrh^| HnKcpnHYd;DC6 %RߧC $m ƺ*m hRRjE`mnЉ#~cwmi^WX蠨2VmGEoN Nҵ8'ue*-,-Ӳu'.(k#ݕ d5Ԧ6KڑDQޓ giuvfj `{%D.VZl2VtW0JE)-H!&HL_vytk)G}fks`'Y; \hZx\S| ~{OP,2a~OzXC9y}4'Y.8FhE?Y4.({A:?ag)Qy♮6X1==SXIٮ@R:{CA4 8%Ztey/q,m&x˔.G6I+ƍl"P%Ǐ`BRTH!jЦjG"xp aq-5| ;~=琉0Cy5hh=nۏߚM\ odsv r, @f;O$5m?޵Kw!R޼n[οob8f^ݱmMO8X>E4#*\DGfJQ+S't8lx7꾙EpbŁ}p2:Af@kc q(˘a@pа3Ѐ;hd,U2yZ_ }d>h!P@h4@!õ78I?KAiOyDz C{yGr6-׭5_!woi^y<y|;_v2;\Þr|cw7_41-ןj~oЊN}Od&d.Ln'r)*OA[oMT S@2@c-!F,'T`Ok4~B[vdvh:j΢o~1O.80ydxealF"xpʰ!Ժx~;5d$X0c;-ѿ40+ōD`1%Wv^s_<5DgNwdGkI>gާ]7^OJWMKeD|8`TDJc9,O8Ahҧ9Իs$Zu9;FJu_RX(( k2m(D)1@Q W@Ȁvw'lՁr5 .  s1i-ѣނźhҼ-HHroD֝TCYg3R3;ۼ5=A4(AVͱ2^9W?v)FZD\]Ҳ=n/9K3.x0 F߀!h)jIUOH2f{rcZfqZ8ɲ9PHb!B!2/W8Bg[6vNE9/$NM[kwCNJO:XF(M3uiJ,-ͣO)`6h ʼnX5kH¬qh+~t5*YTz8`aاwU}+,5D& yfA$e Ϯ=[?Zs2J2}miztlQ|,.KgnN 5?{?}X~w3 b|䝌_J]NaFG.;g7h`SG^ Xpp"@[E@CK&N Ls̛Ks"r p̭@6$2 JdT}lxHm% 悒Nn4t3LoHW/EK/x}puZꓴTaN>T|[k7 /횡ζZ>4fjn{K`:EwkkQ!v˯r1seW3M@k_jѤR}y|A=N!tRQ8Lk::ҽ& #}]d^LI%/C`E5)7 %gc Z~Xw$(Z@)wI% Dg4Oҧ?MsSyQ2@K2}?K|M4j0. :W4]wK?F5)\OF?'h_B{ך_}Z`6*~B@/bwȏ 9^犞AOɚ +;}"RX4}gP,h&%#Gq|S# ](D쒀Bbú0Z] Xn ͤϋ$@:z :iIU])<<0/mS4aŴTMo/ѫ7obf \c4NJ3|>)|"q-$gX_ E'H165aZ#+8)O V ' SM0A ԈN;8A;"ª@#BuaKz'ɩz( T!fF]MQ1D%tqK$eyx4$Uiq:؝PbV.ƜEh&3$Ě\QThKA6ڠQ3 Ќ|@1ppwX$hvyVn{z[1ILp]钡֖䉗T @kMi*G8?Iz j".3/D%0kE]? X`"K% I9(G׍֦o&IHj6Q6  ] lf?znuC\$& Ux%uIխ+Y/a,&Qu0Cɞ`(a:F0I=?KyPYN OLlR'#ʕl]cf@w3 nzQ ΓXvDx1hh#F~kP2JMYp 6ja@.l=,8}JmkJ`@e*Q7g]x=~'=g^zRi/&%@Kp[L%:#;J+HV-0Ua`"\m2vBZP@p-F6. ;*o;aL˘*$Lf-:C5<$q ״r >qu.k[60.hؔxz6N;Yv!4h+ uU8S3. lsR=~D*[,IrQTp8Rk5}b.'u[ꔴx/2vlts"\ncƶ?_a_ODȳ:pc=\ ,fmyA,o7u: :xqҫpSp@,{u;B|?Eé7d+-0q}>ڞWkH+f")afN@ eOEBpuguvw8&q{n8_tLמ!,3Van:~|;R/W9gu2YPN'£Apkݢm@BI;XD#:""Z&z{)t`"%u#e10$ [L{Šb=VE } ^)cֵ5h]#,쾒g.!h"U]"TmU̹n@0ܼ?H6IGP8qf.829B+GDa8q}UfʟBT<,y0U0$,mACG7ZlR2l | (ꎮ1 I O.CTA! ?0F_cG@c<_1w R4zkJY1 ف.YÝ}SDzj$2N( qDHIL=NnKhF] KC6LmfI VBR V<u0<9"nXR\YlZ1 r_"-+JbN&^-nl8 x,籽Fcef1ՀnGҟ8+OСCD8"Ez׻ooy>2. Ga:=INѬڢIS6" P OyE),6LܡdSi1KARj)iO%`_" )*DrT7oJR,C4 @W%|eTMoX7hSWPw + HQeS(43q^S6uAG5n9x241ͥ'9 ޕ  Tc\Wvٷo=%*#z'g?O!J3%}g+p?J LvGHF@ծ^VQm:V oG^u}YެF|vƙhb$s_]*tuO+.`"29 OEm# 8UPJ=f ԏh IA :DҲB8QR,Cę@/nNں$~_Zq ?ǿGR<mҐtc(h'荜H2\tLc@|WIW,Gqu62z"w%b(]a͙8`$HܥJD.o}}mPs ϓJtԴD1^pSbO Qơ"#w jVK6*+$:g<`mAiJ'i Y) "OpdcC~Xd q 6/i=$?`$KF{j}aP=PMqI!cXUI*IZ cA%>F!*$"%wW擰\슠E؊1*+"x6{Y@,G7Ss'J PݻV,'_hoS2=څc Fd?n/}?@1 18@GeTdb  FAY1FqbACoy;HRV{˛nm>_y5wO7_'wѳ&1Gs5-&PXu X=Oh^4VBd< O- % eŎ}iK%̣uzZ,GيI@| X4k=!-Ɲw5(+ɽ!oh 7tMDΈeYHwpcۿѧ&wv~&crFcxcD?~RvbBSt9ôP6Ljsk;̠Y~@7;xlųB!ogoj 463Dtv0d@n~5ϒ-u{RZW^ks=Kq&@#u=GK裏Z B7Me% 0OnKO.hJ"' g'.fi ylJR-3*6#Q_VT{((dFe+ݶSDQ55(U:kwELM@&ӪKJ+"6~{>qwE_lJ,Rfq{iވ_4 "ed%ǀ7{ p</a'ORf{? (1L%& Y,ArˎiV=<)hEնVȓxrrd],Zsϕܸ)mcn)~&'Ky/] '@{H̀￿ٱcGs뭷8mf 2Neh^>)/4Gƅ-e1^`XЏE,-TbC6݋IW_?i嚘A~D 㑄kY)^ɣ?pu?;(S4m{鱏`Z( 'B& T?ߏ~[ڜwyYk2 լ- JR8@ B[cO (3L$m3 VXꚷϏ=. 3$y,y9WG[^=$$䀛 xE{ino~x |N.Pϐ>Ie Ly!C2 m&OUYE۪MCL:Gyܱ˱F-99W9VO?|[韝VgErQ^ylqbpNj`?{vd +R6=JEoU:csf>,o&e16ZĕԅrR*U,5d̼9 ,ZS!Pg3k<(Kڐr3Wke7d0E%4^;I>zXK o/_4x(/,Y(y|ae--@K˧Z%UlrXjr 0WbJϠt{~-ag%g5\egOXlfX,ܳgkV^xas%uX+pYg?! Np 4K0 |aF2 &M#a\t3@n]+]$SɑM)Szlv}uX LY,1Ks1AZ0C8 8ĉ{QNJ2;%kԩ"t:hj*"C)!ոW RS<*e HF7:&䊪A(A唎NZB]4zl!ӟ%XoPwsn{,1'NQ4؀{yA`r!*DR~mLV?w=i#kflkcpL =.e-j_#Gz VNI?r& (q"ґ A3i&IR콴æ(>qUTLV[׬T@2̂҃ҥv`Ym= YzwW_}VW14 'QqНRRΧy(z7B-BO2jm݇*G@ɵgtmkKTMH SC5 7RcvqW9?h V.8<3@Z_QGBPtB M"gc%c^$0\c"_5 DKrq3c9rKv Zhӏ65+3' d#DH-e]R!93iLodLyY{YejfY[xl_A,İT>+V!{@`&),kyzDjd؃cT$NSI]P_KKXy%@}fB/S3Y}ݩn`dޅ8e=;҇ONч&DZ҂݂w{ @JeÇcsH;8?oc`^]p@k6kܢڋՏ*wXƹ9GkpYh B3r220C~zkv8!ƚ``goHQL}}LTjz0B53WQr|12yN)\>`L07Sɬr`׎q^W=Wf7t2R +h} xO?$jf׿k1*Cٔ?1gUܜɜ Y7,|m:[}JN24b0 `EYE_GLt}=Gu+@+Z*[g˘+xC8[Mt\ntkNc4 q3*IyUEfʇߚ1p.f[5d3f7~ &ӵ=>{3(A!Anf^ۿk.,~?bxJ @fTLN% * kѢ=rsBϳTůe,uOV Ɓyv@-y kzz`S{{ދ25:񴅬-(ȘFw[I-T6T~'voy ΅<"mKPUT:{a]%VԱ=rF1Va=4bUSЂ&*V`MXY[ 7 l]}<XO5IYhHU*zz`2Xל)EuE2gsRsZ^c?/>bz`RZrT0f Z±6hY)\J,q|߾}__>?oT̏ s/dzt 7'KZ-ǠpȺؤZ1\1Cr 4F) ڧ7 ?W a`l?b]DkCԼ>hʨhid/ȋqo͘#&8MaBYYBҪa=x|x_j[Ϝ~Iu@Τ`O* /ǴZ$|ĐyM?o~L.L#qXo͖f4+6KyQ]/܋ 1@9Y`Ց0w)8d# '?]:[6`c &28D)F'W9^F˅1i[mAt.mR0/?č1^ʘmBN?~^92[WFƋߊ̋`'Ov򥚁qT m)Gsԧ'hW0+`o,dQ!ht-$d :LFGGןH3t@wv/3oў%/KVfyXѧab"L҃{gZ1}8x顸Г-,.Xh04> 7L;0Cn]p uwVkͶ#.k8BC;ǀ_A0+_#,//wc;~,ԧ_X`},N-8g,ce91?g#[WBvʔ2Ylw-Sӟ;~ނAO}&q;ܸbA?FuLt\}*{!n- |7z@߭ =j&55A@ 8Wh 5,xĩ&vR2T_**Jf9+Yez"dZz6t}HYifPݍ9ѾxE*cտYVLX1,gpkkg޺̀~<<ǝ.ڿ^VΕmW|C/!ՙroَdJQk1r*u?S]%vSwPSSB#mZ4Н'Mp,Kԧyjyl'A_|QmqX!qv|;io“OfoA, $[Āyh<F<%I܂&_ \i ܏|#oٶmy $!K < D cu_x<Ʈ @KHE-_^ w } 5ޚ|ID+M9G@T77JgϞǿM_CP~6kjtY3H ,8u'91Μ/ ΃ 8h P[p۷sύpvу&>kpSIA#jԇZhM|8 }=vЇ!!gq(yl&PUM0OAOX+G.H6`4CמrJ5nn|[k@{=mHt?]I+yh?]sm]y%z}C7D:D%C>1&}}^ڞzZՏMXw c} !!0|->؃nXwfTEa&Z34"*$PBwx =쭻uvf! {$!w$sjR=P=P=P=P=Y?V @#c *6kk 03`)L{L@ %H}ܭc& { _O@Hzs$< m+yRwrVISL?K4@aH7[d@w.0,ڳ6چ(t I-3{ x @`fr3W)pvt{a ,Y$۷3`Tzf $ bߥ!HZ(erS, wxc T‹R~t{ $wVܹ>.?%PAs~ 䠖R cw#ܭ wV`gXVS,Ѱ'{3>Uz+~x$03D`V GY'\K@@@ظƞ,w޼&`(Y]kdʺ$0+OYi~ 0 `?7㯸ꁩkKIbh =y@i$Gb{,k@pF {YV(Y@Rq?f ֽ,-R\2ݏ :kX5 c(e_IKO`X.G$ 9{|<( JuZDK!Y@2Y?9u` A$7teto^A[ X*k2Kg c!s2^`I9A SE~CkȜve!%G"04?G" 3 5) 'R=P=P=:̱:<~$ +[V>`u3y?;V"6L LXmY[xwxDp>"Ed<.=\]+ Ez@@@@@-?Rm@z `FO:֫fwnhwNo _&YV=P=P=P=/{~zuoC)UeUTTTTl&LGzzzzz5(tT%L f:/ @*Y@@@@f2uv~zIENDB`ic12zPNG  IHDR@@iq$iCCPICC Profile8UoT>oR? XGůUS[IJ*$:7鶪O{7@Hkk?<kktq݋m6nƶد-mR;`zv x#=\% oYRڱ#&?>ҹЪn_;j;$}*}+(}'}/LtY"$].9⦅%{_a݊]hk5'SN{<_ t jM{-4%TńtY۟R6#v\喊x:'HO3^&0::m,L%3:qVE t]~Iv6Wٯ) |ʸ2]G4(6w‹$"AEv m[D;Vh[}چN|3HS:KtxU'D;77;_"e?Yqxl+ pHYs%%IR$IDATxk]uml@pD0(m&Di>DUCϭZT!UU@EA I)g[qp}{Pe|޳g֬׬Yk㬪*Q.n ȲLn1k|s5 '&;i]xЫr5^W(6†t&VfS-!-!% ѳoñ-y/W|F©W>h |x,'|ǁEvbVw}rE-Zz+jTGt _I$/'_=r@HEݬ( F{|>4J+fY.>%ͼ)W˫u%(8vbbOٳfNp͠*wA(MgϞJ?QYTBUJ&!|g2ޏ]u>5Ijb333~M4n+` L>G1TUCƁ׭ZR py_oh9#vklQlhY|XA#+سBg^s|=V믒!H/RLkוnm4麜d  0<`}v{z5ܭNC|ָRpA{Ni q>'FYǒOcȈm۶h9.VWX a=*v;v1^ /W2G#‰vH@GnMfVo a+P+,r;vP~A _Σk *$M%jT{ )RN#v+BmQOpVǗdV:]8Z+.-A$ @4A8iZA8h p3$40YfKfa̐je~,]5> G~|Onra\;Vi|XjSQD pD ".2j6ȭf#:akplhfWl9SS,fiLL4 !ラKF!hYo楍4 "hHWYW80@WH/ZO +]) sax'pt`n&{$!NRX+ap^Hst$uCv~VѨ J,}ѻ&)N,w&@_+ B{ +H?O*0=Pښ>vmӞlbiAsJ'&.?dGhl|io7u jγxU$, i0XbFrt "ז:c`b+k{nm-q则| 0H/uXf樈?^g ׿^lAZc@k$<%ܽ-t2Ni}f/ Z$ y~(NH U {T-6D>ԝ@)v"`5׭u$ARY y ;M;0(Is#ZTP݅F.g'-J`ش^)5lgPWPhF t|AKzwdJ[q&Gރ_{9h,I?mcUm$3jba~{Wa&dl%诗;eu[50&ᦉMRaeu2ye8xwx lӆ.""LfG,a_H@Ra9Ã5 *-/a Ԩ+$F9P <*&&C`f5=,_Sڎ-=J>}Saf$:<7D-EzxgH|~.nub>Y-@Qd>bEL PiOFPr2%f-2l7\BH['6ly*y/ȣgvvk ((-4鹎Sڟن [65Sg]ijx q \UuZdwavϙ])3R=\xc|ȔUҧ60a'ޅCs$T*&=RٵWdOw_ŧ.˱ AP;ckpؑ6m`S" JH VT< Kst~gуu _0R_38mYeWirF8`ET~ۘJ۸!GQ?lwMNE(HrRnvwFy/uDAAoFe! 4d~TtfpGT)"G35BNMɪa&fp86Tsqgn:]h1G^ebʄZ8@we,}ke uKeP>@9R è>>xJup6ςu`Sīvf-afJEEq~guWx)XweAy|<=3A8- w]dZ(BiC3*HI 3= $|bU(h9O4*zLyUB]wps bgEY;XWpD?[9=~Ѩx":Ȝ&$ʡ)&k8`b|X`W0Oy %Ė n{XI+IGUS5o8%FDF$8NR;|!^b}uUuv 1C(gTCP}! $^)3Y[ARؼdB/jj: R&Ĝ4EQs&\;>ͦTn]B++vkʠ<A?9 5aY1)c 8vs@#l$FQ)0EL I!GBh%( E_TX@G.)̒7K'Ev4(k-+5}x=,p*@EC՝td3x[x .teDm i1\GqyX8`i ŘD8xD:h &|WX3k9G9,}G/+ӿ@6A$W iS6 hT|LxSK]VU@ˀD8RPCt_uta ;5<0%2p|Oz>u3  {)[q^^ܳ?>q}K2WvP!Cr%ߖģ69>Y3|:<$,Sft15G(х}QWO_bmϞzԔcDa=K{FogI^lW죻z*svKN۩OE~$zA*#b2b ivv,!)'D83>[>Y7^EeaAOp?3{) \ UoQ?ވ3D6%,/$sixf߯%k̸K`[N"j=~=~3=6IfHΏJ>VQy,^?p Q`4`|5OFi;}*,@@Xxty+.jN",y1X/'z ̀<WWQڅXd2UgC0g,n_v[7/t+A43 pfé(>h<ݴ= ϔhpγN!]T;9 ? Nkw>0J*Z8g`aӥYƬ͕X;\G($Iu`"G:6+ƛn΁@!V<e։@رA^~%ǥs)y6g(TSڎ_"ީ[( i"UF8%9q5ƪlL?mJHV O~Gy:%$믿 )͹\/9|*;#C~9܄Of̟{_Ef$qSIxa J@;qZAC #v u@+(BRˢɈ?7`j0[IENDB`ic07HVPNG  IHDR>a$iCCPICC Profile8UoT>oR? XGůUS[IJ*$:7鶪O{7@Hkk?<kktq݋m6nƶد-mR;`zv x#=\% oYRڱ#&?>ҹЪn_;j;$}*}+(}'}/LtY"$].9⦅%{_a݊]hk5'SN{<_ t jM{-4%TńtY۟R6#v\喊x:'HO3^&0::m,L%3:qVE t]~Iv6Wٯ) |ʸ2]G4(6w‹$"AEv m[D;Vh[}چN|3HS:KtxU'D;77;_"e?Yqxl+ pHYs  @IDATx{eu=7o{=؎&iR MJҔDj+AJ%*C *TPDU DAiHPIҴ IpsƯ<<:wwgf̹kAs5|J`{W{\5=.]i{^cVW/mߥ+um_J[Ƿ~߼VyiQBMO]Ҥy@Nϝ~b密IRi tD*CXQ<5L5f.MLІgW6fQw5N#ͭ2`e0 @Dig?O_ݾ}333 ѩ0$ (A`O .<//wQb0!ެ`[z얔\Q>noߧ?sη*j8D/xWIF*AtgΜyCǞ.Zdc @ǂP6]ޛ?~f˖-w5\ @(gGKSs'+.o,骙X.]:g|U.t'3\ 6ڢ C裏knFY]pjhgL4 ZBFU]\1@\ 7}nnٶm[kSN=z@OB1, m7?A) ϟo^y啍ọx]m*[py]n ]XytO7ULE,5|)͈nao)K̵Yџ}{~"]x#:.UwiMh%dHd Scha%ŝyECH7m-sH غuktK! t @@乘ѵCVzO cb6]0:)G\ E'́*| @u.@D[>RT jGIt^6ewb^ԕmQ@񸋼_`'(/\KzNy**t\POV:H ]Ͳ4WI~Ϥ`*2àh!lڴ@f^Hv6ZE-_0gVV:8*.!_OiyĉCX:Y(Pj-Zy#Pj6?b+((R)yv~aw;4v]0h:(8Q_t&/)^@H0bBRObª0PM3c=y#k37` Kf%9:037W)T!k?bOҳ@"_آFi-O>[IŬB5aӺuB ٰ?Ni?u|Vf%fl#(8e1ēqeY'LGeE157h`Fw)Wxƿ% 5@cǚ={P;ⱎF ƻig @eV`AEE'a 1a`4Et#eKW^{X#h`=5o>!9!k{3XԔuM4+ qeǿR6tZPh ғRq$-m\?!uB"[FaH*0!;PY3 T]ge7d+ N(.i%aH !|FP ZxF՞U}0ZRvΕ)}J[tV]V?n/io I k.y1*OB z*L5( 'FHóYmWzP0сqr3GGkdR@=MF\bm>eGs;h¾lpZ[kG YtAsK۠:C3FKZL& ݸ4 &XodC0:(30ZFw)K EK5886/[\RZ/=?t\픍ɸ}166١2խd*Zl[5g6i[uȝ& M1P1L,cX L9HE)3tPVtŁ~1 \ hHq=F`h'bjZV4v6l% 6 gdAndASL )4y3OLWJqmgCo Cy_*_2QNE尤EJxl5•x4.i]vGL㴡I! K*dxC`  cGդߠ^tC.Eئ%(v`=:Uz8(yi2Lѻ.up*`D`\~"Yuطgy8 nPхQp5\n1d4m u <\!BG`ȁԂm+$aOB,#p8&T{Ca9VpR?' 0\gM$ @@m֣Y6paPQiWCm]y IZ cȭv̫u0 \hsrhnsnq\X<¨WP$0.&_]f3ĵ( eÒp)+Vh1C< vGǛ2Msv/4O!O[Lm FTR胩S ~vxnݾyP I@DYҩЩiM:t[\:-ZmaI42)$3kyؤ,#k3z2w dQCB#AezzI4du:8\ѵ!o85 XmN ZkE^<up;u3o^yӯt'z`xm~C#z,3`v1.ӯ5;+"ZD ,{\SwR7T:屭".J^ulCjrbɉOZ!q? /o^ГKߤ X/fhvciK~؇2!$.ly /F--Ũs ޷OR~i 9^SXN3obm.1q:2i Aq  cqCx^n^*-:]AZyj?H6=~cقP6{Kb[U8yr4ANÝr2U]* 8=c\Էo@$ p:WIh1:E[͂Ȭ%`ZaIdM,:V]tG5BH]ʛ8n2O<-L3It`DŏXqcp#G ܋rh欎N VÎUqQJREހq]Nw9pC VS^x3$@<]o +RNV2W w7Wi`w%#? 2ӶiaV.Kp}*V o7)\{ ^TTA;L!vt{nO` <(SrFK6xOŧWmR&`zSAl΍x:<@*]P;^4.mFZXm!^o[19ғ+ !ovrK6ܷ{Fx@& ,[d+>ɻ 1>pi 0G_;R(7~)cʁ8wB& 9>e $6+ fƆcD## ; 0х$5QP}4I#hwic?xwV[Rѓ'f"s= yZΥL Ηq)}{ng! OޢP 5#F1|,>P:xg0uP(<.j& qWj64˅iÕ>wWAg&Tbɡ x#Q7[ \ȢmLɬ}{ oi==4_񂐅-7GFr+ZeWgpL-.w5w| xktxE>2^2Bٷ-i,XԷ *J($+f}-T0nTD\ 8)O h+Ί@=ܢ0R P>Kk (@2OyjpF)٪'rV2aY#6 u+xFAXmyyy#.ꈖn ՙ 7[J7ʓJYF =YiF9 Z6]q&H܁Tvyq' .y۱;4|金"er`vJz !$- 0C#|eu!`l6tDvlo@ QoMeʊh0R䈟6By@T@ U@0!qy,xYicv#(J|ۜWGќGp[RGi\F1ۊ Kxp!0"vH6w;o860{d/RDItj6P@ulhhR8`p@:^5;fssleK-`§,`bD Tx9Ozv=$C}]qȆXMor+v~As:=lbKW,t&~;VSIh* %b?nkc$+"ơPP*6($ YBcW}%9zxVd"oYUbvY\@VnQbJ.i&#]EaI`8&pƾ)ʆ  #¨]QEOAG#xi}H\P=M-T0,kD|]d҆OdP/ 12j(yYc"88|i`\! rNEwrL~ aCBGtWQ,^pY4o\ ;"e^fl1`'"M{nS aC21u. *f/>2ts@">efB?8@dJئX֢h)ڴjbym=^j!dO<#7}6gh!xL7 u)}V2Zc\|ȿI-Ӡ"0BzZ15<2TI0,7Z 52 f"TwJ>!#F ǰa}VT0+m e-Gz#rCP͞ )Q.j ks00Alڌ Գ/Mݯ@"*\!kpR_Ũ٬h +ň/m*;Jn@.As L#sOyd tXa6pI9(Ш~}G/:9Lp",b f1GV̧ubx4b$!Xej~@USS%}|Boq%MPFq˜BD9pv%4 0G "#oW8Q=w~A{_vϊ_m\0MKڕOxWpIiVlX9-M/8pA~¥" ~%.W] zUNykM}4݁4 ^1È8iCRAy$lS: d8}kKy:Z1b`ÆT|TBpBhRz1Y1bww'09y8 f,ʎG;d$iQOw;EHJwIX_8q>VFA9dj@ZCpY5{Xxa .8`Bx(ɫ|s+x׭Ym[(fg;C!'Raǣk>*EVԆQzݮR*8Xӭ v^dk]8 J)`"f2)~,4|8Vgw ^NW0q)pvZZ4~b^i$L4P 7BҴL8@T v4` - A_mj>׌YS 1ݡ K]'c:&b )_\8)-DFqNA֕|ܼoK$x aAuVŤ+82VXu8C perEB+%Rn_n)C]<3`-jT`XLp -Gq%Py Bg}lAkۀA6E5ؼn^a6ޱJN?LPI[A>V> N8B'*hg89 E{z񷄤!]hY1{/UgLP\CN5ĦAfLc /3."j;J \ |tpN?O>S3Q⡣4˯+|[kxLW˃O $\hPM(.d!`1z2M@Ho41P9#_HS/NyG~:CoCEywV ʔ_ 4D6M?o*pf#ݾ6Ϳ}}^O2ʅV(fM:0KMm>0"D<:of9`l C+iylh/s\`n,Ǐ] u (uYO8ȆAI47p]!!ST zUXH뀭P ˼=z'XV{l.gG-B?2ѣNߙ))CEfq<[R) YD2-kkGx [ΎϾk dXډi1?b%0G n62}H4)f4BkA[.Afgd Yj7?G5AVd=*<2:F^@9{^sg naDZ.z=\Zk~ë]kV7뽁.4ߧ/4 /rYy#e'e -=}ބZk?aJ_j'gzAp èqZ:WaBѫjprI…n#+#32k4~'4oVmx4gQyɦ=T}sO9<;v vGϵ[F9C`t]02e\09<סZz9eaĔqA n`X,`$C!,Bѻf)B%`H!K-A$FߊEڶrUn9 f3#nz&Fv<`fFSt.pĿ14(3 $kK=A!6s9asм:tQNJ( ewy%1 ,/%x'i0MSw>hb />4|m< O ^?#ςa0*x*}^ ♄Rvc0 L|@O=f'pioP9߬o%@؏7hgYt[I*`WSHhY/[N uT) _~) :qڤIb',s*P2s Vz+⟯in+-75{?Yr )hiI8!Q9T e"iS |38zn?cz+IJ LKx++uFmia#@w]@-qj59PJy6lUFGr0*-D`wDټF`=|L-i)?zcyͧ>/#]-ncn=ye~1mC}YdQoVfaO.q< B$ ȪZ? lL:>63*"juȐ7 )fcjUht>$dLxX&JŰq3ʲ7wB۶iHA4ig}z?h~ge5kvv{:|lܨm-~rмm2Mb ɭ5Eij¸xmwСC9>ډ, bH%-8h+I).v"*g7#F /撴U1<4P6O(2qK ʼnF)*ytDaVI}^9ݞQ߾Cךw7 AC*}A˘aş֔s@wbȏ(K;@wU2x4Z qO6h>DRt[у@?rLAN5H&M"C̖~poiۂ@ WeG#EE;1 ԴJ?[0/S^^q(b16 (!+5S-~G{/)~RZx%R+پ L6G) fB_.DŽ\g*0F1 aY۶?̸ISP(.[4>[ jM4BHfQ%ĒL,G,K} @}9^`|nL`rEg65wMV}O&uw}ڣ+ Z l b*;|TT$Za/] / iiN?c@],$*PG-}/+0^łZ!!Qv5a ߹ kؔ@Ɗ:x^ؐ|GAd-~ I%i˔Wŗ.]ҜB?ܜ;FD1=Q xY~nNQ;qoF=X>^ &͆5| :T8eʥAףwgЄY;su])d(c6@*N=nmaJ('NI͕,?`9 ggL+UL6; c9̖G}8 J2̺YH}Tڀ]Yla.L5L!aXe?n3GALEbt`Ք=WxCAcW@:$u[Ç?#B/=4CpY?ugtI[&k:kqafvrRT(y .ِ!FF*0׵㓟Wٳ宻U{R5fª<^?;=5:MG}˶$1lwO~mi0vS'x4:^ezutȜDg.=l#nouPޅ?я} tp UT~w[jвѫ롗!J>eɏnik.w AVHTO#ԧt0yBa]E&V`^)'~HdpDêuqF]:xM7~ww߽{׮] P-LO5jE*ΘN)Nj!w(As[VWI @O^bԗR:|[Ͼ opA16t<6? 6(߯k|&).,{ŇK_p׆5*#($guݠδxpD1Nn<:f#@OP>|($X pOUSu0 ʨj0t[(Oׇo](S. Q "`W @BxC0nKnWz]Xk/ 1>& L|΀퓟2x.]qϕqYjx#$g7Sa,)~`x]]M@\9p )_Qg6wIENDB`il32 ֟ClCەܶ҃ϋЃӁ͚nhimjmjmjmjlhoԁ r"$$ $!rс p)#("+!+! +!<;Bpρu '.@7[3A,,0A,,0A,?DS( ú v "08+1$1$ 1$Y\c vȁ x ",J/,#,# ,#\bc xŁ þz "-k7,"," ,"Y[a zÁ~").CQA,+/@,+/@,UZg)"~!5f1$$&=%##/!!60%%5w+%%-#"!)!@_7')P7'&.%%#+!)0gMZNNT25E/.1A)!',r<~ѻ}65>/.+5%4^g777erbX>$ .7F82e10La?E8515)&.6H58W9>VD=K747F.&()9.1hG34A>42=/-,6'(+6/1O@56>543:0.-3(),701E=77A775<1/-5('19L:;JyECG|S~jZ",&))4.12:/-+2(%#* 327N7ەܶҀϋ΀Ӂ͚mfgjhjhjhjhifmԁ p qс n#!## #116nρs %6.K)6$$'6$$'6$47D!sˁ u'0z#'' 'JKOuȁ ſw$?($$ $KQOwŁ ýx%X.$$ $IKNxÁ}!%7wB6$#&5$#&5$EIS"}~)T' 2&z~~,jn(,`$$"5N+ !@- &$!'T?I@?uE)+7'%'5" $]n1eh,*1'%#,-MkT+|h,,Pyj\OG1&,60)Q'%;O46.*',!%*9*+rE,0Dl6/=+(+7&!,%'U7((23)&0&$#* #)%'?3)*0)('-&%#' #+''6|0++3+*)/'&$(  '+=-.:b535jmag?._sjA #.'(*7+-.Af`_2`ml`:"*&(*1-U3}X]`0emlSG" !(#%&+$#"& 32ۀߋ߀ ͍ܶӁ͙kccdbdcdcbbcdckԁn      nс l   lρnnˁ p   oǁ ľq   qā üs   sÁvvx   x{  {|  |}}            32l8mk  ""44;;<<>>??@@AAAADDEEFFIIIIIILLLLOOPPPPQQRRTTTTVVRR66 B||B ic11 PNG  IHDR szz$iCCPICC Profile8UoT>oR? XGůUS[IJ*$:7鶪O{7@Hkk?<kktq݋m6nƶد-mR;`zv x#=\% oYRڱ#&?>ҹЪn_;j;$}*}+(}'}/LtY"$].9⦅%{_a݊]hk5'SN{<_ t jM{-4%TńtY۟R6#v\喊x:'HO3^&0::m,L%3:qVE t]~Iv6Wٯ) |ʸ2]G4(6w‹$"AEv m[D;Vh[}چN|3HS:KtxU'D;77;_"e?Yqxl+ pHYs%%IR$IDATX W[o3g񱍝CB6 Pi!#/OE~ !mPTPZ8Mk\rs߷s@"Q̞Zۮ*?%jsc}D߻\_:T ON>ql#7q۷o{ҥ,K} EQlss|'ZQE?PysLyG,ٳgݠ3(8e94 xy3 x=9ǎyQ!IR hT3E.K>td֯ͤc.f(W3!6n53 8z,rKh0EN(/1@SQLZ2V*ZT"a6`w TET w[5RH"fnN? _H[¢F>0"k9x{"[jzJ'\y3t+IMFv4"dI`"KZt/֯ӻ[x8|yn1$g<:6z%=0b+eqsZDUm;Y(ph 08q4&`Fs+)Ӵ[ 4IH61> pɃVuSZ1=zdy7.!S @2grV|Rr@E֩tOf&tYh/ǘp.VI;cSRUmF!#GA+gL s>:R5įHqFM7-q'%A+2_V,- &G-͎ԻFxP~p7-_{3oH1~v$ ]+^Yp޼ ,*eJD|׿Tx%֘$:*$3 =`+ \f|:O4#xu6yanY0HgF Z 17+CpG#4Ʃt"›q hl^UהnPX4!Ŏ݊*sUmxK&+B%/}z+nPwc.LBZi1L.5_h y t\u-dn P)9;)PpzYCfCQ{:;x_2L+ꑡ=eΓxÈ&RpT k)Uo*+]X1z8yγ[{ %.=.2ߢ+.YRtͅ%1!zױ^gxdCtȍMv09Y|84YeI}JsMAS@d^@#'ΘR,׫p1cO\f`r`1_nB4blmg?\,3uBRA\gÿ`Nlcի߯G$ҔN4?y;3gnw9NprApG 4ZW;*%Ju3Q!IENDB`is32IfIʆqFJFJFJFH@>hT(26-(-(-A0HR!2j%!%!%J0FV%T,%,%+c?JU i()?#%G-JX&{?U+0%#NZJ|{o_G2P]1=h5}F=S0-T\)0F3:21(%U^-7=d;@7:.-Y\&/09C|z1{haffjjkhgbautIDIɀրՃnBDBDBDBC<:gQ!),&!&!%5(EO,X  =(DQDu$$$P3HOVk!!49&HRck4Ex#%LU>kdzdu]M9(LW'/R*d8/'#QPV &7q'+&' QY#+.M-1+,##UW##*3a_&alb\``cddba]]ut$"$΄_4646464534dAAAACDEEGGHHLMLLP7NQQ}VYXZZ[YYWW}uts8mk !!%%%%'')))),,,,--//00'%:QQQQQQQQQQQQ:ic14^PNG  IHDRx$iCCPICC Profile8UoT>oR? XGůUS[IJ*$:7鶪O{7@Hkk?<kktq݋m6nƶد-mR;`zv x#=\% oYRڱ#&?>ҹЪn_;j;$}*}+(}'}/LtY"$].9⦅%{_a݊]hk5'SN{<_ t jM{-4%TńtY۟R6#v\喊x:'HO3^&0::m,L%3:qVE t]~Iv6Wٯ) |ʸ2]G4(6w‹$"AEv m[D;Vh[}چN|3HS:KtxU'D;77;_"e?Yqxl+ pHYs%%IR$@IDATxYnupM6I2-@I ˂B/Rdɖ 6"$0!'Kr[C$  і&mR"EIn6{}sUVj[uv՚תqמNOOZZZZZZnZ6o[ZZZZZhEEEEE@[܂\nhhhhh ZZZZZn-XhmEEEEE@[܂\nhhhhh ZZZZZn-XhmEEEEE@[܂\nhhhhh ZZZZZn-XhmEEEEE@[܂\nhhhhh ZZZZZn-XhmEEEEE@[܂\nhhhhh ZZZZZne(X#\F G3~Ǿ҇~-""""snǿxq`!I,Rbsfjč;O?㋗0ѴvCsGVN'4"&;3^P>`o3(-4r29$܃,n{n&}>zZiFrA!A[ |plEEEEE`"QMh®uq/(]v_Vlk :RNg޹o{RDZEEE`# `YMr}_n[n-c!$n]3?*~}2Oo8[oLEEEE`'#@c:&v's,pDzY(s1#vuG`05g~Ȥ?ZjhhhhE@<4 EwW٩ Az Aco`-2O1Wa7 k?w8\NXij~\sji,aҟC0&[ZZZZD'3|:^PX \݅݀/&#в;0e0T """""> ĉ,8˯/9 $0` 2`}$,n(au5uw9 я~x'ǰA蕔meJڄWy>{gn\qwy_~'xs݀L[YФhlR"D& +g;gi {3i-,7oݸQx,lo޼&-|~ I}Σ} AċZ $Uf6:&xl?~/ME>&&wE`v:"iw^zSտ_!?pvKX<h0E^MlFq{-BN9ǥ;/{&lKgmb?yإ^Lҗ/__/Py~S.L "`Ruc ?&tlΧzZzH.MKU˼ ~ݪib[/'i~ר`!H{>h [ld0aD`(=s5İ8=ÑM-quo|LwS @ek&4īWkk_;cg$0X"` -v/l뇶1I;}w8e75%B撞'{"yM뮷{VފMO^z۴oēgy~~D7"7֞xgu7KcN(^ғE_`mvL[Ɯ"ԆJ28-_}FO`r2q_n&ѡbJk]\%^7#=kgn)?DD~͈@9.(OlHQ7i9}ChXx}R`$($q"؈J4G}?_Ї>]k\~~C)6AT p,vf\pfh<8O=mҥKl$W^K Զy^R'ZZVtdLL?m{W\y~~n=J%K+OkY3b5/wy9:;][I@v'!ƒֵ҉tAaEZ1""xYZ؞:'aқo{gx KG/a X8 0x &?k <?-/2ܯ_~V'=Ѯ}- Hۡ(--{=jk´ y-f!;& uf̢ ~R Y(g8][+]P}_{K ~w~?iN}=gAO+dgO3Dh| |0i,zBgTNk1O=; … H/[ .L"g>rNlut4gu9jMoo0vZG+.5ݘ1.=ነ 6'DJbӲǕՄ8˶o_Y Ѥ D (&魋Zo6)ַ:1sw|w//ü^}^]BGTj'ўQv,x0@ Ҷ?g?1Zq?- U$szΑôN-"fY(^HbڔJe˶=SϹFlس}ny"bΙg?yF ]@a倗]oO h39}@@EY3Y0/8r\+TShp?3p G9{-d@Zj[[ aƷ)2? 𒈯l *r*!^1 ֡Q,h^{hc XH2ȯ"i{D葲{F9 tM(:c:џ~XX# idN..} "XD,&h. ?w}mmep=[[Mҡ<g6"@r!t-*uYfnFKɆؑhZ4%cTD2Fe[d&nQ{tI_4] ]xGw?] 8*UV"@5+# ,ܖ)h`'r+,2娉D>jRۑ9N#>([:pּ`=(qsKFeZax;l1D'?S?'\ /xl!8W# O>y V ꙿnlƕhY־3,yY.RI(4v瘞}sS 0ĺٵ=*K׌umTÿ"v5b!>?% Xv7Gk+, z< z}SY\)dp[*fM "B,;DVf%f(/A#JGh6;܄9j,=OP7V@O,`0\ ~%J!z0TN˓BfSA>i.}LsYyd" y|/iˠ\uUб9.FXs?s_SXljG"}ahN*j nm1z2@ݎd?ҙ|4HlvKr?+D4(,;Ǟ Hp)%?=KHl-|\X` d"x=2|oʳ5\$"*$UMw?VTEzӠ{r=-=Ik,ٵfݍu UYD/tk2%7 zPТS"  s-Qs0Sn.O</"X^s7'pQ^2p]|TA -RJFiDowI } Lcqb`L8+iS 6X QU8 ` DgMD8pr]9܇} x X-hj>8]Y[i ,L<=HK^5^Og$vІ:5(Ց\AiYӓ-h]rțb]0/^d- ̏ 07hoVȀɗ.LGL,hAv֧2 _d,z</ Ai"G0t8{7n^a?w?xMGh)b^"`ƍ}̩vٟUh$@mAUFpZSs[6#`Z?%yH|Ķ İ\׾rXjDJI%jߋt$S:“2[dA_'i#}3- 7t^PI_]Uk~<0",74ڨWL1WP`܉F.Gܸ"Xn$ *rz }|'xYRl=sdG@<Ȉ:8DȜ+>=#˗yWN#[p[քXh[C} d3AFiVɸVm4Y=Y/쑦$i3Nt$( pEXƥ.`ǸF""m=D !2qW1{aH=*B%ԅŧH1]@7h,$G#oK~ӟ#QL[<" OF?=̈́Vj9 .թqSL} /MDϙL `rD C'@]FoLo|xzxgOl%[c*]%REm sЍ7nN[9K&if3hjqo?dS $}m4W36haYVM`\T qRomwygwys_!:t/R_r`1?"@1n"[NHAZ9 s=[[0XNCIT{ -~}+Ow/ǝ*yʲ8%4~rǥd&Ⱥ~㤻zޗ@3,ČV!I?w?$hpV2 H@ѡ_o)sw{}Ô7ȋtxw d4g̓٥Z:e==uDߴreh't~3_>w_{:!YBCLfJ2~2@;A:_F9VwVҎ K G*Q4 (Ib*xeNe5"`ܷBW}_{=Ou?c{B Lc1J$Y?OcKB8"؂oty^PQ[p%hSCXLWhx }ZʼngO?ōUQ2D%.2,pE!@hS%-n)[E`w#v|0mxũPX !d'phO fn L:>{G?Od&tUE0C9ـ~&mLiđ1΢ETؑ[sA#ANߣ>{IU:fttc^"q$ 4\KYD3 d$C1Ә ha[P"cE:}Ev_8/x%65b#yyg2Ґ" /<}ӿ+|$!ݐƊhb C~\czI&~HZq4I Er!C# !"g5rSm>UJHjC2 '%apba9ƒb>MnƓX7#TIvG#8wc:P c|ꫯRv*4"1AF8zʑlzD4c_p⪃+@EAO@fCAC TM T闣=T1J% xTkv2rXN$ʒ [ `ʶ$dȤb`}?KO%%76?T\1+cEo@ W|EvϚӄiǭ/')ФTE_[dgpؚ↙I =2CE@(*x yB^*| L$-߆}{]ȮItQUBϒHlQ+98J91B UP&H!UtNĔ_{kt_eAZ '~b h1 1qA]n~g Ig87)R]. X"P233*Zc& #;‡ MDB_:֕hX_yjq\D5)Gjm/!b>Ԛ+;ѥLV!SB&S`d 38KNdBugPY v&j ]H#,,˄_!sEhYd H\(nG":kF%-xIzJhQ+*ɀo򅤀J :믿n0C\AdžGS!R6eX` d3T+(K0c35w2= 5 ׿3XQi=6. EAKЈH(p,0(Fb!@Op`S@yĐpIhΪNHyt+/4D643'5-`e* ZmA]Ÿ)c&Td8k.kfXF\@UZXBAF[sO:FhjeX;TNx= IgiNiT:LYH. D3®ѹ! '2 !V%=d(10\\PCLSE@dM$wW2'j M__ M/" 0N\[0%EY=Tؤs>2 :VY=~ 'I`h*L Vdj64N"TPCZjOkcuu3l 7C0  ](H'Odw_S-F0qV4J%j8ьAHp20 O1"3FӠq $yx"S#4˒qu &}&,Nyp>U+Ŋ3lZ -!EnMTvL7^KѾ鵖{"lw\,)xE֐5HrW4C vlcS3H|3Av.?߮12#CYU3ԌT8My+J0vh;1Qc,Iweؠ3C@IYQȲ!/9>QppӃd͖,? T 2U3rS E&YkTHl| QWѦv@]K-g(u5T̎D?IР1{ `r[;ehfbaaX@`VqE`mx?̉k,KD4dz7PNIo8 ok":}qj(؆[CT5xKJBZ9"5`R1lKhI] c#l)T`Gy(`LO; LaBShQAv(ܰ1T`2$ g0rN$i,4+͝0ƤsI a<ʄ>j;54Sv& ?եTO)ڮX֩q<#[&"D6b h(nla'h,Ȣ Bezc*sL$7Y:׼c6hZ;hp lMWeN.H|Ar+8Z7n5gT1OuX  }" R2r6iD,%V/[aS ԝ)xdHl-;=Q#RlE!R'&Yب :'})#JRNdd v((c@T,ԓ`R.mV  ׷Gc F&4"HRŨ<eYhG`mbFLXP;W9.v5mwr1MbP;s!9*eh>+kE{9E6L1S.Y*N]ҤON'Q=«yO6ҬC,a<YDDf_0-`"<0N_=iȢk51_!O'.&`9ʮS阫O}UZz ./# PIQ a.@4,L9ᒻ@U䗟 q+)l}PbAuqJjҵF=8% \Ts'iaw_@Ӏznǒ4a 8OSx< ;odNl SWQhm_oh㯴U* 7!\/ F0[JHP" &r-+9M% c+(q 5#%%"s٘wwQ)i`; GdT+f:&llШ64 _B'`9j3#I&,4Ё_A>ox}ɋCv8 W_d_(:^_4U(2 XkȳdA,ƐMRZySϙrZ4%)7*B˦%#e/˿ 9IiL)Cp%S9̊$r5'^e%)leI30:.g ZM(PE>㧉K`U@?|ƍAIH9#(?9|zdG,(?4TEYUX %TEK+$ ;JYAT4.h1I/NTвr?3\ |ӛ'ԮT^$0&!.- U gHh֗m\Hf̗8Jyd&M5A¦)|Y-=>@ p)ߋt;C 9?WBUky L2(wg@x]_B"΅ar"!#AyP`Y(!Q-8KÄhFߚ^H{D3i m`f%M.\WzcDe>OLh ]POXI!Ŗ8(M~9Xdy䦙]Ln< h<cI+vkiC{TÀ9T"I(7,rgf]UttНǵF(:l#_r}[pО7W8z{jBkC9}W : cu:NIYqS99: JBⅭ/7]qٿgGjR7( MYs>x 4SeGdDb(r,Дy:ECȲCfv .js<ȠǼ5O7P+t޻L6(9@NNBcL@&s=tW^;>oO_&"_ 1DIHO4'$S\`QƜw{ 2 Tc8 9L,nO#u4)7(;@ή\DA BVʅM& @_M@s49#QepnN|};G-pO@P+9b\y jJ>tJɩIvr9+ %u?zt>v}?٧ovϾ mCJjdU(65Q/"tYV 0auk'߽`l_ʐmc4WD1 )3p퀂 o S?,2Hzz?g e"|a|44.hRl9J?aWĘ8>ZQn6@hDQ+ L"N&w ރ ߒGnCh,7AJ-hk!SA(2<Ј^9%9. "oלw^݁4$k; ^cnML?^s|jK}"&W6x , q 6_ ~=Dۋ[ ::GOɋHwKDn"&'(Sp $)`'+XmNOݤjPFhmQHx-9pqfiL/mONJD>R&pֆR*}D ` ^Cd]0Qw@ivgVfҏO i<#iSpZHH44a1XMБ,2 "\H$:hǤPdv_:O.:%zI19|="'@3w.(u< `J:Oo]W=H#]וkI *Q~? EhWSB%. _8 Th8 .ZuohlXdp U7Q<*?Ex&G!!siFi8^{^޾B7CD@k۠ ]2?_+B!ro}uĒu?/:ʘ ڿ2Q:0w=RKle ɵ=Xn#gv%#X%Δ՞3/FM08~ uZD6R :qGuøM] Y5|lsoҤ٨G;8:?#Ow M?p ,o\n'޹VF(2'8Wqo"Dqf IuErU dOoF}c,EHb~pS>A]za /`?sD4ho/@' ! XJx^4%x0pג )}l5GF OѨvHg,[}>HAX0,YP#G3Pz sLTRݥ eOeQm@a;k> 58X cHvψ{a?Ȧ߅Hx9M`dzѥi94Sh?Wft!5&wX F"eK3k'VXte'U ( }v$ސ=}N,KT0r#iż4[9s5gIwUAzKD]q׷WH  Ft-q,' M7pF/ :ODi6-C ~##vX`pֿ;ݨtԏoRKqNu((ɳ EO"QJY[u:Xry&4^>ɪF2&w#6Gڰ^mR^]X + d9uƂH 4 !oDRM. \0 XbCQ~2q=^'{ tǓ9 X(HFS9\"[ e)m(7uwW龆&ۖu-21I_Dy,"KBQV:@dm,K㛑)POq!(e,4Pa~#p2znF)x !b,K%9r\.摁`Λ;X"Rh` `s M쯗W>v7iVHd3W`i!8N>F3JΔ#xD';9Hḏ]xz-GaamcH7\cY`(G _F* E02si)2B][[6o 5\t4 {Ƀ Hʠg2ݛW'@P"{|=q+KRʂ3WK$OK0.hQpEp-Xr:}^URɐ#: زkDd1p$U+7^DKfA x݀~' ^3 ..^ZqooQy].׽oڛתrLuoa4$\qB+8Os>CaRƤ*Cp(DB:-v"x^A6Hp? c.|ӌ\@b2f!2ȱqwĨl*P79ii<Ĺӡ + SA%^u) =LejQF.W2II@:K UnH=Aii!M6i}In#{a45˵%n;51_q/}}{?q{þH`}*8qG(ok:_PŖW ,X*G-U *GsXEJg5ac"!a}L0&ֻdWyN$ x޳O>?WZZoSF)峚ۭғZw̓ yh C#P]`tcQY/,b]DLm؊1m^!َtƍ?DVΰǪX(`U+ˎI.lԱR_#јa}{O ^9ˀ6y2AIyQro?8HOy˿6:.NlNT'Wtwt|a4EmxWG2?e,P+ ZG_@ Z b:? =!UԔ+˪,jSsqQ&9=G W?zcє~Z|mtǝ2 \~"\B(aM{B}%֏ݏ`kyooIUtz/5_m)`ݽe!QL-L0Xi\IWCٌ)F4lt{2~XeT@h9  ycqMi-:b:f(I KKԔRd)Jw8ںw۾" :|o(0hvwD@ ͹r3gT4lnz@:@@ae Q {ZԵ9pgs 7];8&k &'eҋgM@n$ӥ݃hQ0ŔN•T  TAqm_tf 鵌 @|lFv;,W sYQɊ G)dîڧDƑL517dnPp_ј*Lӄ_CM1TiWq}ˊ;񥌱dzQKhxMh `b $n N\te3tOuԐaF4?NM=#6jR',ZؠrCxhQ} !luբ_{{^0X\p]y1-Y)7v.6Ly3&EE@4Iki1|Mwt@U z\G D˪}su{]k| Y~+6kڍ3&C%'d!~z۷z!\_@KQ`ʅHe>Tl.)'O!h:c-l`-3wS[T K-,ʄIYQ-ӗXl͡ql}x/@1{#]xHSm-p/FqRu4Vp;G_ @_-=<Ώu߭m8sp̃T+(s&X(T@vPO4Nfeڲ,'dV<ћk 'n=ї>EZ^S ><@p}֏-\z* EHDq#!ưEٲ!iT;8t\  A"yK\- PzLzZڪ彮]=s:|+.0Е.~Y0l5i[Z6 "nFq;?!~FoVۑ9RyδGKviM.9R,"F;ݰPdx_ eʝ ю--;cDd* ePܥn5hY`mY7įK䎄ě,s6=Bɰվ Bہڂj-E`KcYv!8fbR(*S[Ûqm37=F }ZI_t+vlhrxϿFx kgEz,F-xh[Dymvz+/zW 2M3aZEE`~=pq͉ p*Jd6RcQ" k ilTG?q Ά_-+{ ,J:T{-˱V݁UF_]"Mq{ͮ{/_̧xo~@mqkq80767ȜT *}]iDz]VTX_a!?XFA%`CiP>  hq<䏳}{N{`( Qc@i\}SmFQ4*up޺^ݳh/V-f-.268'[xԀ$@톿Z_lJ'c_ڥm\Lܔ⳿=:;V;fN3sǛw=v2.MAS[XVn8}w|-[U36wlU? R*dFXK kmp|;;yxPn=+;M"[^; s#7yE-|RE@m2Syl %fY|1gp˵nY'>q Z2nDa> ʓ(MWoC;؅d]=xs {6 y2 xkSoB,=>Zyq[2j헫0:NUHm2v= ֛+2 {3|LtY7f)aA/oe*1Pbv@= ĦaF7H"Dn^̨3Kf~/xꋹ db"Ŧ4D=\dSHq9- NdY-'dֵXƦƻ&Hkj/ ptF/8AeCP.\pz9ZtpV> "m%EYRJL_VeYeҜ>6h zl&Xdu*~Uaƫy#_uKWdy4O<ٛ;|,(Y0yhʳE,RMGX~4#gk]g8pʳs=w=}}9xK]?.?-m_jwΩ_AE`vjl}KҞglu6[bΛNŁpm_i/{ n=+=9oۭ?Kǻ=FtsM2Vx5Ĩ[he75^| ql_,ڛ\9B2i/PM yV?ar< pDN-i'8ox-S?c ?t ~e,y3v m5y[mG+_\]!nk}Eꐬا/NvbIL1Wy_GL Lr:u~8&UMu>1zWM$R.dT{(P~o)E|F姼"An1W:MQnoiég NS}]]>ɞ,8A27(MzBg]&q0 oP;~j ibܐh%NKMm!<؞߽8*֙}(o|nNpCYDذMݪ# Vqƿk-c -19lw3OaK7hJHMx 4t@죿$Sb#QF7NΧOok­M^'"x_w-] ùw@/ *=2(⌟p16z uH"tt2 ğ20fi01#+?ɠ|\@*usGP(= .]DTHe;=N;ڗ/"'>^V {n?ѵ} B@Sz)銵J MH/MJޗ)#]?W]/~fw[HBcn[2`_Lة=JcKMa(EC3𛳪20!)Rk$eho0qw.N MJ6d}iqN:r?mh;)w=5hs#[ kov~sӔG>.Ow:K{Np~B ?M2xF)-p  2XjԀ?(p:iw2pt4Y>*'=f (Na04qm毶6 UVMΰRrZ,DXw)*l. ejYRyom,E!ZGֹ)\ ^"FO.Gw ͖uGr > JRRB!ŋt=֟7ayt _]3MI0Te ~c32E$<NsZ!)%% Z֜up"jY#$$n=@[P%~*'jPD67O,HLEE_+o/ypJ4&Xya\F)j_Oʯh ֠dvRQPfp5uE`= laSDG tmno Ȳqt{z-E^$]wPFt4ʵt&\3yzPҋ/jQ.VܳyÈmw~A'ejw%⒗K406L^p8FK_uf]d"ٲ>/]3X?Qzp- Z7f<.pyl†"-;Tje+ i[^Pa#!'Ot?oĆKg(‚2JreGJ?塿|%Oߜy߇g`Y ^C (%xs8ſX ۾ P5Uǃd#pZsN 4(o:M"1KA쿌ಮ:BBOļi:N0 1w8&2)i u H4ƻԚsc/6S[ZJmLSm|.WD2}d [YT7d <0-Fx@t#IrzݐGe]l\`"T& BX裡I.&vM<&PAʵko`?.^XP:&n+5O7bg;޾Z~\/@=gxWxҽ Eī0`޼{L"?P"ߥýIXRZ ljW&ۉT,c#ˤ?P?#GaG! l B9[(/{:iv6 QØ/k׃Wecu׳9 ޢe;bQ-{`i}_1&\`yPve?>6Ųv3ԴeG=Q5@sEAỸ`?)/'i \ @( 9 8фQ!;t7yl4L 7l̒'z7W˳Qt|$1to\_MC( M\|=p_#z0Q# . M`ZJ  `mlU4㇑8x~;=R:5_T2d{ r"ǙN˿6j])? -ZI:41@7l(Mh-GmP5NQ)ҵm"Mj2e:(Y^8u,ԇ8ufy`r-e9z(/oN wXZ H S_"\:LE=11 929^D؄"́W1vi x z+q^/32ni<>+퀭IK򹔠e! $x@2` .o],b?#y,[9GCoM̶hMȶdH!ңVq CWoUo뀗'&wKJ'8ŭ' T#V:P_M\E)qL$Jg3Q3tV?gV8xX[Tu o|'t8Df1Iv)rsɶyP ׄA&RuGD|~2eR!xfrǭMb֋F:Jg1< FW't**Z8}E>Ͻ8,ia׶[{eG=bV)]MVUǧȧi@&4@GD O)i07lL,ZHkc7~a@`+ձ/7FaoI0r_ k5tb>˴ӻWrS'AN{"DN(u]ŬLNZD:Ir˷*zbFqc0{dT7Qb/[f?"5H$ j~S%nC.Rlot֚E[ZnxyN4n^dwuz Obm%͖='ñ6 >Y+ο|!`fNN[38rك1Ǚ|x=*q/[oK?΂YX"c%kI Үϼz=YzIʔ;8O[9 YW5D[Ȱ4;WٮX4oJҍ".:9.WL`xGOTZ>8#?^,|aMٚ';NOzI2v\nQsAvܽΟq $GZvwp JJc>?kfMhÒ蕵uZ>W@IDATpXCb?ߝx{\ᐛmQ56ogP._ y qlKuE6B,bE:k1 `NuQٜexI"GEQrOMskAGRh1M@&H> M{Zd1.[${~Fn}'AJ Yy4p%2lC^ JW']mGb\tyFEr6C.Wښ;={4DkhYݍ}Ghx￘yM0ފz`ׁ%r9E-*c)_@G<rPN$\zOaL [Lkt%phRfg3۳L*vs@Ҕs@h9%Z]+Ŝ-+0v) X`.*a.o@U{a 6 ]aߖmҼ)34[&%՘Z{#S<ƾ+86$G8Ȫ&d:zr4[Gu'p[`k:Sϝ!҆_S*q`! i'(-YmK!2:Y:m ~k>Nk!(-!*6xmn(JV8dxaeYH#c@ V._@ZSc]S}:5rsɓ1,ǚGL+>$,C !Ȩ7eeu&LkJ ix5;;iM5̯cBŗa*9 az1.O-;x=7)G-s/a.9> őޝƬG WD]c ,(Ë:˿\>%0B NBCA:ik$?.i_u=oHHq#5(~'a{Y>7<!w{n}-rT}z<W:E.J˭th//"} U3.JTøH?r3!=iOGph;Vj4,-e UEݲS1HA!a[3qٰMktqƹ9# `s}.y,J\")YU /.:Exn(n^!1>'|5q^#kahYx7ÉМhFɇά3۞j8~(k$lySު%0%R~,{᫃齕u-tUbȣBCdxxsg֛_ʇTH !Md :;`ai:k"'p`c}| (P$[f- Ti*8=L#:lӜeaڙV{s ~W׾4S&fCs"6M?X@ХaX'jH>8d&CM0dڐ~ 2Y?ԳA?9;MƓk(}kQ[0 <)!'D}8Bӿ;IG}|£.:|_c?;'cU>ܞj_ǗwOlu~VߦL *K+Ieٜ2OB;o(("F<6c!hƬ[EZ,F|3\ ]@"{&>m-7VP%+"n1EoP@\h*+wYR vO逖{~ 5|镭74 W>}"@~NŵWk<ŏɂ$MzN#vi}I 5^ɽtA=)P?O΁_P#֒ $7ܞ-aqƻ K/#gm<[hј?p]ejrStNf|I;G Ke}A8{U!obL*BWfu^T 5g#eEmT. Pmz\o;qzО @`0:jӘ"J`]MĽKVKg4ءn s桡U*HZ ږEdeZ'r eⲽQ1r:9}yUL*]hqbxŬd6vi@P*34m?5՜ʏܣތo}E^5$}zE0b\hU eGu N]O3?OEy>|'lvQ5I\0m&huv4$m,d$(Sg@QRK "yo:E].uqڷ< ٫=Dv?@uܧ{ Hve`G֌~i`ߠ%1Vz/;w{9ȇ@D~5;/YW^2p`+fzyzJ7 :Z66`1h h:B_B$]D_(VنnhE!c#:~ ވnB TMw%`Ff2F>k| ݻ 9/liGCxȂ6MF6ؔuQeesT9Pc72oRi>ŻLcXxydv3Xldzӌ@n/ D9sѮe:|ֲCRqB_C]ExO5FK=j]߳ߌem3V.M/J׃|dd`V"ZHںPMSJi|xTeF4xGgkTiOL|пӳ$n MVLe OnwMu`?)}aa*h 7T<"ܘamء6 db'BG2<8%/絍}Iqk M,ƅ*N'tM1h٢)tz Lq\xmVqfv[&8+\jkz5 .CPL[}la/ {6FCy8bߠƧ LlɓZ'  MÃ@KNݟPgcU vM 2Aؠ66D[DH϶k]gsL$ A4}Ze=ͥgkI/k#P@E>O N{C(.T -r$Dc4ErZ_l|K އx~Sw<ѷP6cU2|%t~DH&D=Dv*ZCNShK44ڼ) k(Sd}ncԓ`Oz~з9U$[zu6$F.;/SW|97:wj\d'@&JI~5>+Hm&߰[G\ga FsϺD@e+*~`[j@QWԐr7A O1Fget-Zcaͧ* g|$uɖ6m'kAScIk7/W)-u"Wpbj0~ y%MSU|}7DoiQn Qbg82/`[+Ŭر0mVUȁ i 3.=rR݇8@uYg 8ÜZ jpƝ@8lqۨ%[6b mjMSF)_E4X>=y _ R76k-5Ic7OZGӋun >qNU7T0DOv^Hf>(}0*Oz"c+xߙv|mk C8fTq c"XP% T^YQ5 h>E$S$t.:"BNi˕p-=|jvj9ϗE ņh,pU(pK~Lx 5䶇0s|sL {xΐȂ{xtBNh:խ(&W'JZw:P)W;  4 ǯ{kt@"tO{ierA:ȩ( *E *%PH@F:#j:evݘM n.ZT\443#)D ?b/61LbSIM7c?ٔ{Cdop4zW:(& _I[5.\XF :OXui+z'^x܋`CB[u  448$Z ґ`Ǣ֌CBc!b{K(9ρgc}n2FֱLx+V8hz9N$8lW ,vӣ#7.5B ]P-r j3{?O*xwn7S:] @lӌܯ?XV١ ' _ďg~\(2zF~̩Ɖ=0rbc6 hDDP w^g'V* DFP؍Elð1%/x`5{2ga; n21ˮ2.NJǒnޖ&%WEGru}BɯSDzr! WVI2ŮFR+GXַ*l}@^/[[ byZ$VЂ1'~AD:!w>p6'x(r[۳@$vs9'h)kY v|vt=GL[>(3N_O1b@F39(M<|I24rST*k@OYG\vDq)"\Q`d"9h E%.I< HK^71܄,3mbf{q79gOO598kgg}FoC.0PO]|hW"^~"BI*K/0-1 }^9ү@iOOՁєx$艻7sLGbE_EYFx ~)S?[<%4n.KejaR/TF?UlO~ L7_k0zR[P!jfəgĨ~N~(u|zR ?*d-qQs }ؕjyide a`@ Ŭ8 eCҴ8wne-Jbz>G]˝b7uj铜-.ӴK `ǰbȩ)\ݭ^ݖ* =_ş<Ÿi&ze*nGTD@Tqφɟ$7+gh'B._B!:] >(O'mS,#M?" :rԯ7MdVI;N.S['.c;1Up*:p/,v/$)D*|i>P 0]Ge)/}:k$ՠ6G.D0 i L2蠉7vj6:(sUѹI#tɔ 9`hH`0䕐? {( Ƭ#}Su~vkN>FP`vM v (>Gr݆3ϵBN>s(ŸD?Piν6nsq`}`%D])E(߷H$8G} GM,@R(\n4QVV`Y^t;*T*{IFFԊgX͞CI>˅Hi9yKXb3$VhA`aeawY8Ude嗋ՊzkagS*&Nu:U_6bx1|f>H^nʉ \4{'O8Ez{sS@ZGseH8_$lPN|Ayռ iab 3v@:$!pՐ xd >|~T@ eZxUሦR/ZJ|mkߡ;löj~fZ DSlkln"J`eV1DB!L nIx g+ nݫ-UjS >V+[T@6qA1lG!*OEPWVNv:w7W?[mpl ~O;[G{,mR+>MZ"au SgRbQrX*B&3u@R 8]шZ@RdP-RaW9Ԇ1'dŀGٶ)ypd:eC)Pew-JW(ee 0462&اbU -pevvW;qB .c'8&(B%^<7߂7t#4S2hv1Jo\.j{IgѿN2\+~dޱύ~翱:0Z-`j%XY nJCP+M: oX KdSyuTBx{fb-2[BSFiSef +G#rcIk`G)xhz˴:h 2lM(p2YW8FK# a2q(s E`(!;4ٺf߱~0=ŋC񅡷ĝ^w|ZeFËiiO9qnOs>wlF-ԾwO F=_ŜJyC_C`bӱʼnJH%E)X  u\s \.\ԭByuJ<dƉ! 8 tf {%ih! ,D0P{cNr%~]022.^r5M54YD^eMBCc&mg'3'f/DM7Gѻ# pM$?;x4}2R^KB)wIw 21o?՞q]ɯh>smT=|^=^{B7m SWc*'*Jh@ӂq")/6e̗ZpMVbXFlӡ(sO1J͍e =r#L(YO/qt%r3"uzI^9ǭ~̔/i.rj߻<'6'n~՛@Pz+?I?sWĩ :{MK-5dvN Q {/X"-?y duCoCCOG,<z5-V%:#Z邐5(3* YPu,12 `fR'(.Gör$588QL~p,hZ.dwo2zn QPY^pzScc $W,?^YdC=I|K5zz!x(ŭYxqy{v4mN u@1]r;jyc Or.Kbl8-9&Xy'ʬ>Ca,n/0 'wܻ^ oQ2;ѺȖ$Ť4 FҝM @]4`RP'} tVJo  ;I3JA.jm%Y&i4-Q\%.ipNk'Q#:PHi|yBEU`U+cFIwK%.>Q' Vg Oڀt V1iXK5B䑰tRRtJӎuROgxKe#q/OWtq?Cd&_t4iG (Zئ_a~(%6v HA=4p~<"ٹ̱qI{Ff80'@gmߧ 7y iS_?ţSϜ?Tƕ-|yit?ŕ;<ɝU9u? &Ƅ7'N9%Jd_d)^ "X&8v)t+B 2i!y+O]s 6ցͿ adՐa `]|Ls`6սF iԳS E|@qcSt%κ@wX+o@2R%c `'}b,bͶHaV=) >+0EPF2!p*! y:҃ҧ0/YJa[ȳdb9h ylX@"Cb:TA(a&1H8щ76;a DnbS5/bQ`Ltp$()g`'/EFD$T&E^C&,c=XSE r>R|m)cԧ~lDym[^ ]Pߖ_ůJ+<`V&8O` X_S*< HiAP /Da@nj#@'PN@`FnƋ#u&L"~2%ߡ :6BG\߂CDFjr6l dpNߣX =}50tG9>  N⎻o a)|vJ"/._y18xٔPM8S'D4찛sBt1߁_=}qsk'xAכxcgO"q:RzZzLb͓6Mj0J'83LIL]4>Lt_|#D&`-T4Y,EVuD-D2 KEr$ 15RCʑ&$k_2u{JXUʇVuDքRQ9z-!awP]]Bf:. sUg ^ߟz o)Y"T{L)^E,>%NGD#zUktN^@V@JZ% mh$2”%r1"[>HynQ#vɪT ;)x>/CiЌ(t$\InH<2$)\{d׆v !RHSpdFES\*D8_?'y+#[t?h6~j͞c;T6^ёwOВx+sED010 ^u%F Q hkv[_hs<5 ]4Xi,{qʁhVYhQ@>O*frA$x$9ChD֧63}.i#(V}?p,!hcQGQk/$} &M*FP8 |ՕU3m!b$j(<؁)̠*j\8\wT4Yk P])#UڹXEZ0_WWCgD Z&?TGSyE D~2C~7_X];J07&xZRC#!؞_aR\55_%t(/ZeFhʤV 8JFF+ mbUѺ.P(yH PРp NiIv|p`LΕb d6|(Gߔm&PE^ ƴQ;EZ"@'e#Ƹ8+&H֗1& e|x,?!G}JjZق5R[->ǚa QC z1JTfˣaˊ"}ZsV88Mp [ X#g;x%KjHaƒJxMtT W3/5T6ac*D| S'L_f6Ӿg4EtyZ252ג<^ZK52eEF8 νQ ǹy>;$H;xRz$tEʒ OZGXOaݼ2x8m8%1t GT:Іyiw>+JI& y̐wTw&~:~h0ضN(&RKhur0U&vyןm?meb<w*>]I[SD]R75pV]զ !/b懶.wBȨ(D3~{#PUCStP" M1y~HtB5]lath`jnJE9қ f[=QhP`.e3R$ +NpO}hCZʏb-˭)FkL%P%%LplS N0BYp?2H]bLn 'ܣ6.泶PoSeQ+F1qٿ嶠 1W/*s99W}@qo==ENNk9-~>\h ~^q SvZE+{-K/&Ydv/HxʀFuM@ 6K_fİ-H!"%0}g* 3 3BǾ<_m> gr{2; &8LJ&*|~BL1)H4L6_|k2>'᝟/5=jZe ?u4.?bXՙXA;ϟJ?+UՏI7Gz_kC@W~;\C^GSc}fjkjdO-ˆ q㟎)9e`6 /Ya0sgA10AclQ(8 %&xm:|{EAcǯgx,3lI&{5t3ϦCy̪'rnnb; 2I=0n9Z|܆ :r8/w65JSy(y"j(0ve6ſ h(@iu'PFmTvy30O-d9a\'-DT_HXGt4,ӌ@(}f!LXQX T [ER>!c\@"6q}q; A{%j>zG2)g_40 ?2}T$,?{֥GT|sPz~nhzŗ0gt/^/-򈖼~ir}Ý=3Jt ~g{7)u_@dwX{n%!rhgU%ee 4~ MRHqZCS-P5A,TUG0rP!V\RhkH(2#s*41Ub@σ :3<#6%&+?CgݖOc}YL&֙_>yc ^#Lxd'qĨI9%JP-!7=G\H$wAiܓί~aXNA,y$ɂݹ? (jK=O^،<1$ͼqbz4JUi"EP@IDAT~(>+6;VnayehGwiDȚraixC ^PI7tcXb3H#4* 1~^LqQpR E5m*DITR<-nUԁey߉TH XF5]0Q$.tiP5MDJ J"ʱm=QaU_aP F 5-U;re0jGL9IyͩYD!,[-1a6sw5V(8%t:8< pbp ,"ʝ L%<RZ15CS.NQGLF5rJ7B9׶pz"n= 赴a++#?#X\Kq1(Ԯ*76dbטPU _c!P)[ӏ4J}x&f'AX%n|UHF\t  O9h{i rO1=o}^UY):v94|^?5NW&TSkaCh`t՗8|ӗԹP|)$–ؑ,h _"D!00W(N2拚ƥ2(i`\ ^TU* .ѐ@!(} jZ GK#3l*-3+3\iq5r\%6@>En;]Ƿ~r^xi{&;p\HqLJv^yfw{]~O3>ٟfݙGiw43 /-kK=dN wo*T:vȷ:r:?|Zx6+P %'1G;@ vi*?N#`i[lJOJkZ%UUDYӥb0?J{*0 3U08iH΁ `,`Fdpx^IRRP2x]t,( Op.*X!rcǸ+ `Osnr͑]w7I,͝ԅzVS HZhIi`N&:NId@/ǟeMf<j:;uػ/XRkqJbS<0^P̞bmdi@\6v():WIrb8hdA;.H4Q\5W_b 4C{ |uFtS~4 ?)/fqԭ r4x\`D^txd l{b绗_ܚ5,2 >qDtmdR&ɱzr{|_F#KzJ+w}kxMi" lKc:I'o5F}`M6ߪ}BGlп_>J/ 27.x; ZncB:pǷ&7-y|1H&rJ*1#,P [-h_ss\3.,p?QiZuG%dʅ6?@-qHo4 lXxw̟y@"ޜR!`,qZ߉[L /Js_Fk 16 @b-kɈLpVMܸ˲Z E18߷6jۅw!+;SbtW{CI, eRлM_K|*`8Zɀ6*jmKNmg*]fL3ز  rk_F3)2 zv5`,4FVeU&Xx4+i"0 31hfI $\q9*+2 SY)2RY~.TLsrod>s}dAdMɁCp]4Gce}-1 >ccN4!//>@O8,40i؛@$:x%3y5=aKHռЀ&O.m`FZhYh _\OҢ_~~ bۺȳ]NFЅ63T/mPm\*,Rc$e< 6 /Ayxs])oMNDQޚB>0/ڑ Y`̷ Xyr^*giUB{`i<dD Dx2' cXDfrnh Vهw6j2}\dzѳ+#J78h,xTa~ ͅΞвulaPp&oצ5:GnFd WF,+23O##; UYYFIF1@.AA 5JӈQ \B9a:Gkٙv;¶axf[KOаO6oӔk}JZ bSB钜7 9C`,_'pm.&6:oq`ɗ(R~yLHG Lt#HwՊho8O{8 #8E4u;V @NrcePyV,4 Bxڻ0``pAʫϯst:h"Oof-NK\B ~ t2htdeB _Am>)t*=qP.&Φ5<2$eTdywH2t[WZPP;>R_LO2t#@^eX <.g 6n8@.)%6IwӢ,qw]a!1~>g (-EmБfRapa RI,!:ITщ8}>Py;%ۈʥ~0b+x.>(qßt`C xOrԜָCDrBeX7}^2\Az k7`NNv+] Lj6q==a2 F= e4ٙ N2џtLu\x(YrRNA18J֎ #ؙ1BE noBn(D6:ASA@$Ύ@P~B%<\Όe{M:Q"$3'^?CG]<삗{S'OnK:$HN{Ay=Ĉޣ2H>`WZ :˴g$biVqfO0[N)DvO)4=PBj6g"&X8KR4 U}3s5OWpĆOqceGt`q XؠueC ґ7P/=;beLy>1@Гs6Ðȥ~8 s/ɢO w ̖qn'>ĶT]+ +d|{ ˄$9tUi&q~r&kjRHn \ zd7Ʉ*]pp^Ll͆.1C8ʟp- JQ5 ;+v[0i^iHN^D^ܾ貱1ck/31Faٿ3,3a@v'?#=ӟ#y¬=ʶ/|}> +9eu`ܞ@V򇝦U=8i qfeZya#̣yM:\n!q˹$mu4DK 'Q }I2&*LcaLB=oJO2F" kc0)eVQ^*)ҁwKÚ l yAz^.q2O= ]YX-}·Npʶ?AlC"Su_~v6,{%`+'J 3OJ$@"|rP2HL֦A@!)S%}`s#VEUc-:J! ea.d*RtCm;#; Ex}gi 'M:J/]TgeSX z5/ӈ́U_D!DCE5cs ]{M?-b$:67"m\˘ a0>=% S@3Gֵ~>6d&˓1"G ;6gW''_OY), {ߣXa_d9ϱ s<tXqD(Oks%E)PkRPCi"ua|0,^́!@L(4#L S˅[F蠹ÀlBO+)A9-El6`sGvcɪ0 1QEifAf9Wږ)fIK]T$Pez<g*kSdM-h6Tƴ=X[ zLz]ŝc꺒wCHYЩ<0KN+m^4_H8v_S/ؓ#%>IGF0q<Žy: ]kac>>|g{] +yoQs\̣9l%2G<;EFbylp;y=Ilzߗdc6;7'Q="jI&W.zk`5XCOl;LوxݷJ 6GSMfj5*'}`(o"8S4W*^ TXizp+vk1'〢>8tvդE,p):Peo00V*#aU\EYWBw WZd0a%쌫+XggnD- uh'[8h, W#i)C(3!JM̚C -S\Lb~V#_DO9RO<%"8zgcON*|i0:y݃G\n҈\tBaA :sUB ҤxԼ C'3.)+vLryED’l<+y`QeZt$Y'y+.zLp9d(UfA!Lb`_wem#|j|n2a̬M.(,|MRǓ4;¬㤣0+ ΅*4%3$:@4*-x>ZV`VVf͵rjTٓYW5N;<-qT\a nW, D%T@n?>8w |t:\GY%0Yk$O򲖀 wt"'C6O@E bP [[d,MG1M&E (9I~<&Q#Bl Q%x> 簯 (iUDٓS w0`9 %ph)r e pƼDF*t(m|hX93$'B 4G3 wf'wc[SMK)uj˦#r/sEł=73-W[:EE0=t꧘D0s} "໠9́CTpHؗ.jCHY2Xyuə ?7J{9hkPUxdsD3ATCZB[\,=?ж}I@-m,acrnz;Hք Zrm:zaϻh\2. rzǫw\Xa 1|f2(_k[[Cd LMB0d)!3-gBiJuZA3ArzO!N&ƨ? :U?cOv }j׸k~nXu+& 0]l;߁Gboc!dt:tj04Rg4nH7>?2{T9?ďhEjwv2*#tKb)H)Hc"E}Z2F.8_=FÍc1aYRfM [MLlZyG:vyIJE<ve)TR  op$SZub'~TyI2`3u,ːlUP."˽rVgs C2 tkiEgsP/+XUC)ye PעS=Yڮѐn% J 4L 5.Kfd.aV) 6σḱlBWŅ>-Y'#pE<#xcM۲M80ڦK?@9E9,mшgy3p'2;J~D>p&tK|wD(N! j瑍=)Iʛ_ʙ2 0 aoq'DW>cd0v{HwQY+ڕEB6bqDgx,pgWP6g2\L_qifo0Yth 5_ʊ֩=,Fܜ4DUd&p. evÕ|G@f5 A^<+r$oqwPWY$WpC@ʵ%N<fėtvQ@6bUE7 .!Dau8ؤs}N5pimKgi2Іlpex2(>D!շ/ʣ;ۀ~OꌽCG XӫhiڪD4pgwj["Q _I)sNhKP7o/gԖcS|51oe>9rbPl;'?۟c6Uq6iQ" Ihfi$K`юif$XrP2I4#F3d%̿tV#vUya OTAʸDN xf`OJ0uG+3YK^ԻaY @3%C8DA5qsKLzY*-Ciԁh/?<0.oi#PxV-H|_GvBڣ>&qkCړYtz 4r}Ͼy`X@NnZZt7\ MsV n#F "7:%A"qaaȥrG#J M 4csa.c?:qdv75XX:9`.ٶƅcD\kAl ϫTBT&ryE/@_ 1s64ҷ.3КӥS<1q'/nNc;Zh (ݮ G2ib:>| М N47  < #  H3H)'5~o@*UWapFyG"˩o[UAUOt eSP%.ZDOc%!nѦѶEY ?Tl44ե i0Yd+)5a?)?dD@Fphhs}6ԁKF_t̆yoV!>n5p ǦxiM 'bcfRGr ]2CQp$)_=2WoƼIO9L^MA8XXB @겼3T?0q-PZ)vOtW"M _eQd% 53J!} S&A,l@ \h.Q#˚H""_(J冗q:腬D_$IC} X!Ada\ћe,4 ^Di!z c'fO"y]7~Dy&dc(@NQo>F!0rt* ;hIQ_1>FPd4? d3~d)i@ʉKB5G֘SyBC rVpe )0UsbRN9"P1K o[$IaOÊSs`,ԂOM ^M}mL Hc-gB?j]Z(RŠgp.t~r*Q_٨kyrNE߶'Lb~3j$ ;vr( U!C\4PcX AcQ?_>:ӫZps` w'!Nh j h&cq/_#c gellI${`Q6P{l찁08Q]PO+7x]4J]~PKvヨt_:"?6Z]B*Si3fao"~В23)Q,C^$S2eL<Ҷ;q{ hb@sMP}ju"۴(WKRg|G ݅}=@^qlOOjo*.C)=OÝ",w1e_JڧyR( f]0.)+dYk`Ȁ%pҫ/MQ%ce4UJĭ e٦+mOkR՟:z6,V6͢S'j/G*jX8|zHR2JLQfC;x*܅-9wlZ.OonSfXY[~ad jqcG?YL/A, yCnU1tl> :K#XlgV1%@oBb· V~[) x 9Ni镧aDr ؠ>mGA=6~Q֧>*X+tB!F83aS[;~^$ɗ@0]&CUSDdӆY`YKpӠ{Go7fn:ڦ^ELpKdGe^̋i_'X azykAme퀿\_cƠ< ALI VB߈e fH= 㔵 1i"5E5/c|BJfOVzO7m4YedI la `C$ER{R!IU*B,dk[Kd}ԫW{pϹ=גn#&2xm)6tU]Ʃ8"ΠA< SD'$3%Lk\5q7:"^;n'*T{:"AYXʸ,%00-a'¸?K2PLgC1Љ30 2I5* D656E)I-" coi.tUnLːuS\vZAHژ @oѯ@ҫ{o*x(n/al RރeU2ql*+puIނg~&]Ct*0%It8hY@$杈%p<$x1#Fe]X)(&+ҦZD3 gBh""}Yc^P2ZFxVA3AKՏ./̝$NcǸ\#`c@~?$<6Yčjb6 TY|3'n~/K,'%^Zro\ yx5;J?f 2 ůϞDK0Lᖪ3g|PB̢7)X T fڮE*Pqz><p=Ғ:^VV৏$?%һƀ%ĽIIFmRt__t|ڹ|}(Jv2#iiLD2-P 1񏱲1e(,(Ow&mO֯ Ȳ׼SFKpS/: ~2Y*a{D.,b(t,!W[fp JAbK ? yPz ]g(!Op0FhǑ#14R%=G̥L?ZQНj`AY FIZL2@fY ϱD wU`eҀǨ9NЭuCFTňvp Ň11&G7ٲ4Ugۧ+ȶfmCΊ'˅"bU*6heI:Yyx+ qآfh /]%9N^?^GlB?˹N8AHeS M,mqd=B c(3KlYِQ  ,(\6 ذVYq}vHq@DѶ k\Mg{!7Zk`a"Hu/VH$0@1l\=(F=1szĺs)jPR7tOԳW:П- g#yKtch{ Oudk6;I_PL16  ӟ `/6d-rH?OL%0Ӡ&H+X*Ď>NYC)Ⱦ+G>`'7Rod2|+MNZP_.;βQF 50UXdǏxzgH1Ʃ;!΄N\Jɴhg?-UI]U*I[ͨ5ݐw /4tg#$NjG߰ 8$WM7y+,r3$AN2kqS܌"{$fcwUqFK7}*ׄ>^n[ daI賿FfAp$o 뻖/c=!WC bE`ҟa"+̛e*2iI`d!xILp6&ΈMvDyCgNwlSvkV~F7YӅ'ҫ/.kyPЮ땫_\ӧQ^ 7x ys1zYN/)M co T`g{a%/heqG|7;oh5zQV]mev8{Z($r&Ax3:KuE s`U8{[d0-<% $zM"=+hY}@~'V:3=1(˾mHS!jJE:0jxĠ獘aL;ka;@d%}t )1u;X+'=AWlҠ@IDAT ž9qхeqrFP^֟4"ko}WA݂ۂÀPg_| PCovvl^|ơT ~2"쌹d2`] `\>EqƑ ~0.Po ˛JTٛGDFJ [b3N=O4{ܹCԩ63:ѣe/vQEי@?c+_H}P3[h<rfuYIKƓr<-uv^9 IV. vV!겊BUp5QJiUF''5s;]g cyH+<@ \Tp~ Gj. !v3@[@h1VR%i23rp'3z)EŢvh$$WDaZBRyR"!|>C$@{܇#N8:bΜJop^lӎr={܌kYh|79jIj+ &nF]Yˆ<TĨ;jK T@L7|,83N%s)~e2^7ޕ2"x낊DɎs5Uq_1`]C>\I >Iwoݖ=SCފ4U_v_*.nlnt#XܔcÿmFPj|z a&$}l˳ɉ]`2*}}-q b>Q)^ɃMp󫁘D99;X4[Q2|b>~\K K7.8/?!@hJ2Tu ^zTzU7 Q{϶s/mǝ5*XqVb{C#qM8uYqZQt<1U@ !l&RSX7&̅rf &ئ$"iv(`N0,Vu(#P0l& VUnF;Zۃ 4E{ 5)TDӔygnVWl󊫽l^11cMLwC-4Q4ţ\:@l8!t 퉦 세X2赘XHDQzcwzwAC5XblmyG*qSn_ YD,u$r05#G;+N8s8;DgĮ8(T}*HW}0$YT` '(XW .t)7PbRǕFJOoФOMm.uV(Ϫɡyn%HtARpJ~P3[U:c KѸl z40@˨j u*a1&=C~6XWhxx. ^6×7#58~q~"Qj1RJCI)tFݝE2Λ7zu?l@ہ2)M8vj^oЬֶ+]Wx U9'2s+b;qQHd'IY':".@7'GGyRANiWZ% Y8ɃO`88g)^QG/liO[U\؁'륦 W,}|'M[#tLRi2{4(%atf_+3Npue]k 5Ъxkg>JtT(3Ƽ-u @rY-PHW's)9Mǫ,?dȭnfg%Ͱ~nKE^a ^ARǩXoٰs-IQ6)A| 4ts}~\nSη@K:[Sxv?[f 0;랈lm"1Ѵ֋$VWlk6zuUu(ObO`ď_I~>Hiu+m%{/6ATP VAk*gkG6.Q/p-p68GovU@.HM<5"UXՓ <(MpXq @<ϥ9?%7aAǕ'?3}zn<#Sqfo`uAM\T95:_| oқ IW Q[-| Jv^[Ϗ-6HUadtSFq\K<XSFl4Zwqč:ӛgCe_0U`6OccOA;j[F̄ 1&/*gje˰%& AJJ)_SvPq%/Zv7̡s) ZʁPJl-{J≤ó*xLX: 6BQ['M,p a@J>]`J "EsFǕ@?lxP~醌uԨIdB2"HN95fReXaʊ6 35ؑ e ukNN=q#w^eWco׹ Ns}$a]fb|ΞgtpPG}r fSN)KD,jCISeNbAx"U1>{ cA|gbLO>^3;w"گZ:Ȑl(d,[߱YbM_ DjQ,e6(aJQBj@2CVi -&'Sk?X'Tpɍ"ڝij+^|9:.Ox}G#+1Gbӄ^ ݣK1&o!1#ˣMSu${,Sxf0XZrxcu8߭;?LIgv /b@WdKpo?+Ňuya bb}uL[2.үHU [ д8+:zzXc~>Ia %1gӟ4ӂk}vV{:j8b:[0S6Hle֔ 9!$bwXF0Sd,ɬ;0ŠzP"{xPx /Vw}-_/~V{dnW=nxD-9"ڰEŇÂƕ9g8k`O"5Nt8M 5cu95oYN.|_m6k鄍~Z0!Pܫ fI'U)"9kmmBUGz,g0/bk>ٽn$Э;m%"$9E&~4hvq/V$pkM_\|ژ5tt}"+D"c}GmRqcoiQ:LN|t t9i^]؆B[}> Ou1nl'DAȫ LA" m IZy!!<5Tfgۦfx|ZuXK#ʄ11۴AfLM']3JqAj\b|PZqn7Xٝ0 ~O%ӹŀψY*TEk6tWa̳бjbYb#*bC.2̬3 4>(&FOv-㵻.0P?^qc)@Kx :lg/Dz$m8VRD &$[ҵy&mhW$}#a(Qr71WH^#E_C׼O cѾi!]Im^'y;Qkp;so9>6cfOĒNepnd@5.61wĐi: U:ftU%}]OAuSq?S,ъ-{3oAd`k6inI&XX+@WnuMMQ{ݯǞdKMR(iݬ̈́Qʢl4SY{ (m@k+&Umi82?[O *eu.DQL0%['5Dtzxdc- +cA; b2! $Y}G8U%%j,As]/Ll$ KFM>c&}TNU/=w kΒ.#AmӦQOC>jʍ9`5͹N=Ē<297Ͻ,_r.kCdنTnT/f` AW %mk&>;Y8BAt'l̮Ո4+mk:@p eEXR;)9K?IpF:w1wgc~Q>65v/$h혐܇;W9/4)L&Ài3e"6y ~waIbrcG)Ja CW.j d#,G|XWT]%m2m,TI"o#`c&Y11ǎL@dW|o[^ 7o\tC񖨢AtURL1-l,uCYg{#Kd4 ;2{,W BG`1B;U<£ZDI ? ] +i{g\loQ}kԡ JFRh1l SpR;1vR,L_HlDҖge1Kقh·]P ;_e P"nc3~(Ғk)j>k*cP$nZ]*ca(]rr6HZpC{ĕc0֝lQ5%bFRtNrz.zr~]zk 6 Ч0sZ!l@o Y \.j8N(#-c. ۱ve߅gЉo@ߙ1d%k3N`f7lǻF~# r$ht4[}jGĎezy˭؉#g:Y55+R"&t_~ ɚc/aVe_4]PXyǥ `12ZYT23!7h[)_qҟјG #L Xn=AT6g>@>N75zK&S h+7ljhCmyʰZ6P^V |~:kG:{;AZgR!*>z9by='Xljz]{F훤k2}Ь^m^Վ`vkD- 7L`#< q%!NA螕DE 2OI-f)3r7~e#O?ܝ@A OaLaz10,0;&LOb gG:?bJ|8>Yxf(s/bvZW2姶(5?|;;sv~&4pPJ= 1r&t%GeF\0p?!kbؐHJnV`vIj8 |QFB-XYQU*@ 2*CW N*?O@z~U\.%9#v9􃃕$f5*Oߍ lFyZѧ?#t z"?QN/+ʙHV*-D/0?|e|Av y]Րmb{Gmfv/cD+ ]zvX3:uڙd񉎁cl𢜄pXsSs/U]&d;7SfԔ&Vvoe>AmLݕ7f3ؕ?MyM6VASE573H_ө4ڹFHLM`oj Tv]Ba{erv=Nރݓ\ekt'>/*c5 Ź}n\% (|# ;lmqP-0*v4v0857@Ͽ,/(-d{@-y+ڬ8nV4k;2Xt.= B 50ށxP$|BAR6 ` `A" ٥PJ0AMm |=82#¸Oe[6lF8^ь*_[O~Nt|ڎQuy^՗Hq`gRV^w>~Ω]BT)Ĩf %T`Š̈́pu8EGGƯ9y3\777rB^d Jbc8h# L|u:M Q@Vk9;HIIʪME$H!Q~ 792xa_x Y7^綄;;AaCa`sy)%^z1S3Nڸ%-3Շ$;jaf <^l#dE/<\H:ae _g~mܟ7,N{< x_T&ǚJTpȁLy:&k2]U]13-aH [;I$6Pn? W<tO/bR"Kn ÎԾgvBBA !n"K[xC!m3Aù& 6adlΞr2qMmN^z*-,\y+溭bѓDf%8,kgJ§3`g )-Flo1Ӿqf F-˛(@o @8\hnM(l.S]WTDp %v}e@@C,װ(ư#LAfEP-X^TR:'DkhgXpPPddm'Bl Hq-2UC7[ڲlPC7VNY?B,}ӟNPS2d.|v52it raSxSAZ&dvWj5$'񠚓:H!zET*V jD] ~(pA3B x6{iC>ݯ*nX+T˺M"5,f_PL*cY7he9aA6CڂU:e5ޤ+g s" ~1yۢЕ:%yd1߿J<# &F (h! (:^P0O:ĩHUĚeF0ye.~ƃGx)Q񧄓i, iz^sm=sF=$pk>a =@EĠ{({uBLHA)-0 tm1GBP)h`c b8vxpV-aĴ fBHc&b|!46 Κd8e)'R۝|ct$13&^ * @R+6,Ƹotl xHh~ ? ) k̾`ɴt'Ik l|P:5^̀/W7^n* \xR KG_[S  [:AeL['hR `RԺfcLq91rf p"cSD5u{bo7$e7*l/˪^93=ޯ7{ ZzrH]Ia (!"\ND.WO4ܥJ0ߡ\ O?wdDkUC6E勺Lp R=b3?9oL.mtO tLĘ̞>ɄMYd_${qCl"lEı)]zcF) {qpͅ;_@BNNң0W{gZHjXy5Gj`/BHeW-=,GDŒILRC֭kBψgҬՕj1 %f"/ЫLz(P1ٙ718vBXK,PFRznTj93?qyu4)]F%Avey_~ Yl(e« I-s@*z(g_9G"zQCב FMR_F?  )%UMڿHYY%/)߆HS fbX~C!ik("F^є.xޘ+?~ODn0sTJ@Phi|[r[?\W`YOTL CyJc7ie3Bڬeyd(siV(qE4b'?k6-wxM|е:x╛GK/-ӦI3LtmTXSN@L I緽7-##/|eyeApD1sX}PH'ZҎǭ %|yj tlWSxJoH:! wn`h9-SQVntrd/EZ>`qE:LSʅ7BAmH-˰"4-t$g9G)q mkd kP.:tb}{G ( l AW_b|ů-KWg*BBtG`]oЎU*"Op0 t$+AC[rK|r>a| \C 7oxy~bqzc1p֣k}(b$Ʉ` CԠVSmJ-7Vxf{chtǎgЦa8|pMODS5D(b,').X2&Xfnvb"0MGxWꇧ]J"`^b*KG#SP 7m* '?J~T4R[:6gR7w,?NTՀ+5]c&VG #1GB.2ψ{e#MzER9W 1_,vu7ʍ=}@_ ъNT/.O˒;L@&fdf6k!SL^ecw x=8C p#u=[,4[>(CILDVƪĈ|w )o$3ԦsxD4QWLDd}ﺽďBy{jM˟/)&„ウ?IN2~.{ge:[!i} v8Zcv;>sRMGƼ!;`4JaUf)~\e+{u@IDAT5{ ½6-˟;?G/i䃚-óQOseqpaXOu.|Z#eS N"uLD,#C7t ~(O G QHӑt ' Ց9KH~ B+ Yzd |?Ih0 l<6 d97̪1-6UڴёYུ{,߳ȆG.O&W6nJb25".;|'-=Qcz_ C̕]ˡ0 ^R ]!FczW.Qݛ;\јAv]mOCخڕ =T7:)_= Hz*bQv":?uXTgy3ڇKϴ.CD4h<ʑ/ɭ (&6ɠ yaolCoD%G819#×"ӆ2F0_臭[a9oKΤ& &,y6|îwOCs[+8ş/L Mc?b cwLNWnoP?򧫭+F&Wv+YQs` w{JOμ e yƱFVZ I\\MpDwH$g!N3g'Eh^lR`D kՐ$>&z%?P[em15mzZghW8M!˔hb6/f]p2 Rd{|d"P+crB JDG+ņW0b@+q#+bB$1O#W ܪ4x^~Esįi;s|܇o[i#J[G73wh\*g!\xE|Q'&;چcfhyMnvJ$Uq-&(6v*4e*08iɹ, W@gjFw,\w\au8Ϩ@ۢ8xk,A9}=O*GZ@>,uD=HxTq8_ԑ\}pTuJR\rrG2A9tXGRLSH<ܠN72(C˗~W]ߦÿ\]˿xsy ? Co<@Qa-|w' ! 2} 4\H`?Iy9Y*(;E"}!8k4?&16' T?U(6!Sϊ"EiFM/5R_ ݯֲCW,vɄf}4k݈A"_M6׻; B2@$a9 ﵃V{d;~=KbGQC-zpdoup`@; $$MAY\-ne&X. 29&9B'JLg2Q+7ʊz„Y!(wΆn^!IyYW[~ot^}A@gXˏ4E?sRiǔ~ŶHc"YÂ|7Ci aE 4E鑌WgQo.lY3("lt`RȌsz\;H2E ez^-w4]ìH+=~%j"l i,?\w`L@N ;K&S,|ujiGn> bV^<] 0"%1}1G`ƕUϵFG <| Q;%q_|['0Pw6;>6d?3̲)چc2=t̽绮SecQM[Ʋغo|~A5!Z|幗FG>T)W}hؑ4¯/!ˍ펿)1¨j;idO?wkZeE^{۲|ǛD<rS3KpeuiՔ^ yj1}ű ? м‹M_K^(#e?z=r{yN1C|y- |w6>%Q%co: Cɼa˵8EAA~5"^+b$hbưGlk8pBNG6sDIk~K`?/.V 5|\ E3u\OW=Cx<*5D֞ @dArSS mwChf%Sp< feSӽa C)Cϸi{Qlʶ`}[ǵv+[KwXxR5;,/?]ot_mĀ6_)&`O5M,+["ck-.~F+75㎬)0jbN55Fk}m^4_54=$ب?:0;֢=>fe%ge:-× uxRb ,&YU"u25{S#_6&A9C/t3[O:6*cw 4>/0 ؆J>V^˱+ΜkD\%QDŇ(q./{i𥯪XH@UbM@,@!;ٟ/%ox7҆凿Y[) _ͥWf^A} ,n 6$$+v %HR: %\Ϳ`$ga,Y(:ȿǖOlUo-Y㹂 FTGPL.x5tԘAo7V-~$fF d^o@g `2xpM`o(ܚ._ *R{$(9`)dꀐM_!gAVr2{qa HoQfα>`0@%C͇ulGS\ΰ &<~y"jQkuXԓVWY5nIRprjO_Os 'XpP]yǑ; Զx}^ \~~ēCto 3 07'ÅJ˚0N+? ^_S|lɔŁM`}9ד]K[$Ptga\#Z 39,X,Zh`,u8tQѹ~ ks'/FqgUED!KzcIQHG [3wXb ;E:61wENЁjŗPQ 3˳٬d&CAtR&pT_swuk()xzYGkr΄ZeIBq~3GzB&=^.Q*YyiI]0d)_CȜ"‡h3#r?͈C,$#3Ikxfo4 .O7'Gh+JFIc+*P9<#:qupdf2}H r!k=8B~_|)lyv0EC "tOӻxi`L ]|73,oGlE1⏂m"_;^?4ʔT!ב`7|(`7 wJw`֯V;SvwBE3iGFL&10C?@q,Rry:cM}%U,s5bœ ^y~&p>kPK 9F:ip' 31R)&-x7\*B6}2*Ceվ?Lx ?ϯțD~1n-ܺ5Yh7`t]T/ %󘏃GeF(}`-.8=FycU-!.aےj]570T!6Eʬ?q)tXq?j;򅯐ZR񾷒3Ymi 0[|i^| 0bY? (ǿz5C*}xv%qS$[2#I/LpD+FD;-}$%J34x]ڝF!#]r?2-f/ wba]Txlf/MgK9@0BGp"YBT0=S:?(+Wr;TAj@k>̤+M.WJ@{=7~2<2=sX/zjc̕ݚ0 il:bmϴj!=_ ,+GxoeGPl!񹤹ȣ q++,y}FFC >kXM]-R.{AUr;q)7ɣnϬy:˞P 9#!v75( -9;o嘺0婰RQˮs;8w^UwYjdʄ)~mU!]P 2wC1~Fx[_:d̄X~|}ﺻ<^޸_sW З# "7XZPYftIT=‡ق _Jdh{%JpmwƼ ,6́i!hnү=8^Yo8Z~7;d'7nuqt_ ^eկΜt)7\0U8>+1ݩINCc[ҥ]uv'1B @y89HYFeԋ'apͱEbT(OW?A atIϜՊ,tK$5OȝIyYV }h(c>]'WUI-,~j7 TAP 0J#kfmwɔ a]Qso%.N'f}&eQx{``mU룰+V^ӛ=Z?kĥW6-:+,(K_$-oz!SLxwԯ'Z6ꠉiݑZ.朙P56{?Aa+mLATvSf}aPNe(O8$PN7ʊ9 >t~ M}p Y)+3&LC~ܴ'Ognd( @J3OHz&ccy:O U(sY.P)63?/.2~./Z4fq߮Г2jL}YIgQha6"cn^1sٸev59M(UL% ?}S}*dYkN֩b}h m>os9n<ϳl`7U/6 'gDY$RwS=S]s{oc:c^UpDړY6D%<@d=vjƻtkyGzBoܿ'9)u?/9ӔMk'sE2nKN =;3(:&4UHg6ˆFs,HsE?p;k`Tz6k-.I _jB.Pk0@CR?s|ÿ́RbKEserzA1%qGYUۓ0l韼9/:$O4[;6_pNSӚ=n7 vP g;%5_|+$,ӻԞ'ɂn0.pٵ'Ie" |iH%A j^N' bN0@ccL_Qt 1 k -H2T3'< Jf-S!WTV5M8Q֙ȲHfi_1 e$uP"Ah aQ6>? _IX *OhEԂ>~'3/[=NS8D52`ȣD,'],zkaM{>K1' {^CɉjC_ԻB1YV#L,K9Eoc7%b(yт݋5JJbgRZ >jUWv{cnoWrh^| HnKcpnHYd;DC6 %RߧC $m ƺ*m hRRjE`mnЉ#~cwmi^WX蠨2VmGEoN Nҵ8'ue*-,-Ӳu'.(k#ݕ d5Ԧ6KڑDQޓ giuvfj `{%D.VZl2VtW0JE)-H!&HL_vytk)G}fks`'Y; \hZx\S| ~{OP,2a~OzXC9y}4'Y.8FhE?Y4.({A:?ag)Qy♮6X1==SXIٮ@R:{CA4 8%Ztey/q,m&x˔.G6I+ƍl"P%Ǐ`BRTH!jЦjG"xp aq-5| ;~=琉0Cy5hh=nۏߚM\ odsv r, @f;O$5m?޵Kw!R޼n[οob8f^ݱmMO8X>E4#*\DGfJQ+S't8lx7꾙EpbŁ}p2:Af@kc q(˘a@pа3Ѐ;hd,U2yZ_ }d>h!P@h4@!õ78I?KAiOyDz C{yGr6-׭5_!woi^y<y|;_v2;\Þr|cw7_41-ןj~oЊN}Od&d.Ln'r)*OA[oMT S@2@c-!F,'T`Ok4~B[vdvh:j΢o~1O.80ydxealF"xpʰ!Ժx~;5d$X0c;-ѿ40+ōD`1%Wv^s_<5DgNwdGkI>gާ]7^OJWMKeD|8`TDJc9,O8Ahҧ9Իs$Zu9;FJu_RX(( k2m(D)1@Q W@Ȁvw'lՁr5 .  s1i-ѣނźhҼ-HHroD֝TCYg3R3;ۼ5=A4(AVͱ2^9W?v)FZD\]Ҳ=n/9K3.x0 F߀!h)jIUOH2f{rcZfqZ8ɲ9PHb!B!2/W8Bg[6vNE9/$NM[kwCNJO:XF(M3uiJ,-ͣO)`6h ʼnX5kH¬qh+~t5*YTz8`aاwU}+,5D& yfA$e Ϯ=[?Zs2J2}miztlQ|,.KgnN 5?{?}X~w3 b|䝌_J]NaFG.;g7h`SG^ Xpp"@[E@CK&N Ls̛Ks"r p̭@6$2 JdT}lxHm% 悒Nn4t3LoHW/EK/x}puZꓴTaN>T|[k7 /횡ζZ>4fjn{K`:EwkkQ!v˯r1seW3M@k_jѤR}y|A=N!tRQ8Lk::ҽ& #}]d^LI%/C`E5)7 %gc Z~Xw$(Z@)wI% Dg4Oҧ?MsSyQ2@K2}?K|M4j0. :W4]wK?F5)\OF?'h_B{ך_}Z`6*~B@/bwȏ 9^犞AOɚ +;}"RX4}gP,h&%#Gq|S# ](D쒀Bbú0Z] Xn ͤϋ$@:z :iIU])<<0/mS4aŴTMo/ѫ7obf \c4NJ3|>)|"q-$gX_ E'H165aZ#+8)O V ' SM0A ԈN;8A;"ª@#BuaKz'ɩz( T!fF]MQ1D%tqK$eyx4$Uiq:؝PbV.ƜEh&3$Ě\QThKA6ڠQ3 Ќ|@1ppwX$hvyVn{z[1ILp]钡֖䉗T @kMi*G8?Iz j".3/D%0kE]? X`"K% I9(G׍֦o&IHj6Q6  ] lf?znuC\$& Ux%uIխ+Y/a,&Qu0Cɞ`(a:F0I=?KyPYN OLlR'#ʕl]cf@w3 nzQ ΓXvDx1hh#F~kP2JMYp 6ja@.l=,8}JmkJ`@e*Q7g]x=~'=g^zRi/&%@Kp[L%:#;J+HV-0Ua`"\m2vBZP@p-F6. ;*o;aL˘*$Lf-:C5<$q ״r >qu.k[60.hؔxz6N;Yv!4h+ uU8S3. lsR=~D*[,IrQTp8Rk5}b.'u[ꔴx/2vlts"\ncƶ?_a_ODȳ:pc=\ ,fmyA,o7u: :xqҫpSp@,{u;B|?Eé7d+-0q}>ڞWkH+f")afN@ eOEBpuguvw8&q{n8_tLמ!,3Van:~|;R/W9gu2YPN'£Apkݢm@BI;XD#:""Z&z{)t`"%u#e10$ [L{Šb=VE } ^)cֵ5h]#,쾒g.!h"U]"TmU̹n@0ܼ?H6IGP8qf.829B+GDa8q}UfʟBT<,y0U0$,mACG7ZlR2l | (ꎮ1 I O.CTA! ?0F_cG@c<_1w R4zkJY1 ف.YÝ}SDzj$2N( qDHIL=NnKhF] KC6LmfI VBR V<u0<9"nXR\YlZ1 r_"-+JbN&^-nl8 x,籽Fcef1ՀnGҟ8+OСCD8"Ez׻ooy>2. Ga:=INѬڢIS6" P OyE),6LܡdSi1KARj)iO%`_" )*DrT7oJR,C4 @W%|eTMoX7hSWPw + HQeS(43q^S6uAG5n9x241ͥ'9 ޕ  Tc\Wvٷo=%*#z'g?O!J3%}g+p?J LvGHF@ծ^VQm:V oG^u}YެF|vƙhb$s_]*tuO+.`"29 OEm# 8UPJ=f ԏh IA :DҲB8QR,Cę@/nNں$~_Zq ?ǿGR<mҐtc(h'荜H2\tLc@|WIW,Gqu62z"w%b(]a͙8`$HܥJD.o}}mPs ϓJtԴD1^pSbO Qơ"#w jVK6*+$:g<`mAiJ'i Y) "OpdcC~Xd q 6/i=$?`$KF{j}aP=PMqI!cXUI*IZ cA%>F!*$"%wW擰\슠E؊1*+"x6{Y@,G7Ss'J PݻV,'_hoS2=څc Fd?n/}?@1 18@GeTdb  FAY1FqbACoy;HRV{˛nm>_y5wO7_'wѳ&1Gs5-&PXu X=Oh^4VBd< O- % eŎ}iK%̣uzZ,GيI@| X4k=!-Ɲw5(+ɽ!oh 7tMDΈeYHwpcۿѧ&wv~&crFcxcD?~RvbBSt9ôP6Ljsk;̠Y~@7;xlųB!ogoj 463Dtv0d@n~5ϒ-u{RZW^ks=Kq&@#u=GK裏Z B7Me% 0OnKO.hJ"' g'.fi ylJR-3*6#Q_VT{((dFe+ݶSDQ55(U:kwELM@&ӪKJ+"6~{>qwE_lJ,Rfq{iވ_4 "ed%ǀ7{ p</a'ORf{? (1L%& Y,ArˎiV=<)hEնVȓxrrd],Zsϕܸ)mcn)~&'Ky/] '@{H̀￿ٱcGs뭷8mf 2Neh^>)/4Gƅ-e1^`XЏE,-TbC6݋IW_?i嚘A~D 㑄kY)^ɣ?pu?;(S4m{鱏`Z( 'B& T?ߏ~[ڜwyYk2 լ- JR8@ B[cO (3L$m3 VXꚷϏ=. 3$y,y9WG[^=$$䀛 xE{ino~x |N.Pϐ>Ie Ly!C2 m&OUYE۪MCL:Gyܱ˱F-99W9VO?|[韝VgErQ^ylqbpNj`?{vd +R6=JEoU:csf>,o&e16ZĕԅrR*U,5d̼9 ,ZS!Pg3k<(Kڐr3Wke7d0E%4^;I>zXK o/_4x(/,Y(y|ae--@K˧Z%UlrXjr 0WbJϠt{~-ag%g5\egOXlfX,ܳgkV^xas%uX+pYg?! Np 4K0 |aF2 &M#a\t3@n]+]$SɑM)Szlv}uX LY,1Ks1AZ0C8 8ĉ{QNJ2;%kԩ"t:hj*"C)!ոW RS<*e HF7:&䊪A(A唎NZB]4zl!ӟ%XoPwsn{,1'NQ4؀{yA`r!*DR~mLV?w=i#kflkcpL =.e-j_#Gz VNI?r& (q"ґ A3i&IR콴æ(>qUTLV[׬T@2̂҃ҥv`Ym= YzwW_}VW14 'QqНRRΧy(z7B-BO2jm݇*G@ɵgtmkKTMH SC5 7RcvqW9?h V.8<3@Z_QGBPtB M"gc%c^$0\c"_5 DKrq3c9rKv Zhӏ65+3' d#DH-e]R!93iLodLyY{YejfY[xl_A,İT>+V!{@`&),kyzDjd؃cT$NSI]P_KKXy%@}fB/S3Y}ݩn`dޅ8e=;҇ONч&DZ҂݂w{ @JeÇcsH;8?oc`^]p@k6kܢڋՏ*wXƹ9GkpYh B3r220C~zkv8!ƚ``goHQL}}LTjz0B53WQr|12yN)\>`L07Sɬr`׎q^W=Wf7t2R +h} xO?$jf׿k1*Cٔ?1gUܜɜ Y7,|m:[}JN24b0 `EYE_GLt}=Gu+@+Z*[g˘+xC8[Mt\ntkNc4 q3*IyUEfʇߚ1p.f[5d3f7~ &ӵ=>{3(A!Anf^ۿk.,~?bxJ @fTLN% * kѢ=rsBϳTůe,uOV Ɓyv@-y kzz`S{{ދ25:񴅬-(ȘFw[I-T6T~'voy ΅<"mKPUT:{a]%VԱ=rF1Va=4bUSЂ&*V`MXY[ 7 l]}<XO5IYhHU*zz`2Xל)EuE2gsRsZ^c?/>bz`RZrT0f Z±6hY)\J,q|߾}__>?oT̏ s/dzt 7'KZ-ǠpȺؤZ1\1Cr 4F) ڧ7 ?W a`l?b]DkCԼ>hʨhid/ȋqo͘#&8MaBYYBҪa=x|x_j[Ϝ~Iu@Τ`O* /ǴZ$|ĐyM?o~L.L#qXo͖f4+6KyQ]/܋ 1@9Y`Ց0w)8d# '?]:[6`c &28D)F'W9^F˅1i[mAt.mR0/?č1^ʘmBN?~^92[WFƋߊ̋`'Ov򥚁qT m)Gsԧ'hW0+`o,dQ!ht-$d :LFGGןH3t@wv/3oў%/KVfyXѧab"L҃{gZ1}8x顸Г-,.Xh04> 7L;0Cn]p uwVkͶ#.k8BC;ǀ_A0+_#,//wc;~,ԧ_X`},N-8g,ce91?g#[WBvʔ2Ylw-Sӟ;~ނAO}&q;ܸbA?FuLt\}*{!n- |7z@߭ =j&55A@ 8Wh 5,xĩ&vR2T_**Jf9+Yez"dZz6t}HYifPݍ9ѾxE*cտYVLX1,gpkkg޺̀~<<ǝ.ڿ^VΕmW|C/!ՙroَdJQk1r*u?S]%vSwPSSB#mZ4Н'Mp,Kԧyjyl'A_|QmqX!qv|;io“OfoA, $[Āyh<F<%I܂&_ \i ܏|#oٶmy $!K < D cu_x<Ʈ @KHE-_^ w } 5ޚ|ID+M9G@T77JgϞǿM_CP~6kjtY3H ,8u'91Μ/ ΃ 8h P[p۷sύpvу&>kpSIA#jԇZhM|8 }=vЇ!!gq(yl&PUM0OAOX+G.H6`4CמrJ5nn|[k@{=mHt?]I+yh?]sm]y%z}C7D:D%C>1&}}^ڞzZՏMXw c} !!0|->؃nXwfTEa&Z34"*$PBwx =쭻uvf! {$!w$sjR=P=P=P=P=Y?V @#c *6kk 03`)L{L@ %H}ܭc& { _O@Hzs$< m+yRwrVISL?K4@aH7[d@w.0,ڳ6چ(t I-3{ x @`fr3W)pvt{a ,Y$۷3`Tzf $ bߥ!HZ(erS, wxc T‹R~t{ $wVܹ>.?%PAs~ 䠖R cw#ܭ wV`gXVS,Ѱ'{3>Uz+~x$03D`V GY'\K@@@ظƞ,w޼&`(Y]kdʺ$0+OYi~ 0 `?7㯸ꁩkKIbh =y@i$Gb{,k@pF {YV(Y@Rq?f ֽ,-R\2ݏ :kX5 c(e_IKO`X.G$ 9{|<( JuZDK!Y@2Y?9u` A$7teto^A[ X*k2Kg c!s2^`I9A SE~CkȜve!%G"04?G" 3 5) 'R=P=P=:̱:<~$ +[V>`u3y?;V"6L LXmY[xwxDp>"Ed<.=\]+ Ez@@@@@-?Rm@z `FO:֫fwnhwNo _&YV=P=P=P=/{~zuoC)UeUTTTTl&LGzzzzz5(tT%L f:/ @*Y@@@@f2uv~zIENDB`gqrx-sdr-2.2.0.74.d97bd7/icons/scope.svg000066400000000000000000001264761226036373100175020ustar00rootroot00000000000000 -20 dB gqrx-sdr-2.2.0.74.d97bd7/icons/settings.svg000066400000000000000000000650041226036373100202160ustar00rootroot00000000000000 image/svg+xml System Preferences Andreas Nilsson category system preferences settings control center Jakub Steiner Ulisse Perusin image/svg+xml Preferences Andreas Nilsson Lapo Calamandrei, Ulisse Perusin, Jakub Steiner category system preferences settings control center gqrx-sdr-2.2.0.74.d97bd7/icons/signal.svg000066400000000000000000000414471226036373100176400ustar00rootroot00000000000000 image/svg+xml System Monitor 2005-10-10 Andreas Nilsson system monitor performance Jakub Steiner gqrx-sdr-2.2.0.74.d97bd7/icons/tangeo-network-idle.svg000066400000000000000000002131651226036373100222400ustar00rootroot00000000000000 image/svg+xml Computer 2005-03-08 Jakub Steiner workstation computer node client http://jimmac.musichall.cz/ gqrx-sdr-2.2.0.74.d97bd7/icons/terminal.svg000066400000000000000000000434151226036373100201730ustar00rootroot00000000000000 image/svg+xml Terminal 2005-10-15 Andreas Nilsson terminal emulator term command line Jakub Steiner gqrx-sdr-2.2.0.74.d97bd7/interfaces/000077500000000000000000000000001226036373100166405ustar00rootroot00000000000000gqrx-sdr-2.2.0.74.d97bd7/interfaces/udp_sink_f.cpp000066400000000000000000000045271226036373100214750ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2013 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include "udp_sink_f.h" /* * Create a new instance of gr::fcd::source_c_impl and return an * upcasted boost shared_ptr. This is effectively the public * constructor. */ udp_sink_f_sptr make_udp_sink_f() { return gnuradio::get_initial_sptr(new udp_sink_f()); } static const int MIN_IN = 1; /*!< Mininum number of input streams. */ static const int MAX_IN = 1; /*!< Maximum number of input streams. */ static const int MIN_OUT = 0; /*!< Minimum number of output streams. */ static const int MAX_OUT = 0; /*!< Maximum number of output streams. */ udp_sink_f::udp_sink_f() : gr::hier_block2("udp_sink_f", gr::io_signature::make(MIN_IN, MAX_IN, sizeof(float)), gr::io_signature::make(MIN_OUT, MAX_OUT, sizeof(float))) { d_f2s = gr::blocks::float_to_short::make(1, 32767); d_sink = gr::blocks::udp_sink::make(sizeof(short), "localhost", 7355); d_sink->disconnect(); connect(self(), 0, d_f2s, 0); connect(d_f2s, 0, d_sink, 0); } udp_sink_f::~udp_sink_f() { } /*! \brief Start streaming through the UDP sink * \param host The hostname or IP address of the client. * \param port The port used for the UDP stream */ void udp_sink_f::start_streaming(const std::string host, int port) { d_sink->connect(host, port); } void udp_sink_f::stop_streaming(void) { d_sink->disconnect(); } gqrx-sdr-2.2.0.74.d97bd7/interfaces/udp_sink_f.h000066400000000000000000000027651226036373100211440ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2013 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef UDP_SINK_F_H #define UDP_SINK_F_H #include #include #include class udp_sink_f; typedef boost::shared_ptr udp_sink_f_sptr; udp_sink_f_sptr make_udp_sink_f(void); class udp_sink_f : public gr::hier_block2 { public: udp_sink_f(void); ~udp_sink_f(); void start_streaming(const std::string host, int port); void stop_streaming(void); private: gr::blocks::udp_sink::sptr d_sink; /*!< The gnuradio UDP sink. */ gr::blocks::float_to_short::sptr d_f2s; /*!< Converts float to short. */ }; #endif // UDP_SINK_F_H gqrx-sdr-2.2.0.74.d97bd7/news.txt000066400000000000000000000103041226036373100162300ustar00rootroot00000000000000 2.3.0 TBD... NEW: Support for setting analog bandwidth of receivers that support it. NEW: Support for setting gain stages individually. NEW: Remember visibility status of main toolbar. NEW: Restore the geometry of the main window between sessions. NEW: Restore the state and placement of the dock windows. NEW: Nuand BladeRF support. NEW: RF Space SDR-IQ, SDR-IP and Netsdr support. NEW: Peak detection on the FFT plot. NEW: Max hold on the FFT plot. NEW: Audio streaming over UDP socket. NEW: Set and get frequency over TCP socket. NEW: Set squelch level from current signal/noise level. FIXED: Incorrect handling of decimal values in LNB LO frequency. FIXED: Correctly apply initial LNB LO frequency. FIXED: Audio output device selection on Mac OS X. FIXED: Properly store settings when using Save As function. FIXED: Crash when recording audio whith no rec directory set. FIXED: Only allow audio playback while DSP is running. IMPROVED: Gqrx can fit on small screens (900x600 pixels). IMPROVED: Better color gradient for the waterfall. IMPROVED: FFT presentation at high rates and high sizes. 2.2.0 Released August 8, 2013 NEW: Official Ubuntu PPA and Mac OS X app bundle. NEW: Allow using audio output devices other than the default. NEW: Automatic I/Q phase and gain correction (requires gr-iqbal). NEW: Qt 5 support. NEW: GNU Radio 3.7 support. NEW: HackRF Jawbreaker support. NEW: Funcube Dongle Pro+ support. NEW: Option to select antenna connector on e.g. USRP devices. NEW: Configuration option to choose location of WAV recordings. NEW: Uses gr-audio as default on all platform (pulseaudio still possible). NEW: Command line option to remove configuration file. NEW: Command line option to launch I/O device editor before application. FIXED: Frequency control widget with recent Qt was broken. FIXED: Bug that caused audio FFT data to be leaked into the I/Q FFT. FIXED: Audio recording & playback. FIXED: Proper handling of frequencies above 2147483647 Hz. IMPROVED: Y-axis panning (click-and-drag). IMPROVED: Rescale waterfall when resizing the main window. IMPROVED: gr-audio support on Linux. IMPROVED: Audio gain setting no longer has impact on WAV recording. 2.1.251 Released on March 1, 2013 FIXED: Incorrect device selection in config dialog. 2.1.246 Released on February 29, 2013 NEW: I/Q swapping option. NEW: FFT video filter with non-linear gain (still WIP). NEW: Save and restore FFT settings. NEW: Save and restore receiver options (offset, mode and squelch level). NEW: AM DC removal using IIR filter. FIXED: Removed graphics redraw delay when changing frequency or demodulator. FIXED: Respect RF frequency range of input device. FIXED: Added workaround for pixel error introduced in Qt 4.8. FIXED: Crash when FFT size is smaller than plot width. FIXED: Show correct initial filter preset in filter selector. FIXED: Workaround for 'jerky streaming' when DSP is started using rtlsdr. FIXED: Freeze when channel filter becomes too narrow in AM and FM modes. FIXED: Crash when changing sample rate of the current input device. CHANGED: Show device ID (e.g. rtl=0) in title bar instead of description. IMPROVED: AGC settings moved to dialog window. IMPROVED: Increase LO resolution to 1 Hz. IMPROVED: GUI layout fixes for Mac OS X. IMPROVED: Shortcuts for resetting and re-centering FFT plot after zooming. IMPROVED: Crash recovery in case of bad config file. IMPROVED: Support 50 fps FFT rate and allow disabling FFT. IMPROVED: I/Q DC canceling. 2.1.148 Released on December 9, 2012 NEW: Support for USRP and RTL2832U (rtlsdr) devices. NEW: Pan & zoom on the pandapter axes. NEW: Allow changing FFT size. FIXED: Mac OS X compatibility. IMPROVED: Various improvements to the user iterface. 2.0.0 Released on July 7, 2012 First stable release of C++ version with Funcube Dongle support. 1.x Initial experiments using Python and gr-qtgui. gqrx-sdr-2.2.0.74.d97bd7/portaudio/000077500000000000000000000000001226036373100165235ustar00rootroot00000000000000gqrx-sdr-2.2.0.74.d97bd7/portaudio/device_list.cpp000066400000000000000000000061331226036373100215240ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2013 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include #include #include "device_list.h" portaudio_device::portaudio_device(unsigned int idx, string name, string desc) : d_index(idx), d_name(name), d_description(desc) { } portaudio_device::~portaudio_device() { } portaudio_device_list::portaudio_device_list() { populate_device_list(); } portaudio_device_list::~portaudio_device_list() { d_sources.clear(); d_sinks.clear(); } /** \brief Populate portaudioaudio device list. * * This method iterates through all the input and output decives * and stores them for later retrieval using getInputDevices() and * getOutputDevices(). */ int portaudio_device_list::populate_device_list() { int i, num_dev; const PaDeviceInfo *dev_info; PaError err; Pa_Initialize(); std::cout << Pa_GetVersionText() << " (version " << Pa_GetVersion() << ")" << std::endl; num_dev = Pa_GetDeviceCount(); if (num_dev < 0) { std::cerr << "ERROR: Pa_GetDeviceCount returned " << num_dev << std::endl; err = num_dev; goto error; } std::cout << "Number of audio devices: " << num_dev << std::endl; for (i = 0; i < num_dev; i++) { dev_info = Pa_GetDeviceInfo(i); std::cout << " " << i << ": " << dev_info->name << " I:" << dev_info->maxInputChannels << " O:" << dev_info->maxOutputChannels << std::endl; if (dev_info->maxInputChannels > 0) { add_source(i, dev_info->name, dev_info->name); } if (dev_info->maxOutputChannels > 0) { add_sink(i, dev_info->name, dev_info->name); } } Pa_Terminate(); return 0; error: Pa_Terminate(); std::cerr << "An error occured while using the portaudio stream" << std::endl; std::cerr << "Error number: " << err << std::endl; std::cerr << "Error message: " << Pa_GetErrorText(err) << std::endl; return err; } void portaudio_device_list::add_sink(unsigned int idx, string name, string desc) { d_sinks.push_back(portaudio_device(idx, name, desc)); } void portaudio_device_list::add_source(unsigned int idx, string name, string desc) { d_sources.push_back(portaudio_device(idx, name, desc)); } gqrx-sdr-2.2.0.74.d97bd7/portaudio/device_list.h000066400000000000000000000051111226036373100211640ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2013 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef PORTAUDIO_DEVICE_LIST_H #define PORTAUDIO_DEVICE_LIST_H #include #include #include using namespace std; /*! \brief Simple class to represent portaudio devices. * * This class represents a portaudio device. The device can be either source * or sink, this class does not differentiate between them. */ class portaudio_device { public: portaudio_device(unsigned int idx=0, string name="", string desc=""); ~portaudio_device(); void set_index(unsigned int idx) { d_index = idx; } void set_name(string name) { d_name = name; } void set_description(string desc) { d_description = desc; } unsigned int get_index() { return d_index; } string get_name() { return d_name; } string get_description() { return d_description; } private: unsigned int d_index; /*! The index of the audio device (unique for each source/sink). */ string d_name; /*! The name of the audio device. Used when creating souces/sinks. */ string d_description; /*! The description of the audio device. */ }; /*! \brief Class for storing and retrieving a list of portaudio sinks and sources. */ class portaudio_device_list { public: portaudio_device_list(); ~portaudio_device_list(); vector get_input_devices() { return d_sources; } vector get_output_devices() {return d_sinks; } private: vector d_sources; /*! List of pulseaudio sources. */ vector d_sinks; /*! List of pulseaudio sinks. */ int populate_device_list(); void add_sink(unsigned int idx, string name, string desc); void add_source(unsigned int idx, string name, string desc); }; #endif // PORTAUDIO_DEVICE_LIST_H gqrx-sdr-2.2.0.74.d97bd7/pulseaudio/000077500000000000000000000000001226036373100166675ustar00rootroot00000000000000gqrx-sdr-2.2.0.74.d97bd7/pulseaudio/pa_device_list.cc000066400000000000000000000176461226036373100221660ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include "pa_device_list.h" pa_device::pa_device(unsigned int idx, string name, string desc) : d_index(idx), d_name(name), d_description(desc) { } pa_device::~pa_device() { } pa_device_list::pa_device_list() { populate_device_list(); } pa_device_list::~pa_device_list() { d_sources.clear(); d_sinks.clear(); } /** \brief Populate pulseaudio device list. * * This method iterates through all the input and output decives * and stores them for later retrieval using getInputDevices() and * getOutputDevices(). */ int pa_device_list::populate_device_list() { pa_mainloop *pa_ml; pa_mainloop_api *pa_mlapi; pa_operation *pa_op; pa_context *pa_ctx; // State variables to keep track of our requests int state = 0; int pa_ready = 0; // Create a mainloop API and connection to the default server pa_ml = pa_mainloop_new(); pa_mlapi = pa_mainloop_get_api(pa_ml); pa_ctx = pa_context_new(pa_mlapi, "test"); // Connect to the pulseaudio server pa_context_connect(pa_ctx, NULL, PA_CONTEXT_NOFLAGS , NULL); // This function defines a callback so the server will tell us it's state. // Our callback will wait for the state to be ready. The callback will // modify the variable to 1 so we know when we have a connection and it's // ready. // If there's an error, the callback will set pa_ready to 2 pa_context_set_state_callback(pa_ctx, pa_state_cb, &pa_ready); // Now we'll enter into an infinite loop until we get the data we receive // or if there's an error for (;;) { // We can't do anything until PA is ready, so just iterate the mainloop // and continue if (pa_ready == 0) { pa_mainloop_iterate(pa_ml, 1, NULL); continue; } // We couldn't get a connection to the server, so exit out if (pa_ready == 2) { pa_context_disconnect(pa_ctx); pa_context_unref(pa_ctx); pa_mainloop_free(pa_ml); return -1; } // At this point, we're connected to the server and ready to make // requests switch (state) { // State 0: we haven't done anything yet case 0: // This sends an operation to the server. pa_sinklist_info is // our callback function and a pointer to our devicelist will // be passed to the callback The operation ID is stored in the // pa_op variable pa_op = pa_context_get_sink_info_list(pa_ctx, pa_sinklist_cb, this); // Update state for next iteration through the loop state++; break; case 1: // Now we wait for our operation to complete. When it's // complete our pa_output_devicelist is filled out, and we move // along to the next state if (pa_operation_get_state(pa_op) == PA_OPERATION_DONE) { pa_operation_unref(pa_op); // Now we perform another operation to get the source // (input device) list just like before. This time we pass // a pointer to our input structure pa_op = pa_context_get_source_info_list(pa_ctx, pa_sourcelist_cb, this); // Update the state so we know what to do next state++; } break; case 2: if (pa_operation_get_state(pa_op) == PA_OPERATION_DONE) { // Now we're done, clean up and disconnect and return pa_operation_unref(pa_op); pa_context_disconnect(pa_ctx); pa_context_unref(pa_ctx); pa_mainloop_free(pa_ml); return 0; } break; default: // We should never see this state //fprintf(stderr, "in state %d\n", state); return -1; } // Iterate the main loop and go again. The second argument is whether // or not the iteration should block until something is ready to be // done. Set it to zero for non-blocking. pa_mainloop_iterate(pa_ml, 1, NULL); } } /*! \brief Pulseaudio state change callback. * * This callback gets called when our context changes state. We really only * care about when it's ready or if it has failed. */ void pa_device_list::pa_state_cb(pa_context *c, void *userdata) { pa_context_state_t state; int *pa_ready = (int *)userdata; state = pa_context_get_state(c); switch (state) { // There are just here for reference case PA_CONTEXT_UNCONNECTED: case PA_CONTEXT_CONNECTING: case PA_CONTEXT_AUTHORIZING: case PA_CONTEXT_SETTING_NAME: default: break; case PA_CONTEXT_FAILED: case PA_CONTEXT_TERMINATED: *pa_ready = 2; break; case PA_CONTEXT_READY: *pa_ready = 1; break; } } /*! \brief Callback function to populate audio sink list * \param ctx The pulseaudio context. * \param info The sink info. * \param eol If greateer than 0 we have reached the end of list. * \param userdata Pointer to "this" PaDeviceList. * * pa_mainloop will call this function when it's ready to tell us about a sink. * It is necessary to have this method declared as static in order to use it as * a C-callback function. For the same reason, a pointer to "this" is passed in * the usedata parameter, otherwise we wouldn't be able to access non-static members. */ void pa_device_list::pa_sinklist_cb(pa_context *ctx, const pa_sink_info *info, int eol, void *userdata) { (void) ctx; pa_device_list *pdl = reinterpret_cast(userdata); // exit if we have reached the end of the list if (eol > 0) { return; } pdl->d_sinks.push_back(pa_device(info->index, info->name, info->description)); } /*! \brief Callback function to populate audio source list * \param ctx The pulseaudio context. * \param info The source info. * \param eol If greateer than 0 we have reached the end of list. * \param userdata User data (currently NULL). * * pa_mainloop will call this function when it's ready to tell us about a source. * It is necessary to have this method declared as static in order to use it as * a C-callback function. For the same reason, a pointer to "this" is passed in * the usedata parameter, otherwise we wouldn't be able to access non-static members. */ void pa_device_list::pa_sourcelist_cb(pa_context *ctx, const pa_source_info *info, int eol, void *userdata) { pa_device_list *pdl = reinterpret_cast(userdata); (void) ctx; // exit if we have reached the end of the list if (eol > 0) { return; } pdl->d_sources.push_back(pa_device(info->index, info->name, info->description)); } gqrx-sdr-2.2.0.74.d97bd7/pulseaudio/pa_device_list.h000066400000000000000000000052331226036373100220150ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef PA_DEVICE_LIST_H #define PA_DEVICE_LIST_H #include #include #include using namespace std; /*! \brief Simple class to represent pulseaudio devices. * * This class represents a pulseaudio device. The device can be either source * or sink, this class does not differentiate between them. */ class pa_device { public: pa_device(unsigned int idx=0, string name="", string desc=""); ~pa_device(); void set_index(unsigned int idx) { d_index = idx; } void set_name(string name) { d_name = name; } void set_description(string desc) { d_description = desc; } unsigned int get_index() { return d_index; } string get_name() { return d_name; } string get_description() { return d_description; } private: unsigned int d_index; /*! The index of the audio device (unique for each source/sink). */ string d_name; /*! The name of the audio device. Used when creating souces/sinks. */ string d_description; /*! The description of the audio device. */ }; /*! \brief Class for storing and retrieving a list of pulseaudio sinks and sources. */ class pa_device_list { public: pa_device_list(); ~pa_device_list(); vector get_input_devices() { return d_sources; } vector get_output_devices() {return d_sinks; } private: vector d_sources; /*! List of pulseaudio sources. */ vector d_sinks; /*! List of pulseaudio sinks. */ int populate_device_list(); // pulseaudio callbacks static void pa_state_cb(pa_context *c, void *userdata); static void pa_sinklist_cb(pa_context *ctx, const pa_sink_info *info, int eol, void *userdata); static void pa_sourcelist_cb(pa_context *ctx, const pa_source_info *info, int eol, void *userdata); }; #endif // PA_DEVICE_LIST_H gqrx-sdr-2.2.0.74.d97bd7/pulseaudio/pa_sink.cc000066400000000000000000000127651226036373100206350ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2012 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include "pa_sink.h" #include #include #include //#include #include #include #include /*! \brief Create a new pulseaudio sink object. * \param device_name The name of the audio device, or NULL for default. * \param audio_rate The sample rate of the audio stream. * \param app_name Application name. * \param stream_name The audio stream name. * * This is effectively the public constructor for pa_sink. */ pa_sink_sptr make_pa_sink(const string device_name, int audio_rate, const string app_name, const string stream_name) { return gnuradio::get_initial_sptr(new pa_sink(device_name, audio_rate, app_name, stream_name)); } pa_sink::pa_sink(const string device_name, int audio_rate, const string app_name, const string stream_name) : gr::sync_block ("pa_sink", gr::io_signature::make (1, 2, sizeof(float)), gr::io_signature::make (0, 0, 0)), d_stream_name(stream_name), d_app_name(app_name), d_auto_flush(300) { int error; /* The sample type to use */ d_ss.format = PA_SAMPLE_FLOAT32LE; d_ss.rate = audio_rate; d_ss.channels = 2; /* Buffer attributes tuned for low latency, see Documentation/Developer/Clients/LactencyControl */ size_t latency = pa_usec_to_bytes(10000, &d_ss); d_attr.maxlength = d_attr.minreq = d_attr.prebuf = (uint32_t)-1; d_attr.fragsize = latency; d_attr.tlength = latency; d_pasink = pa_simple_new(NULL, d_app_name.c_str(), PA_STREAM_PLAYBACK, device_name.empty() ? NULL : device_name.c_str(), d_stream_name.c_str(), &d_ss, NULL, &d_attr, &error); if (!d_pasink) { /** FIXME: Throw an exception **/ fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error)); } } pa_sink::~pa_sink() { if (d_pasink) { pa_simple_free(d_pasink); } } bool pa_sink::start() { d_last_flush = gr::high_res_timer_now(); return true; } bool pa_sink::stop() { return true; } /*! \brief Select a new pulseaudio output device. * \param device_name The name of the new output. */ void pa_sink::select_device(string device_name) { int error; pa_simple_free(d_pasink); d_pasink = pa_simple_new(NULL, d_app_name.c_str(), PA_STREAM_PLAYBACK, device_name.empty() ? NULL : device_name.c_str(), d_stream_name.c_str(), &d_ss, NULL, NULL, &error); if (!d_pasink) { /** FIXME: Throw an exception **/ fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error)); } } #define BUFFER_SIZE 100000 int pa_sink::work (int noutput_items, gr_vector_const_void_star &input_items, gr_vector_void_star &output_items) { static float audio_buffer[BUFFER_SIZE]; float *ptr = &audio_buffer[0]; int i, error; (void) output_items; if (noutput_items > BUFFER_SIZE/2) noutput_items = BUFFER_SIZE/2; if (d_auto_flush > 0) { gr::high_res_timer_type tnow = gr::high_res_timer_now(); if ((tnow-d_last_flush)/gr::high_res_timer_tps() > d_auto_flush) { pa_simple_flush(d_pasink, 0); d_last_flush = tnow; #ifndef QT_NO_DEBUG_OUTPUT fprintf(stderr, "Flushing pa_sink\n"); #endif } } if (input_items.size() == 2) { // two channels (stereo) const float *data_l = (const float*) input_items[0]; // left channel const float *data_r = (const float*) input_items[1]; // right channel for (i = noutput_items; i > 0; i--) { *ptr++ = *data_l++; *ptr++ = *data_r++; } } else { // assume 1 channel (mono) const float *data = (const float*) input_items[0]; for (i = noutput_items; i > 0; i--) { float a = *data++; *ptr++ = a; // same data in left and right channel *ptr++ = a; } } if (pa_simple_write(d_pasink, audio_buffer, 2*noutput_items*sizeof(float), &error) < 0) { //!!! fprintf(stderr, __FILE__": pa_simple_write() failed: %s\n", pa_strerror(error)); } return noutput_items; } gqrx-sdr-2.2.0.74.d97bd7/pulseaudio/pa_sink.h000066400000000000000000000050361226036373100204700ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2012 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef PA_SINK_H #define PA_SINK_H #include #include #include #include using namespace std; class pa_sink; typedef boost::shared_ptr pa_sink_sptr; pa_sink_sptr make_pa_sink(const string device_name, int audio_rate, const string app_name="GNU Radio", const string stream_name="SDR"); /*! \brief Pulseaudio sink * \ingroup IO * * This block implements a two-channel pulseaudio sink using the Pulseaudio simple API. * */ class pa_sink : public gr::sync_block { friend pa_sink_sptr make_pa_sink(const string device_name, int audio_rate, const string app_name, const string stream_name); public: pa_sink(const string device_name, int audio_rate, const string app_name="GNU Radio", const string stream_name="SDR"); ~pa_sink(); int work (int noutput_items, gr_vector_const_void_star &input_items, gr_vector_void_star &output_items); bool start(); bool stop(); void select_device(string device_name); private: pa_simple *d_pasink; /*! The pulseaudio object. */ string d_stream_name; /*! Descriptive name of the stream. */ string d_app_name; /*! Descriptive name of the applcation. */ pa_sample_spec d_ss; /*! pulseaudio sample specification. */ pa_buffer_attr d_attr; /*! Buffer attributes. */ // FIXME // periodic flushing of audio buffer (until we have soundcard calibration) int d_auto_flush; // flush interval in seconds (negative means off) gr::high_res_timer_type d_last_flush; }; #endif /* PA_SINK_H */ gqrx-sdr-2.2.0.74.d97bd7/pulseaudio/pa_source.cc000066400000000000000000000115131226036373100211570ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2012 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include "pa_source.h" #define SAMPLES_PER_BUFFER 1024 /*! Max number of samples we read every cycle. */ /*! \brief Create a new pulseaudio source object. * \param device_name The name of the audio device, or NULL for default. * \param audio_rate The sample rate of the audio stream. * \param num_chan The number of channels (1 for mono, 2 for stereo) * \param app_name Application name. * \param stream_name The audio stream name. * * This is effectively the public constructor for pa_source. */ pa_source_sptr make_pa_source(const string device_name, int sample_rate, int num_chan, const string app_name, const string stream_name) { return gnuradio::get_initial_sptr (new pa_source (device_name, sample_rate, num_chan, app_name, stream_name)); } pa_source::pa_source (const string device_name, int sample_rate, int num_chan, const string app_name, const string stream_name) : gr::sync_block ("pa_source", gr::io_signature::make (0, 0, 0), gr::io_signature::make (0, 0, 0)), d_stream_name(stream_name), d_app_name(app_name) { int error; /** FIXME: only 2 channels supported **/ // if ((num_chan != 1) && (num_chan != 2)) { // num_chan = 2; // } num_chan = 2; set_output_signature(gr::io_signature::make (1, num_chan, sizeof(float))); /* The sample type to use */ d_ss.format = PA_SAMPLE_FLOAT32LE; d_ss.rate = sample_rate; d_ss.channels = num_chan; /* Buffer attributes. Inspired by ghpsdr2-alex/softrockio */ d_attr.maxlength = d_attr.minreq = d_attr.prebuf = (uint32_t)-1; d_attr.fragsize = SAMPLES_PER_BUFFER*2 * sizeof(float); d_attr.tlength = SAMPLES_PER_BUFFER*2 * sizeof(float); d_pasrc = pa_simple_new(NULL, d_app_name.c_str(), PA_STREAM_RECORD, device_name.empty() ? NULL : device_name.c_str(), d_stream_name.c_str(), &d_ss, NULL, &d_attr, &error); if (!d_pasrc) { /** FIXME: throw and exception */ fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error)); } } pa_source::~pa_source() { if (d_pasrc) { pa_simple_free(d_pasrc); } } /*! \brief Select a new pulseaudio input device. * \param device_name The name of the new output. */ void pa_source::select_device(string device_name) { int error; pa_simple_free(d_pasrc); d_pasrc = pa_simple_new(NULL, d_app_name.c_str(), PA_STREAM_RECORD, device_name.empty() ? NULL : device_name.c_str(), d_stream_name.c_str(), &d_ss, NULL, &d_attr, &error); if (!d_pasrc) { /** FIXME: Throw an exception **/ fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error)); } } int pa_source::work(int noutput_items, gr_vector_const_void_star &input_items, gr_vector_void_star &output_items) { (void) input_items; float audio_buffer[SAMPLES_PER_BUFFER*2]; /** FIXME: Channels **/ float *out0 = (float *) output_items[0]; float *out1 = (float *) output_items[1]; // see gr_complex_to_float int error=0; int i=0; if (pa_simple_read(d_pasrc, &audio_buffer[0], sizeof(audio_buffer), &error) < 0) { fprintf(stderr, __FILE__": pa_simple_read() failed: %s\n", pa_strerror(error)); return 0; } for (i = 0; i < SAMPLES_PER_BUFFER; i++) { out0[i] = audio_buffer[i*2]; out1[i] = audio_buffer[(i*2)+1]; } return SAMPLES_PER_BUFFER; } gqrx-sdr-2.2.0.74.d97bd7/pulseaudio/pa_source.h000066400000000000000000000045371226036373100210310ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2012 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef PA_SOURCE_H #define PA_SOURCE_H #include #include #include using namespace std; class pa_source; typedef boost::shared_ptr pa_source_sptr; pa_source_sptr make_pa_source(const string device_name, int sample_rate, int num_chan=1, const string app_name="GNU Radio", const string stream_name="SDR"); /*! \brief Pulseaudio source. * \ingroup IO * */ class pa_source : public gr::sync_block { friend pa_source_sptr make_pa_source(const string device_name, int sample_rate, int num_chan, const string app_name, const string stream_name); public: pa_source(const string device_name="", int sample_rate=96000, int num_chan=1, const string app_name="GNU Radio", const string stream_name="SDR"); ~pa_source(); int work (int noutput_items, gr_vector_const_void_star &input_items, gr_vector_void_star &output_items); void select_device(string device_name); private: pa_sample_spec d_ss; /*! Sample specification. */ pa_buffer_attr d_attr; /*! Buffer attributes. */ string d_stream_name; /*! Descriptive name of the stream. */ string d_app_name; /*! Descriptive name of the applcation. */ pa_simple *d_pasrc; /*! The pulseaudio object. */ }; #endif /* PA_SOURCE_H */ gqrx-sdr-2.2.0.74.d97bd7/qtgui/000077500000000000000000000000001226036373100156465ustar00rootroot00000000000000gqrx-sdr-2.2.0.74.d97bd7/qtgui/afsk1200win.cpp000066400000000000000000000102021226036373100203120ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include "afsk1200win.h" #include "ui_afsk1200win.h" Afsk1200Win::Afsk1200Win(QWidget *parent) : QMainWindow(parent), ui(new Ui::Afsk1200Win) { ui->setupUi(this); /* select font for text viewer */ #ifdef Q_OS_MAC ui->textView->setFont(QFont("Monaco", 12)); #else ui->textView->setFont(QFont("Monospace", 11)); #endif /* Add right-aligned info button */ QWidget *spacer = new QWidget(); spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); ui->toolBar->addWidget(spacer); ui->toolBar->addAction(ui->actionInfo); /* AFSK1200 decoder */ decoder = new CAfsk12(this); connect(decoder, SIGNAL(newMessage(QString)), ui->textView, SLOT(appendPlainText(QString))); } Afsk1200Win::~Afsk1200Win() { qDebug() << "AFSK1200 decoder destroyed."; delete decoder; delete ui; } /*! \brief Process new set of samples. */ void Afsk1200Win::process_samples(float *buffer, int length) { int overlap = 18; int i; for (i = 0; i < length; i++) { tmpbuf.append(buffer[i]); } decoder->demod(tmpbuf.data(), length); /* clear tmpbuf and store "overlap" */ tmpbuf.clear(); for (i = length-overlap; i < length; i++) { tmpbuf.append(buffer[i]); } } /*! \brief Catch window close events and emit signal so that main application can destroy us. */ void Afsk1200Win::closeEvent(QCloseEvent *ev) { Q_UNUSED(ev); emit windowClosed(); } /*! \brief User clicked on the Clear button. */ void Afsk1200Win::on_actionClear_triggered() { ui->textView->clear(); } /*! \brief User clicked on the Save button. */ void Afsk1200Win::on_actionSave_triggered() { /* empty text view has blockCount = 1 */ if (ui->textView->blockCount() < 2) { QMessageBox::warning(this, tr("Gqrx error"), tr("Nothing to save."), QMessageBox::Ok, QMessageBox::Ok); return; } QString fileName = QFileDialog::getSaveFileName(this, tr("Save File"), QDir::homePath(), tr("Text Files (*.txt)")); if (fileName.isEmpty()) { qDebug() << "Save cancelled by user"; return; } QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qDebug() << "Error creating file: " << fileName; return; } QTextStream out(&file); out << ui->textView->toPlainText(); file.close(); } /*! \brief User clicked Info button. */ void Afsk1200Win::on_actionInfo_triggered() { QMessageBox::about(this, tr("About AFSK1200 Decoder"), tr("

    Gqrx AFSK1200 Decoder %1

    " "

    The Gqrx AFSK1200 decoder taps directly into the SDR signal path " "eliminating the need to mess with virtual or real audio cables. " "It can decode AX.25 packets and displays the decoded packets in a text view.

    " "

    The decoder is based on Qtmm, which is avaialble for Linux, Mac and Windows " "at http://qtmm.sf.net.

    " ).arg(VERSION)); } gqrx-sdr-2.2.0.74.d97bd7/qtgui/afsk1200win.h000066400000000000000000000033371226036373100177720ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef AFSK1200WIN_H #define AFSK1200WIN_H #include #include #include "dsp/afsk1200/cafsk12.h" namespace Ui { class Afsk1200Win; } /*! \brief AFSK1200 decoder window. */ class Afsk1200Win : public QMainWindow { Q_OBJECT public: explicit Afsk1200Win(QWidget *parent = 0); ~Afsk1200Win(); void process_samples(float *buffer, int length); protected: void closeEvent(QCloseEvent *ev); signals: void windowClosed(); /*! Signal we emit when window is closed. */ private slots: void on_actionClear_triggered(); void on_actionSave_triggered(); void on_actionInfo_triggered(); private: Ui::Afsk1200Win *ui; /*! Qt Designer form. */ CAfsk12 *decoder; /*! The AFSK1200 decoder object. */ QVarLengthArray tmpbuf; /*! Needed to remember "overlap" smples. */ }; #endif // AFSK1200WIN_H gqrx-sdr-2.2.0.74.d97bd7/qtgui/afsk1200win.ui000066400000000000000000000075671226036373100201710ustar00rootroot00000000000000 Afsk1200Win 0 0 800 400 AFSK1200 Decoder :/icons/icons/terminal.svg:/icons/icons/terminal.svg 5 0 Monospace Qt::ScrollBarAsNeeded QPlainTextEdit::NoWrap true 10000 0 0 800 25 toolBar TopToolBarArea false :/icons/icons/clear.svg:/icons/icons/clear.svg Clear Clear received packets :/icons/icons/floppy.svg:/icons/icons/floppy.svg Save Save received packets to a text file :/icons/icons/close.svg:/icons/icons/close.svg Close Close AFSK1200 decoder window :/icons/icons/info.svg:/icons/icons/info.svg Info Show info about AFKS1200 decoder actionClose triggered() Afsk1200Win close() -1 -1 399 299 gqrx-sdr-2.2.0.74.d97bd7/qtgui/agc_options.cpp000066400000000000000000000135261226036373100206660ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2012-2013 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include #include #include "agc_options.h" #include "ui_agc_options.h" CAgcOptions::CAgcOptions(QWidget *parent) : QDialog(parent), ui(new Ui::CAgcOptions) { ui->setupUi(this); } CAgcOptions::~CAgcOptions() { delete ui; } /*! \brief Catch window close events. * * This method is called when the user closes the dialog window using the * window close icon. We catch the event and hide the dialog but keep it * around for later use. */ void CAgcOptions::closeEvent(QCloseEvent *event) { hide(); event->ignore(); } /*! \brief Get current gain slider value. */ int CAgcOptions::gain() { return ui->gainSlider->value(); } /*! \brief Set AGC preset. */ void CAgcOptions::setPreset(agc_preset_e preset) { switch (preset) { case AGC_FAST: setDecay(100); enableDecay(false); setSlope(2); enableSlope(false); enableGain(false); break; case AGC_MEDIUM: setDecay(800); enableDecay(false); setSlope(2); enableSlope(false); enableGain(false); break; case AGC_SLOW: setDecay(2000); enableDecay(false); setSlope(2); enableSlope(false); enableGain(false); break; case AGC_USER: enableDecay(true); enableSlope(true); enableGain(false); break; case AGC_OFF: enableGain(true); break; default: qDebug() << __func__ << "Invalid AGC preset" << preset; break; } } /*! \brief Set new gain slider value. */ void CAgcOptions::setGain(int value) { ui->gainSlider->setValue(value); ui->gainLabel->setText(QString("%1 dB").arg(ui->gainSlider->value())); } /*! \brief Enable or disable gain slider. * \param enabled Whether the slider should be enabled or not. * * The gain slider is enabled when AGC is OFF to provide manual gain * control. It is disabled when AGC is ON. */ void CAgcOptions::enableGain(bool enabled) { ui->gainLabel->setEnabled(enabled); ui->gainSlider->setEnabled(enabled); ui->label1->setEnabled(enabled); } /*! \brief Get current AGC threshold. */ int CAgcOptions::threshold() { return ui->thresholdSlider->value(); } /*! \brief Set new AGC threshold. */ void CAgcOptions::setThreshold(int value) { ui->thresholdSlider->setValue(value); ui->thresholdLabel->setText(QString("%1 dB").arg(ui->thresholdSlider->value())); } /*! \brief Get current AGC slope. */ int CAgcOptions::slope() { return ui->slopeSlider->value(); } /*! \brief Set new AGC slope. */ void CAgcOptions::setSlope(int value) { ui->slopeSlider->setValue(value); ui->slopeLabel->setText(QString("%1 dB").arg(ui->slopeSlider->value())); } /*! \brief Enable or disable AGC slope slider. * \param enabled Whether the slider should be enabled or not. * * The slope slider is enabled when AGC is in user mode. */ void CAgcOptions::enableSlope(bool enabled) { ui->slopeSlider->setEnabled(enabled); ui->slopeLabel->setEnabled(enabled); ui->label3->setEnabled(enabled); } /*! \brief Get current decay value. */ int CAgcOptions::decay() { return ui->decaySlider->value(); } /*! \brief Set new decay value. */ void CAgcOptions::setDecay(int value) { ui->decaySlider->setValue(value); ui->decayLabel->setText(QString("%1 ms").arg(ui->decaySlider->value())); } /*! \brief Enable or disable AGC decay slider. * \param enabled Whether the slider should be enabled or not. * * The decay slider is enabled when AGC is in user mode. */ void CAgcOptions::enableDecay(bool enabled) { ui->decaySlider->setEnabled(enabled); ui->decayLabel->setEnabled(enabled); ui->label4->setEnabled(enabled); } /*! \brief Get current state of AGC hang button. */ bool CAgcOptions::hang() { return ui->hangButton->isChecked(); } /*! \brief Set state og AGC hang button. */ void CAgcOptions::setHang(bool checked) { ui->hangButton->setChecked(checked); } /*! \brief AGC gain slider value has changed. */ void CAgcOptions::on_gainSlider_valueChanged(int gain) { ui->gainLabel->setText(QString("%1 dB").arg(ui->gainSlider->value())); emit gainChanged(gain); } /*! \brief AGC threshold slider value has changed. */ void CAgcOptions::on_thresholdSlider_valueChanged(int threshold) { ui->thresholdLabel->setText(QString("%1 dB").arg(ui->thresholdSlider->value())); emit thresholdChanged(threshold); } /*! \brief AGC slope slider value has changed. */ void CAgcOptions::on_slopeSlider_valueChanged(int slope) { ui->slopeLabel->setText(QString("%1 dB").arg(ui->slopeSlider->value())); emit slopeChanged(slope); } /*! \brief AGC decay slider value has changed. */ void CAgcOptions::on_decaySlider_valueChanged(int decay) { ui->decayLabel->setText(QString("%1 ms").arg(ui->decaySlider->value())); emit decayChanged(decay); } /*! \brief AGC hang button has been toggled. */ void CAgcOptions::on_hangButton_toggled(bool checked) { ui->hangButton->setText(checked ? tr("Enabled") : tr("Disabled")); emit hangChanged(checked); } gqrx-sdr-2.2.0.74.d97bd7/qtgui/agc_options.h000066400000000000000000000054451226036373100203340ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2012-2013 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef AGC_OPTIONS_H #define AGC_OPTIONS_H #include #include namespace Ui { class CAgcOptions; } /*! \brief Dialog windows with advanced AGC controls. * \ingroup UI * * By default the user is presented with a combo box and a small tool button. * The combo box contains the most common AGC presets (fast, medium, slow, user) * plus an option to switch AGC OFF. The controls for the individual AGC parameters * are inside this dialog and can be shown using the small tool button next to * the combo box containing the presets. * * \todo A graph that shows the current AGC profile updated in real time. */ class CAgcOptions : public QDialog { Q_OBJECT public: explicit CAgcOptions(QWidget *parent = 0); ~CAgcOptions(); void closeEvent(QCloseEvent *event); int gain(); void setGain(int value); void enableGain(bool enabled); int threshold(); void setThreshold(int value); int slope(); void setSlope(int value); void enableSlope(bool enabled); int decay(); void setDecay(int value); void enableDecay(bool enabled); bool hang(); void setHang(bool checked); enum agc_preset_e { AGC_FAST = 0, /*! decay = 500 ms, slope = 2 */ AGC_MEDIUM = 1, /*! decay = 1500 ms, slope = 2 */ AGC_SLOW = 2, /*! decay = 3000 ms, slope = 2 */ AGC_USER = 3, AGC_OFF = 4 }; void setPreset(agc_preset_e preset); signals: void gainChanged(int gain); void thresholdChanged(int threshold); void slopeChanged(int slope); void decayChanged(int decay); void hangChanged(bool on); private slots: void on_gainSlider_valueChanged(int gain); void on_thresholdSlider_valueChanged(int threshold); void on_slopeSlider_valueChanged(int slope); void on_decaySlider_valueChanged(int decay); void on_hangButton_toggled(bool checked); private: Ui::CAgcOptions *ui; }; #endif // AGC_OPTIONS_H gqrx-sdr-2.2.0.74.d97bd7/qtgui/agc_options.ui000066400000000000000000000201321226036373100205100ustar00rootroot00000000000000 CAgcOptions 0 0 263 197 Qt::StrongFocus AGC Settings :/icons/icons/signal.svg:/icons/icons/signal.svg AGC settings 10 5 false Slope Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Threshold Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter false Qt::StrongFocus AGC slope 10 1 2 Qt::Horizontal -100 dB Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter false Decay Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Enable / disable AGC hang Disabled true false false false Manual gain. Used when AGC is switched off 0 dB Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter false Qt::StrongFocus AGC decay time 50 5000 10 50 100 Qt::Horizontal false 100 ms Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter false Qt::StrongFocus Manual gain. Used when AGC is switched off 100 Qt::Horizontal false Gain Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter false 2 dB Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Hang Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Qt::StrongFocus AGC threshold (aka. knee) -160 0 -100 Qt::Horizontal gainSlider thresholdSlider slopeSlider decaySlider hangButton gqrx-sdr-2.2.0.74.d97bd7/qtgui/audio_options.cpp000066400000000000000000000070041226036373100212270ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2013 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include #include #include #include "audio_options.h" #include "ui_audio_options.h" CAudioOptions::CAudioOptions(QWidget *parent) : QDialog(parent), ui(new Ui::CAudioOptions) { ui->setupUi(this); work_dir = new QDir(); error_palette = new QPalette(); error_palette->setColor(QPalette::Text, Qt::red); } CAudioOptions::~CAudioOptions() { delete work_dir; delete error_palette; delete ui; } /*! \brief Catch window close events. * * This method is called when the user closes the audio options dialog * window using the window close icon. We catch the event and hide the * dialog but keep it around for later use. */ void CAudioOptions::closeEvent(QCloseEvent *event) { hide(); event->ignore(); // check if we ended up with empty dir, if yes reset to $HOME if (ui->recDirEdit->text().isEmpty()) { setRecDir(QDir::homePath()); emit newRecDirSelected(QDir::homePath()); } if (ui->udpHost->text().isEmpty()) { ui->udpHost->setText("localhost"); } } /*! \brief Set initial location of WAV files. */ void CAudioOptions::setRecDir(const QString &dir) { ui->recDirEdit->setText(dir); } /*! \brief Set new UDP host name or IP. */ void CAudioOptions::setUdpHost(const QString &host) { ui->udpHost->setText(host); } /*! \brief Set new UDP port. */ void CAudioOptions::setUdpPort(int port) { ui->udpPort->setValue(port); } /*! \brief Slot called when the recordings directory has changed either * because of user input or programmatically. */ void CAudioOptions::on_recDirEdit_textChanged(const QString &dir) { if (work_dir->exists(dir)) { ui->recDirEdit->setPalette(QPalette()); // Clear custom color emit newRecDirSelected(dir); } else { ui->recDirEdit->setPalette(*error_palette); // indicate error } } /*! \brief Slot called when the user clicks on the "Select" button. */ void CAudioOptions::on_recDirButton_clicked() { QString dir = QFileDialog::getExistingDirectory(this, tr("Select a directory"), ui->recDirEdit->text(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); if (!dir.isNull()) ui->recDirEdit->setText(dir); } /*! \brief UDP host name has changed. */ void CAudioOptions::on_udpHost_textChanged(const QString &text) { if (!text.isEmpty()) emit newUdpHost(text); } /*! \brief UDP port number has changed. */ void CAudioOptions::on_udpPort_valueChanged(int port) { emit newUdpPort(port); } gqrx-sdr-2.2.0.74.d97bd7/qtgui/audio_options.h000066400000000000000000000040411226036373100206720ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2013 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef AUDIO_OPTIONS_H #define AUDIO_OPTIONS_H #include #include #include #include namespace Ui { class CAudioOptions; } /*! \brief GUI widget for configuring audio options. */ class CAudioOptions : public QDialog { Q_OBJECT public: explicit CAudioOptions(QWidget *parent = 0); ~CAudioOptions(); void closeEvent(QCloseEvent *event); void setRecDir(const QString &dir); void setUdpHost(const QString &host); void setUdpPort(int port); signals: /*! \brief Signal emitted when a new valid directory has been selected. */ void newRecDirSelected(const QString &dir); void newUdpHost(const QString text); void newUdpPort(int port); private slots: void on_recDirEdit_textChanged(const QString &text); void on_recDirButton_clicked(); void on_udpHost_textChanged(const QString &text); void on_udpPort_valueChanged(int port); private: Ui::CAudioOptions *ui; /*!< The user interface widget. */ QDir *work_dir; /*!< Used for validating chosen directory. */ QPalette *error_palette; /*!< Palette used to indicate an error. */ }; #endif // AUDIO_OPTIONS_H gqrx-sdr-2.2.0.74.d97bd7/qtgui/audio_options.ui000066400000000000000000000120411226036373100210570ustar00rootroot00000000000000 CAudioOptions 0 0 350 144 Audio options :/icons/icons/audio-card.svg:/icons/icons/audio-card.svg 0 Recording Recording settings Location for recorded audio files: Enter a location for recordings Select a location using a browser Select Qt::Vertical 20 40 Network Network streaming settings 0 0 Network host to stream to UDP host Network host to stream to localhost 0 0 Network port to stream to UDP port Network port to stream to 1 65535 7355 Qt::Vertical 20 1 gqrx-sdr-2.2.0.74.d97bd7/qtgui/demod_options.cpp000066400000000000000000000052071226036373100212210ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2013 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include #include "demod_options.h" #include "ui_demod_options.h" CDemodOptions::CDemodOptions(QWidget *parent) : QDialog(parent), ui(new Ui::CDemodOptions) { ui->setupUi(this); } CDemodOptions::~CDemodOptions() { delete ui; } /*! \brief Catch window close events. * * This method is called when the user closes the demod options dialog * window using the window close icon. We catch the event and hide the * dialog but keep it around for later use. */ void CDemodOptions::closeEvent(QCloseEvent *event) { hide(); event->ignore(); } void CDemodOptions::setCurrentPage(int index) { if (index < PAGE_NUM) ui->demodOptions->setCurrentIndex(index); } int CDemodOptions::currentPage() { return ui->demodOptions->currentIndex(); } void CDemodOptions::on_maxdevSelector_activated(int index) { float max_dev; switch(index) { case 0: /* Voice 2.5k */ max_dev = 2500.0; break; case 1: /* Voice 5k */ max_dev = 5000.0; break; case 2: /* APT 17k */ max_dev = 17000.0; break; case 3: /* Broadcast FM 75k */ max_dev = 75000.0; break; default: /* Voice 5k */ qDebug() << "Invalid max_dev index: " << index; max_dev = 5000.0; break; } emit fmMaxdevSelected(max_dev); } void CDemodOptions::on_emphSelector_activated(int index) { double tau_tbl[] = {0.0, 25.0, 50.0, 75.0, 100.0, 250.0, 530.0, 1000.0}; double tau; if ((index < 0) || (index > 7)) { qDebug() << "Invalid tau selection index: " << index; return; } tau = tau_tbl[index] * 1.0e-6; emit fmEmphSelected(tau); } void CDemodOptions::on_dcrCheckBox_toggled(bool checked) { emit amDcrToggled(checked); } gqrx-sdr-2.2.0.74.d97bd7/qtgui/demod_options.h000066400000000000000000000037601226036373100206700ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2013 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef DEMOD_OPTIONS_H #define DEMOD_OPTIONS_H #include #include namespace Ui { class CDemodOptions; } /*! \brief Widget for adjusting demodulator options. * */ class CDemodOptions : public QDialog { Q_OBJECT public: /*! \brief Index in the QStackedWidget. */ enum page { PAGE_NO_OPT = 0, PAGE_FM_OPT = 1, PAGE_AM_OPT = 2, PAGE_NUM = 3 }; explicit CDemodOptions(QWidget *parent = 0); ~CDemodOptions(); void closeEvent(QCloseEvent *event); void setCurrentPage(int index); int currentPage(); signals: /*! \brief Signal emitted when new FM deviation is selected. */ void fmMaxdevSelected(float max_dev); /*! \brief Signal emitted when new FM de-emphasis constant is selected. */ void fmEmphSelected(double tau); /*! \brief Signal emitted when AM DCR is toggled. */ void amDcrToggled(bool enabled); private slots: void on_maxdevSelector_activated(int index); void on_emphSelector_activated(int index); void on_dcrCheckBox_toggled(bool checked); private: Ui::CDemodOptions *ui; }; #endif // DEMOD_OPTIONS_H gqrx-sdr-2.2.0.74.d97bd7/qtgui/demod_options.ui000066400000000000000000000154201226036373100210520ustar00rootroot00000000000000 CDemodOptions 0 0 223 110 Mode options 0 72 1 No options for this demodulator Qt::AlignCenter QFormLayout::AllNonFixedFieldsGrow Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 10 5 5 5 5 Max dev Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 0 24 Maximum FM deviation Maximum FM deviation 1 Voice (2.5k) Voice (5k) APT (17k) BC (75k) Tau Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 0 24 Time constant for the FM de-emphasis IIR filter. For narrow band FM use 530 μs. 50 μs and 75 μs are used for broadcast FM depending on region. For digital modes it is best to switch it off. false 3 Off 25 μs 50 μs 75 μs 100 μs 250 μs 530 μs 1 ms Enable/disable DC removal. DCR true gqrx-sdr-2.2.0.74.d97bd7/qtgui/dockaudio.cpp000066400000000000000000000234051226036373100203200ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2013 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include #include #include #include "dockaudio.h" #include "ui_dockaudio.h" DockAudio::DockAudio(QWidget *parent) : QDockWidget(parent), ui(new Ui::DockAudio), autoSpan(true) { ui->setupUi(this); #ifdef Q_WS_MAC // Workaround for Mac, see http://stackoverflow.com/questions/3978889/why-is-qhboxlayout-causing-widgets-to-overlap // Might be fixed in Qt 5? ui->audioPlayButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); ui->audioRecButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); ui->audioConfButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); #endif audioOptions = new CAudioOptions(this); connect(audioOptions, SIGNAL(newRecDirSelected(QString)), this, SLOT(setNewRecDir(QString))); connect(audioOptions, SIGNAL(newUdpHost(QString)), this, SLOT(setNewUdpHost(QString))); connect(audioOptions, SIGNAL(newUdpPort(int)), this, SLOT(setNewUdpPort(int))); ui->audioSpectrum->setPercent2DScreen(100); ui->audioSpectrum->setFreqUnits(1000); ui->audioSpectrum->setSampleRate(48000); // Full bandwidth ui->audioSpectrum->setSpanFreq(12000); ui->audioSpectrum->setCenterFreq(0); ui->audioSpectrum->setFftCenterFreq(6000); ui->audioSpectrum->setDemodCenterFreq(0); ui->audioSpectrum->setFilterBoxEnabled(false); ui->audioSpectrum->setCenterLineEnabled(false); ui->audioSpectrum->setMinMaxDB(-120, 0); ui->audioSpectrum->setFontSize(8); ui->audioSpectrum->setVdivDelta(20); ui->audioSpectrum->setHdivDelta(25); ui->audioSpectrum->setFreqDigits(1); } DockAudio::~DockAudio() { delete ui; } void DockAudio::setFftRange(quint64 minf, quint64 maxf) { if (autoSpan) { qint32 span = (qint32)(maxf - minf); quint64 fc = minf + (maxf - minf)/2; ui->audioSpectrum->setFftCenterFreq(fc); ui->audioSpectrum->setSpanFreq(span); ui->audioSpectrum->setCenterFreq(0); } } void DockAudio::setNewFttData(double *fftData, int size) { ui->audioSpectrum->setNewFttData(fftData, size); } /*! \brief Set new audio gain. * \param gain the new audio gain in tens of dB (0 dB = 10) */ void DockAudio::setAudioGain(int gain) { ui->audioGainSlider->setValue(gain); } /*! \brief Get current audio gain. * \returns The current audio gain in tens of dB (0 dB = 10). */ int DockAudio::audioGain() { return ui->audioGainSlider->value(); } /*! Set FFT plot color. */ void DockAudio::setFftColor(QColor color) { ui->audioSpectrum->setFftPlotColor(color); } /*! Enable/disable filling area under FFT plot. */ void DockAudio::setFftFill(bool enabled) { ui->audioSpectrum->setFftFill(enabled); } /*! Public slot to trig audio recording by external events (e.g. satellite AOS). * * If a recording is already in progress we ignore the event. */ void DockAudio::startAudioRecorder(void) { if (ui->audioRecButton->isChecked()) { qDebug() << __func__ << "An audio recording is already in progress"; return; } // emulate a button click ui->audioRecButton->click(); } /*! Public slot to stop audio recording by external events (e.g. satellite LOS). * * The event is ignored if no audio recording is in progress. */ void DockAudio::stopAudioRecorder(void) { if (ui->audioRecButton->isChecked()) ui->audioRecButton->click(); // emulate a button click else qDebug() << __func__ << "No audio recording in progress"; } /*! \brief Audio gain changed. * \param value The new audio gain value in tens of dB (because slider uses int) */ void DockAudio::on_audioGainSlider_valueChanged(int value) { float gain = float(value) / 10.0; // update dB label ui->audioGainDbLabel->setText(QString("%1 dB").arg(gain)); emit audioGainChanged(gain); } /*! \brief Streaming button clicked. * \param checked Whether streaming is ON or OFF. */ void DockAudio::on_audioStreamButton_clicked(bool checked) { if (checked) emit audioStreamingStarted(udp_host, udp_port); else emit audioStreamingStopped(); } /*! \brief Record button clicked. * \param checked Whether recording is ON or OFF. * * We use the clicked signal instead of the toggled which allows us to change the * state programatically using toggle() without triggering the signal. */ void DockAudio::on_audioRecButton_clicked(bool checked) { if (checked) { // FIXME: option to use local time // use toUTC() function compatible with older versions of Qt. QString file_name = QDateTime::currentDateTime().toUTC().toString("gqrx-yyyyMMdd-hhmmss.'wav'"); last_audio = QString("%1/%2").arg(rec_dir).arg(file_name); // emit signal and start timer emit audioRecStarted(last_audio); ui->audioRecButton->setToolTip(tr("Stop audio recorder")); ui->audioPlayButton->setEnabled(false); /* prevent playback while recording */ } else { ui->audioRecButton->setToolTip(tr("Start audio recorder")); emit audioRecStopped(); ui->audioPlayButton->setEnabled(true); } } /*! \brief Playback button clicked. * \param checked Whether playback is ON or OFF. * * We use the clicked signal instead of the toggled which allows us to change the * state programatically using toggle() without triggering the signal. */ void DockAudio::on_audioPlayButton_clicked(bool checked) { if (checked) { // emit signal and start timer emit audioPlayStarted(last_audio); ui->audioRecLabel->setText(QFileInfo(last_audio).fileName()); ui->audioPlayButton->setToolTip(tr("Stop audio playback")); ui->audioRecButton->setEnabled(false); // prevent recording while we play } else { ui->audioRecLabel->setText("DSP"); ui->audioPlayButton->setToolTip(tr("Start playback of last recorded audio file")); emit audioPlayStopped(); ui->audioRecButton->setEnabled(true); } } /*! \brief Configure button clicked. */ void DockAudio::on_audioConfButton_clicked() { audioOptions->show(); } /*! \brief Set status of audio record button. */ void DockAudio::setAudioRecButtonState(bool checked) { if (checked == ui->audioRecButton->isChecked()) { /* nothing to do */ return; } // toggle the button and set the state of the other buttons accordingly ui->audioRecButton->toggle(); bool isChecked = ui->audioRecButton->isChecked(); ui->audioRecButton->setToolTip(isChecked ? tr("Stop audio recorder") : tr("Start audio recorder")); ui->audioPlayButton->setEnabled(!isChecked); //ui->audioRecConfButton->setEnabled(!isChecked); } /*! \brief Set status of audio record button. */ void DockAudio::setAudioPlayButtonState(bool checked) { if (checked == ui->audioPlayButton->isChecked()) { // nothing to do return; } // toggle the button and set the state of the other buttons accordingly ui->audioPlayButton->toggle(); bool isChecked = ui->audioPlayButton->isChecked(); ui->audioPlayButton->setToolTip(isChecked ? tr("Stop audio playback") : tr("Start playback of last recorded audio file")); ui->audioRecButton->setEnabled(!isChecked); //ui->audioRecConfButton->setEnabled(!isChecked); } void DockAudio::saveSettings(QSettings *settings) { if (!settings) return; settings->setValue("audio/gain", audioGain()); if (rec_dir != QDir::homePath()) settings->setValue("audio/rec_dir", rec_dir); else settings->remove("audio/rec_dir"); if ((udp_host != "localhost") && (udp_host != "127.0.0.1")) settings->setValue("audio/udp_host", udp_host); else settings->remove("audio/udp_host"); if (udp_port != 7355) settings->setValue("audio/udp_port", udp_port); else settings->remove("audio/udp_port"); } void DockAudio::readSettings(QSettings *settings) { if (!settings) return; bool conv_ok = false; int gain = settings->value("audio/gain", QVariant(-200)).toInt(&conv_ok); if (conv_ok) setAudioGain(gain); // Location of audio recordings rec_dir = settings->value("audio/rec_dir", QDir::homePath()).toString(); audioOptions->setRecDir(rec_dir); // Audio streaming host and port udp_host = settings->value("audio/udp_host", "localhost").toString(); udp_port = settings->value("audio/udp_port", 7355).toInt(&conv_ok); if (!conv_ok) udp_port = 7355; audioOptions->setUdpHost(udp_host); audioOptions->setUdpPort(udp_port); } /*! \brief Slot called when a new valid recording directory has been selected * in the audio conf dialog. */ void DockAudio::setNewRecDir(const QString &dir) { rec_dir = dir; } /*! \brief Slot called when a new network host has been entered. */ void DockAudio::setNewUdpHost(const QString &host) { if (host.isEmpty()) udp_host = "localhost"; else udp_host = host; } /*! \brief Slot called when a new network port has been entered. */ void DockAudio::setNewUdpPort(int port) { udp_port = port; } gqrx-sdr-2.2.0.74.d97bd7/qtgui/dockaudio.h000066400000000000000000000071261226036373100177670ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2013 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef DOCKAUDIO_H #define DOCKAUDIO_H #include #include #include #include "audio_options.h" namespace Ui { class DockAudio; } /*! \brief Dock window with audio options. * \ingroup UI * * This dock widget encapsulates the audio options. * The UI itself is in the dockaudio.ui file. * * This class also provides the signal/slot API necessary to connect * the encapsulated widgets to the rest of the application. */ class DockAudio : public QDockWidget { Q_OBJECT public: explicit DockAudio(QWidget *parent = 0); ~DockAudio(); void setFftRange(quint64 minf, quint64 maxf); void setNewFttData(double *fftData, int size); int fftRate() const { return 10; } void setAudioGain(int gain); int audioGain(); void setAudioRecButtonState(bool checked); void setAudioPlayButtonState(bool checked); void setFftColor(QColor color); void setFftFill(bool enabled); void saveSettings(QSettings *settings); void readSettings(QSettings *settings); public slots: void startAudioRecorder(void); void stopAudioRecorder(void); signals: /*! \brief Signal emitted when audio gain has changed. Gain is in dB. */ void audioGainChanged(float gain); /*! \brief Audio streaming over UDP has started. */ void audioStreamingStarted(const QString host, int port); /*! \brief Audio streaming stopped. */ void audioStreamingStopped(); /*! \brief Signal emitted when audio recording is started. */ void audioRecStarted(const QString filename); /*! \brief Signal emitted when audio recording is stopped. */ void audioRecStopped(); /*! \brief Signal emitted when audio playback is started. */ void audioPlayStarted(const QString filename); /*! \brief Signal emitted when audio playback is stopped. */ void audioPlayStopped(); /*! \brief FFT rate changed. */ void fftRateChanged(int fps); private slots: void on_audioGainSlider_valueChanged(int value); void on_audioStreamButton_clicked(bool checked); void on_audioRecButton_clicked(bool checked); void on_audioPlayButton_clicked(bool checked); void on_audioConfButton_clicked(); void setNewRecDir(const QString &dir); void setNewUdpHost(const QString &host); void setNewUdpPort(int port); private: Ui::DockAudio *ui; CAudioOptions *audioOptions; /*! Audio options dialog. */ QString rec_dir; /*! Location for audio recordings. */ QString last_audio; /*! Last audio recording. */ QString udp_host; /*! UDP client host name. */ int udp_port; /*! UDP client port number. */ bool autoSpan; /*! Whether to allow mode-dependent auto span. */ }; #endif // DOCKAUDIO_H gqrx-sdr-2.2.0.74.d97bd7/qtgui/dockaudio.ui000066400000000000000000000234031226036373100201510ustar00rootroot00000000000000 DockAudio 0 0 200 247 200 0 200 220 Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea Audio 0 0 48 48 Audio spectrum QFrame::StyledPanel QFrame::Raised 6 0 0 Gain: Qt::AlignCenter 0 0 -20 dB Qt::AlignCenter 0 1 0 0 0 22 Audio gain Audio gain -800 500 -200 Qt::Horizontal 0 0 0 28 28 Stream audio over UDP :/icons/icons/tangeo-network-idle.svg:/icons/icons/tangeo-network-idle.svg 16 16 true 0 0 28 28 Record audio :/icons/icons/record.svg:/icons/icons/record.svg 16 16 true false 0 0 0 0 28 28 Playback previously recorded audio :/icons/icons/play.svg:/icons/icons/play.svg 16 16 true Qt::Horizontal 40 20 true 32 32 0 0 28 28 Audio settings :/icons/icons/settings.svg:/icons/icons/settings.svg 16 16 0 0 Current audio source (DSP or WAV file) <i>DSP</i> Qt::AlignCenter CPlotter QFrame
    qtgui/plotter.h
    1
    gqrx-sdr-2.2.0.74.d97bd7/qtgui/dockfft.cpp000066400000000000000000000172621226036373100200020ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2013 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include #include #include #include "dockfft.h" #include "ui_dockfft.h" #define DEFAULT_FFT_RATE 15 #define DEFAULT_FFT_SIZE 2048 #define DEFAULT_FFT_SPLIT 50 #define DEFAULT_FFT_AVG 50 DockFft::DockFft(QWidget *parent) : QDockWidget(parent), ui(new Ui::DockFft) { ui->setupUi(this); #ifdef Q_WS_MAC // Workaround for Mac, see http://stackoverflow.com/questions/3978889/why-is-qhboxlayout-causing-widgets-to-overlap // Might be fixed in Qt 5? ui->resetButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); ui->centerButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); ui->demodButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); ui->fillButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); #endif // Add predefined gqrx colors to chooser. ui->colorPicker->insertColor(QColor(0xFF,0xFF,0xFF,0xFF), "White"); ui->colorPicker->insertColor(QColor(0xFA,0xFA,0x7F,0xFF), "Yellow"); ui->colorPicker->insertColor(QColor(0x97,0xD0,0x97,0xFF), "Green"); ui->colorPicker->insertColor(QColor(0xFF,0xC8,0xC8,0xFF), "Pink"); ui->colorPicker->insertColor(QColor(0xB7,0xE0,0xFF,0xFF), "Blue"); ui->colorPicker->insertColor(QColor(0x7F,0xFA,0xFA,0xFF), "Cyan"); } DockFft::~DockFft() { delete ui; } /*! \brief Get current FFT rate setting. * \return The current FFT rate in frames per second (always non-zero) */ int DockFft::fftRate() { bool ok; int fps = 10; QString strval = ui->fftRateComboBox->currentText(); strval.remove(" fps"); fps = strval.toInt(&ok, 10); if (!ok) qDebug() << "DockFft::fftRate : Could not convert" << strval << "to number."; else qDebug() << "New FFT rate:" << fps << "Hz"; return fps; } /*! \brief Select new FFT rate in the combo box. * \param rate The new rate. * \returns The actual FFT rate selected. */ int DockFft::setFftRate(int fft_rate) { int idx = -1; QString rate_str = QString("%1 fps").arg(fft_rate); qDebug() << __func__ << "to" << rate_str; idx = ui->fftRateComboBox->findText(rate_str, Qt::MatchExactly); if(idx != -1) ui->fftRateComboBox->setCurrentIndex(idx); return fftRate(); } /*! \brief Select new FFT size in the combo box. * \param rate The new FFT size. * \returns The actual FFT size selected. */ int DockFft::setFftSize(int fft_size) { int idx = -1; QString size_str = QString::number(fft_size); qDebug() << __func__ << "to" << size_str; idx = ui->fftSizeComboBox->findText(size_str, Qt::MatchExactly); if(idx != -1) ui->fftSizeComboBox->setCurrentIndex(idx); return fftSize(); } /*! \brief Get current FFT rate setting. * \return The current FFT rate in frames per second (always non-zero) */ int DockFft::fftSize() { bool ok; int fft_size = 10; QString strval = ui->fftSizeComboBox->currentText(); fft_size = strval.toInt(&ok, 10); if (!ok) { qDebug() << __func__ << "could not convert" << strval << "to number."; } if (fft_size == 0) { qDebug() << "Somehow we ended up with FFT size = 0; using" << DEFAULT_FFT_SIZE; fft_size = DEFAULT_FFT_SIZE; } return fft_size; } /*! \brief Save FFT settings. */ void DockFft::saveSettings(QSettings *settings) { int intval; if (!settings) return; settings->beginGroup("fft"); intval = fftSize(); if (intval != DEFAULT_FFT_SIZE) settings->setValue("fft_size", intval); else settings->remove("fft_size"); intval = fftRate(); if (intval != DEFAULT_FFT_RATE) settings->setValue("fft_rate", fftRate()); else settings->remove("fft_rate"); if (ui->fftAvgSlider->value() != DEFAULT_FFT_AVG) settings->setValue("averaging", ui->fftAvgSlider->value()); else settings->remove("averaging"); if (ui->fftSplitSlider->value() != DEFAULT_FFT_SPLIT) settings->setValue("split", ui->fftSplitSlider->value()); else settings->remove("split"); QColor fftColor = ui->colorPicker->currentColor(); if (fftColor != QColor(0xFF,0xFF,0xFF,0xFF)) settings->setValue("pandapter_color", fftColor); else settings->remove("pandapter_color"); if (ui->fillButton->isChecked()) settings->setValue("pandapter_fill", true); else settings->remove("pandapter_fill"); settings->endGroup(); } /*! \brief Read FFT settings. */ void DockFft::readSettings(QSettings *settings) { int intval; bool bool_val = false; bool conv_ok = false; QColor color; if (!settings) return; settings->beginGroup("fft"); intval = settings->value("fft_rate", DEFAULT_FFT_RATE).toInt(&conv_ok); if (conv_ok) setFftRate(intval); intval = settings->value("fft_size", DEFAULT_FFT_SIZE).toInt(&conv_ok); if (conv_ok) setFftSize(intval); intval = settings->value("averaging", DEFAULT_FFT_AVG).toInt(&conv_ok); if (conv_ok) ui->fftAvgSlider->setValue(intval); intval = settings->value("split", DEFAULT_FFT_SPLIT).toInt(&conv_ok); if (conv_ok) ui->fftSplitSlider->setValue(intval); color = settings->value("pandapter_color", QColor(0xFF,0xFF,0xFF,0xFF)).value(); ui->colorPicker->setCurrentColor(color); bool_val = settings->value("pandapter_fill", false).toBool(); ui->fillButton->setChecked(bool_val); settings->endGroup(); } /*! \brief FFT size changed. */ void DockFft::on_fftSizeComboBox_currentIndexChanged(const QString &text) { int value = text.toInt(); emit fftSizeChanged(value); } /*! \brief FFT rate changed. */ void DockFft::on_fftRateComboBox_currentIndexChanged(const QString & text) { int fps = fftRate(); Q_UNUSED(text); emit fftRateChanged(fps); } /*! \brief Split between waterfall and pandapter changed. * \param value The percentage of the waterfall. */ void DockFft::on_fftSplitSlider_valueChanged(int value) { emit fftSplitChanged(value); } /*! \brief FFT filter gain changed. */ void DockFft::on_fftAvgSlider_valueChanged(int value) { double avg = 1.0 - 1.0e-2*((double)value); emit fftAvgChanged(avg); } void DockFft::on_resetButton_clicked(void) { emit resetFftZoom(); } void DockFft::on_centerButton_clicked(void) { emit gotoFftCenter(); } void DockFft::on_demodButton_clicked(void) { emit gotoDemodFreq(); } /*! FFT color has changed. */ void DockFft::on_colorPicker_colorChanged(const QColor &color) { emit fftColorChanged(color); } /*! FFT plot fill button toggled. */ void DockFft::on_fillButton_toggled(bool checked) { emit fftFillToggled(checked); } /*! peakHold button toggled */ void DockFft::on_peakHoldButton_toggled(bool checked) { emit fftPeakHoldToggled(checked); } /*! peakDetection button toggled */ void DockFft::on_peakDetectionButton_toggled(bool checked) { emit peakDetectionToggled(checked); } gqrx-sdr-2.2.0.74.d97bd7/qtgui/dockfft.h000066400000000000000000000054011226036373100174370ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2013 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef DOCKFFT_H #define DOCKFFT_H #include #include namespace Ui { class DockFft; } /*! \brief Dock widget with FFT settings. */ class DockFft : public QDockWidget { Q_OBJECT public: explicit DockFft(QWidget *parent = 0); ~DockFft(); int fftRate(); int setFftRate(int fft_rate); int fftSize(); int setFftSize(int fft_size); void saveSettings(QSettings *settings); void readSettings(QSettings *settings); signals: void fftSizeChanged(int size); /*! FFT size changed. */ void fftRateChanged(int fps); /*! FFT rate changed. */ void fftSplitChanged(int pct); /*! Split between pandapter and waterfall changed. */ void fftAvgChanged(double gain); /*! FFT video filter gain has changed. */ void resetFftZoom(void); /*! FFT zoom reset. */ void gotoFftCenter(void); /*! Go to FFT center. */ void gotoDemodFreq(void); /*! Center FFT around demodulator frequency. */ void fftColorChanged(const QColor &); /*! FFT color has changed. */ void fftFillToggled(bool fill); /*! Toggle filling area under FFT plot. */ void fftPeakHoldToggled(bool enable); /*! Toggle peak hold in FFT area. */ void peakDetectionToggled(bool enabled); /*! Enable peak detection in FFT plot */ private slots: void on_fftSizeComboBox_currentIndexChanged(const QString & text); void on_fftRateComboBox_currentIndexChanged(const QString & text); void on_fftSplitSlider_valueChanged(int value); void on_fftAvgSlider_valueChanged(int value); void on_resetButton_clicked(void); void on_centerButton_clicked(void); void on_demodButton_clicked(void); void on_colorPicker_colorChanged(const QColor &); void on_fillButton_toggled(bool checked); void on_peakHoldButton_toggled(bool checked); void on_peakDetectionButton_toggled(bool checked); private: Ui::DockFft *ui; }; #endif // DOCKFFT_H gqrx-sdr-2.2.0.74.d97bd7/qtgui/dockfft.ui000066400000000000000000000461341226036373100176350ustar00rootroot00000000000000 DockFft 0 0 250 314 250 150 :/icons/icons/signal.svg:/icons/icons/signal.svg FFT performance and display settings Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea FFT Settings 0 0 0 0 QFrame::NoFrame Qt::ScrollBarAlwaysOff true 0 0 250 240 0 0 0 0 0 22 Spatial distribution between pandapter and waterfall 0 100 50 Qt::Horizontal 0 0 28 28 32 32 Center FFT around demodulator frequency D WF Qt::AlignCenter Peak Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Zooming shortcuts Zoom Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 0 22 <html><head/><body><p>FFT averaging speed.</p><p>The averaging does not apply to the waterfall, which is always updated with the latest unfiltered samples.</p></body></html> 100 50 Qt::Horizontal false false 0 0 0 0 FFT refresh rate 6 60 fps 50 fps 30 fps 25 fps 20 fps 17 fps 15 fps 10 fps 5 fps 1 fps 0 fps FFT size Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Pandapter Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Rate Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Color for the FFT plot Color Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 28 28 32 32 Enable peak detection in FFT D true <html><head/><body><p>FFT averaging speed.</p><p>Also known as video filter on spectrum analyzers.</p></body></html> Averaging 0 0 28 28 32 32 Toggle peak hold in FFT H true 0 0 28 28 32 32 Center FFT around original center frequency C 0 0 28 28 32 32 Reset zoom level to 100% R true 0 0 0 0 <html>Number of FFT points to calculate. Higher values will require more CPU time. This will not influence the number of points on the display since that parameter is adjusted automatically according to the display size. </html> false 2 QComboBox::InsertAlphabetically 16384 8192 4096 3840 2048 1024 768 512 0 0 28 28 32 32 Fill the area below the FFT plot with a gradient F true 0 0 Color for the FFT plot zoomLAbel fftAvgLabel fftSizeComboBox wfLabel fftSplitSlider fftRateLabel fftSizeLabel pandLabel fftAvgSlider fftRateComboBox colorLabel peakLabel resetButton centerButton demodButton peakDetectionButton peakHoldButton colorPicker fillButton Qt::Vertical QSizePolicy::Expanding 20 20 QtColorPicker QPushButton
    qtgui/qtcolorpicker.h
    gqrx-sdr-2.2.0.74.d97bd7/qtgui/dockinputctl.cpp000066400000000000000000000333251226036373100210630ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2013 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include #include "dockinputctl.h" #include "ui_dockinputctl.h" DockInputCtl::DockInputCtl(QWidget *parent) : QDockWidget(parent), ui(new Ui::DockInputCtl) { ui->setupUi(this); // Grid layout with gain controls (device dependent) gainLayout = new QGridLayout(); gainLayout->setObjectName(QString::fromUtf8("gainLayout")); ui->verticalLayout->insertLayout(2, gainLayout); } DockInputCtl::~DockInputCtl() { delete ui; delete gainLayout; } void DockInputCtl::readSettings(QSettings *settings) { bool conv_ok; setFreqCorr(settings->value("input/corr_freq", 0).toInt(&conv_ok)); emit freqCorrChanged(ui->freqCorrSpinBox->value()); setIqSwap(settings->value("input/swap_iq", false).toBool()); emit iqSwapChanged(ui->iqSwapButton->isChecked()); setDcCancel(settings->value("input/dc_cancel", false).toBool()); emit dcCancelChanged(ui->dcCancelButton->isChecked()); setIqBalance(settings->value("input/iq_balance", false).toBool()); emit iqBalanceChanged(ui->iqBalanceButton->isChecked()); qint64 lnb_lo = settings->value("input/lnb_lo", 0).toLongLong(&conv_ok); setLnbLo(((double)lnb_lo)/1.0e6); emit lnbLoChanged(ui->lnbSpinBox->value()); bool bool_val = settings->value("input/ignore_limits", false).toBool(); setIgnoreLimits(bool_val); emit ignoreLimitsChanged(bool_val); bool_val = settings->value("input/hwagc", false).toBool(); setAgc(bool_val); // Ignore antenna selection if there is only one option if (ui->antSelector->count() > 1) { QString ant = settings->value("input/antenna", "").toString(); setAntenna(ant); } // gains are stored as a QMap // note that we store the integer values, i.e. dB*10 QMap allgains; QString gain_name; double gain_value; if (settings->contains("input/gains")) { allgains = settings->value("input/gains").toMap(); QMapIterator gain_iter(allgains); while (gain_iter.hasNext()) { gain_iter.next(); gain_name = gain_iter.key(); gain_value = 0.1 * (double)(gain_iter.value().toInt()); setGain(gain_name, gain_value); emit gainChanged(gain_name, gain_value); } } } void DockInputCtl::saveSettings(QSettings *settings) { qint64 lnb_lo = (qint64)(ui->lnbSpinBox->value()*1.e6); if (lnb_lo) settings->setValue("input/lnb_lo", lnb_lo); else settings->remove("input/lnb_lo"); // gains are stored as a QMap // note that we store the integer values, i.e. dB*10 QMap gains; getGains(&gains); if (gains.empty()) settings->remove("input/gains"); else settings->setValue("input/gains", gains); if (freqCorr()) settings->setValue("input/corr_freq", freqCorr()); else settings->remove("input/corr_freq"); if (iqSwap()) settings->setValue("input/swap_iq", true); else settings->remove("input/swap_iq"); if (dcCancel()) settings->setValue("input/dc_cancel", true); else settings->remove("input/dc_cancel"); if (iqBalance()) settings->setValue("input/iq_balance", true); else settings->remove("input/iq_balance"); if (ignoreLimits()) settings->setValue("input/ignore_limits", true); else settings->remove("input/ignore_limits"); if (agc()) settings->setValue("input/hwagc", true); else settings->remove("input/hwagc"); // save antenna selection if there is more than one option if (ui->antSelector->count() > 1) settings->setValue("input/antenna", ui->antSelector->currentText()); else settings->remove("input/antenna"); } void DockInputCtl::setLnbLo(double freq_mhz) { ui->lnbSpinBox->setValue(freq_mhz); } double DockInputCtl::lnbLo() { return ui->lnbSpinBox->value(); } /*! \brief Set new value of a specific gain. * \param name The name of the gain to change. * \param value The new value. */ void DockInputCtl::setGain(QString &name, double value) { int gain = -1; for (int idx = 0; idx < gain_labels.length(); idx++) { if (gain_labels.at(idx)->text().contains(name)) { gain = (int)(10 * value); gain_sliders.at(idx)->setValue(gain); break; } } } /*! \brief Get current gain. * \returns The relative gain between 0.0 and 1.0 or -1 if HW AGC is enabled. */ double DockInputCtl::gain(QString &name) { double gain = 0.0; for (int idx = 0; idx < gain_labels.length(); ++idx) { if (gain_labels.at(idx)->text() == name) { gain = 0.1 * (double)gain_sliders.at(idx)->value(); break; } } return gain; } /*! \brief Set status of hardware AGC button. * \param enabled Whether hardware AGC is enabled or not. */ void DockInputCtl::setAgc(bool enabled) { ui->agcButton->setChecked(enabled); } /*! \brief Get status of hardware AGC button. * \return Whether hardware AGC is enabled or not. */ bool DockInputCtl::agc() { return ui->agcButton->isChecked(); } /*! \brief Set new frequency correction. * \param corr The new frequency correction in PPM. */ void DockInputCtl::setFreqCorr(int corr) { ui->freqCorrSpinBox->setValue(corr); } /*! \brief Get current frequency correction. */ int DockInputCtl::freqCorr() { return ui->freqCorrSpinBox->value(); } /*! \brief Enasble/disable I/Q swapping. */ void DockInputCtl::setIqSwap(bool reversed) { ui->iqSwapButton->setChecked(reversed); } /*! \brief Get current I/Q swapping. */ bool DockInputCtl::iqSwap(void) { return ui->iqSwapButton->isChecked(); } /*! \brief Enable automatic DC removal. */ void DockInputCtl::setDcCancel(bool enabled) { ui->dcCancelButton->setChecked(enabled); } /*! \brief Get current DC remove status. */ bool DockInputCtl::dcCancel(void) { return ui->dcCancelButton->isChecked(); } /*! \brief Enable automatic IQ balance. */ void DockInputCtl::setIqBalance(bool enabled) { ui->iqBalanceButton->setChecked(enabled); } /*! \brief Get current IQ balance status. */ bool DockInputCtl::iqBalance(void) { return ui->iqBalanceButton->isChecked(); } /*! \brief Enasble/disable ignoring hardware limits. */ void DockInputCtl::setIgnoreLimits(bool reversed) { ui->ignoreButton->setChecked(reversed); } /*! \brief Get current status of whether limits should be ignored or not. */ bool DockInputCtl::ignoreLimits(void) { return ui->ignoreButton->isChecked(); } /*! \brief Populate antenna selector combo box with strings. */ void DockInputCtl::setAntennas(std::vector &antennas) { ui->antSelector->clear(); for (std::vector::iterator it = antennas.begin(); it != antennas.end(); ++it) { ui->antSelector->addItem(QString(it->c_str())); } } /*! \brief Select antenna. */ void DockInputCtl::setAntenna(const QString &antenna) { int index = ui->antSelector->findText(antenna, Qt::MatchExactly); if (index != -1) ui->antSelector->setCurrentIndex(index); } /*! \brief Set gain stages. * \param gain_list A list containing the gain stages for this device. */ void DockInputCtl::setGainStages(gain_list_t &gain_list) { QLabel *label; QSlider *slider; QLabel *value; int start, stop, step, gain; // ensure that gain lists are empty clearWidgets(); for (unsigned int i = 0; i < gain_list.size(); i++) { start = (int)(10.0 * gain_list[i].start); stop = (int)(10.0 * gain_list[i].stop); step = (int)(10.0 * gain_list[i].step); gain = (int)(10.0 * gain_list[i].value); label = new QLabel(QString("%1 gain").arg(gain_list[i].name.c_str()), this); label->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum)); value = new QLabel(QString("%1 dB").arg(gain_list[i].value, 0, 'f', 1), this); value->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum)); slider = new QSlider(Qt::Horizontal, this); slider->setProperty("idx", i); slider->setProperty("name", QString(gain_list[i].name.c_str())); slider->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum)); slider->setRange(start, stop); slider->setSingleStep(step); slider->setValue(gain); if (abs(stop - start) > 10 * step) slider->setPageStep(10 * step); gainLayout->addWidget(label, i, 0, Qt::AlignLeft); gainLayout->addWidget(slider, i, 1, Qt::AlignCenter); gainLayout->addWidget(value, i, 2, Qt::AlignLeft); gain_labels.push_back(label); gain_sliders.push_back(slider); value_labels.push_back(value); connect(slider, SIGNAL(valueChanged(int)), this, SLOT(sliderValueChanged(int))); } qDebug() << "********************"; for (gain_list_t::iterator it = gain_list.begin(); it != gain_list.end(); ++it) { qDebug() << "Gain name:" << QString(it->name.c_str()); qDebug() << " min:" << it->start; qDebug() << " max:" << it->stop; qDebug() << " step:" << it->step; } qDebug() << "********************"; } /*! \brief LNB LO value has changed. */ void DockInputCtl::on_lnbSpinBox_valueChanged(double value) { emit lnbLoChanged(value); } /*! \brief Automatic gain control button has been toggled. */ void DockInputCtl::on_agcButton_toggled(bool checked) { for (int i = 0; i < gain_sliders.length(); ++i) { gain_sliders.at(i)->setEnabled(!checked); } emit autoGainChanged(checked); } /*! \brief Frequency correction changed. * \param value The new frequency correction in ppm. */ void DockInputCtl::on_freqCorrSpinBox_valueChanged(int value) { emit freqCorrChanged(value); } /*! \brief I/Q swapping checkbox changed. * \param checked True if I/Q swapping is enabled, false otherwise */ void DockInputCtl::on_iqSwapButton_toggled(bool checked) { emit iqSwapChanged(checked); } /*! \brief DC removal checkbox changed. * \param checked True if DC removal is enabled, false otherwise */ void DockInputCtl::on_dcCancelButton_toggled(bool checked) { emit dcCancelChanged(checked); } /*! \brief IQ balance checkbox changed. * \param checked True if automatic IQ balance is enabled, false otherwise */ void DockInputCtl::on_iqBalanceButton_toggled(bool checked) { emit iqBalanceChanged(checked); } /*! \brief Ignore hardware limits checkbox changed. * \param checked True if hardware limits should be ignored, false otherwise * * This option exists to allow experimenting with out-of-spec settings. */ void DockInputCtl::on_ignoreButton_toggled(bool checked) { emit ignoreLimitsChanged(checked); } /*! \brief Antenna selection has changed. */ void DockInputCtl::on_antSelector_currentIndexChanged(const QString &antenna) { emit antennaSelected(antenna); } /*! \brief Remove all widgets from the lists. */ void DockInputCtl::clearWidgets() { QWidget *widget; // sliders while (!gain_sliders.isEmpty()) { widget = gain_sliders.takeFirst(); gainLayout->removeWidget(widget); delete widget; } // labels while (!gain_labels.isEmpty()) { widget = gain_labels.takeFirst(); gainLayout->removeWidget(widget); delete widget; } // value labels while (!value_labels.isEmpty()) { widget = value_labels.takeFirst(); gainLayout->removeWidget(widget); delete widget; } } /*! \brief Slot for managing slider value changed signals. * \param value The value of the slider. * * Note. We use the sender() function to find out which slider has emitted the signal. */ void DockInputCtl::sliderValueChanged(int value) { QSlider *slider = (QSlider *) sender(); int idx = slider->property("idx").toInt(); // convert to discrete value according to step if (slider->singleStep()) { value = slider->singleStep() * (value / slider->singleStep()); } // convert to double and send signal double gain = (double)value / 10.0; updateLabel(idx, gain); emit gainChanged(slider->property("name").toString(), gain); } /*! \brief Update value label * \param idx The index of the gain * \param value The new value */ void DockInputCtl::updateLabel(int idx, double value) { QLabel *label = value_labels.at(idx); label->setText(QString("%1 dB").arg(value, 0, 'f', 1)); } /*! \brief Get all gains. * \param gains Pointer to a map where the gains and thier names are stored. * * This is a private utility function used when storing the settings. */ void DockInputCtl::getGains(QMap *gains) { for (int idx = 0; idx < gain_sliders.length(); ++idx) { gains->insert(gain_sliders.at(idx)->property("name").toString(), QVariant(gain_sliders.at(idx)->value())); } } gqrx-sdr-2.2.0.74.d97bd7/qtgui/dockinputctl.h000066400000000000000000000077531226036373100205360ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2013 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef DOCKINPUTCTL_H #define DOCKINPUTCTL_H #include #include #include #include #include #include #include #include #include #include #include /*! \brief Structure describing a gain parameter with its range. */ typedef struct { std::string name; /*!< The name of this gain stage. */ double value; /*!< Initial value. */ double start; /*!< The lower limit. */ double stop; /*!< The uppewr limit. */ double step; /*!< The resolution/step. */ } gain_t; /*! \brief A vector with gain parameters. * * This data structure is used for transfering * information about available gain stages. */ typedef std::vector gain_list_t; namespace Ui { class DockInputCtl; } class DockInputCtl : public QDockWidget { Q_OBJECT public: explicit DockInputCtl(QWidget *parent = 0); ~DockInputCtl(); void readSettings(QSettings *settings); void saveSettings(QSettings *settings); void setLnbLo(double freq_mhz); double lnbLo(); void setGain(QString &name, double value); double gain(QString &name); void setAgc(bool enabled); bool agc(); void setFreqCorr(int corr); int freqCorr(); void setIqSwap(bool reversed); bool iqSwap(void); void setDcCancel(bool enabled); bool dcCancel(void); void setIqBalance(bool enabled); bool iqBalance(void); void setIgnoreLimits(bool reversed); bool ignoreLimits(void); void setAntennas(std::vector &antennas); void setAntenna(const QString &antenna); void setGainStages(gain_list_t &gain_list); signals: void gainChanged(QString name, double value); void autoGainChanged(bool enabled); void freqCorrChanged(int value); void lnbLoChanged(double freq_mhz); void iqSwapChanged(bool reverse); void dcCancelChanged(bool enabled); void iqBalanceChanged(bool enabled); void ignoreLimitsChanged(bool ignore); void antennaSelected(QString antenna); private slots: void on_lnbSpinBox_valueChanged(double value); void on_agcButton_toggled(bool checked); void on_freqCorrSpinBox_valueChanged(int value); void on_iqSwapButton_toggled(bool checked); void on_dcCancelButton_toggled(bool checked); void on_iqBalanceButton_toggled(bool checked); void on_ignoreButton_toggled(bool checked); void on_antSelector_currentIndexChanged(const QString &antenna); void sliderValueChanged(int value); private: void clearWidgets(); void updateLabel(int idx, double value); void getGains(QMap *gains); void setGains(QMap *gains); private: QList gain_sliders; /*!< A list containing the gain sliders. */ QList gain_labels; /*!< A list containing the gain labels. */ QList value_labels; /*!< A list containing labels showing the current gain value. */ Ui::DockInputCtl *ui; /*!< User interface. */ QGridLayout *gainLayout; /*!< Grid layout containing gain controls and labels. */ }; #endif // DOCKINPUTCTL_H gqrx-sdr-2.2.0.74.d97bd7/qtgui/dockinputctl.ui000066400000000000000000000200661226036373100207140ustar00rootroot00000000000000 DockInputCtl 0 0 220 329 220 150 false Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea Input controls 0 0 0 0 0 QFrame::NoFrame Qt::ScrollBarAlwaysOff true 0 0 220 304 LNB LO -1 0 0 Local oscillator of up or down converter Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter MHz 6 -2000.000000000000000 7900.000000000000000 <html><head/><body><p>Toggle hardware-based automatic gain control.</p><p>This feature requires hardware that supports it. It is known to work with RTL-SDR devices.</p></body></html> Hardware AGC <html><head/><body><p>Swap the the I/Q channels. This can be useful with audio-based input or when dealing with inverted IF.</p></body></html> Swap I/Q false <html><head/><body><p>Ignore hardware limits such as frequency range. This allows experimenting with out-of-spec settings.</p></body></html> No limits Enable automatic DC removal DC rem. Enable automatic I/Q balance (requires gr-iqbal component) IQ bal. Freq. correction Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter ppm -1000 1000 0 Select active antenna connector Antenna 0 0 Select active antenna connector Qt::Vertical 20 21 gqrx-sdr-2.2.0.74.d97bd7/qtgui/dockiqplayer.cpp000066400000000000000000000071621226036373100210470ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include #include #include #include "dockiqplayer.h" #include "ui_dockiqplayer.h" DockIqPlayer::DockIqPlayer(QWidget *parent) : QDockWidget(parent), ui(new Ui::DockIqPlayer), d_pos(0), d_samprate(96000), d_bps(8) /* sizeof(gr_complex) */ { ui->setupUi(this); } DockIqPlayer::~DockIqPlayer() { delete ui; } /*! \brief Open button clicked. Select a new file. */ void DockIqPlayer::on_openButton_clicked() { QString newFile; qDebug() << "Open button clicked."; newFile = QFileDialog::getOpenFileName(this, tr("Open I/Q recording"), "", tr("I/Q recordings (*.bin *.raw)")); if (newFile.isEmpty()) { /* user cancelled */ return; } /* store new file name */ d_fileName = newFile; /* estimate duration */ QFile f(d_fileName); QTime zero(0, 0, 0, 0); QTime dur; d_duration = f.size() / (d_samprate*d_bps); ui->seekSlider->setRange(0, (int)d_duration); /* show duration */ dur = zero.addSecs((int)d_duration); ui->durLabel->setText(dur.toString("HH:mm:ss")); /* update widgets */ setWindowTitle(tr("I/Q Player: %1").arg(d_fileName)); ui->playButton->setEnabled(true); emit fileOpened(d_fileName); } /*! \brief Play button clicked. Start or stop playback, depending on button state. */ void DockIqPlayer::on_playButton_clicked(bool checked) { if (checked) { qDebug() << "Start playback"; ui->openButton->setEnabled(false); ui->seekSlider->setEnabled(true); } else { qDebug() << "Stop playback"; ui->openButton->setEnabled(true); ui->seekSlider->setEnabled(false); } emit playbackToggled(checked, d_fileName); } /*! \brief Seek slider position changed. * \param new_pos The new position in secionds. * * This slot is triggered when the seek slider position changes. This can * either be due to user moving the slider or a new position set via setPos(). * To prevent the latter from triggering emission of posChanged() the new * position is compared to the last one known. If they are equal, the position * was set using setPos(). If they are different the position change was a * result of user action. */ void DockIqPlayer::on_seekSlider_valueChanged(int new_pos) { if (new_pos != d_pos) { /* This was a user action */ d_pos = new_pos; emit posChanged(new_pos); } /* we need to update label in any case */ QTime zero(0, 0, 0, 0); QTime t; t = zero.addSecs(d_pos); ui->posLabel->setText(t.toString("HH:mm:ss")); } /*! \brief Set new playback position. * \param pos The new position in seconds. */ void DockIqPlayer::setPos(int pos) { if (pos != d_pos) { d_pos = pos; ui->seekSlider->setValue(pos); } } gqrx-sdr-2.2.0.74.d97bd7/qtgui/dockiqplayer.h000066400000000000000000000044061226036373100205120ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef DOCKIQPLAYER_H #define DOCKIQPLAYER_H #include #include #include namespace Ui { class DockIqPlayer; } /*! \brief I/Q playback widget. * * This dock widget is used to replay previously recorded I/Q data files. * The widget contains and button to load a file, a play button, a seek * slider and two labels showing the current posision and the duration of * the recording. * * The play button is only active if a file has been loaded. * The seek slider is only active while playing. */ class DockIqPlayer : public QDockWidget { Q_OBJECT public: explicit DockIqPlayer(QWidget *parent = 0); ~DockIqPlayer(); void setPos(int pos); signals: void fileOpened(const QString filename); void playbackToggled(bool play, const QString filename); void posChanged(int new_pos); private slots: void on_openButton_clicked(); void on_playButton_clicked(bool checked); void on_seekSlider_valueChanged(int pos); private: Ui::DockIqPlayer *ui; /*! UI generated by Qt Designer. */ QString d_fileName; /*! Currently loaded file name. */ int d_pos; /*! Last set play position. */ int d_samprate; /*! Current sample rate of input file. */ int d_bps; /*! Byter / sample. */ qint64 d_duration; /*! Duration of current recording (sec). */ /** FIXME: use state? **/ }; #endif // DOCKIQPLAYER_H gqrx-sdr-2.2.0.74.d97bd7/qtgui/dockiqplayer.ui000066400000000000000000000075261226036373100207060ustar00rootroot00000000000000 DockIqPlayer 0 0 482 61 :/icons/icons/signal.svg:/icons/icons/signal.svg Qt::BottomDockWidgetArea|Qt::TopDockWidgetArea I/Q Player 2 5 2 5 2 32 32 32 32 Load I/Q data file :/icons/icons/document.svg:/icons/icons/document.svg false false 32 32 32 32 Replay previously recorded I/Q data :/icons/icons/play.svg:/icons/icons/play.svg true Current position of the player 00:00:00 Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 false Seek Qt::Horizontal total time of the recording 00:00:00 gqrx-sdr-2.2.0.74.d97bd7/qtgui/dockrxopt.cpp000066400000000000000000000301761226036373100203760ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2013 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include #include #include "dockrxopt.h" #include "ui_dockrxopt.h" #define FILT_SEL_USER_IDX 3 DockRxOpt::DockRxOpt(qint64 filterOffsetRange, QWidget *parent) : QDockWidget(parent), ui(new Ui::DockRxOpt), agc_is_on(true), hw_freq_hz(144500000) { ui->setupUi(this); #ifdef Q_WS_MAC // Workaround for Mac, see http://stackoverflow.com/questions/3978889/why-is-qhboxlayout-causing-widgets-to-overlap // Might be fixed in Qt 5? ui->filterButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); ui->modeButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); ui->agcButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); ui->autoSquelchButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); #endif ui->filterFreq->setup(7, -filterOffsetRange/2, filterOffsetRange/2, 1, UNITS_KHZ); ui->filterFreq->setFrequency(0); // demodulator options dialog demodOpt = new CDemodOptions(this); demodOpt->setCurrentPage(CDemodOptions::PAGE_FM_OPT); connect(demodOpt, SIGNAL(fmMaxdevSelected(float)), this, SLOT(demodOpt_fmMaxdevSelected(float))); connect(demodOpt, SIGNAL(fmEmphSelected(double)), this, SLOT(demodOpt_fmEmphSelected(double))); connect(demodOpt, SIGNAL(amDcrToggled(bool)), this, SLOT(demodOpt_amDcrToggled(bool))); // AGC options dialog agcOpt = new CAgcOptions(this); connect(agcOpt, SIGNAL(gainChanged(int)), this, SLOT(agcOpt_gainChanged(int))); connect(agcOpt, SIGNAL(thresholdChanged(int)), this, SLOT(agcOpt_thresholdChanged(int))); connect(agcOpt, SIGNAL(decayChanged(int)), this, SLOT(agcOpt_decayChanged(int))); connect(agcOpt, SIGNAL(slopeChanged(int)), this, SLOT(agcOpt_slopeChanged(int))); connect(agcOpt, SIGNAL(hangChanged(bool)), this, SLOT(agcOpt_hangToggled(bool))); // Noise blanker options nbOpt = new CNbOptions(this); connect(nbOpt, SIGNAL(thresholdChanged(int,double)), this, SLOT(nbOpt_thresholdChanged(int,double))); } DockRxOpt::~DockRxOpt() { delete ui; delete demodOpt; delete agcOpt; delete nbOpt; } /*! \brief Set value of channel filter offset selector. * \param freq_hz The frequency in Hz */ void DockRxOpt::setFilterOffset(qint64 freq_hz) { ui->filterFreq->setFrequency(freq_hz); } /*! \brief Set filter offset range. * \param range_hz The new range in Hz. */ void DockRxOpt::setFilterOffsetRange(qint64 range_hz) { if (range_hz > 0) ui->filterFreq->setup(7, -range_hz/2, range_hz/2, 1, UNITS_KHZ); } /*! \brief Set new RF frequency * \param freq_hz The frequency in Hz * * RF frequency is the frequency to which the device device is tuned to * The actual RX frequency is the sum of the RF frequency and the filter * offset. */ void DockRxOpt::setHwFreq(qint64 freq_hz) { hw_freq_hz = freq_hz; updateHwFreq(); } /*! \brief Update RX frequency label. */ void DockRxOpt::updateHwFreq() { double hw_freq_mhz = hw_freq_hz / 1.0e6; ui->hwFreq->setText(QString("%1 MHz").arg(hw_freq_mhz, 11, 'f', 6, ' ')); } /*! \brief Set filter parameters * \param lo Low cutoff frequency in Hz * \param hi High cutoff frequency in Hz. * * This function will automatically select te "User" preset in the * combo box. */ void DockRxOpt::setFilterParam(int lo, int hi) { float width_f = fabs((hi-lo)/1000.0); ui->filterCombo->setCurrentIndex(FILT_SEL_USER_IDX); ui->filterCombo->setItemText(FILT_SEL_USER_IDX, QString("User (%1k)").arg(width_f)); } /*! \brief Select new filter preset. * \param index Index of the new filter preset (0=wide, 1=normal, 2=narrow). */ void DockRxOpt::setCurrentFilter(int index) { ui->filterCombo->setCurrentIndex(index); } /*! \brief Get current filter preset. * \param The current filter preset (0=wide, 1=normal, 2=narrow). */ int DockRxOpt::currentFilter() { return ui->filterCombo->currentIndex(); } /*! \brief Select new demodulator. * \param demod Demodulator index corresponding to receiver::demod. */ void DockRxOpt::setCurrentDemod(int demod) { if ((demod >= MODE_OFF) && (demod <= MODE_CWU)) ui->modeSelector->setCurrentIndex(demod); } /*! \brief Get current demodulator selection. * \return The current demodulator corresponding to receiver::demod. */ int DockRxOpt::currentDemod() { return ui->modeSelector->currentIndex(); } float DockRxOpt::currentMaxdev() { qDebug() << __FILE__ << __FUNCTION__ << "FIXME"; return 5000.0; } /*! \brief Read receiver configuration from settings data. */ void DockRxOpt::readSettings(QSettings *settings) { bool conv_ok; int intVal; intVal = settings->value("receiver/demod", -1).toInt(&conv_ok); if (intVal >= 0) { setCurrentDemod(intVal); emit demodSelected(intVal); } qint64 offs = settings->value("receiver/offset", 0).toInt(&conv_ok); if (offs) { setFilterOffset(offs); emit filterOffsetChanged(offs); } double dblVal = settings->value("receiver/sql_level", 1.0).toDouble(&conv_ok); if (conv_ok && dblVal < 1.0) { //ui->sqlSlider->setValue(intVal); // signal emitted automatically ui->sqlSpinBox->setValue(dblVal); } } /*! \brief Save receiver configuration to settings. */ void DockRxOpt::saveSettings(QSettings *settings) { settings->setValue("receiver/demod", ui->modeSelector->currentIndex()); qint64 offs = ui->filterFreq->getFrequency(); if (offs) settings->setValue("receiver/offset", offs); else settings->remove("receiver/offset"); qDebug() << __func__ << "*** FIXME_ SQL on/off"; //int sql_lvl = double(ui->sqlSlider->value()); // note: dBFS*10 as int double sql_lvl = ui->sqlSpinBox->value(); if (sql_lvl > -150.0) settings->setValue("receiver/sql_level", sql_lvl); else settings->remove("receiver/sql_level"); } /*! \brief Channel filter offset has changed * \param freq The new filter offset in Hz * * This slot is activated when a new filter offset has been selected either * usig the mouse or using the keyboard. */ void DockRxOpt::on_filterFreq_newFrequency(qint64 freq) { qDebug() << "New filter offset:" << freq << "Hz"; updateHwFreq(); emit filterOffsetChanged(freq); } /*! \brief New filter preset selected. * * Instead of implementing a new signal, we simply emit demodSelected() since demodulator * and filter preset are tightly coupled. */ void DockRxOpt::on_filterCombo_activated(int index) { Q_UNUSED(index); qDebug() << "New filter preset:" << ui->filterCombo->currentText(); emit demodSelected(ui->modeSelector->currentIndex()); } /*! \brief Filter shape (TBC). */ void DockRxOpt::on_filterButton_clicked() { } /*! \brief Mode selector activated. * \param New mode selection. * * This slot is activated when the user selects a new demodulator (mode change). * It is connected automatically by the UI constructor, and it emits the demodSelected() * signal. * * Note that the modes listed in the selector are different from those defined by * receiver::demod (we want to list LSB/USB separately but they have identical demods). */ void DockRxOpt::on_modeSelector_activated(int index) { qDebug() << "New mode: " << index; if (index == MODE_RAW) { qDebug() << "Raw I/Q not implemented (fallback to FM-N)"; ui->modeSelector->setCurrentIndex(MODE_NFM); emit demodSelected(MODE_NFM); return; } // update demodulator option widget if (index == MODE_NFM) demodOpt->setCurrentPage(CDemodOptions::PAGE_FM_OPT); else if (index == MODE_AM) demodOpt->setCurrentPage(CDemodOptions::PAGE_AM_OPT); else demodOpt->setCurrentPage(CDemodOptions::PAGE_NO_OPT); emit demodSelected(index); } /*! \brief Show demodulator options. */ void DockRxOpt::on_modeButton_clicked() { demodOpt->show(); } /*! \brief Show AGC options. */ void DockRxOpt::on_agcButton_clicked() { agcOpt->show(); } /*! \brief Auto-squelch button clicked. * * This slot is called when the user clicks on the auto-squelch button. * */ void DockRxOpt::on_autoSquelchButton_clicked() { // Emit signal double newval = sqlAutoClicked(); // FIXME: We rely on signal only being connected to one slot ui->sqlSpinBox->setValue(newval); } /*! \brief AGC preset has changed. */ void DockRxOpt::on_agcPresetCombo_activated(int index) { CAgcOptions::agc_preset_e preset = (CAgcOptions::agc_preset_e) index; switch (preset) { case CAgcOptions::AGC_FAST: case CAgcOptions::AGC_MEDIUM: case CAgcOptions::AGC_SLOW: case CAgcOptions::AGC_USER: if (!agc_is_on) { emit agcToggled(true); agc_is_on = true; } agcOpt->setPreset(preset); break; case CAgcOptions::AGC_OFF: if (agc_is_on) { emit agcToggled(false); agc_is_on = false; } agcOpt->setPreset(preset); break; default: qDebug() << "Invalid AGC preset:" << index; } } void DockRxOpt::agcOpt_hangToggled(bool checked) { qDebug() << "AGC hang" << (checked ? "ON" : "OFF"); emit agcHangToggled(checked); } /*! \brief AGC threshold ("knee") changed. * \param value The new AGC threshold in dB. */ void DockRxOpt::agcOpt_thresholdChanged(int value) { qDebug() << "AGC threshold:" << value; emit agcThresholdChanged(value); } /*! \brief AGC slope factor changed. * \param value The new slope factor in dB. */ void DockRxOpt::agcOpt_slopeChanged(int value) { qDebug() << "AGC slope:" << value; emit agcSlopeChanged(value); } /*! \brief AGC decay changed. * \param value The new decay rate in ms (tbc). */ void DockRxOpt::agcOpt_decayChanged(int value) { qDebug() << "AGC decay:" << value; emit agcDecayChanged(value); } /*! \brief AGC manual gain changed. * \param gain The new gain in dB. */ void DockRxOpt::agcOpt_gainChanged(int gain) { qDebug() << "AGC manual gain:" << gain; emit agcGainChanged(gain); } /*! \brief Squelch level change. * \param value The new squelch level in dB. */ void DockRxOpt::on_sqlSpinBox_valueChanged(double value) { emit sqlLevelChanged(value); } /*! \brief FM deviation changed by user. * \param max_dev The new deviation in Hz. */ void DockRxOpt::demodOpt_fmMaxdevSelected(float max_dev) { emit fmMaxdevSelected(max_dev); } /*! \brief FM de-emphasis changed by user. * \param tau The new time constant in uS. */ void DockRxOpt::demodOpt_fmEmphSelected(double tau) { emit fmEmphSelected(tau); } /*! \brief AM DC removal toggled by user. * \param enabled Whether DCR is enabled or not. */ void DockRxOpt::demodOpt_amDcrToggled(bool enabled) { emit amDcrToggled(enabled); } /*! \brief Noise blanker 1 button has been toggled. */ void DockRxOpt::on_nb1Button_toggled(bool checked) { emit noiseBlankerChanged(1, checked, (float) nbOpt->nbThreshold(1)); } /*! \brief Noise blanker 2 button has been toggled. */ void DockRxOpt::on_nb2Button_toggled(bool checked) { emit noiseBlankerChanged(2, checked, (float) nbOpt->nbThreshold(2)); } /*! \brief Noise blanker threshold has been changed. */ void DockRxOpt::nbOpt_thresholdChanged(int nbid, double value) { if (nbid == 1) emit noiseBlankerChanged(nbid, ui->nb1Button->isChecked(), (float) value); else emit noiseBlankerChanged(nbid, ui->nb2Button->isChecked(), (float) value); } void DockRxOpt::on_nbOptButton_clicked() { nbOpt->show(); } gqrx-sdr-2.2.0.74.d97bd7/qtgui/dockrxopt.h000066400000000000000000000135361226036373100200440ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2013 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef DOCKRXOPT_H #define DOCKRXOPT_H #include #include #include "qtgui/agc_options.h" #include "qtgui/demod_options.h" #include "qtgui/nb_options.h" namespace Ui { class DockRxOpt; } /*! \brief Dock window with receiver options. * \ingroup UI * * This dock widget encapsulates the receiver options. The controls * are grouped in a tool box that allows packing many controls in little space. * The UI itself is in the dockrxopt.ui file. * * This class also provides the signal/slot API necessary to connect * the encapsulated widgets to the rest of the application. */ class DockRxOpt : public QDockWidget { Q_OBJECT public: /*! \brief Mode selector entries. */ enum rxopt_mode_idx { MODE_OFF = 0, /*!< Demodulator completely off. */ MODE_RAW = 1, /*!< Raw I/Q passthrough. */ MODE_AM = 2, /*!< Amplitude modulation. */ MODE_NFM = 3, /*!< Narrow band FM. */ MODE_WFM_MONO = 4, /*!< Broadcast FM (mono). */ MODE_WFM_STEREO = 5, /*!< Broadcast FM (stereo). */ MODE_LSB = 6, /*!< Lower side band. */ MODE_USB = 7, /*!< Upper side band. */ MODE_CWL = 8, /*!< CW using LSB filter. */ MODE_CWU = 9 /*!< CW using USB filter. */ }; explicit DockRxOpt(qint64 filterOffsetRange = 90000, QWidget *parent = 0); ~DockRxOpt(); void readSettings(QSettings *settings); void saveSettings(QSettings *settings); void setFilterOffsetRange(qint64 range_hz); void setFilterParam(int lo, int hi); void setCurrentFilter(int index); int currentFilter(); void setHwFreq(qint64 freq_hz); void setCurrentDemod(int demod); int currentDemod(); float currentMaxdev(); public slots: void setFilterOffset(qint64 freq_hz); private: void updateHwFreq(); signals: /*! \brief Signal emitted when the channel filter frequency has changed. */ void filterOffsetChanged(qint64 freq_hz); /*! \brief Signal emitted when new demodulator is selected. */ void demodSelected(int demod); /*! \brief Signal emitted when new FM deviation is selected. */ void fmMaxdevSelected(float max_dev); /*! \brief Signal emitted when new FM de-emphasis constant is selected. */ void fmEmphSelected(double tau); /*! \brief Signal emitted when AM DCR status is toggled. */ void amDcrToggled(bool enabled); /*! \brief Signal emitted when baseband gain has changed. Gain is in dB. */ //void bbGainChanged(float gain); /*! \brief Signal emitted when squelch level has changed. Level is in dBFS. */ void sqlLevelChanged(double level); /*! \brief Signal emitted when auto squelch level is clicked. * * \note Need current signal/noise level returned */ double sqlAutoClicked(); /*! \brief Signal emitted when AGC is togglen ON/OFF. */ void agcToggled(bool agc_on); /*! \brief Signal emitted when AGC hang is toggled. */ void agcHangToggled(bool use_hang); /*! \brief Signal emitted when AGC threshold has changed. Threshold in dB. */ void agcThresholdChanged(int value); /*! \brief Signal emitted when AGC slope has changed. Slope is in dB.*/ void agcSlopeChanged(int slope); /*! \brief Signal emitted when AGC decay has changed. Decay is in millisec.*/ void agcDecayChanged(int decay); /*! \brief Signal emitted when AGC manual gain has changed. Gain is in dB.*/ void agcGainChanged(int gain); /*! \brief Signal emitted when noise blanker status has changed. */ void noiseBlankerChanged(int nbid, bool on, float threshold); private slots: void on_filterFreq_newFrequency(qint64 freq); void on_filterCombo_activated(int index); void on_filterButton_clicked(); void on_modeSelector_activated(int index); void on_modeButton_clicked(); void on_agcButton_clicked(); void on_autoSquelchButton_clicked(); void on_agcPresetCombo_activated(int index); void on_sqlSpinBox_valueChanged(double value); void on_nb1Button_toggled(bool checked); void on_nb2Button_toggled(bool checked); void on_nbOptButton_clicked(); // Signals coming from noise blanker pop-up void nbOpt_thresholdChanged(int nbid, double value); // Signals coming from demod options pop-up void demodOpt_fmMaxdevSelected(float max_dev); void demodOpt_fmEmphSelected(double tau); void demodOpt_amDcrToggled(bool enabled); // Signals coming from AGC options popup void agcOpt_hangToggled(bool checked); void agcOpt_gainChanged(int value); void agcOpt_thresholdChanged(int value); void agcOpt_slopeChanged(int value); void agcOpt_decayChanged(int value); private: Ui::DockRxOpt *ui; /*! The Qt designer UI file. */ CDemodOptions *demodOpt; /*! Demodulator options. */ CAgcOptions *agcOpt; /*! AGC options. */ CNbOptions *nbOpt; /*! Noise blanker options. */ bool agc_is_on; qint64 hw_freq_hz; /*! Current PLL frequency in Hz. */ }; #endif // DOCKRXOPT_H gqrx-sdr-2.2.0.74.d97bd7/qtgui/dockrxopt.ui000066400000000000000000000555711226036373100202370ustar00rootroot00000000000000 DockRxOpt 0 0 250 312 0 0 250 162 524287 524287 Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea Receiver Options 5 0 0 0 0 0 0 220 32 <html><head/><body><p>Channel filter offset.<br/> This is an offset from the hardware RF frequency.</p></body></html> QFrame::StyledPanel QFrame::Raised QFrame::NoFrame Qt::ScrollBarAlwaysOff true 0 0 250 250 3 5 5 5 5 QLayout::SetDefaultConstraint The frequency of the hardware (not including LNB) Hardware freq: Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter The frequency of the hardware (not including LNB) 144.500000 MHz Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Qt::Horizontal 10 5 0 0 28 28 10 AGC options :/icons/icons/settings.svg:/icons/icons/settings.svg 16 16 false false false 0 0 28 28 10 Channel filter settings :/icons/icons/settings.svg:/icons/icons/settings.svg 16 16 false false 0 0 28 28 10 Demodulator options :/icons/icons/settings.svg:/icons/icons/settings.svg 16 16 false false AGC Filter Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 0 0 Mode Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 0 -1 0 0 0 28 Demodulator type (mode) Demodulator type (mode) 3 Demod Off Raw I/Q AM Narrow FM WFM (mono) WFM (stereo) LSB USB CW-L CW-U true 0 0 0 28 AGC presets Fast Medium Slow User Off true 0 0 0 28 Apply mode dependent filter preset 1 Wide Normal Narrow User Squelch level in dB below full scale Squelch Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 0 0 Squelch level in dB below full scale Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter true dBFS 1 -150.000000000000000 0.000000000000000 1.000000000000000 -150.000000000000000 0 0 28 28 Set squelch to the current signal or noise level A 16 16 Qt::Horizontal true 0 28 0 28 16777215 16777215 Noise blanker for atatic type noise NB1 true true 0 0 0 28 16777215 16777215 Noise blanker for pulse type noise NB2 true 0 0 28 28 10 Noise blanker options :/icons/icons/settings.svg:/icons/icons/settings.svg 16 16 false false Qt::Vertical 20 35 CFreqCtrl QFrame
    qtgui/freqctrl.h
    1
    gqrx-sdr-2.2.0.74.d97bd7/qtgui/freqctrl.cpp000066400000000000000000000650661226036373100202110ustar00rootroot00000000000000///////////////////////////////////////////////////////////////////// // freqctrl.cpp: implementation of the CFreqCtrl class. // // This class implements a frequency control widget to set/change //frequency data. // // History: // 2010-09-15 Initial creation MSW // 2011-03-27 Initial release // 2011-04-17 Fixed bug with m_Oldfreq being set after emit instead of before ///////////////////////////////////////////////////////////////////// //========================================================================================== // + + + This Software is released under the "Simplified BSD License" + + + //Copyright 2010 Moe Wheatley. All rights reserved. // //Redistribution and use in source and binary forms, with or without modification, are //permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, this list of // conditions and the following disclaimer. // // 2. Redistributions in binary form must reproduce the above copyright notice, this list // of conditions and the following disclaimer in the documentation and/or other materials // provided with the distribution. // //THIS SOFTWARE IS PROVIDED BY Moe Wheatley ``AS IS'' AND ANY EXPRESS OR IMPLIED //WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND //FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Moe Wheatley OR //CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR //CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR //SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON //ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING //NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF //ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // //The views and conclusions contained in the software and documentation are those of the //authors and should not be interpreted as representing official policies, either expressed //or implied, of Moe Wheatley. //========================================================================================== //#include #include #include "freqctrl.h" //Manual adjustment of Font size as percent of control height #define DIGIT_SIZE_PERCENT 90 #define UNITS_SIZE_PERCENT 60 //adjustment for separation between digits #define SEPRATIO_N 100 //separation rectangle size ratio numerator times 100 #define SEPRATIO_D 3 //separation rectangle size ratio denominator ///////////////////////////////////////////////////////////////////// // Constructor/Destructor ///////////////////////////////////////////////////////////////////// CFreqCtrl::CFreqCtrl(QWidget *parent) : QFrame(parent) { setAutoFillBackground(false); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setFocusPolicy(Qt::StrongFocus); setMouseTracking(true); m_BkColor = QColor(0x20,0x20,0x20,0xFF); m_DigitColor = QColor(0xFF, 0xE6, 0xC8, 0xFF); m_HighlightColor = QColor(0x5A, 0x5A, 0x5A, 0xFF); m_UnitsColor = Qt::gray; m_freq = 146123456; setup(10, 1, 4000000000U, 1, UNITS_MHZ); m_Oldfreq = 0; m_LastLeadZeroPos = 0; m_LRMouseFreqSel = false; m_ActiveEditDigit = -1; m_ResetLowerDigits = false; m_UnitsFont = QFont("Arial", 12, QFont::Normal); m_DigitFont = QFont("Arial", 12, QFont::Normal); } CFreqCtrl::~CFreqCtrl() { } ///////////////////////////////////////////////////////////////////// // Size hint stuff ///////////////////////////////////////////////////////////////////// QSize CFreqCtrl::minimumSizeHint() const { return QSize(100, 20); } QSize CFreqCtrl::sizeHint() const { return QSize(100, 20); } ///////////////////////////////////////////////////////////////////// // Various helper functions ///////////////////////////////////////////////////////////////////// bool CFreqCtrl::inRect(QRect &rect, QPoint &point) { if ((point.x() < rect.right()) && (point.x() > rect.x()) && (point.y() < rect.bottom()) && (point.y() > rect.y())) return true; else return false; } ////////////////////////////////////////////////////////////////////////////// // Setup various parameters for the control ////////////////////////////////////////////////////////////////////////////// void CFreqCtrl::setup(int NumDigits, qint64 Minf, qint64 Maxf,int MinStep, FUNITS UnitsType) { int i; qint64 pwr = 1; m_LastEditDigit = 0; m_Oldfreq = -1; m_NumDigits = NumDigits; if (m_NumDigits > MAX_DIGITS) m_NumDigits = MAX_DIGITS; if (m_NumDigits < MIN_DIGITS) m_NumDigits = MIN_DIGITS; m_UnitString = ""; m_MinStep = MinStep; if (m_MinStep == 0) m_MinStep = 1; m_MinFreq = Minf; m_MaxFreq = Maxf; if (m_freq < m_MinFreq) m_freq = m_MinFreq; if (m_freq > m_MaxFreq) m_freq = m_MaxFreq; for (i = 0; i < m_NumDigits; i++) { m_DigitInfo[i].weight = pwr; m_DigitInfo[i].incval = pwr; m_DigitInfo[i].modified = true; m_DigitInfo[i].editmode = false; m_DigitInfo[i].val = 0; pwr *= 10; } if (m_MaxFreq > pwr) m_MaxFreq = pwr-1; m_MaxFreq = m_MaxFreq - m_MaxFreq%m_MinStep; if (m_MinFreq > pwr) m_MinFreq = 1; m_MinFreq = m_MinFreq - m_MinFreq % m_MinStep; m_DigStart = 0; switch (UnitsType) { case UNITS_HZ: m_DecPos = 0; m_UnitString = "Hz "; break; case UNITS_KHZ: m_DecPos = 3; m_UnitString = "kHz"; break; case UNITS_MHZ: m_DecPos = 6; m_UnitString = "MHz"; break; case UNITS_GHZ: m_DecPos = 9; m_UnitString = "GHz"; break; case UNITS_SEC: m_DecPos = 6; m_UnitString = "Sec"; break; case UNITS_MSEC: m_DecPos = 3; m_UnitString = "mS "; break; case UNITS_USEC: m_DecPos = 0; m_UnitString = "uS "; break; case UNITS_NSEC: m_DecPos = 0; m_UnitString = "nS "; break; } for (i = m_NumDigits-1; i >= 0; i--) { if (m_DigitInfo[i].weight <= m_MinStep) { if (m_DigStart == 0) { m_DigitInfo[i].incval = m_MinStep; m_DigStart = i; } else { if ((m_MinStep%m_DigitInfo[i+1].weight) != 0) m_DigStart = i; m_DigitInfo[i].incval = 0; } } } m_NumSeps = (m_NumDigits-1)/3 - m_DigStart/3; } ////////////////////////////////////////////////////////////////////////////// // Sets the frequency and individual digit values ////////////////////////////////////////////////////////////////////////////// void CFreqCtrl::setFrequency(qint64 freq) { int i; qint64 acc = 0; qint64 rem; int val; if (freq == m_Oldfreq) return; if (freq < m_MinFreq) freq = m_MinFreq; if (freq > m_MaxFreq) freq = m_MaxFreq; m_freq = freq - freq % m_MinStep; rem = m_freq; m_LeadZeroPos = m_NumDigits; for (i = m_NumDigits-1; i >= m_DigStart; i--) { val = (int)(rem / m_DigitInfo[i].weight); if (m_DigitInfo[i].val != val) { m_DigitInfo[i].val = val; m_DigitInfo[i].modified = true; } rem = rem - val*m_DigitInfo[i].weight; acc += val; if ((acc == 0) && (i > m_DecPos)) { m_LeadZeroPos = i; } } // When frequency is negative all non-zero digits that // have changed will have a negative sign. This loop will // change all digits back to positive, except the one at // position m_leadZeroPos-1 /** TBC if this works for all configurations */ if (m_freq < 0) { if (m_DigitInfo[m_LeadZeroPos-1].val > 0) m_DigitInfo[m_LeadZeroPos-1].val = -m_DigitInfo[m_LeadZeroPos-1].val; for (i = 0; i < (m_LeadZeroPos-1); i++) { if (m_DigitInfo[i].val < 0) m_DigitInfo[i].val = -m_DigitInfo[i].val; } } // signal the new frequency to world m_Oldfreq = m_freq; emit newFrequency(m_freq); updateCtrl(m_LastLeadZeroPos != m_LeadZeroPos); m_LastLeadZeroPos = m_LeadZeroPos; } ////////////////////////////////////////////////////////////////////////////// // Sets the Digit and comma and decimal pt color ////////////////////////////////////////////////////////////////////////////// void CFreqCtrl::setDigitColor(QColor cr) { m_UpdateAll = true; m_DigitColor = cr; for(int i=m_DigStart; i=0) { if( m_DigitInfo[m_ActiveEditDigit].editmode ) { m_DigitInfo[m_ActiveEditDigit].editmode = false; m_DigitInfo[m_ActiveEditDigit].modified = true; m_ActiveEditDigit = -1; updateCtrl(false); } } } ///////////////////////////////////////////////////////////////////// // main draw event for this control ///////////////////////////////////////////////////////////////////// void CFreqCtrl::paintEvent(QPaintEvent *) { QPainter painter(&m_Pixmap); if (m_UpdateAll) //if need to redraw everything { drawBkGround(painter); m_UpdateAll = false; } // draw any modified digits to the m_MemDC drawDigits(painter); //now draw pixmap onto screen QPainter scrnpainter(this); scrnpainter.drawPixmap(0,0,m_Pixmap); //blt to the screen(flickers like a candle, why?) } ///////////////////////////////////////////////////////////////////// // Mouse Event overrides ///////////////////////////////////////////////////////////////////// void CFreqCtrl::mouseMoveEvent(QMouseEvent * event) { QPoint pt = event->pos(); //find which digit is to be edited if (isActiveWindow()) { if (!hasFocus()) setFocus(Qt::MouseFocusReason); for (int i = m_DigStart; i < m_NumDigits; i++) { if (inRect(m_DigitInfo[i].dQRect, pt)) { if (!m_DigitInfo[i].editmode) { m_DigitInfo[i].editmode = true; m_ActiveEditDigit = i; } } else { //un-highlight the previous digit if moved off it if (m_DigitInfo[i].editmode) { m_DigitInfo[i].editmode = false; m_DigitInfo[i].modified = true; } } } updateCtrl(false); } } ////////////////////////////////////////////////////////////////////////////// // Service mouse button clicks to inc or dec the selected frequency ////////////////////////////////////////////////////////////////////////////// void CFreqCtrl::mousePressEvent(QMouseEvent * event) { QPoint pt = event->pos(); if (event->button() == Qt::LeftButton) { for (int i = m_DigStart; i < m_NumDigits; i++) { if (inRect(m_DigitInfo[i].dQRect, pt)) //if in i'th digit { if (m_LRMouseFreqSel) { incFreq(); } else { if (pt.y() < m_DigitInfo[i].dQRect.bottom()/2) //top half? incFreq(); else decFreq(); //bottom half } } } } else if (event->button() == Qt::RightButton) { for (int i = m_DigStart; i < m_NumDigits; i++) { if (inRect(m_DigitInfo[i].dQRect, pt)) //if in i'th digit { if (m_LRMouseFreqSel) { decFreq(); } else { if (pt.y() < m_DigitInfo[i].dQRect.bottom()/2) //top half? incFreq();//IncDigit(); else decFreq();//DecDigit(); //botom half } } } } } ///////////////////////////////////////////////////////////////////// // Mouse Wheel Event overrides ///////////////////////////////////////////////////////////////////// void CFreqCtrl::wheelEvent(QWheelEvent * event) { QPoint pt = event->pos(); int numDegrees = event->delta() / 8; int numSteps = numDegrees / 15; for (int i = m_DigStart; i < m_NumDigits; i++) { if (inRect(m_DigitInfo[i].dQRect, pt)) //if in i'th digit { if (numSteps > 0) incFreq(); else if (numSteps < 0) decFreq(); } } } ///////////////////////////////////////////////////////////////////// // Keyboard Event overrides ///////////////////////////////////////////////////////////////////// void CFreqCtrl::keyPressEvent( QKeyEvent * event ) { //call base class if dont over ride key bool fSkipMsg = false; qint64 tmp; //qDebug() <key(); switch(event->key()) { case Qt::Key_0: case Qt::Key_1: case Qt::Key_2: case Qt::Key_3: case Qt::Key_4: case Qt::Key_5: case Qt::Key_6: case Qt::Key_7: case Qt::Key_8: case Qt::Key_9: if (m_ActiveEditDigit >= 0) { if (m_DigitInfo[m_ActiveEditDigit].editmode) { tmp = (m_freq/m_DigitInfo[m_ActiveEditDigit].weight) % 10; m_freq -= tmp*m_DigitInfo[m_ActiveEditDigit].weight; m_freq = m_freq+(event->key()-'0')*m_DigitInfo[m_ActiveEditDigit].weight; setFrequency(m_freq); } } moveCursorRight(); fSkipMsg = true; break; case Qt::Key_Left: if (m_ActiveEditDigit != -1) { moveCursorLeft(); fSkipMsg = true; } break; case Qt::Key_Up: if (m_ActiveEditDigit != -1) { incFreq(); fSkipMsg = true; } break; case Qt::Key_Down: if (m_ActiveEditDigit != -1) { decFreq(); fSkipMsg = true; } break; case Qt::Key_Right: if (m_ActiveEditDigit != -1) { moveCursorRight(); fSkipMsg = true; } break; case Qt::Key_Home: cursorHome(); fSkipMsg = true; break; case Qt::Key_End: cursorEnd(); fSkipMsg = true; break; default: break; } if (!fSkipMsg) QFrame::keyPressEvent(event); } ////////////////////////////////////////////////////////////////////////////// // Calculates all the rectangles for the digits, separators, and units text // and creates the fonts for them. ////////////////////////////////////////////////////////////////////////////// void CFreqCtrl::drawBkGround(QPainter &Painter) { QRect rect(0, 0, width(), height()); //qDebug() < m_DigStart) && ((i%3)==0)) { m_SepRect[i].setCoords(digpos - sepwidth, rect.top(), digpos, rect.bottom()); Painter.fillRect(m_SepRect[i], m_BkColor); digpos -= sepwidth; if (i==m_DecPos) Painter.drawText(m_SepRect[i], Qt::AlignHCenter|Qt::AlignVCenter, "."); else if (i>m_DecPos && i= m_LeadZeroPos) Painter.setPen(m_BkColor); else Painter.setPen(m_DigitColor); Painter.drawText(m_DigitInfo[i].dQRect, Qt::AlignHCenter|Qt::AlignVCenter, QString().number(m_DigitInfo[i].val)); m_DigitInfo[i].modified = false; } } } ////////////////////////////////////////////////////////////////////////////// // Increment just the digit active in edit mode ////////////////////////////////////////////////////////////////////////////// void CFreqCtrl::incDigit() { /** FIXME: no longer used? */ int tmp; qint64 tmpl; if (m_ActiveEditDigit >= 0) { if (m_DigitInfo[m_ActiveEditDigit].editmode) { if (m_DigitInfo[m_ActiveEditDigit].weight == m_DigitInfo[m_ActiveEditDigit].incval) { // get the current digit value tmp = (int)((m_freq/m_DigitInfo[m_ActiveEditDigit].weight) % 10); // set the current digit value to zero m_freq -= tmp*m_DigitInfo[m_ActiveEditDigit].weight; tmp++; if (tmp > 9) tmp = 0; m_freq = m_freq + (qint64)tmp*m_DigitInfo[m_ActiveEditDigit].weight; } else { tmp = (int)((m_freq/m_DigitInfo[m_ActiveEditDigit+1].weight) % 10); tmpl = m_freq + m_DigitInfo[m_ActiveEditDigit].incval; if (tmp != (int)((tmpl/m_DigitInfo[m_ActiveEditDigit+1].weight) % 10)) { tmpl -= m_DigitInfo[m_ActiveEditDigit+1].weight; } m_freq = tmpl; } setFrequency(m_freq); } } } ////////////////////////////////////////////////////////////////////////////// // Increment the frequency by this digit active in edit mode ////////////////////////////////////////////////////////////////////////////// void CFreqCtrl::incFreq() { if (m_ActiveEditDigit >= 0) { if (m_DigitInfo[m_ActiveEditDigit].editmode) { m_freq += m_DigitInfo[m_ActiveEditDigit].incval; if (m_ResetLowerDigits) { /* Set digits below the active one to 0 */ m_freq = m_freq - m_freq%m_DigitInfo[m_ActiveEditDigit].weight; } setFrequency(m_freq); m_LastEditDigit = m_ActiveEditDigit; } } } ////////////////////////////////////////////////////////////////////////////// // Decrement the digit active in edit mode ////////////////////////////////////////////////////////////////////////////// void CFreqCtrl::decDigit() { /** FIXME: no longer used? */ int tmp; qint64 tmpl; if (m_ActiveEditDigit >= 0) { if (m_DigitInfo[m_ActiveEditDigit].editmode) { if (m_DigitInfo[m_ActiveEditDigit].weight == m_DigitInfo[m_ActiveEditDigit].incval) { // get the current digit value tmp = (int)((m_freq/m_DigitInfo[m_ActiveEditDigit].weight) % 10); // set the current digit value to zero m_freq -= tmp*m_DigitInfo[m_ActiveEditDigit].weight; tmp--; if (tmp < 0) tmp = 9; m_freq = m_freq+(qint64)tmp*m_DigitInfo[m_ActiveEditDigit].weight; } else { tmp = (int)((m_freq/m_DigitInfo[m_ActiveEditDigit+1].weight) % 10); tmpl = m_freq - m_DigitInfo[m_ActiveEditDigit].incval; if (tmp != (int)((tmpl/m_DigitInfo[m_ActiveEditDigit+1].weight)%10)) { tmpl += m_DigitInfo[m_ActiveEditDigit+1].weight; } m_freq = tmpl; } setFrequency(m_freq); } } } ////////////////////////////////////////////////////////////////////////////// // Decrement the frequency by this digit active in edit mode ////////////////////////////////////////////////////////////////////////////// void CFreqCtrl::decFreq() { if (m_ActiveEditDigit >= 0) { if (m_DigitInfo[m_ActiveEditDigit].editmode) { m_freq -= m_DigitInfo[m_ActiveEditDigit].incval; if (m_ResetLowerDigits) { /* digits below the active one are reset to 0 */ m_freq = m_freq - m_freq%m_DigitInfo[m_ActiveEditDigit].weight; } setFrequency(m_freq); m_LastEditDigit = m_ActiveEditDigit; } } } ///////////////////////////////////////////////////////////////////// // Cursor move routines for arrow key editing ///////////////////////////////////////////////////////////////////// void CFreqCtrl::moveCursorLeft() { if ((m_ActiveEditDigit >= 0) && (m_ActiveEditDigit < m_NumDigits-1)) { cursor().setPos(mapToGlobal(m_DigitInfo[++m_ActiveEditDigit].dQRect.center())); } } void CFreqCtrl::moveCursorRight() { if (m_ActiveEditDigit > m_FirstEditableDigit) { cursor().setPos(mapToGlobal(m_DigitInfo[--m_ActiveEditDigit].dQRect.center())); } } void CFreqCtrl::cursorHome() { if (m_ActiveEditDigit >= 0) { cursor().setPos(mapToGlobal( m_DigitInfo[m_NumDigits-1].dQRect.center())); } } void CFreqCtrl::cursorEnd() { if (m_ActiveEditDigit > 0) { cursor().setPos(mapToGlobal(m_DigitInfo[m_FirstEditableDigit].dQRect.center())); } } gqrx-sdr-2.2.0.74.d97bd7/qtgui/freqctrl.h000066400000000000000000000102361226036373100176430ustar00rootroot00000000000000////////////////////////////////////////////////////////////////////// // freqctrl.h: interface for the CFreqCtrl class. // // History: // 2010-09-15 Initial creation MSW // 2011-03-27 Initial release ///////////////////////////////////////////////////////////////////// #ifndef FREQCTRL_H #define FREQCTRL_H /////////////////////////////////////////////////////////////////////// // To use this control, add a frame using the QT designer editor. // Promote it to the CFreqCtrl class and include file freqctrl.h // Initilaize the control in the constructor of the controls parent // ex: ui->frameFreqCtrl->Setup(9, 10000U, 230000000U, 1, UNITS_MHZ ); // where 9 is the number of display digits, min freq is 10kHz , Max is 230MHz // the minimum step size is 1Hz and the freq is displayed as MHz // NOTE: the frequency is a qint64 64 bit integer value // to change frequency call SetFrequency() // ex: ui->frameFreqCtrl->SetFrequency(146000000); // // One signal is sent when the control frequency changes: //void NewFrequency(qint64 freq); //emitted when frequency has changed /////////////////////////////////////////////////////////////////////// #include #include #include enum FUNITS { UNITS_HZ, UNITS_KHZ, UNITS_MHZ, UNITS_GHZ, UNITS_SEC, UNITS_MSEC, UNITS_USEC, UNITS_NSEC }; #define MAX_DIGITS 12 #define MIN_DIGITS 4 class CFreqCtrl : public QFrame { Q_OBJECT public: explicit CFreqCtrl(QWidget *parent = 0); ~CFreqCtrl(); QSize minimumSizeHint() const; QSize sizeHint() const; //primary access routines void setup(int NumDigits, qint64 Minf, qint64 Maxf,int MinStep, FUNITS UnitsType); void setFrequency(qint64 freq); void setUnits(FUNITS units); void setDigitColor(QColor cr); void setBkColor(QColor cr); void setUnitsColor(QColor cr); void setHighlightColor(QColor cr); qint64 getFrequency() { return m_freq; } signals: void newFrequency(qint64 freq); //emitted when frequency has changed public slots: protected: //overrides for this control void paintEvent(QPaintEvent *); void resizeEvent(QResizeEvent *); void mouseMoveEvent(QMouseEvent *); void mousePressEvent(QMouseEvent *); void wheelEvent(QWheelEvent *); void leaveEvent(QEvent *); void keyPressEvent(QKeyEvent *); private: void updateCtrl(bool all); void drawBkGround(QPainter &Painter); void drawDigits(QPainter &Painter); void incDigit(); void decDigit(); void incFreq(); void decFreq(); void cursorHome(); void cursorEnd(); void moveCursorLeft(); void moveCursorRight(); bool inRect(QRect &rect, QPoint &point); bool m_UpdateAll; bool m_ExternalKeyActive; bool m_LRMouseFreqSel; /*! Use left/right mouse buttons. If FALSE click area determines up/down. */ bool m_ResetLowerDigits; /*! If TRUE digits below the active one will be reset to 0 when the active digit is incremented or decremented. */ int m_FirstEditableDigit; int m_LastLeadZeroPos; int m_LeadZeroPos; int m_NumDigits; int m_DigStart; int m_ActiveEditDigit; int m_LastEditDigit; int m_DecPos; int m_NumSeps; qint64 m_MinStep; qint64 m_freq; qint64 m_Oldfreq; qint64 m_MinFreq; qint64 m_MaxFreq; QColor m_DigitColor; QColor m_BkColor; QColor m_UnitsColor; QColor m_HighlightColor; QPixmap m_Pixmap; QSize m_Size; FUNITS m_Units; QRect m_rectCtrl; //main control rectangle QRect m_UnitsRect; //rectangle where Units text goes QRect m_SepRect[MAX_DIGITS]; //separation rectangles for commas,dec pt, etc. QString m_UnitString; QFont m_DigitFont; QFont m_UnitsFont; struct DigStuct { qint64 weight; //decimal weight of this digit qint64 incval; //value this digit increments or decrements QRect dQRect; //Digit bounding rectangle int val; //value of this digit(0-9) bool modified; //set if this digit has been modified bool editmode; //set if this digit is selected for editing } m_DigitInfo[MAX_DIGITS]; }; #endif // FREQCTRL_H gqrx-sdr-2.2.0.74.d97bd7/qtgui/ioconfig.cpp000066400000000000000000000344471226036373100201630ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2013 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #ifdef WITH_PULSEAUDIO #include "pulseaudio/pa_device_list.h" #elif defined(WITH_PORTAUDIO) #include "portaudio/device_list.h" #endif #include "qtgui/ioconfig.h" #include "ui_ioconfig.h" CIoConfig::CIoConfig(QSettings *settings, QWidget *parent) : QDialog(parent), ui(new Ui::CIoConfig), m_settings(settings) { unsigned int i=0; QString devstr; QString devlabel; bool cfgmatch=false; //flag to indicate that device from config was found ui->setupUi(this); QString indev = settings->value("input/device", "").toString(); // automatic discovery of FCD does not work on Mac // so we do it ourselves if we have portaudio #if defined(Q_WS_MAC) && defined(WITH_PORTAUDIO) portaudio_device_list devices; inDevList = devices.get_input_devices(); string this_dev; for (i = 0; i < inDevList.size(); i++) { this_dev = inDevList[i].get_name(); if (this_dev.find("FUNcube Dongle V1.0") != string::npos) { devstr = "fcd,type=1,device='FUNcube Dongle V1.0'"; ui->inDevCombo->addItem("FUNcube Dongle V1.0", QVariant(devstr)); } else if (this_dev.find("FUNcube Dongle V2.0") != string::npos) { devstr = "fcd,type=2,device='FUNcube Dongle V2.0'"; ui->inDevCombo->addItem("FUNcube Dongle V2.0", QVariant(devstr)); } if (indev == QString(inDevList[i].get_name().c_str())) { ui->inDevCombo->setCurrentIndex(i); ui->inDevEdit->setText(devstr); cfgmatch = true; } } #endif // Get list of input devices discovered by gr-osmosdr and store them in // the input device selector together with the device descriptor strings osmosdr::devices_t devs = osmosdr::device::find(); qDebug() << __FUNCTION__ << ": Available input devices:"; BOOST_FOREACH(osmosdr::device_t &dev, devs) { if (dev.count("label")) { devlabel = QString(dev["label"].c_str()); dev.erase("label"); } else { devlabel = "Unknown"; } devstr = QString(dev.to_string().c_str()); ui->inDevCombo->addItem(devlabel, QVariant(devstr)); // is this the device stored in config? if (indev == devstr) { ui->inDevCombo->setCurrentIndex(i); ui->inDevEdit->setText(devstr); cfgmatch = true; } qDebug() << " " << i << ":" << devlabel; ++i; // Following code could be used for multiple matches /* QStringList list; int pos = 0; while ((pos = rx.indexIn(devstr, pos)) != -1) { list << rx.cap(1); pos += rx.matchedLength(); } */ } ui->inDevCombo->addItem(tr("Other..."), QVariant("")); // If device string from config is not one of the detected devices // it could be that device is not plugged in (in which case we select // other) or that this is the first time (select the first detected device). if (!cfgmatch) { if (indev.isEmpty()) { // First time config: select the first detected device ui->inDevCombo->setCurrentIndex(0); ui->inDevEdit->setText(ui->inDevCombo->itemData(0).toString()); if (ui->inDevEdit->text().isEmpty()) ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); } else { // Select other ui->inDevCombo->setCurrentIndex(i); ui->inDevEdit->setText(indev); } } updateInputSampleRates(settings->value("input/sample_rate", 0).toInt()); // Analog bandwidth ui->bwSpinBox->setValue(1.0e-6*settings->value("input/bandwidth", 0.0).toDouble()); // LNB LO ui->loSpinBox->setValue(1.0e-6*settings->value("input/lnb_lo", 0.0).toDouble()); // Output device QString outdev = settings->value("output/device", "").toString(); #ifdef WITH_PULSEAUDIO // get list of output devices pa_device_list devices; outDevList = devices.get_output_devices(); qDebug() << __FUNCTION__ << ": Available output devices:"; for (i = 0; i < outDevList.size(); i++) { qDebug() << " " << i << ":" << QString(outDevList[i].get_description().c_str()); //qDebug() << " " << QString(outDevList[i].get_name().c_str()); ui->outDevCombo->addItem(QString(outDevList[i].get_description().c_str())); // note that item #i in devlist will be item #(i+1) // in combo box due to "default" if (outdev == QString(outDevList[i].get_name().c_str())) ui->outDevCombo->setCurrentIndex(i+1); } #elif defined(Q_WS_MAC) && defined(WITH_PORTAUDIO) // get list of output devices // (already defined) portaudio_device_list devices; outDevList = devices.get_output_devices(); qDebug() << __FUNCTION__ << ": Available output devices:"; for (i = 0; i < outDevList.size(); i++) { qDebug() << " " << i << ":" << QString(outDevList[i].get_name().c_str()); ui->outDevCombo->addItem(QString(outDevList[i].get_name().c_str())); // note that item #i in devlist will be item #(i+1) // in combo box due to "default" if (outdev == QString(outDevList[i].get_name().c_str())) ui->outDevCombo->setCurrentIndex(i+1); } #else ui->outDevCombo->setEditable(true); #endif // WITH_PULSEAUDIO // Signals and slots connect(this, SIGNAL(accepted()), this, SLOT(saveConfig())); connect(ui->inDevCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(inputDeviceSelected(int))); connect(ui->inDevEdit, SIGNAL(textChanged(QString)), this, SLOT(inputDevstrChanged(QString))); } CIoConfig::~CIoConfig() { delete ui; } /*! \brief Save configuration. */ void CIoConfig::saveConfig() { qDebug() << __FUNCTION__; int idx = ui->outDevCombo->currentIndex(); if (idx > 0) { #if defined(WITH_PULSEAUDIO) || defined(WITH_PORTAUDIO) qDebug() << "Output device" << idx << ":" << QString(outDevList[idx-1].get_name().c_str()); m_settings->setValue("output/device", QString(outDevList[idx-1].get_name().c_str())); #endif } else { m_settings->remove("output/device"); } // input settings m_settings->setValue("input/device", ui->inDevEdit->text()); // "OK" button disabled if empty qint64 value = (qint64)(ui->bwSpinBox->value()*1.e6); if (value) m_settings->setValue("input/bandwidth", value); else m_settings->remove("input/bandwidth"); value = (qint64)(ui->loSpinBox->value()*1.e6); if (value) m_settings->setValue("input/lnb_lo", value); bool ok=false; int sr = ui->inSrCombo->currentText().toInt(&ok); if (ok) m_settings->setValue("input/sample_rate", sr); else m_settings->remove("input/sample_rate"); } /*! \brief Update list of sample rates based on selected device. * \param rate The current sample rate from the configuration. */ void CIoConfig::updateInputSampleRates(int rate) { ui->inSrCombo->clear(); if (ui->inDevEdit->text().isEmpty()) { return; } /** FIXME: this code crashes on RTL device so we use fixed rates **/ //osmosdr_source_c_sptr src = osmosdr_make_source_c(ui->inDevEdit->text().toStdString()); //osmosdr::meta_range_t rates = src->get_sample_rates(); //BOOST_FOREACH(osmosdr::range_t &rate, rates) //{ // ui->inSrCombo->addItem(QString("%1 kHz").arg(rate.start()/1000, 0, 'f', 0)); //} //src.reset(); if (ui->inDevEdit->text().contains("fcd")) { if (ui->inDevCombo->currentText().contains("V2.0")) { ui->inSrCombo->addItem("192000"); } else { ui->inSrCombo->addItem("96000"); } } else if (ui->inDevEdit->text().contains("rtl")) { ui->inSrCombo->addItem("250000"); ui->inSrCombo->addItem("1200000"); ui->inSrCombo->addItem("1500000"); ui->inSrCombo->addItem("1700000"); ui->inSrCombo->addItem("2000000"); ui->inSrCombo->addItem("2200000"); ui->inSrCombo->addItem("2400000"); ui->inSrCombo->addItem("2700000"); ui->inSrCombo->addItem("3200000"); if (rate > 0) { ui->inSrCombo->addItem(QString("%1").arg(rate)); ui->inSrCombo->setCurrentIndex(9); } else { ui->inSrCombo->setCurrentIndex(2); } } else if (ui->inDevEdit->text().contains("uhd")) { if (rate > 0) ui->inSrCombo->addItem(QString("%1").arg(rate)); ui->inSrCombo->addItem("250000"); ui->inSrCombo->addItem("500000"); ui->inSrCombo->addItem("2000000"); ui->inSrCombo->addItem("4000000"); ui->inSrCombo->addItem("8000000"); } else if (ui->inDevEdit->text().contains("hackrf")) { if (rate > 0) ui->inSrCombo->addItem(QString("%1").arg(rate)); ui->inSrCombo->addItem("8000000"); ui->inSrCombo->addItem("10000000"); ui->inSrCombo->addItem("12500000"); ui->inSrCombo->addItem("16000000"); ui->inSrCombo->addItem("20000000"); } else if (ui->inDevEdit->text().contains("bladerf")) { if (rate > 0) ui->inSrCombo->addItem(QString("%1").arg(rate)); ui->inSrCombo->addItem("160000"); ui->inSrCombo->addItem("250000"); ui->inSrCombo->addItem("500000"); ui->inSrCombo->addItem("1000000"); ui->inSrCombo->addItem("2000000"); ui->inSrCombo->addItem("5000000"); ui->inSrCombo->addItem("8000000"); ui->inSrCombo->addItem("10000000"); ui->inSrCombo->addItem("15000000"); ui->inSrCombo->addItem("20000000"); ui->inSrCombo->addItem("25000000"); ui->inSrCombo->addItem("30000000"); ui->inSrCombo->addItem("35000000"); ui->inSrCombo->addItem("40000000"); } else if (ui->inDevEdit->text().contains("sdr-iq")) { if (rate > 0) ui->inSrCombo->addItem(QString("%1").arg(rate)); ui->inSrCombo->addItem("8138"); ui->inSrCombo->addItem("16276"); ui->inSrCombo->addItem("37793"); ui->inSrCombo->addItem("55556"); ui->inSrCombo->addItem("111111"); ui->inSrCombo->addItem("158730"); ui->inSrCombo->addItem("196078"); } else if (ui->inDevEdit->text().contains("sdr-ip")) { if (rate > 0) ui->inSrCombo->addItem(QString("%1").arg(rate)); ui->inSrCombo->addItem("31250"); ui->inSrCombo->addItem("32000"); ui->inSrCombo->addItem("40000"); ui->inSrCombo->addItem("50000"); ui->inSrCombo->addItem("62500"); ui->inSrCombo->addItem("64000"); ui->inSrCombo->addItem("80000"); ui->inSrCombo->addItem("100000"); ui->inSrCombo->addItem("125000"); ui->inSrCombo->addItem("160000"); ui->inSrCombo->addItem("200000"); ui->inSrCombo->addItem("250000"); ui->inSrCombo->addItem("320000"); ui->inSrCombo->addItem("400000"); ui->inSrCombo->addItem("500000"); ui->inSrCombo->addItem("800000"); ui->inSrCombo->addItem("1000000"); ui->inSrCombo->addItem("1600000"); ui->inSrCombo->addItem("2000000"); } else if (ui->inDevEdit->text().contains("netsdr")) { if (rate > 0) ui->inSrCombo->addItem(QString("%1").arg(rate)); ui->inSrCombo->addItem("32000"); ui->inSrCombo->addItem("40000"); ui->inSrCombo->addItem("50000"); ui->inSrCombo->addItem("62500"); ui->inSrCombo->addItem("78125"); ui->inSrCombo->addItem("80000"); ui->inSrCombo->addItem("100000"); ui->inSrCombo->addItem("125000"); ui->inSrCombo->addItem("156250"); ui->inSrCombo->addItem("160000"); ui->inSrCombo->addItem("200000"); ui->inSrCombo->addItem("250000"); ui->inSrCombo->addItem("312500"); ui->inSrCombo->addItem("400000"); ui->inSrCombo->addItem("500000"); ui->inSrCombo->addItem("625000"); ui->inSrCombo->addItem("800000"); ui->inSrCombo->addItem("1000000"); ui->inSrCombo->addItem("1250000"); ui->inSrCombo->addItem("2000000"); } } /*! \brief New input device has been selected by the user. * \param index The index of the item that has been selected in the combo box. */ void CIoConfig::inputDeviceSelected(int index) { qDebug() << "New input device selected:" << index; qDebug() << " Label:" << ui->inDevCombo->itemText(index); qDebug() << " Devstr:" << ui->inDevCombo->itemData(index).toString(); ui->inDevEdit->setText(ui->inDevCombo->itemData(index).toString()); updateInputSampleRates(0); } /*! \brief Input device string has changed * \param text THe new device string * * This slot is activated when the device string in the text edit box has changed * either by the user or programatically. We use this to enable/disable the OK * button - we allo OK only if there is some text in the text entry. */ void CIoConfig::inputDevstrChanged(const QString &text) { if (text.isEmpty()) ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); else ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); } gqrx-sdr-2.2.0.74.d97bd7/qtgui/ioconfig.h000066400000000000000000000033651226036373100176230ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2012 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef IOCONFIG_H #define IOCONFIG_H #include #include #include #ifdef WITH_PULSEAUDIO #include "pulseaudio/pa_device_list.h" #elif defined(WITH_PORTAUDIO) #include "portaudio/device_list.h" #endif namespace Ui { class CIoConfig; } /*! \brief Inout/output device configurator. */ class CIoConfig : public QDialog { Q_OBJECT public: explicit CIoConfig(QSettings *settings, QWidget *parent = 0); ~CIoConfig(); private slots: void saveConfig(); void inputDeviceSelected(int index); void inputDevstrChanged(const QString &text); private: void updateInputSampleRates(int rate); private: Ui::CIoConfig *ui; QSettings *m_settings; #ifdef WITH_PULSEAUDIO vector outDevList; #elif defined(WITH_PORTAUDIO) vector outDevList; vector inDevList; #endif }; #endif // IOCONFIG_H gqrx-sdr-2.2.0.74.d97bd7/qtgui/ioconfig.ui000066400000000000000000000200471226036373100200050ustar00rootroot00000000000000 CIoConfig 0 0 301 371 Configure I/O devices :/icons/icons/flash.svg:/icons/icons/flash.svg I/Q input false QFormLayout::AllNonFixedFieldsGrow Device true Select an input device QComboBox::AdjustToMinimumContentsLength 15 Device arguments Device string Sample rate Select the sample rate of the input source true LNB LO frequency. Use negative frequency for upconverters. LNB LO LNB LO frequency. Use negative frequency for upconverters. QAbstractSpinBox::PlusMinus MHz 6 -15000.000000000000000 15000.000000000000000 The device argument is a delimited string used to locate devices on your system. Use the device id or name (if applicable) to specify a certain device or list of devices. If left blank, the first device found will be used. Examples (some arguments may be optional): fcd=0 rtl=0,rtl_xtal=28.80001e6,tuner_xtal=26e6,buffers=64 ... rtl_tcp=127.0.0.1:1234,psize=16384 uhd,subdev=A:0,label='USRP1' osmosdr=0|name,mcr=64e6,nchan=5,port=/dev/ttyUSB0 ... file=/path/to/file.ext,freq=428e6,rate=1e6,repeat=true,throttle=true ... You can test the device strings in gnuradio-companion. Analog bandwidth (leave at 0 for default) MHz 6 100.000000000000000 0.100000000000000 Analog bandwidth (leave at 0 for default) Bandwidth Qt::Horizontal Audio output Device Select which device to use for audio output. Leave it at default unless you know what you are doing. QComboBox::AdjustToMinimumContentsLength 15 Default Sample rate Select the audio sample rate 48 kHz Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox accepted() CIoConfig accept() 248 254 157 274 buttonBox rejected() CIoConfig reject() 316 260 286 274 gqrx-sdr-2.2.0.74.d97bd7/qtgui/meter.cpp000066400000000000000000000212741226036373100174740ustar00rootroot00000000000000/* -*- c++ -*- */ /* + + + This Software is released under the "Simplified BSD License" + + + * Copyright 2010 Moe Wheatley. All rights reserved. * Copyright 2011-2013 Alexandru Csete OZ9AEC. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY Moe Wheatley ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Moe Wheatley OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * The views and conclusions contained in the software and documentation are those of the * authors and should not be interpreted as representing official policies, either expressed * or implied, of Moe Wheatley. */ #include #include #include "meter.h" //ratio to total control width or height #define CTRL_MARGIN 0.07 //left/right margin #define CTRL_MAJOR_START 0.3 //top of major tic line #define CTRL_MINOR_START 0.3 //top of minor tic line #define CTRL_XAXIS_HEGHT 0.4 //vert position of horizontal axis #define CTRL_NEEDLE_TOP 0.4 //vert position of top of needle triangle #define MIN_DB -100.0 #define MAX_DB +0.0 CMeter::CMeter(QWidget *parent) : QFrame(parent) { setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setFocusPolicy(Qt::StrongFocus); setAttribute(Qt::WA_PaintOnScreen,false); setAutoFillBackground(false); setAttribute(Qt::WA_OpaquePaintEvent, false); setAttribute(Qt::WA_NoSystemBackground, true); setMouseTracking(true); m_2DPixmap = QPixmap(0,0); m_OverlayPixmap = QPixmap(0,0); m_Size = QSize(0,0); m_Slevel = 0; m_dBm = -120; d_alpha_decay = 0.25; // FIXME: Should set delta-t and Fs instead d_alpha_rise = 0.7; // FIXME: Should set delta-t and Fs instead } CMeter::~CMeter() { } ////////////////////////////////////////////////////////////////////// // Sizing interface ////////////////////////////////////////////////////////////////////// QSize CMeter::minimumSizeHint() const { return QSize(20, 10); } QSize CMeter::sizeHint() const { return QSize(100, 30); } ////////////////////////////////////////////////////////////////////// // Called when screen size changes so must recalculate bitmaps ////////////////////////////////////////////////////////////////////// void CMeter::resizeEvent(QResizeEvent* ) { if (!size().isValid()) return; if (m_Size != size()) { //if size changed, resize pixmaps to new screensize m_Size = size(); m_OverlayPixmap = QPixmap(m_Size.width(), m_Size.height()); m_OverlayPixmap.fill(Qt::black); m_2DPixmap = QPixmap(m_Size.width(), m_Size.height()); m_2DPixmap.fill(Qt::black); } DrawOverlay(); draw(); } ////////////////////////////////////////////////////////////////////// // Slot called to update meter level position ////////////////////////////////////////////////////////////////////// void CMeter::setLevel(float dbfs) { if(dbfs < MIN_DB) dbfs = MIN_DB; if(dbfs > MAX_DB) dbfs = MAX_DB; // decay delay float level = (float)m_dBm; if (dbfs < level) { level = level*(1-d_alpha_decay) + dbfs*d_alpha_decay; } else { level = level*(1-d_alpha_rise) + dbfs*d_alpha_rise; } m_dBm = (int)level; qreal w = (qreal)m_2DPixmap.width(); w = w - 2.0*CTRL_MARGIN*w; // width of meter scale in pixels // pixels / dB qreal pixperdb = w / fabs(MAX_DB - MIN_DB); m_Slevel = (int)(-(MIN_DB-level)*pixperdb); draw(); } ////////////////////////////////////////////////////////////////////// // Called by QT when screen needs to be redrawn ////////////////////////////////////////////////////////////////////// void CMeter::paintEvent(QPaintEvent *) { QPainter painter(this); painter.drawPixmap(0,0,m_2DPixmap); return; } ////////////////////////////////////////////////////////////////////// // Called to update s-meter data for displaying on the screen ////////////////////////////////////////////////////////////////////// void CMeter::draw() { int w; int h; if(m_2DPixmap.isNull()) return; // get/draw the 2D spectrum w = m_2DPixmap.width(); h = m_2DPixmap.height(); // first copy into 2Dbitmap the overlay bitmap. m_2DPixmap = m_OverlayPixmap.copy(0,0,w,h); QPainter painter(&m_2DPixmap); // DrawCurrent position indicator qreal hline = (qreal)h*CTRL_XAXIS_HEGHT; qreal marg = (qreal)w*CTRL_MARGIN; qreal ht = (qreal)h*CTRL_NEEDLE_TOP; qreal x = marg + m_Slevel; QPoint pts[3]; pts[0].setX(x); pts[0].setY(ht+2); pts[1].setX(x-6); pts[1].setY(hline+8); pts[2].setX(x+6); pts[2].setY(hline+8); //painter.setBrush(QBrush(Qt::green)); painter.setBrush(QBrush(QColor(0, 190, 0, 255))); painter.setOpacity(1.0); // Qt 4.8+ has a 1-pixel error (or they fixed line drawing) // see http://stackoverflow.com/questions/16990326 #if QT_VERSION >= 0x040800 painter.drawRect(marg-1, ht+1, x-marg, 6); #else painter.drawRect(marg, ht+2, x-marg, 6); #endif // create Font to use for scales QFont Font("Arial"); QFontMetrics metrics(Font); int y = (h)/4; Font.setPixelSize(y); Font.setWeight(QFont::Normal); painter.setFont(Font); painter.setPen(QColor(0xEF,0xEF,0xEF,0xFF)); painter.setOpacity(1.0); m_Str.setNum(m_dBm); painter.drawText(marg, h-2, m_Str+" dBFS" ); update(); } ////////////////////////////////////////////////////////////////////// // Called to draw an overlay bitmap containing items that // does not need to be recreated every fft data update. ////////////////////////////////////////////////////////////////////// void CMeter::DrawOverlay() { if(m_OverlayPixmap.isNull()) return; int w = m_OverlayPixmap.width(); int h = m_OverlayPixmap.height(); int x,y; QRect rect; QPainter painter(&m_OverlayPixmap); m_OverlayPixmap.fill(QColor(0x20,0x20,0x20,0xFF)); #if 0 //fill background with gradient QLinearGradient gradient(0, 0, 0 ,h); gradient.setColorAt(1, Qt::black); gradient.setColorAt(0, Qt::black); painter.setBrush(gradient); painter.drawRect(0, 0, w, h); #endif //Draw scale lines qreal marg = (qreal)w*CTRL_MARGIN; qreal hline = (qreal)h*CTRL_XAXIS_HEGHT; qreal magstart = (qreal)h*CTRL_MAJOR_START; qreal minstart = (qreal)h*CTRL_MINOR_START; qreal hstop = (qreal)w-marg; painter.setPen(QPen(Qt::white, 1, Qt::SolidLine)); painter.drawLine(QLineF(marg, hline, hstop, hline)); // top line with ticks painter.drawLine(QLineF(marg, hline+8, hstop, hline+8)); // bottom line qreal xpos = marg; for(x=0; x<11; x++) { if(x&1) //minor tics painter.drawLine( QLineF(xpos, minstart, xpos, hline) ); else painter.drawLine( QLineF(xpos, magstart, xpos, hline) ); xpos += (hstop-marg)/10.0; } //draw scale text //create Font to use for scales QFont Font("Arial"); QFontMetrics metrics(Font); y = h/4; Font.setPixelSize(y); Font.setWeight(QFont::Normal); painter.setFont(Font); int rwidth = (int)((hstop-marg)/5.0); m_Str = "-100"; rect.setRect(marg/2-5, 0, rwidth, magstart); for (x = MIN_DB; x <= MAX_DB; x += 20) { m_Str.setNum(x); painter.drawText(rect, Qt::AlignHCenter|Qt::AlignVCenter, m_Str); rect.translate(rwidth, 0); } /* for(x=1; x<=9; x+=2) { m_Str.setNum(x); painter.drawText(rect, Qt::AlignHCenter|Qt::AlignVCenter, m_Str); rect.translate( rwidth,0); } painter.setPen(QPen(Qt::red, 1,Qt::SolidLine)); for(x=20; x<=60; x+=20) { m_Str = "+" + m_Str.setNum(x); painter.drawText(rect, Qt::AlignHCenter|Qt::AlignVCenter, m_Str); rect.translate( rwidth,0); } */ } gqrx-sdr-2.2.0.74.d97bd7/qtgui/meter.h000066400000000000000000000052671226036373100171450ustar00rootroot00000000000000/* -*- c++ -*- */ /* + + + This Software is released under the "Simplified BSD License" + + + * Copyright 2010 Moe Wheatley. All rights reserved. * Copyright 2011-2013 Alexandru Csete OZ9AEC. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY Moe Wheatley ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Moe Wheatley OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * The views and conclusions contained in the software and documentation are those of the * authors and should not be interpreted as representing official policies, either expressed * or implied, of Moe Wheatley. */ #ifndef METER_H #define METER_H #include #include #include class CMeter : public QFrame { Q_OBJECT public: explicit CMeter(QWidget *parent = 0); explicit CMeter(float min_level = -100.0, float max_level = 10.0, QWidget *parent = 0); ~CMeter(); QSize minimumSizeHint() const; QSize sizeHint() const; void setMin(float min_level); void setMax(float max_level); void setRange(float min_level, float max_level); void draw(); void UpdateOverlay(){DrawOverlay();} signals: public slots: void setLevel(float dbfs); protected: //re-implemented widget event handlers void paintEvent(QPaintEvent *event); void resizeEvent(QResizeEvent* event); private: void DrawOverlay(); QPixmap m_2DPixmap; QPixmap m_OverlayPixmap; QSize m_Size; QString m_Str; int m_Slevel; int m_dBm; float d_alpha_decay; float d_alpha_rise; }; #endif // METER_H gqrx-sdr-2.2.0.74.d97bd7/qtgui/nb_options.cpp000066400000000000000000000033131226036373100205240ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2013 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include "nb_options.h" #include "ui_nb_options.h" CNbOptions::CNbOptions(QWidget *parent) : QDialog(parent), ui(new Ui::CNbOptions) { ui->setupUi(this); } CNbOptions::~CNbOptions() { delete ui; } /*! \brief Catch window close events. * * This method is called when the user closes the dialog window using the * window close icon. We catch the event and hide the dialog but keep it * around for later use. */ void CNbOptions::closeEvent(QCloseEvent *event) { hide(); event->ignore(); } double CNbOptions::nbThreshold(int nbid) { if (nbid == 1) return ui->nb1Threshold->value(); else return ui->nb2Threshold->value(); } void CNbOptions::on_nb1Threshold_valueChanged(double val) { emit thresholdChanged(1, val); } void CNbOptions::on_nb2Threshold_valueChanged(double val) { emit thresholdChanged(2, val); } gqrx-sdr-2.2.0.74.d97bd7/qtgui/nb_options.h000066400000000000000000000026251226036373100201760ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2013 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef NB_OPTIONS_H #define NB_OPTIONS_H #include #include namespace Ui { class CNbOptions; } class CNbOptions : public QDialog { Q_OBJECT public: explicit CNbOptions(QWidget *parent = 0); ~CNbOptions(); void closeEvent(QCloseEvent *event); double nbThreshold(int nbid); signals: void thresholdChanged(int nb, double val); private slots: void on_nb1Threshold_valueChanged(double val); void on_nb2Threshold_valueChanged(double val); private: Ui::CNbOptions *ui; }; #endif // NB_OPTIONS_H gqrx-sdr-2.2.0.74.d97bd7/qtgui/nb_options.ui000066400000000000000000000043361226036373100203650ustar00rootroot00000000000000 CNbOptions 0 0 176 105 Noise blanker options :/icons/icons/signal.svg:/icons/icons/signal.svg 1 1.000000000000000 20.000000000000000 0.100000000000000 3.300000000000000 1 15.000000000000000 0.100000000000000 2.500000000000000 NB1 threshold Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter NB2 threshold Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter gqrx-sdr-2.2.0.74.d97bd7/qtgui/plotter.cpp000066400000000000000000001210241226036373100200430ustar00rootroot00000000000000/* -*- c++ -*- */ /* + + + This Software is released under the "Simplified BSD License" + + + * Copyright 2010 Moe Wheatley. All rights reserved. * Copyright 2011-2013 Alexandru Csete OZ9AEC * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY Moe Wheatley ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Moe Wheatley OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * The views and conclusions contained in the software and documentation are those of the * authors and should not be interpreted as representing official policies, either expressed * or implied, of Moe Wheatley. */ #include "plotter.h" #include #include #include #include ////////////////////////////////////////////////////////////////////// // Local defines ////////////////////////////////////////////////////////////////////// #define CUR_CUT_DELTA 5 //cursor capture delta in pixels ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// CPlotter::CPlotter(QWidget *parent) : QFrame(parent) { setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setFocusPolicy(Qt::StrongFocus); setAttribute(Qt::WA_PaintOnScreen,false); setAutoFillBackground(false); setAttribute(Qt::WA_OpaquePaintEvent, false); setAttribute(Qt::WA_NoSystemBackground, true); setMouseTracking(true); // default waterfall color scheme for (int i = 0; i < 256; i++) { // level 0: black background if (i < 20) m_ColorTbl[i].setRgb(0, 0, 0); // level 1: black -> blue else if ((i >= 20) && (i < 70)) m_ColorTbl[i].setRgb(0, 0, 140*(i-20)/50); // level 2: blue -> light-blue / greenish else if ((i >= 70) && (i < 100)) m_ColorTbl[i].setRgb(60*(i-70)/30, 125*(i-70)/30, 115*(i-70)/30 + 140); // level 3: light blue -> yellow else if ((i >= 100) && (i < 150)) m_ColorTbl[i].setRgb(195*(i-100)/50 + 60, 130*(i-100)/50 + 125, 255-(255*(i-100)/50)); // level 4: yellow -> red else if ((i >= 150) && (i < 250)) m_ColorTbl[i].setRgb(255, 255-255*(i-150)/100, 0); // level 5: red -> white else if (i >= 250) m_ColorTbl[i].setRgb(255, 255*(i-250)/5, 255*(i-250)/5); } m_PeakHoldActive=false; m_PeakHoldValid=false; m_FftCenter = 0; m_CenterFreq = 144500000; m_DemodCenterFreq = 144500000; m_DemodHiCutFreq = 5000; m_DemodLowCutFreq = -5000; m_FLowCmin = -25000; m_FLowCmax = -1000; m_FHiCmin = 1000; m_FHiCmax = 25000; m_symetric = true; m_ClickResolution = 100; m_FilterClickResolution = 100; m_CursorCaptureDelta = CUR_CUT_DELTA; m_FilterBoxEnabled = true; m_CenterLineEnabled = true; m_Span = 96000; m_SampleFreq = 96000; m_HorDivs = 12; m_VerDivs = 6; m_MaxdB = 0; m_MindB = -120; m_dBStepSize = abs(m_MaxdB-m_MindB)/m_VerDivs; m_FreqUnits = 1000000; m_CursorCaptured = NONE; m_Running = false; m_DrawOverlay = true; m_2DPixmap = QPixmap(0,0); m_OverlayPixmap = QPixmap(0,0); m_WaterfallPixmap = QPixmap(0,0); m_Size = QSize(0,0); m_GrabPosition = 0; m_Percent2DScreen = 50; //percent of screen used for 2D display #ifdef Q_WS_MAC m_FontSize = 11; #else m_FontSize = 9; #endif m_VdivDelta = 40; m_HdivDelta = 60; m_FreqDigits = 3; m_Peaks = QMap(); setPeakDetection(false, 2); setFftPlotColor(QColor(0xFF,0xFF,0xFF,0xFF)); setFftFill(false); } CPlotter::~CPlotter() { } ////////////////////////////////////////////////////////////////////// // Sizing interface ////////////////////////////////////////////////////////////////////// QSize CPlotter::minimumSizeHint() const { return QSize(50, 50); } QSize CPlotter::sizeHint() const { return QSize(180, 180); } ////////////////////////////////////////////////////////////////////// // Called when mouse moves and does different things depending //on the state of the mouse buttons for dragging the demod bar or // filter edges. ////////////////////////////////////////////////////////////////////// void CPlotter::mouseMoveEvent(QMouseEvent* event) { QPoint pt = event->pos(); /* mouse enter / mouse leave events */ if (m_OverlayPixmap.rect().contains(pt)) { //is in Overlay bitmap region if (event->buttons() == Qt::NoButton) { //if no mouse button monitor grab regions and change cursor icon if (isPointCloseTo(pt.x(), m_DemodFreqX, m_CursorCaptureDelta)) { //in move demod box center frequency region if (CENTER != m_CursorCaptured) setCursor(QCursor(Qt::SizeHorCursor)); m_CursorCaptured = CENTER; } else if (isPointCloseTo(pt.x(), m_DemodHiCutFreqX, m_CursorCaptureDelta)) { //in move demod hicut region if (RIGHT != m_CursorCaptured) setCursor(QCursor(Qt::SizeFDiagCursor)); m_CursorCaptured = RIGHT; } else if (isPointCloseTo(pt.x(), m_DemodLowCutFreqX, m_CursorCaptureDelta)) { //in move demod lowcut region if (LEFT != m_CursorCaptured) setCursor(QCursor(Qt::SizeBDiagCursor)); m_CursorCaptured = LEFT; } else if (isPointCloseTo(pt.x(), m_YAxisWidth/2, m_YAxisWidth/2)) { if (YAXIS != m_CursorCaptured) setCursor(QCursor(Qt::OpenHandCursor)); m_CursorCaptured = YAXIS; } else if (isPointCloseTo(pt.y(), m_XAxisYCenter, m_CursorCaptureDelta+5)) { if (XAXIS != m_CursorCaptured) setCursor(QCursor(Qt::OpenHandCursor)); m_CursorCaptured = XAXIS; } else { //if not near any grab boundaries if (NONE != m_CursorCaptured) { setCursor(QCursor(Qt::ArrowCursor)); m_CursorCaptured = NONE; } } m_GrabPosition = 0; } } else { //not in Overlay region if (event->buttons() == Qt::NoButton) { if (NONE != m_CursorCaptured) setCursor(QCursor(Qt::ArrowCursor)); m_CursorCaptured = NONE; m_GrabPosition = 0; } } // process mouse moves while in cursor capture modes if (YAXIS == m_CursorCaptured) { if (event->buttons() & Qt::LeftButton) { setCursor(QCursor(Qt::ClosedHandCursor)); // move Y scale up/down double delta_px = m_Yzero - pt.y(); double delta_db = delta_px * abs(m_MindB-m_MaxdB)/(double)m_OverlayPixmap.height(); m_MindB -= delta_db; m_MaxdB -= delta_db; if (m_Running) m_DrawOverlay = true; else drawOverlay(); m_Yzero = pt.y(); } } else if (XAXIS == m_CursorCaptured) { if (event->buttons() & (Qt::LeftButton | Qt::MidButton)) { setCursor(QCursor(Qt::ClosedHandCursor)); // pan viewable range or move center frequency int delta_px = m_Xzero - pt.x(); qint64 delta_hz = delta_px * m_Span / m_OverlayPixmap.width(); if (event->buttons() & Qt::MidButton) { m_CenterFreq += delta_hz; m_DemodCenterFreq += delta_hz; emit newCenterFreq(m_CenterFreq); } else { setFftCenterFreq(m_FftCenter + delta_hz); } if (m_Running) m_DrawOverlay = true; else drawOverlay(); m_Xzero = pt.x(); } } else if (LEFT == m_CursorCaptured) { // moving in demod lowcut region if (event->buttons() & (Qt::LeftButton|Qt::RightButton)) { //moving in demod lowcut region with left button held if (m_GrabPosition != 0) { m_DemodLowCutFreq = freqFromX(pt.x()-m_GrabPosition ) - m_DemodCenterFreq; m_DemodLowCutFreq = roundFreq(m_DemodLowCutFreq, m_FilterClickResolution); if (m_symetric && (event->buttons() & Qt::LeftButton)) // symetric adjustment { m_DemodHiCutFreq = -m_DemodLowCutFreq; } clampDemodParameters(); emit newFilterFreq(m_DemodLowCutFreq, m_DemodHiCutFreq); if (m_Running) m_DrawOverlay = true; // schedule update of overlay during draw() else drawOverlay(); // not running so update oiverlay now } else { //save initial grab postion from m_DemodFreqX m_GrabPosition = pt.x()-m_DemodLowCutFreqX; } } else if (event->buttons() & ~Qt::NoButton) { setCursor(QCursor(Qt::ArrowCursor)); m_CursorCaptured = NONE; } } else if (RIGHT == m_CursorCaptured) { // moving in demod highcut region if (event->buttons() & (Qt::LeftButton|Qt::RightButton)) { // moving in demod highcut region with right button held if (m_GrabPosition != 0) { m_DemodHiCutFreq = freqFromX( pt.x()-m_GrabPosition ) - m_DemodCenterFreq; m_DemodHiCutFreq = roundFreq(m_DemodHiCutFreq, m_FilterClickResolution); if (m_symetric && (event->buttons() & Qt::LeftButton)) // symetric adjustment { m_DemodLowCutFreq = -m_DemodHiCutFreq; } clampDemodParameters(); emit newFilterFreq(m_DemodLowCutFreq, m_DemodHiCutFreq); if (m_Running) m_DrawOverlay = true; // schedule update of overlay during draw() else drawOverlay(); // not running so update oiverlay now } else { //save initial grab postion from m_DemodFreqX m_GrabPosition = pt.x()-m_DemodHiCutFreqX; } } else if (event->buttons() & ~Qt::NoButton) { setCursor(QCursor(Qt::ArrowCursor)); m_CursorCaptured = NONE; } } else if (CENTER == m_CursorCaptured) { // moving inbetween demod lowcut and highcut region if (event->buttons() & Qt::LeftButton) { // moving inbetween demod lowcut and highcut region with left button held if (m_GrabPosition != 0) { m_DemodCenterFreq = roundFreq(freqFromX(pt.x()-m_GrabPosition), m_ClickResolution ); emit newDemodFreq(m_DemodCenterFreq, m_DemodCenterFreq-m_CenterFreq); if (m_Running) m_DrawOverlay = true; // schedule update of overlay during draw() else drawOverlay(); // not running so update oiverlay now } else { //save initial grab postion from m_DemodFreqX m_GrabPosition = pt.x()-m_DemodFreqX; } } else if (event->buttons() & ~Qt::NoButton) { setCursor(QCursor(Qt::ArrowCursor)); m_CursorCaptured = NONE; } } else //if cursor not captured { m_GrabPosition = 0; } if (!this->rect().contains(pt)) { if(NONE != m_CursorCaptured) setCursor(QCursor(Qt::ArrowCursor)); m_CursorCaptured = NONE; } } int CPlotter::getNearestPeak(QPoint pt) { QMap::const_iterator i = m_Peaks.lowerBound(pt.x()-PEAK_CLICK_MAX_H_DISTANCE); QMap::const_iterator upperBound = m_Peaks.upperBound(pt.x()+PEAK_CLICK_MAX_H_DISTANCE); double dist=1e10; int best=-1; for(;i != upperBound;i++) { int x=i.key(); int y=i.value(); if(abs(y-pt.y())>PEAK_CLICK_MAX_V_DISTANCE) continue; double d=pow(y-pt.y(),2)+pow(x-pt.x(),2); if(dpos(); if (NONE == m_CursorCaptured) { if (isPointCloseTo(pt.x(), m_DemodFreqX, m_CursorCaptureDelta)) { //in move demod box center frequency region m_CursorCaptured = CENTER; m_GrabPosition = pt.x()-m_DemodFreqX; } else if (isPointCloseTo(pt.x(), m_DemodLowCutFreqX, m_CursorCaptureDelta)) { // filter low cut m_CursorCaptured = LEFT; m_GrabPosition = pt.x()-m_DemodLowCutFreqX; } else if (isPointCloseTo(pt.x(), m_DemodHiCutFreqX, m_CursorCaptureDelta)) { // filter high cut m_CursorCaptured = RIGHT; m_GrabPosition = pt.x()-m_DemodHiCutFreqX; } else { if (event->buttons() == Qt::LeftButton) { int best=-1; if(m_PeakDetection>0) best = getNearestPeak(pt); if(best!=-1) m_DemodCenterFreq = freqFromX(best); else m_DemodCenterFreq = roundFreq(freqFromX(pt.x()),m_ClickResolution ); //if cursor not captured set demod frequency and start demod box capture emit newDemodFreq(m_DemodCenterFreq, m_DemodCenterFreq-m_CenterFreq); //save initial grab postion from m_DemodFreqX //setCursor(QCursor(Qt::CrossCursor)); m_CursorCaptured = CENTER; m_GrabPosition = 1; //m_GrabPosition = pt.x()-m_DemodFreqX; drawOverlay(); } else if (event->buttons() == Qt::MidButton) { // set center freq m_CenterFreq = roundFreq(freqFromX(pt.x()), m_ClickResolution); m_DemodCenterFreq = m_CenterFreq; emit newCenterFreq(m_CenterFreq); emit newDemodFreq(m_DemodCenterFreq, m_DemodCenterFreq-m_CenterFreq); } } } else { if (m_CursorCaptured == YAXIS) // get ready for moving Y axis m_Yzero = pt.y(); else if (m_CursorCaptured == XAXIS) m_Xzero = pt.x(); } } ////////////////////////////////////////////////////////////////////// // Called when a mouse button is released ////////////////////////////////////////////////////////////////////// void CPlotter::mouseReleaseEvent(QMouseEvent * event) { QPoint pt = event->pos(); if (!m_OverlayPixmap.rect().contains(pt)) { //not in Overlay region if (NONE != m_CursorCaptured) setCursor(QCursor(Qt::ArrowCursor)); m_CursorCaptured = NONE; m_GrabPosition = 0; } else { if (YAXIS == m_CursorCaptured) { setCursor(QCursor(Qt::OpenHandCursor)); m_Yzero = -1; } else if (XAXIS == m_CursorCaptured) { setCursor(QCursor(Qt::OpenHandCursor)); m_Xzero = -1; } } } ////////////////////////////////////////////////////////////////////// // Called when a mouse wheel is turned ////////////////////////////////////////////////////////////////////// void CPlotter::wheelEvent(QWheelEvent * event) { QPoint pt = event->pos(); int numDegrees = event->delta() / 8; int numSteps = numDegrees / 15; /** FIXME: Only used for direction **/ /** FIXME: zooming could use some optimisation **/ if (m_CursorCaptured == YAXIS) { // Vertical zoom. Wheel down: zoom out, wheel up: zoom in // During zoom we try to keep the point (dB or kHz) under the cursor fixed float zoom_fac = event->delta() < 0 ? 1.1 : 0.9; float ratio = (float)pt.y() / (float)m_OverlayPixmap.height(); float db_range = (float)(m_MaxdB - m_MindB); float y_range = (float)m_OverlayPixmap.height(); float db_per_pix = db_range / y_range; float fixed_db = m_MaxdB - pt.y() * db_per_pix; db_range = qBound(1.0f, db_range * zoom_fac, 2000.0f); m_MaxdB = fixed_db + ratio*db_range; m_MindB = m_MaxdB - db_range; } else if (m_CursorCaptured == XAXIS) { // calculate new range shown on FFT float zoom_factor = event->delta() < 0 ? 1.1 : 0.9; float new_range = qBound(10.0f, (float)(m_Span) * zoom_factor, (float)(m_SampleFreq) * 10.0f); // Frequency where event occured is kept fixed under mouse float ratio = (float)pt.x() / (float)m_OverlayPixmap.width(); float fixed_hz = freqFromX(pt.x()); float f_max = fixed_hz + (1.0 - ratio) * new_range; float f_min = f_max - new_range; qint64 fc = (qint64)(f_min + (f_max - f_min) / 2.0); setFftCenterFreq(fc-m_CenterFreq); setSpanFreq((quint32)new_range); zoom_factor = (float)m_SampleFreq/(float)m_Span; qDebug() << QString("Spectrum zoom: %1x").arg(zoom_factor, 0, 'f', 1); } else if (event->modifiers() & Qt::ControlModifier) { // filter width m_DemodLowCutFreq -= numSteps*m_ClickResolution; m_DemodHiCutFreq += numSteps*m_ClickResolution; clampDemodParameters(); emit newFilterFreq(m_DemodLowCutFreq, m_DemodHiCutFreq); } else if (event->modifiers() & Qt::ShiftModifier) { // filter shift m_DemodLowCutFreq += numSteps*m_ClickResolution; m_DemodHiCutFreq += numSteps*m_ClickResolution; clampDemodParameters(); emit newFilterFreq(m_DemodLowCutFreq, m_DemodHiCutFreq); } else { // inc/dec demod frequency m_DemodCenterFreq += (numSteps*m_ClickResolution); m_DemodCenterFreq = roundFreq(m_DemodCenterFreq, m_ClickResolution ); emit newDemodFreq(m_DemodCenterFreq, m_DemodCenterFreq-m_CenterFreq); } if (m_Running) m_DrawOverlay = true; else drawOverlay(); } ////////////////////////////////////////////////////////////////////// // Called when screen size changes so must recalculate bitmaps ////////////////////////////////////////////////////////////////////// void CPlotter::resizeEvent(QResizeEvent* ) { if (!size().isValid()) return; if (m_Size != size()) { //if changed, resize pixmaps to new screensize m_Size = size(); m_OverlayPixmap = QPixmap(m_Size.width(), m_Percent2DScreen*m_Size.height()/100); m_OverlayPixmap.fill(Qt::black); m_2DPixmap = QPixmap(m_Size.width(), m_Percent2DScreen*m_Size.height()/100); m_2DPixmap.fill(Qt::black); int height = (100-m_Percent2DScreen)*m_Size.height()/100; if (m_WaterfallPixmap.isNull()) { m_WaterfallPixmap = QPixmap(m_Size.width(), height); m_WaterfallPixmap.fill(Qt::black); } else { m_WaterfallPixmap = m_WaterfallPixmap.scaled(m_Size.width(), height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } m_PeakHoldValid=false; } drawOverlay(); } ////////////////////////////////////////////////////////////////////// // Called by QT when screen needs to be redrawn ////////////////////////////////////////////////////////////////////// void CPlotter::paintEvent(QPaintEvent *) { QPainter painter(this); painter.drawPixmap(0,0,m_2DPixmap); painter.drawPixmap(0, m_Percent2DScreen*m_Size.height()/100,m_WaterfallPixmap); //tell interface that its ok to signal a new line of fft data //m_pSdrInterface->ScreenUpdateDone(); return; } ////////////////////////////////////////////////////////////////////// // Called to update spectrum data for displaying on the screen ////////////////////////////////////////////////////////////////////// void CPlotter::draw() { int i,n; int w; int h; int xmin, xmax; if (m_DrawOverlay) { drawOverlay(); m_DrawOverlay = false; //FIXME: dirty hack to avoid invalidating Peak data througout the code m_PeakHoldValid=false; } QPoint LineBuf[MAX_SCREENSIZE]; if (!m_Running) return; // get/draw the waterfall w = m_WaterfallPixmap.width(); h = m_WaterfallPixmap.height(); // no need to draw if pixmap is invisible if ((w != 0) || (h != 0)) { // move current data down one line(must do before attaching a QPainter object) m_WaterfallPixmap.scroll(0,1,0,0, w, h); QPainter painter1(&m_WaterfallPixmap); // get scaled FFT data getScreenIntegerFFTData(255, qMin(w, MAX_SCREENSIZE), m_MaxdB, m_MindB, m_FftCenter - (qint64)m_Span/2, m_FftCenter + (qint64)m_Span/2, m_wfData, m_fftbuf, &xmin, &xmax); // draw new line of fft data at top of waterfall bitmap painter1.setPen(QColor(0, 0, 0)); for (i = 0; i < xmin; i++) painter1.drawPoint(i,0); for (i = xmax; i < w; i++) painter1.drawPoint(i,0); for (i = xmin; i < xmax; i++) { painter1.setPen(m_ColorTbl[ 255-m_fftbuf[i] ]); painter1.drawPoint(i,0); } } // get/draw the 2D spectrum w = m_2DPixmap.width(); h = m_2DPixmap.height(); if ((w != 0) || (h != 0)) { // first copy into 2Dbitmap the overlay bitmap. m_2DPixmap = m_OverlayPixmap.copy(0,0,w,h); QPainter painter2(&m_2DPixmap); // workaround for "fixed" line drawing since Qt 5 // see http://stackoverflow.com/questions/16990326 #if QT_VERSION >= 0x050000 painter2.translate(0.5, 0.5); #endif // get new scaled fft data getScreenIntegerFFTData(h, qMin(w, MAX_SCREENSIZE), m_MaxdB, m_MindB, m_FftCenter - (qint64)m_Span/2, m_FftCenter + (qint64)m_Span/2, m_fftData, m_fftbuf, &xmin, &xmax); // draw the pandapter painter2.setPen(m_FftColor); n = xmax - xmin; for (i = 0; i < n; i++) { LineBuf[i].setX(i + xmin); LineBuf[i].setY(m_fftbuf[i + xmin]); } if (m_FftFill) { QLinearGradient linGrad(QPointF(xmin, h), QPointF(xmin, 0)); linGrad.setColorAt(0.0, m_FftCol0); linGrad.setColorAt(1.0, m_FftCol1); painter2.setBrush(QBrush(QGradient(linGrad))); if (n < MAX_SCREENSIZE-2) { LineBuf[n].setX(xmax-1); LineBuf[n].setY(h); LineBuf[n+1].setX(xmin); LineBuf[n+1].setY(h); painter2.drawPolygon(LineBuf, n+2); } else { LineBuf[MAX_SCREENSIZE-2].setX(xmax-1); LineBuf[MAX_SCREENSIZE-2].setY(h); LineBuf[MAX_SCREENSIZE-1].setX(xmin); LineBuf[MAX_SCREENSIZE-1].setY(h); painter2.drawPolygon(LineBuf, n); } } else { painter2.drawPolyline(LineBuf, n); } //Peak detection if(m_PeakDetection>0) { m_Peaks.clear(); double mean=0; double sum_of_sq=0; for (i = 0; i < n; i++) { mean+=m_fftbuf[i + xmin]; sum_of_sq+=m_fftbuf[i + xmin]*m_fftbuf[i + xmin]; } mean/=n; double stdev= sqrt( sum_of_sq/n-mean*mean ); int lastPeak=-1; for (i = 0; i < n; i++) { //m_PeakDetection times the std over the mean or better than current peak double d = (lastPeak==-1)?(mean-m_PeakDetection*stdev):m_fftbuf[lastPeak+xmin]; if(m_fftbuf[i + xmin] < d) lastPeak=i; if(lastPeak!=-1 && (i-lastPeak>PEAK_H_TOLERANCE || i==n-1)) { m_Peaks.insert(lastPeak+xmin, m_fftbuf[lastPeak + xmin]); painter2.drawEllipse(lastPeak+xmin-5, m_fftbuf[lastPeak + xmin]-5, 10, 10); lastPeak=-1; } } } //Peak hold if(m_PeakHoldActive) { for (i = 0; i < n; i++) { if(!m_PeakHoldValid || m_fftbuf[i] < m_fftPeakHoldBuf[i]) m_fftPeakHoldBuf[i]=m_fftbuf[i]; LineBuf[i].setX(i + xmin); LineBuf[i].setY(m_fftPeakHoldBuf[i + xmin]); } painter2.setPen(m_PeakHoldColor); painter2.drawPolyline(LineBuf, n); m_PeakHoldValid=true; } painter2.end(); } // trigger a new paintEvent update(); } /*! \brief Set new FFT data. * \param fftData Pointer to the new FFT data (same data for pandapter and waterfall). * \param size The FFT size. * * When FFT data is set using this method, the same data will be used for bith the * pandapter and the waterfall. */ void CPlotter::setNewFttData(double *fftData, int size) { /** FIXME **/ if (!m_Running) m_Running = true; m_wfData = fftData; m_fftData = fftData; m_fftDataSize = size; draw(); } /*! \brief Set new FFT data. * \param fftData Pointer to the new FFT data used on the pandapter. * \param wfData Pointer to the FFT data used in the waterfall. * \param size The FFT size. * * This method can be used to set different FFT data set for the pandapter and the * waterfall. */ void CPlotter::setNewFttData(double *fftData, double *wfData, int size) { /** FIXME **/ if (!m_Running) m_Running = true; m_wfData = wfData; m_fftData = fftData; m_fftDataSize = size; draw(); } void CPlotter::getScreenIntegerFFTData(qint32 plotHeight, qint32 plotWidth, double maxdB, double mindB, qint64 startFreq, qint64 stopFreq, double *inBuf, qint32 *outBuf, int *xmin, int *xmax) { qint32 i; qint32 y; qint32 x; qint32 ymax = 10000; qint32 xprev = -1; qint32 minbin, maxbin; qint32 m_BinMin, m_BinMax; qint32 m_FFTSize = m_fftDataSize; double *m_pFFTAveBuf = inBuf; double dBGainFactor = ((double)plotHeight)/abs(maxdB-mindB); qint32* m_pTranslateTbl = new qint32[qMax(m_FFTSize, plotWidth)]; /** FIXME: qint64 -> qint32 **/ m_BinMin = (qint32)((double)startFreq*(double)m_FFTSize/m_SampleFreq); m_BinMin += (m_FFTSize/2); m_BinMax = (qint32)((double)stopFreq*(double)m_FFTSize/m_SampleFreq); m_BinMax += (m_FFTSize/2); minbin = m_BinMin < 0 ? 0 : m_BinMin; if (m_BinMin > m_FFTSize) m_BinMin = m_FFTSize - 1; if (m_BinMax <= m_BinMin) m_BinMax = m_BinMin + 1; maxbin = m_BinMax < m_FFTSize ? m_BinMax : m_FFTSize; bool largeFft = (m_BinMax-m_BinMin) > plotWidth; // true if more fft point than plot points if (largeFft) { // more FFT points than plot points for (i = minbin; i < maxbin; i++) m_pTranslateTbl[i] = ((i-m_BinMin)*plotWidth) / (m_BinMax - m_BinMin); *xmin = m_pTranslateTbl[minbin]; *xmax = m_pTranslateTbl[maxbin - 1]; } else { // more plot points than FFT points for (i = 0; i < plotWidth; i++) m_pTranslateTbl[i] = m_BinMin + (i*(m_BinMax - m_BinMin)) / plotWidth; *xmin = 0; *xmax = plotWidth; } if (largeFft) { // more FFT points than plot points for (i = minbin; i < maxbin; i++ ) { y = (qint32)(dBGainFactor*(maxdB-m_pFFTAveBuf[i])); if (y > plotHeight) y = plotHeight; else if (y < 0) y = 0; x = m_pTranslateTbl[i]; //get fft bin to plot x coordinate transform if (x == xprev) // still mappped to same fft bin coordinate { if (y < ymax) // store only the max value { outBuf[x] = y; ymax = y; } } else { outBuf[x] = y; xprev = x; ymax = y; } } } else { // more plot points than FFT points for (x = 0; x < plotWidth; x++ ) { i = m_pTranslateTbl[x]; // get plot to fft bin coordinate transform y = (qint32)(dBGainFactor*(maxdB-m_pFFTAveBuf[i])); if (y > plotHeight) y = plotHeight; else if (y < 0) y = 0; outBuf[x] = y; } } delete [] m_pTranslateTbl; } /*! \brief Set upper limit of dB scale. */ void CPlotter::setMaxDB(double max) { m_MaxdB = max; if (m_Running) m_DrawOverlay = true; else drawOverlay(); } /*! \brief Set lower limit of dB scale. */ void CPlotter::setMinDB(double min) { m_MindB = min; if (m_Running) m_DrawOverlay = true; else drawOverlay(); } /*! \brief Set limits of dB scale. */ void CPlotter::setMinMaxDB(double min, double max) { m_MaxdB = max; m_MindB = min; if (m_Running) m_DrawOverlay = true; else drawOverlay(); } ////////////////////////////////////////////////////////////////////// // Called to draw an overlay bitmap containing grid and text that // does not need to be recreated every fft data update. ////////////////////////////////////////////////////////////////////// void CPlotter::drawOverlay() { if (m_OverlayPixmap.isNull()) return; int w = m_OverlayPixmap.width(); int h = m_OverlayPixmap.height(); int x,y; float pixperdiv; QRect rect; QPainter painter(&m_OverlayPixmap); painter.initFrom(this); // horizontal grids (size and grid calcs could be moved to resize) m_VerDivs = h/m_VdivDelta+1; m_HorDivs = qMin(w/m_HdivDelta, HORZ_DIVS_MAX); if (m_HorDivs % 2) m_HorDivs++; // we want an odd number of divs so that we have a center line //m_OverlayPixmap.fill(Qt::black); // fill background with gradient QLinearGradient gradient(0, 0, 0 ,h); gradient.setColorAt(0, QColor(0x20,0x20,0x20,0xFF)); gradient.setColorAt(1, QColor(0x4F,0x4F,0x4F,0xFF)); painter.setBrush(gradient); painter.drawRect(0, 0, w, h); // Draw demod filter box if (m_FilterBoxEnabled) { // Clamping no longer necessary as we do it in mouseMove() //ClampDemodParameters(); m_DemodFreqX = xFromFreq(m_DemodCenterFreq); m_DemodLowCutFreqX = xFromFreq(m_DemodCenterFreq + m_DemodLowCutFreq); m_DemodHiCutFreqX = xFromFreq(m_DemodCenterFreq + m_DemodHiCutFreq); int dw = m_DemodHiCutFreqX - m_DemodLowCutFreqX; painter.setBrush(Qt::SolidPattern); painter.setOpacity(0.3); painter.fillRect(m_DemodLowCutFreqX, 0, dw, h, Qt::gray); painter.setOpacity(1.0); painter.setPen(QPen(QColor(0xFF,0x71,0x71,0xFF), 1, Qt::SolidLine)); painter.drawLine(m_DemodFreqX, 0, m_DemodFreqX, h); } // create Font to use for scales QFont Font("Arial"); Font.setPointSize(m_FontSize); QFontMetrics metrics(Font); Font.setWeight(QFont::Normal); painter.setFont(Font); // draw vertical grids pixperdiv = (float)w / (float)m_HorDivs; y = h - h/m_VerDivs/2; painter.setPen(QPen(QColor(0xF0,0xF0,0xF0,0x30), 1, Qt::DotLine)); for (int i = 1; i < m_HorDivs; i++) { x = (int)((float)i*pixperdiv); painter.drawLine(x, 0, x, y); } if (m_CenterLineEnabled) { // center line x = xFromFreq(m_CenterFreq); if (x > 0 && x < w) { painter.setPen(QPen(QColor(0x78,0x82,0x96,0xFF), 1, Qt::SolidLine)); painter.drawLine(x, 0, x, y); } } // draw frequency values makeFrequencyStrs(); painter.setPen(QColor(0xD8,0xBA,0xA1,0xFF)); y = h - (h/m_VerDivs); m_XAxisYCenter = h - metrics.height()/2; for (int i = 1; i < m_HorDivs; i++) { x = (int)((float)i*pixperdiv - pixperdiv/2); rect.setRect(x, y, (int)pixperdiv, h/m_VerDivs); painter.drawText(rect, Qt::AlignHCenter|Qt::AlignBottom, m_HDivText[i]); } m_dBStepSize = abs(m_MaxdB-m_MindB)/(double)m_VerDivs; pixperdiv = (float)h / (float)m_VerDivs; painter.setPen(QPen(QColor(0xF0,0xF0,0xF0,0x30), 1,Qt::DotLine)); for (int i = 1; i < m_VerDivs; i++) { y = (int)((float) i*pixperdiv); painter.drawLine(5*metrics.width("0",-1), y, w, y); } // draw amplitude values painter.setPen(QColor(0xD8,0xBA,0xA1,0xFF)); //Font.setWeight(QFont::Light); painter.setFont(Font); int dB = m_MaxdB; m_YAxisWidth = metrics.width("-120 "); for (int i = 1; i < m_VerDivs; i++) { dB -= m_dBStepSize; // move to end if want to include maxdb y = (int)((float)i*pixperdiv); rect.setRect(0, y-metrics.height()/2, m_YAxisWidth, metrics.height()); painter.drawText(rect, Qt::AlignRight|Qt::AlignVCenter, QString::number(dB)); } if (!m_Running) { // if not running so is no data updates to draw to screen // copy into 2Dbitmap the overlay bitmap. m_2DPixmap = m_OverlayPixmap.copy(0,0,w,h); // trigger a new paintEvent update(); } } ////////////////////////////////////////////////////////////////////// // Helper function Called to create all the frequency division text //strings based on start frequency, span frequency, frequency units. //Places in QString array m_HDivText //Keeps all strings the same fractional length ////////////////////////////////////////////////////////////////////// void CPlotter::makeFrequencyStrs() { qint64 FreqPerDiv = m_Span/m_HorDivs; qint64 StartFreq = m_CenterFreq + m_FftCenter - m_Span/2; float freq; int i,j; if ((1 == m_FreqUnits) || (m_FreqDigits == 0)) { //if units is Hz then just output integer freq for (int i = 0; i <= m_HorDivs; i++) { freq = (float)StartFreq/(float)m_FreqUnits; m_HDivText[i].setNum((int)freq); StartFreq += FreqPerDiv; } return; } // here if is fractional frequency values // so create max sized text based on frequency units for (int i = 0; i <= m_HorDivs; i++) { freq = (float)StartFreq/(float)m_FreqUnits; m_HDivText[i].setNum(freq,'f', m_FreqDigits); StartFreq += FreqPerDiv; } // now find the division text with the longest non-zero digit // to the right of the decimal point. int max = 0; for (i = 0; i <= m_HorDivs; i++) { int dp = m_HDivText[i].indexOf('.'); int l = m_HDivText[i].length()-1; for (j = l; j > dp; j--) { if (m_HDivText[i][j] != '0') break; } if ((j-dp) > max) max = j-dp; } // truncate all strings to maximum fractional length StartFreq = m_CenterFreq + m_FftCenter - m_Span/2; for (i = 0; i <= m_HorDivs; i++) { freq = (float)StartFreq/(float)m_FreqUnits; m_HDivText[i].setNum(freq,'f', max); StartFreq += FreqPerDiv; } } ////////////////////////////////////////////////////////////////////// // Helper functions to convert to/from screen coordinates to frequency ////////////////////////////////////////////////////////////////////// int CPlotter::xFromFreq(qint64 freq) { int w = m_OverlayPixmap.width(); qint64 StartFreq = m_CenterFreq + m_FftCenter - m_Span/2; int x = (int) w * ((float)freq - StartFreq)/(float)m_Span; if (x < 0) return 0; if (x > (int)w) return m_OverlayPixmap.width(); return x; } qint64 CPlotter::freqFromX(int x) { int w = m_OverlayPixmap.width(); qint64 StartFreq = m_CenterFreq + m_FftCenter - m_Span/2; qint64 f = (qint64)(StartFreq + (float)m_Span * (float)x/(float)w ); return f; } ////////////////////////////////////////////////////////////////////// // Helper function to round frequency to click resolution value ////////////////////////////////////////////////////////////////////// qint64 CPlotter::roundFreq(qint64 freq, int resolution) { qint64 delta = resolution; qint64 delta_2 = delta/2; if (freq >= 0) return ( freq - (freq+delta_2)%delta + delta_2); else return ( freq - (freq+delta_2)%delta - delta_2); } ////////////////////////////////////////////////////////////////////// // Helper function clamps demod freqeuency limits of // m_DemodCenterFreq ////////////////////////////////////////////////////////////////////// void CPlotter::clampDemodParameters() { if(m_DemodLowCutFreq < m_FLowCmin) m_DemodLowCutFreq = m_FLowCmin; if(m_DemodLowCutFreq > m_FLowCmax) m_DemodLowCutFreq = m_FLowCmax; if(m_DemodHiCutFreq < m_FHiCmin) m_DemodHiCutFreq = m_FHiCmin; if(m_DemodHiCutFreq > m_FHiCmax) m_DemodHiCutFreq = m_FHiCmax; } void CPlotter::setDemodRanges(int FLowCmin, int FLowCmax, int FHiCmin, int FHiCmax, bool symetric) { m_FLowCmin=FLowCmin; m_FLowCmax=FLowCmax; m_FHiCmin=FHiCmin; m_FHiCmax=FHiCmax; m_symetric=symetric; clampDemodParameters(); if (m_Running) m_DrawOverlay = true; else drawOverlay(); } void CPlotter::setCenterFreq(quint64 f) { qint64 offset = m_CenterFreq - m_DemodCenterFreq; m_CenterFreq = f; m_DemodCenterFreq = m_CenterFreq - offset; if (m_Running) m_DrawOverlay = true; else drawOverlay(); } /*! \brief Reset horizontal zoom to 100% and centered around 0. */ void CPlotter::resetHorizontalZoom(void) { setFftCenterFreq(0); setSpanFreq((qint32)m_SampleFreq); } /*! \brief Center FFT plot around 0 (corresponds to center freq). */ void CPlotter::moveToCenterFreq(void) { setFftCenterFreq(0); if (m_Running) m_DrawOverlay = true; else drawOverlay(); } /*! \brief Center FFT plot around the dmeodulator frequency. */ void CPlotter::moveToDemodFreq(void) { setFftCenterFreq(m_DemodCenterFreq-m_CenterFreq); if (m_Running) m_DrawOverlay = true; else drawOverlay(); } /*! Set FFT plot color. */ void CPlotter::setFftPlotColor(const QColor color) { m_FftColor = color; m_FftCol0 = color; m_FftCol0.setAlpha(0x00); m_FftCol1 = color; m_FftCol1.setAlpha(0xA0); m_PeakHoldColor=color; m_PeakHoldColor.setAlpha(60); } /*! Enable/disable filling the area below the FFT plot. */ void CPlotter::setFftFill(bool enabled) { m_FftFill = enabled; } /*! \brief Set peak hold on or off. * \param enabled The new state of peak hold. */ void CPlotter::setPeakHold(bool enabled) { m_PeakHoldActive=enabled; m_PeakHoldValid=false; } /*! \brief Set peak detection on or off. * \param enabled The new state of peak detection. * \param c Minimum distance of peaks from mean, in multiples of standard deviation. */ void CPlotter::setPeakDetection(bool enabled, double c) { if(!enabled || c<=0) m_PeakDetection=-1; else m_PeakDetection=c; } gqrx-sdr-2.2.0.74.d97bd7/qtgui/plotter.h000066400000000000000000000156131226036373100175160ustar00rootroot00000000000000/* -*- c++ -*- */ #ifndef PLOTTER_H #define PLOTTER_H #include #include #include #include #include #define HORZ_DIVS_MAX 50 //12 #define MAX_SCREENSIZE 4096 #define PEAK_CLICK_MAX_H_DISTANCE 10 //Maximum horizontal distance of clicked point from peak #define PEAK_CLICK_MAX_V_DISTANCE 20 //Maximum vertical distance of clicked point from peak #define PEAK_H_TOLERANCE 2 class CPlotter : public QFrame { Q_OBJECT public: explicit CPlotter(QWidget *parent = 0); ~CPlotter(); QSize minimumSizeHint() const; QSize sizeHint() const; //void SetSdrInterface(CSdrInterface* ptr){m_pSdrInterface = ptr;} void draw(); //call to draw new fft data onto screen plot void setRunningState(bool running) { m_Running = running; } void setClickResolution(int clickres) { m_ClickResolution = clickres; } void setFilterClickResolution(int clickres) { m_FilterClickResolution = clickres; } void setFilterBoxEnabled(bool enabled) { m_FilterBoxEnabled = enabled; } void setCenterLineEnabled(bool enabled) { m_CenterLineEnabled = enabled; } void setPercent2DScreen(int percent) { m_Percent2DScreen = percent; m_Size = QSize(0,0); resizeEvent(NULL); } void setNewFttData(double *fftData, int size); void setNewFttData(double *fftData, double *wfData, int size); void setCenterFreq(quint64 f); void setFreqUnits(qint32 unit) { m_FreqUnits = unit; } void setDemodCenterFreq(quint64 f) { m_DemodCenterFreq = f; } /*! \brief Move the filter to freq_hz from center. */ void setFilterOffset(qint64 freq_hz) { m_DemodCenterFreq = m_CenterFreq + freq_hz; drawOverlay(); } qint64 getFilterOffset(void) { return m_DemodCenterFreq - m_CenterFreq; } void setHiLowCutFrequencies(int LowCut, int HiCut) { m_DemodLowCutFreq = LowCut; m_DemodHiCutFreq = HiCut; drawOverlay(); } void setDemodRanges(int FLowCmin, int FLowCmax, int FHiCmin, int FHiCmax, bool symetric); /* Shown bandwidth around SetCenterFreq() */ void setSpanFreq(quint32 s) { if (s > 0 && s < INT_MAX) { m_Span = (qint32)s; setFftCenterFreq(m_FftCenter); } drawOverlay(); } void updateOverlay() { drawOverlay(); } void setMaxDB(double max); void setMinDB(double min); void setMinMaxDB(double min, double max); void setFontSize(int points) { m_FontSize = points; } void setHdivDelta(int delta) { m_HdivDelta = delta; } void setVdivDelta(int delta) { m_VdivDelta = delta; } void setFreqDigits(int digits) { m_FreqDigits = digits>=0 ? digits : 0; } /* Determines full bandwidth. */ void setSampleRate(double rate) { if (rate > 0.0) { m_SampleFreq = rate; drawOverlay(); } } double getSampleRate(void) { return m_SampleFreq; } void setFftCenterFreq(qint64 f) { qint64 limit = ((qint64)m_SampleFreq + m_Span) / 2 - 1; m_FftCenter = qBound(-limit, f, limit); } int getNearestPeak(QPoint pt); signals: void newCenterFreq(qint64 f); void newDemodFreq(qint64 freq, qint64 delta); /* delta is the offset from the center */ void newLowCutFreq(int f); void newHighCutFreq(int f); void newFilterFreq(int low, int high); /* substute for NewLow / NewHigh */ public slots: // zoom functions void resetHorizontalZoom(void); void moveToCenterFreq(void); void moveToDemodFreq(void); void setFftPlotColor(const QColor color); void setFftFill(bool enabled); void setPeakHold(bool enabled); void setPeakDetection(bool enabled, double c); protected: //re-implemented widget event handlers void paintEvent(QPaintEvent *event); void resizeEvent(QResizeEvent* event); void mouseMoveEvent(QMouseEvent * event); void mousePressEvent(QMouseEvent * event); void mouseReleaseEvent(QMouseEvent * event); void wheelEvent( QWheelEvent * event ); private: enum eCapturetype { NONE, LEFT, CENTER, RIGHT, YAXIS, XAXIS }; void drawOverlay(); void makeFrequencyStrs(); int xFromFreq(qint64 freq); qint64 freqFromX(int x); qint64 roundFreq(qint64 freq, int resolution); bool isPointCloseTo(int x, int xr, int delta){return ((x > (xr-delta) ) && ( x<(xr+delta)) );} void clampDemodParameters(); void getScreenIntegerFFTData(qint32 plotHeight, qint32 plotWidth, double maxdB, double mindB, qint64 startFreq, qint64 stopFreq, double *inBuf, qint32 *outBuf, qint32 *maxbin, qint32 *minbin); bool m_PeakHoldActive; bool m_PeakHoldValid; qint32 m_fftbuf[MAX_SCREENSIZE]; qint32 m_fftPeakHoldBuf[MAX_SCREENSIZE]; double *m_fftData; /*! pointer to incoming FFT data */ double *m_wfData; int m_fftDataSize; int m_XAxisYCenter; int m_YAxisWidth; eCapturetype m_CursorCaptured; QPixmap m_2DPixmap; QPixmap m_OverlayPixmap; QPixmap m_WaterfallPixmap; QColor m_ColorTbl[256]; QSize m_Size; QString m_Str; QString m_HDivText[HORZ_DIVS_MAX+1]; bool m_Running; bool m_DrawOverlay; qint64 m_CenterFreq; qint64 m_FftCenter; qint64 m_DemodCenterFreq; bool m_CenterLineEnabled; /*!< Distinguish center line. */ bool m_FilterBoxEnabled; /*!< Draw filter box. */ int m_DemodHiCutFreq; int m_DemodLowCutFreq; int m_DemodFreqX; //screen coordinate x position int m_DemodHiCutFreqX; //screen coordinate x position int m_DemodLowCutFreqX; //screen coordinate x position int m_CursorCaptureDelta; int m_GrabPosition; int m_Percent2DScreen; int m_FLowCmin; int m_FLowCmax; int m_FHiCmin; int m_FHiCmax; bool m_symetric; int m_HorDivs; /*!< Current number of horizontal divisions. Calculated from width. */ int m_VerDivs; /*!< Current number of vertical divisions. Calculated from height. */ double m_MaxdB; double m_MindB; qint32 m_dBStepSize; qint32 m_Span; double m_SampleFreq; /*!< Sample rate. */ qint32 m_FreqUnits; int m_ClickResolution; int m_FilterClickResolution; int m_Xzero; int m_Yzero; /*!< Used to measure mouse drag direction. */ int m_FreqDigits; /*!< Number of decimal digits in frequency strings. */ int m_FontSize; /*!< Font size in points. */ int m_HdivDelta; /*!< Minimum distance in pixels between two horizontal grid lines (vertical division). */ int m_VdivDelta; /*!< Minimum distance in pixels between two vertical grid lines (horizontal division). */ quint32 m_LastSampleRate; QColor m_FftColor, m_FftCol0, m_FftCol1, m_PeakHoldColor; bool m_FftFill; double m_PeakDetection; QMap m_Peaks; }; #endif // PLOTTER_H gqrx-sdr-2.2.0.74.d97bd7/qtgui/qtcolorpicker.cpp000066400000000000000000000663621226036373100212500ustar00rootroot00000000000000/**************************************************************************** ** ** This file is part of a Qt Solutions component. ** ** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). ** ** Contact: Qt Software Information (qt-info@nokia.com) ** ** Commercial Usage ** Licensees holding valid Qt Commercial licenses may use this file in ** accordance with the Qt Solutions Commercial License Agreement provided ** with the Software or, alternatively, in accordance with the terms ** contained in a written agreement between you and Nokia. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain ** additional rights. These rights are described in the Nokia Qt LGPL ** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this ** package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** Please note Third Party Software included with Qt Solutions may impose ** additional restrictions and it is the user's responsibility to ensure ** that they have met the licensing requirements of the GPL, LGPL, or Qt ** Solutions Commercial license and the relevant license of the Third ** Party Software they are using. ** ** If you are unsure which license is appropriate for your use, please ** contact the sales department at qt-sales@nokia.com. ** ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "qtcolorpicker.h" /*! \class QtColorPicker \brief The QtColorPicker class provides a widget for selecting colors from a popup color grid. Users can invoke the color picker by clicking on it, or by navigating to it and pressing Space. They can use the mouse or arrow keys to navigate between colors on the grid, and select a color by clicking or by pressing Enter or Space. The colorChanged() signal is emitted whenever the color picker's color changes. The widget also supports negative selection: Users can click and hold the mouse button on the QtColorPicker widget, then move the mouse over the color grid and release the mouse button over the color they wish to select. The color grid shows a customized selection of colors. An optional ellipsis "..." button (signifying "more") can be added at the bottom of the grid; if the user presses this, a QColorDialog pops up and lets them choose any color they like. This button is made available by using setColorDialogEnabled(). When a color is selected, the QtColorPicker widget shows the color and its name. If the name cannot be determined, the translatable name "Custom" is used. The QtColorPicker object is optionally initialized with the number of columns in the color grid. Colors are then added left to right, top to bottom using insertColor(). If the number of columns is not set, QtColorPicker calculates the number of columns and rows that will make the grid as square as possible. \code DrawWidget::DrawWidget(QWidget *parent, const char *name) { QtColorPicker *picker = new QtColorPicker(this); picker->insertColor(red, "Red")); picker->insertColor(QColor("green"), "Green")); picker->insertColor(QColor(0, 0, 255), "Blue")); picker->insertColor(white); connect(colors, SIGNAL(colorChanged(const QColor &)), SLOT(setCurrentColor(const QColor &))); } \endcode An alternative to adding colors manually is to initialize the grid with QColorDialog's standard colors using setStandardColors(). QtColorPicker also provides a the static function getColor(), which pops up the grid of standard colors at any given point. \img colorpicker1.png \img colorpicker2.png \sa QColorDialog */ /*! \fn QtColorPicker::colorChanged(const QColor &color) This signal is emitted when the QtColorPicker's color is changed. \a color is the new color. To obtain the color's name, use text(). */ /* A class that acts very much like a QPushButton. It's not styled, so we can expect the exact same look, feel and geometry everywhere. Also, this button always emits clicked on mouseRelease, even if the mouse button was not pressed inside the widget. */ class ColorPickerButton : public QFrame { Q_OBJECT public: ColorPickerButton(QWidget *parent); signals: void clicked(); protected: void mousePressEvent(QMouseEvent *e); void mouseMoveEvent(QMouseEvent *e); void mouseReleaseEvent(QMouseEvent *e); void keyPressEvent(QKeyEvent *e); void keyReleaseEvent(QKeyEvent *e); void paintEvent(QPaintEvent *e); void focusInEvent(QFocusEvent *e); void focusOutEvent(QFocusEvent *e); }; /* This class represents each "color" or item in the color grid. */ class ColorPickerItem : public QFrame { Q_OBJECT public: ColorPickerItem(const QColor &color = Qt::white, const QString &text = QString::null, QWidget *parent = 0); ~ColorPickerItem(); QColor color() const; QString text() const; void setSelected(bool); bool isSelected() const; signals: void clicked(); void selected(); public slots: void setColor(const QColor &color, const QString &text = QString()); protected: void mousePressEvent(QMouseEvent *e); void mouseReleaseEvent(QMouseEvent *e); void mouseMoveEvent(QMouseEvent *e); void paintEvent(QPaintEvent *e); private: QColor c; QString t; bool sel; }; /* */ class ColorPickerPopup : public QFrame { Q_OBJECT public: ColorPickerPopup(int width, bool withColorDialog, QWidget *parent = 0); ~ColorPickerPopup(); void insertColor(const QColor &col, const QString &text, int index); void exec(); void setExecFlag(); QColor lastSelected() const; ColorPickerItem *find(const QColor &col) const; QColor color(int index) const; signals: void selected(const QColor &); void hid(); public slots: void getColorFromDialog(); protected slots: void updateSelected(); protected: void keyPressEvent(QKeyEvent *e); void showEvent(QShowEvent *e); void hideEvent(QHideEvent *e); void mouseReleaseEvent(QMouseEvent *e); void regenerateGrid(); private: QMap > widgetAt; QList items; QGridLayout *grid; ColorPickerButton *moreButton; QEventLoop *eventLoop; int lastPos; int cols; QColor lastSel; }; /*! Constructs a QtColorPicker widget. The popup will display a grid with \a cols columns, or if \a cols is -1, the number of columns will be calculated automatically. If \a enableColorDialog is true, the popup will also have a "More" button (signified by an ellipsis "...") that presents a QColorDialog when clicked. After constructing a QtColorPicker, call insertColor() to add individual colors to the popup grid, or call setStandardColors() to add all the standard colors in one go. The \a parent argument is passed to QFrame's constructor. \sa QFrame */ QtColorPicker::QtColorPicker(QWidget *parent, int cols, bool enableColorDialog) : QPushButton(parent), popup(0), withColorDialog(enableColorDialog) { setFocusPolicy(Qt::StrongFocus); setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); setAutoDefault(false); setAutoFillBackground(true); setCheckable(true); // Set text setText(tr("Black")); firstInserted = false; // Create and set icon col = Qt::black; dirty = true; // Create color grid popup and connect to it. popup = new ColorPickerPopup(cols, withColorDialog, this); connect(popup, SIGNAL(selected(const QColor &)), SLOT(setCurrentColor(const QColor &))); connect(popup, SIGNAL(hid()), SLOT(popupClosed())); // Connect this push button's pressed() signal. connect(this, SIGNAL(toggled(bool)), SLOT(buttonPressed(bool))); } /*! Destructs the QtColorPicker. */ QtColorPicker::~QtColorPicker() { } /*! \internal Pops up the color grid, and makes sure the status of QtColorPicker's button is right. */ void QtColorPicker::buttonPressed(bool toggled) { if (!toggled) return; const QRect desktop = QApplication::desktop()->geometry(); // Make sure the popup is inside the desktop. QPoint pos = mapToGlobal(rect().bottomLeft()); if (pos.x() < desktop.left()) pos.setX(desktop.left()); if (pos.y() < desktop.top()) pos.setY(desktop.top()); if ((pos.x() + popup->sizeHint().width()) > desktop.width()) pos.setX(desktop.width() - popup->sizeHint().width()); if ((pos.y() + popup->sizeHint().height()) > desktop.bottom()) pos.setY(desktop.bottom() - popup->sizeHint().height()); popup->move(pos); if (ColorPickerItem *item = popup->find(col)) item->setSelected(true); // Remove focus from this widget, preventing the focus rect // from showing when the popup is shown. Order an update to // make sure the focus rect is cleared. clearFocus(); update(); // Allow keyboard navigation as soon as the popup shows. popup->setFocus(); // Execute the popup. The popup will enter the event loop. popup->show(); } /*! \internal */ void QtColorPicker::paintEvent(QPaintEvent *e) { if (dirty) { int iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize); QPixmap pix(iconSize, iconSize); pix.fill(palette().button().color()); QPainter p(&pix); int w = pix.width(); // width of cell in pixels int h = pix.height(); // height of cell in pixels p.setPen(QPen(Qt::gray)); p.setBrush(col); p.drawRect(2, 2, w - 5, h - 5); setIcon(QIcon(pix)); dirty = false; } QPushButton::paintEvent(e); } /*! \internal Makes sure the button isn't pressed when the popup hides. */ void QtColorPicker::popupClosed() { setChecked(false); setFocus(); } /*! Returns the currently selected color. \sa text() */ QColor QtColorPicker::currentColor() const { return col; } /*! Returns the color at position \a index. */ QColor QtColorPicker::color(int index) const { return popup->color(index); } /*! Adds the 17 predefined colors from the Qt namespace. (The names given to the colors, "Black", "White", "Red", etc., are all translatable.) \sa insertColor() */ void QtColorPicker::setStandardColors() { insertColor(Qt::black, tr("Black")); insertColor(Qt::white, tr("White")); insertColor(Qt::red, tr("Red")); insertColor(Qt::darkRed, tr("Dark red")); insertColor(Qt::green, tr("Green")); insertColor(Qt::darkGreen, tr("Dark green")); insertColor(Qt::blue, tr("Blue")); insertColor(Qt::darkBlue, tr("Dark blue")); insertColor(Qt::cyan, tr("Cyan")); insertColor(Qt::darkCyan, tr("Dark cyan")); insertColor(Qt::magenta, tr("Magenta")); insertColor(Qt::darkMagenta, tr("Dark magenta")); insertColor(Qt::yellow, tr("Yellow")); insertColor(Qt::darkYellow, tr("Dark yellow")); insertColor(Qt::gray, tr("Gray")); insertColor(Qt::darkGray, tr("Dark gray")); insertColor(Qt::lightGray, tr("Light gray")); } /*! Makes \a color current. If \a color is not already in the color grid, it is inserted with the text "Custom". This function emits the colorChanged() signal if the new color is valid, and different from the old one. */ void QtColorPicker::setCurrentColor(const QColor &color) { if (col == color || !color.isValid()) return; ColorPickerItem *item = popup->find(color); if (!item) { insertColor(color, tr("Custom")); item = popup->find(color); } col = color; setText(item->text()); dirty = true; popup->hide(); repaint(); item->setSelected(true); emit colorChanged(color); } /*! Adds the color \a color with the name \a text to the color grid, at position \a index. If index is -1, the color is assigned automatically assigned a position, starting from left to right, top to bottom. */ void QtColorPicker::insertColor(const QColor &color, const QString &text, int index) { popup->insertColor(color, text, index); if (!firstInserted) { col = color; setText(text); firstInserted = true; } } /*! \property QtColorPicker::colorDialog \brief Whether the ellipsis "..." (more) button is available. If this property is set to TRUE, the color grid popup will include a "More" button (signified by an ellipsis, "...") which pops up a QColorDialog when clicked. The user will then be able to select any custom color they like. */ void QtColorPicker::setColorDialogEnabled(bool enabled) { withColorDialog = enabled; } bool QtColorPicker::colorDialogEnabled() const { return withColorDialog; } /*! Pops up a color grid with Qt default colors at \a point, using global coordinates. If \a allowCustomColors is true, there will also be a button on the popup that invokes QColorDialog. For example: \code void Drawer::mouseReleaseEvent(QMouseEvent *e) { if (e->button() & RightButton) { QColor color = QtColorPicker::getColor(mapToGlobal(e->pos())); } } \endcode */ QColor QtColorPicker::getColor(const QPoint &point, bool allowCustomColors) { ColorPickerPopup popup(-1, allowCustomColors); popup.insertColor(Qt::black, tr("Black"), 0); popup.insertColor(Qt::white, tr("White"), 1); popup.insertColor(Qt::red, tr("Red"), 2); popup.insertColor(Qt::darkRed, tr("Dark red"), 3); popup.insertColor(Qt::green, tr("Green"), 4); popup.insertColor(Qt::darkGreen, tr("Dark green"), 5); popup.insertColor(Qt::blue, tr("Blue"), 6); popup.insertColor(Qt::darkBlue, tr("Dark blue"), 7); popup.insertColor(Qt::cyan, tr("Cyan"), 8); popup.insertColor(Qt::darkCyan, tr("Dark cyan"), 9); popup.insertColor(Qt::magenta, tr("Magenta"), 10); popup.insertColor(Qt::darkMagenta, tr("Dark magenta"), 11); popup.insertColor(Qt::yellow, tr("Yellow"), 12); popup.insertColor(Qt::darkYellow, tr("Dark yellow"), 13); popup.insertColor(Qt::gray, tr("Gray"), 14); popup.insertColor(Qt::darkGray, tr("Dark gray"), 15); popup.insertColor(Qt::lightGray, tr("Light gray"), 16); popup.move(point); popup.exec(); return popup.lastSelected(); } /*! \internal Constructs the popup widget. */ ColorPickerPopup::ColorPickerPopup(int width, bool withColorDialog, QWidget *parent) : QFrame(parent, Qt::Popup) { setFrameStyle(QFrame::StyledPanel); setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); setFocusPolicy(Qt::StrongFocus); setMouseTracking(true); cols = width; if (withColorDialog) { moreButton = new ColorPickerButton(this); moreButton->setFixedWidth(24); moreButton->setFixedHeight(21); moreButton->setFrameRect(QRect(2, 2, 20, 17)); connect(moreButton, SIGNAL(clicked()), SLOT(getColorFromDialog())); } else { moreButton = 0; } eventLoop = 0; grid = 0; regenerateGrid(); } /*! \internal Destructs the popup widget. */ ColorPickerPopup::~ColorPickerPopup() { if (eventLoop) eventLoop->exit(); } /*! \internal If there is an item whole color is equal to \a col, returns a pointer to this item; otherwise returns 0. */ ColorPickerItem *ColorPickerPopup::find(const QColor &col) const { for (int i = 0; i < items.size(); ++i) { if (items.at(i) && items.at(i)->color() == col) return items.at(i); } return 0; } /*! \internal Adds \a item to the grid. The items are added from top-left to bottom-right. */ void ColorPickerPopup::insertColor(const QColor &col, const QString &text, int index) { // Don't add colors that we have already. ColorPickerItem *existingItem = find(col); ColorPickerItem *lastSelectedItem = find(lastSelected()); if (existingItem) { if (lastSelectedItem && existingItem != lastSelectedItem) lastSelectedItem->setSelected(false); existingItem->setFocus(); existingItem->setSelected(true); return; } ColorPickerItem *item = new ColorPickerItem(col, text, this); if (lastSelectedItem) { lastSelectedItem->setSelected(false); } else { item->setSelected(true); lastSel = col; } item->setFocus(); connect(item, SIGNAL(selected()), SLOT(updateSelected())); if (index == -1) index = items.count(); items.insert((unsigned int)index, item); regenerateGrid(); update(); } /*! \internal */ QColor ColorPickerPopup::color(int index) const { if (index < 0 || index > (int) items.count() - 1) return QColor(); ColorPickerPopup *that = (ColorPickerPopup *)this; return that->items.at(index)->color(); } /*! \internal */ void ColorPickerPopup::exec() { show(); QEventLoop e; eventLoop = &e; (void) e.exec(); eventLoop = 0; } /*! \internal */ void ColorPickerPopup::updateSelected() { QLayoutItem *layoutItem; int i = 0; while ((layoutItem = grid->itemAt(i)) != 0) { QWidget *w = layoutItem->widget(); if (w && w->inherits("ColorPickerItem")) { ColorPickerItem *litem = reinterpret_cast(layoutItem->widget()); if (litem != sender()) litem->setSelected(false); } ++i; } if (sender() && sender()->inherits("ColorPickerItem")) { ColorPickerItem *item = (ColorPickerItem *)sender(); lastSel = item->color(); emit selected(item->color()); } hide(); } /*! \internal */ void ColorPickerPopup::mouseReleaseEvent(QMouseEvent *e) { if (!rect().contains(e->pos())) hide(); } /*! \internal Controls keyboard navigation and selection on the color grid. */ void ColorPickerPopup::keyPressEvent(QKeyEvent *e) { int curRow = 0; int curCol = 0; bool foundFocus = false; for (int j = 0; !foundFocus && j < grid->rowCount(); ++j) { for (int i = 0; !foundFocus && i < grid->columnCount(); ++i) { if (widgetAt[j][i] && widgetAt[j][i]->hasFocus()) { curRow = j; curCol = i; foundFocus = true; break; } } } switch (e->key()) { case Qt::Key_Left: if (curCol > 0) --curCol; else if (curRow > 0) { --curRow; curCol = grid->columnCount() - 1; } break; case Qt::Key_Right: if (curCol < grid->columnCount() - 1 && widgetAt[curRow][curCol + 1]) ++curCol; else if (curRow < grid->rowCount() - 1) { ++curRow; curCol = 0; } break; case Qt::Key_Up: if (curRow > 0) --curRow; else curCol = 0; break; case Qt::Key_Down: if (curRow < grid->rowCount() - 1) { QWidget *w = widgetAt[curRow + 1][curCol]; if (w) { ++curRow; } else for (int i = 1; i < grid->columnCount(); ++i) { if (!widgetAt[curRow + 1][i]) { curCol = i - 1; ++curRow; break; } } } break; case Qt::Key_Space: case Qt::Key_Return: case Qt::Key_Enter: { QWidget *w = widgetAt[curRow][curCol]; if (w && w->inherits("ColorPickerItem")) { ColorPickerItem *wi = reinterpret_cast(w); wi->setSelected(true); QLayoutItem *layoutItem; int i = 0; while ((layoutItem = grid->itemAt(i)) != 0) { QWidget *w = layoutItem->widget(); if (w && w->inherits("ColorPickerItem")) { ColorPickerItem *litem = reinterpret_cast(layoutItem->widget()); if (litem != wi) litem->setSelected(false); } ++i; } lastSel = wi->color(); emit selected(wi->color()); hide(); } else if (w && w->inherits("QPushButton")) { ColorPickerItem *wi = reinterpret_cast(w); wi->setSelected(true); QLayoutItem *layoutItem; int i = 0; while ((layoutItem = grid->itemAt(i)) != 0) { QWidget *w = layoutItem->widget(); if (w && w->inherits("ColorPickerItem")) { ColorPickerItem *litem = reinterpret_cast(layoutItem->widget()); if (litem != wi) litem->setSelected(false); } ++i; } lastSel = wi->color(); emit selected(wi->color()); hide(); } } break; case Qt::Key_Escape: hide(); break; default: e->ignore(); break; } widgetAt[curRow][curCol]->setFocus(); } /*! \internal */ void ColorPickerPopup::hideEvent(QHideEvent *e) { if (eventLoop) { eventLoop->exit(); } setFocus(); emit hid(); QFrame::hideEvent(e); } /*! \internal */ QColor ColorPickerPopup::lastSelected() const { return lastSel; } /*! \internal Sets focus on the popup to enable keyboard navigation. Draws focusRect and selection rect. */ void ColorPickerPopup::showEvent(QShowEvent *) { bool foundSelected = false; for (int i = 0; i < grid->columnCount(); ++i) { for (int j = 0; j < grid->rowCount(); ++j) { QWidget *w = widgetAt[j][i]; if (w && w->inherits("ColorPickerItem")) { if (((ColorPickerItem *)w)->isSelected()) { w->setFocus(); foundSelected = true; break; } } } } if (!foundSelected) { if (items.count() == 0) setFocus(); else widgetAt[0][0]->setFocus(); } } /*! */ void ColorPickerPopup::regenerateGrid() { widgetAt.clear(); int columns = cols; if (columns == -1) columns = (int) ceil(sqrt((float) items.count())); // When the number of columns grows, the number of rows will // fall. There's no way to shrink a grid, so we create a new // one. if (grid) delete grid; grid = new QGridLayout(this); grid->setMargin(1); grid->setSpacing(0); int ccol = 0, crow = 0; for (int i = 0; i < items.size(); ++i) { if (items.at(i)) { widgetAt[crow][ccol] = items.at(i); grid->addWidget(items.at(i), crow, ccol++); if (ccol == columns) { ++crow; ccol = 0; } } } if (moreButton) { grid->addWidget(moreButton, crow, ccol); widgetAt[crow][ccol] = moreButton; } updateGeometry(); } /*! \internal Copies the color dialog's currently selected item and emits itemSelected(). */ void ColorPickerPopup::getColorFromDialog() { bool ok; QRgb rgb = QColorDialog::getRgba(lastSel.rgba(), &ok, parentWidget()); if (!ok) return; QColor col = QColor::fromRgba(rgb); insertColor(col, tr("Custom"), -1); lastSel = col; emit selected(col); } /*! Constructs a ColorPickerItem whose color is set to \a color, and whose name is set to \a text. */ ColorPickerItem::ColorPickerItem(const QColor &color, const QString &text, QWidget *parent) : QFrame(parent), c(color), t(text), sel(false) { setToolTip(t); setFixedWidth(24); setFixedHeight(21); } /*! Destructs a ColorPickerItem. */ ColorPickerItem::~ColorPickerItem() { } /*! Returns the item's color. \sa text() */ QColor ColorPickerItem::color() const { return c; } /*! Returns the item's text. \sa color() */ QString ColorPickerItem::text() const { return t; } /*! */ bool ColorPickerItem::isSelected() const { return sel; } /*! */ void ColorPickerItem::setSelected(bool selected) { sel = selected; update(); } /*! Sets the item's color to \a color, and its name to \a text. */ void ColorPickerItem::setColor(const QColor &color, const QString &text) { c = color; t = text; setToolTip(t); update(); } /*! */ void ColorPickerItem::mouseMoveEvent(QMouseEvent *) { setFocus(); update(); } /*! */ void ColorPickerItem::mouseReleaseEvent(QMouseEvent *) { sel = true; emit selected(); } /*! */ void ColorPickerItem::mousePressEvent(QMouseEvent *) { setFocus(); update(); } /*! */ void ColorPickerItem::paintEvent(QPaintEvent *) { QPainter p(this); int w = width(); // width of cell in pixels int h = height(); // height of cell in pixels p.setPen( QPen( Qt::gray, 0, Qt::SolidLine ) ); if (sel) p.drawRect(1, 1, w - 3, h - 3); p.setPen( QPen( Qt::black, 0, Qt::SolidLine ) ); p.drawRect(3, 3, w - 7, h - 7); p.fillRect(QRect(4, 4, w - 8, h - 8), QBrush(c)); if (hasFocus()) p.drawRect(0, 0, w - 1, h - 1); } /*! */ ColorPickerButton::ColorPickerButton(QWidget *parent) : QFrame(parent) { setFrameStyle(StyledPanel); } /*! */ void ColorPickerButton::mousePressEvent(QMouseEvent *) { setFrameShadow(Sunken); update(); } /*! */ void ColorPickerButton::mouseMoveEvent(QMouseEvent *) { setFocus(); update(); } /*! */ void ColorPickerButton::mouseReleaseEvent(QMouseEvent *) { setFrameShadow(Raised); repaint(); emit clicked(); } /*! */ void ColorPickerButton::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Up || e->key() == Qt::Key_Down || e->key() == Qt::Key_Left || e->key() == Qt::Key_Right) { qApp->sendEvent(parent(), e); } else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Space || e->key() == Qt::Key_Return) { setFrameShadow(Sunken); update(); } else { QFrame::keyPressEvent(e); } } /*! */ void ColorPickerButton::keyReleaseEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Up || e->key() == Qt::Key_Down || e->key() == Qt::Key_Left || e->key() == Qt::Key_Right) { qApp->sendEvent(parent(), e); } else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Space || e->key() == Qt::Key_Return) { setFrameShadow(Raised); repaint(); emit clicked(); } else { QFrame::keyReleaseEvent(e); } } /*! */ void ColorPickerButton::focusInEvent(QFocusEvent *e) { setFrameShadow(Raised); update(); QFrame::focusOutEvent(e); } /*! */ void ColorPickerButton::focusOutEvent(QFocusEvent *e) { setFrameShadow(Raised); update(); QFrame::focusOutEvent(e); } /*! */ void ColorPickerButton::paintEvent(QPaintEvent *e) { QFrame::paintEvent(e); QPainter p(this); p.fillRect(contentsRect(), palette().button()); QRect r = rect(); int offset = frameShadow() == Sunken ? 1 : 0; QPen pen(palette().buttonText(), 1); p.setPen(pen); p.drawRect(r.center().x() + offset - 4, r.center().y() + offset, 1, 1); p.drawRect(r.center().x() + offset , r.center().y() + offset, 1, 1); p.drawRect(r.center().x() + offset + 4, r.center().y() + offset, 1, 1); if (hasFocus()) { p.setPen( QPen( Qt::black, 0, Qt::SolidLine ) ); p.drawRect(0, 0, width() - 1, height() - 1); } p.end(); } #include "qtcolorpicker.moc" gqrx-sdr-2.2.0.74.d97bd7/qtgui/qtcolorpicker.h000066400000000000000000000076241226036373100207110ustar00rootroot00000000000000/**************************************************************************** ** ** This file is part of a Qt Solutions component. ** ** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). ** ** Contact: Qt Software Information (qt-info@nokia.com) ** ** Commercial Usage ** Licensees holding valid Qt Commercial licenses may use this file in ** accordance with the Qt Solutions Commercial License Agreement provided ** with the Software or, alternatively, in accordance with the terms ** contained in a written agreement between you and Nokia. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain ** additional rights. These rights are described in the Nokia Qt LGPL ** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this ** package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** Please note Third Party Software included with Qt Solutions may impose ** additional restrictions and it is the user's responsibility to ensure ** that they have met the licensing requirements of the GPL, LGPL, or Qt ** Solutions Commercial license and the relevant license of the Third ** Party Software they are using. ** ** If you are unsure which license is appropriate for your use, please ** contact the sales department at qt-sales@nokia.com. ** ****************************************************************************/ #ifndef QTCOLORPICKER_H #define QTCOLORPICKER_H #include #include #include #include #include #include #if defined(Q_WS_WIN) # if !defined(QT_QTCOLORPICKER_EXPORT) && !defined(QT_QTCOLORPICKER_IMPORT) # define QT_QTCOLORPICKER_EXPORT # elif defined(QT_QTCOLORPICKER_IMPORT) # if defined(QT_QTCOLORPICKER_EXPORT) # undef QT_QTCOLORPICKER_EXPORT # endif # define QT_QTCOLORPICKER_EXPORT __declspec(dllimport) # elif defined(QT_QTCOLORPICKER_EXPORT) # undef QT_QTCOLORPICKER_EXPORT # define QT_QTCOLORPICKER_EXPORT __declspec(dllexport) # endif #else # define QT_QTCOLORPICKER_EXPORT #endif class ColorPickerPopup; class QT_QTCOLORPICKER_EXPORT QtColorPicker : public QPushButton { Q_OBJECT Q_PROPERTY(bool colorDialog READ colorDialogEnabled WRITE setColorDialogEnabled) public: QtColorPicker(QWidget *parent = 0, int columns = -1, bool enableColorDialog = true); ~QtColorPicker(); void insertColor(const QColor &color, const QString &text = QString::null, int index = -1); QColor currentColor() const; QColor color(int index) const; void setColorDialogEnabled(bool enabled); bool colorDialogEnabled() const; void setStandardColors(); static QColor getColor(const QPoint &pos, bool allowCustomColors = true); public slots: void setCurrentColor(const QColor &col); signals: void colorChanged(const QColor &); protected: void paintEvent(QPaintEvent *e); private slots: void buttonPressed(bool toggled); void popupClosed(); private: ColorPickerPopup *popup; QColor col; bool withColorDialog; bool dirty; bool firstInserted; }; #endif gqrx-sdr-2.2.0.74.d97bd7/receivers/000077500000000000000000000000001226036373100165045ustar00rootroot00000000000000gqrx-sdr-2.2.0.74.d97bd7/receivers/nbrx.cpp000066400000000000000000000135751226036373100201740ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2013 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include #include "receivers/nbrx.h" #define PREF_QUAD_RATE 48000.0 #define PREF_AUDIO_RATE 48000.0 nbrx_sptr make_nbrx(float quad_rate, float audio_rate) { return gnuradio::get_initial_sptr(new nbrx(quad_rate, audio_rate)); } nbrx::nbrx(float quad_rate, float audio_rate) : receiver_base_cf("NBRX"), d_running(false), d_quad_rate(quad_rate), d_audio_rate(audio_rate), d_demod(NBRX_DEMOD_FM) { iq_resamp = make_resampler_cc(PREF_QUAD_RATE/d_quad_rate); nb = make_rx_nb_cc(PREF_QUAD_RATE, 3.3, 2.5); filter = make_rx_filter(PREF_QUAD_RATE, -5000.0, 5000.0, 1000.0); agc = make_rx_agc_cc(PREF_QUAD_RATE, true, -100, 0, 2, 100, false); sql = gr::analog::simple_squelch_cc::make(-150.0, 0.001); meter = make_rx_meter_c(DETECTOR_TYPE_RMS); demod_ssb = gr::blocks::complex_to_real::make(1); demod_fm = make_rx_demod_fm(PREF_QUAD_RATE, PREF_AUDIO_RATE, 5000.0, 75.0e-6); demod_am = make_rx_demod_am(PREF_QUAD_RATE, PREF_AUDIO_RATE, true); audio_rr = make_resampler_ff(d_audio_rate/PREF_AUDIO_RATE); connect(self(), 0, iq_resamp, 0); connect(iq_resamp, 0, nb, 0); connect(nb, 0, filter, 0); connect(filter, 0, meter, 0); connect(filter, 0, sql, 0); connect(sql, 0, agc, 0); connect(agc, 0, demod_fm, 0); connect(demod_fm, 0, audio_rr, 0); connect(audio_rr, 0, self(), 0); // left channel connect(audio_rr, 0, self(), 1); // right channel // FIXME: we only need audio_rr when audio_rate != PREF_AUDIO_RATE } nbrx::~nbrx() { } bool nbrx::start() { d_running = true; return true; } bool nbrx::stop() { d_running = false; return true; } void nbrx::set_quad_rate(float quad_rate) { if (abs(d_quad_rate-quad_rate) > 0.5) { #ifndef QT_NO_DEBUG_OUTPUT std::cout << "Changing NB_RX quad rate: " << d_quad_rate << " -> " << quad_rate << std::endl; #endif d_quad_rate = quad_rate; lock(); iq_resamp->set_rate(PREF_QUAD_RATE/d_quad_rate); unlock(); } } void nbrx::set_audio_rate(float audio_rate) { (void) audio_rate; } void nbrx::set_filter(double low, double high, double tw) { filter->set_param(low, high, tw); } float nbrx::get_signal_level(bool dbfs) { if (dbfs) return meter->get_level_db(); else return meter->get_level(); } void nbrx::set_nb_on(int nbid, bool on) { if (nbid == 1) nb->set_nb1_on(on); else if (nbid == 2) nb->set_nb2_on(on); } void nbrx::set_nb_threshold(int nbid, float threshold) { if (nbid == 1) nb->set_threshold1(threshold); else if (nbid == 2) nb->set_threshold2(threshold); } void nbrx::set_sql_level(double level_db) { sql->set_threshold(level_db); } void nbrx::set_sql_alpha(double alpha) { sql->set_alpha(alpha); } void nbrx::set_agc_on(bool agc_on) { agc->set_agc_on(agc_on); } void nbrx::set_agc_hang(bool use_hang) { agc->set_use_hang(use_hang); } void nbrx::set_agc_threshold(int threshold) { agc->set_threshold(threshold); } void nbrx::set_agc_slope(int slope) { agc->set_slope(slope); } void nbrx::set_agc_decay(int decay_ms) { agc->set_decay(decay_ms); } void nbrx::set_agc_manual_gain(int gain) { agc->set_manual_gain(gain); } void nbrx::set_demod(int rx_demod) { nbrx_demod current_demod = d_demod; /* check if new demodulator selection is valid */ if ((rx_demod < NBRX_DEMOD_NONE) || (rx_demod >= NBRX_DEMOD_NUM)) return; if (rx_demod == current_demod) { /* nothing to do */ return; } /* lock graph while we reconfigure */ lock(); /* disconnect current demodulator */ switch (current_demod) { default: case NBRX_DEMOD_NONE: /** FIXME! **/ case NBRX_DEMOD_SSB: disconnect(agc, 0, demod_ssb, 0); disconnect(demod_ssb, 0, audio_rr, 0); break; case NBRX_DEMOD_AM: disconnect(agc, 0, demod_am, 0); disconnect(demod_am, 0, audio_rr, 0); break; case NBRX_DEMOD_FM: disconnect(agc, 0, demod_fm, 0); disconnect(demod_fm, 0, audio_rr, 0); break; } switch (rx_demod) { case NBRX_DEMOD_NONE: /** FIXME! **/ case NBRX_DEMOD_SSB: d_demod = NBRX_DEMOD_SSB; connect(agc, 0, demod_ssb, 0); connect(demod_ssb, 0, audio_rr, 0); break; case NBRX_DEMOD_AM: d_demod = NBRX_DEMOD_AM; connect(agc, 0, demod_am, 0); connect(demod_am, 0, audio_rr, 0); break; case NBRX_DEMOD_FM: d_demod = NBRX_DEMOD_FM; connect(agc, 0, demod_fm, 0); connect(demod_fm, 0, audio_rr, 0); break; default: /* use FMN */ d_demod = NBRX_DEMOD_FM; connect(agc, 0, demod_fm, 0); connect(demod_fm, 0, audio_rr, 0); break; } /* continue processing */ unlock(); } void nbrx::set_fm_maxdev(float maxdev_hz) { demod_fm->set_max_dev(maxdev_hz); } void nbrx::set_fm_deemph(double tau) { demod_fm->set_tau(tau); } void nbrx::set_am_dcr(bool enabled) { demod_am->set_dcr(enabled); } gqrx-sdr-2.2.0.74.d97bd7/receivers/nbrx.h000066400000000000000000000100051226036373100176220ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2013 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef NBRX_H #define NBRX_H #include #include #include "receivers/receiver_base.h" #include "dsp/rx_noise_blanker_cc.h" #include "dsp/rx_filter.h" #include "dsp/rx_meter.h" #include "dsp/rx_agc_xx.h" #include "dsp/rx_demod_fm.h" #include "dsp/rx_demod_am.h" //#include "dsp/resampler_ff.h" #include "dsp/resampler_xx.h" class nbrx; typedef boost::shared_ptr nbrx_sptr; /*! \brief Public constructor of nbrx_sptr. */ nbrx_sptr make_nbrx(float quad_rate, float audio_rate); /*! \brief Narrow band analog receiver * \ingroup RX * * This block provides receiver for AM, narrow band FM and SSB modes. */ class nbrx : public receiver_base_cf { public: /*! \brief Available demodulators. */ enum nbrx_demod { NBRX_DEMOD_NONE = 0, /*!< No demod. Raw I/Q to audio. */ NBRX_DEMOD_AM = 1, /*!< Amplitude modulation. */ NBRX_DEMOD_FM = 2, /*!< Frequency modulation. */ NBRX_DEMOD_SSB = 3, /*!< Single Side Band. */ NBRX_DEMOD_NUM = 4 /*!< Included for convenience. */ }; public: nbrx(float quad_rate, float audio_rate); ~nbrx(); bool start(); bool stop(); void set_quad_rate(float quad_rate); void set_audio_rate(float audio_rate); void set_filter(double low, double high, double tw); float get_signal_level(bool dbfs); /* Noise blanker */ bool has_nb() { return true; } void set_nb_on(int nbid, bool on); void set_nb_threshold(int nbid, float threshold); /* Squelch parameter */ bool has_sql() { return true; } void set_sql_level(double level_db); void set_sql_alpha(double alpha); /* AGC */ bool has_agc() { return true; } void set_agc_on(bool agc_on); void set_agc_hang(bool use_hang); void set_agc_threshold(int threshold); void set_agc_slope(int slope); void set_agc_decay(int decay_ms); void set_agc_manual_gain(int gain); void set_demod(int demod); /* FM parameters */ bool has_fm() { return true; } void set_fm_maxdev(float maxdev_hz); void set_fm_deemph(double tau); /* AM parameters */ bool has_am() { return true; } void set_am_dcr(bool enabled); private: bool d_running; /*!< Whether receiver is running or not. */ float d_quad_rate; /*!< Input sample rate. */ int d_audio_rate; /*!< Audio output rate. */ nbrx_demod d_demod; /*!< Current demodulator. */ resampler_cc_sptr iq_resamp; /*!< Baseband resampler. */ rx_filter_sptr filter; /*!< Non-translating bandpass filter.*/ rx_nb_cc_sptr nb; /*!< Noise blanker. */ rx_meter_c_sptr meter; /*!< Signal strength. */ rx_agc_cc_sptr agc; /*!< Receiver AGC. */ gr::analog::simple_squelch_cc::sptr sql; /*!< Squelch. */ gr::blocks::complex_to_real::sptr demod_ssb; /*!< SSB demodulator. */ rx_demod_fm_sptr demod_fm; /*!< FM demodulator. */ rx_demod_am_sptr demod_am; /*!< AM demodulator. */ resampler_ff_sptr audio_rr; /*!< Audio resampler. */ }; #endif // NBRX_H gqrx-sdr-2.2.0.74.d97bd7/receivers/receiver_base.cpp000066400000000000000000000054201226036373100220070ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2012 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include #include "receivers/receiver_base.h" static const int MIN_IN = 1; /* Mininum number of input streams. */ static const int MAX_IN = 1; /* Maximum number of input streams. */ static const int MIN_OUT = 2; /* Minimum number of output streams. */ static const int MAX_OUT = 2; /* Maximum number of output streams. */ receiver_base_cf::receiver_base_cf(std::string src_name) : gr::hier_block2 (src_name, gr::io_signature::make (MIN_IN, MAX_IN, sizeof(gr_complex)), gr::io_signature::make (MIN_OUT, MAX_OUT, sizeof(float))) { } receiver_base_cf::~receiver_base_cf() { } bool receiver_base_cf::has_nb() { return false; } void receiver_base_cf::set_nb_on(int nbid, bool on) { (void) nbid; (void) on; } void receiver_base_cf::set_nb_threshold(int nbid, float threshold) { (void) nbid; (void) threshold; } bool receiver_base_cf::has_sql() { return false; } void receiver_base_cf::set_sql_level(double level_db) { (void) level_db; } void receiver_base_cf::set_sql_alpha(double alpha) { (void) alpha; } bool receiver_base_cf::has_agc() { return false; } void receiver_base_cf::set_agc_on(bool agc_on) { (void) agc_on; } void receiver_base_cf::set_agc_hang(bool use_hang) { (void) use_hang; } void receiver_base_cf::set_agc_threshold(int threshold) { (void) threshold; } void receiver_base_cf::set_agc_slope(int slope) { (void) slope; } void receiver_base_cf::set_agc_decay(int decay_ms) { (void) decay_ms; } void receiver_base_cf::set_agc_manual_gain(int gain) { (void) gain; } bool receiver_base_cf::has_fm() { return false; } void receiver_base_cf::set_fm_maxdev(float maxdev_hz) { (void) maxdev_hz; } void receiver_base_cf::set_fm_deemph(double tau) { (void) tau; } bool receiver_base_cf::has_am() { return false; } void receiver_base_cf::set_am_dcr(bool enabled) { (void) enabled; } gqrx-sdr-2.2.0.74.d97bd7/receivers/receiver_base.h000066400000000000000000000053331226036373100214570ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2012-2013 Alexandru Csete OZ9AEC. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef RECEIVER_BASE_H #define RECEIVER_BASE_H #include class receiver_base_cf; typedef boost::shared_ptr receiver_base_cf_sptr; /*! \brief Base class for receivers that output audio. * \ingroup RX * * This block provides a base class and common interface for receivers that * outpout audio (or other kind of float data). * */ class receiver_base_cf : public gr::hier_block2 { public: /*! \brief Public contructor. * \param src_name Descriptive name used in the contructor of gr::hier_block2 */ receiver_base_cf(std::string src_name); ~receiver_base_cf(); virtual bool start() = 0; virtual bool stop() = 0; virtual void set_quad_rate(float quad_rate) = 0; virtual void set_audio_rate(float audio_rate) = 0; virtual void set_filter(double low, double high, double tw) = 0; virtual float get_signal_level(bool dbfs) = 0; virtual void set_demod(int demod) = 0; /* the rest is optional */ /* Noise blanker */ virtual bool has_nb(); virtual void set_nb_on(int nbid, bool on); virtual void set_nb_threshold(int nbid, float threshold); /* Squelch parameter */ virtual bool has_sql(); virtual void set_sql_level(double level_db); virtual void set_sql_alpha(double alpha); /* AGC */ virtual bool has_agc(); virtual void set_agc_on(bool agc_on); virtual void set_agc_hang(bool use_hang); virtual void set_agc_threshold(int threshold); virtual void set_agc_slope(int slope); virtual void set_agc_decay(int decay_ms); virtual void set_agc_manual_gain(int gain); /* FM parameters */ virtual bool has_fm(); virtual void set_fm_maxdev(float maxdev_hz); virtual void set_fm_deemph(double tau); /* AM parameters */ virtual bool has_am(); virtual void set_am_dcr(bool enabled); }; #endif // RECEIVER_BASE_H gqrx-sdr-2.2.0.74.d97bd7/receivers/wfmrx.cpp000066400000000000000000000130201226036373100203470ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2012 Alexandru Csete OZ9AEC. * FM stereo implementation by Alex Grinkov a.grinkov(at)gmail.com. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include #include "receivers/wfmrx.h" #define PREF_QUAD_RATE 240e3 // Nominal channel spacing is 200 kHz #define PREF_MIDLE_RATE 120e3 // Midle rate for stereo decoder wfmrx_sptr make_wfmrx(float quad_rate, float audio_rate) { return gnuradio::get_initial_sptr(new wfmrx(quad_rate, audio_rate)); } wfmrx::wfmrx(float quad_rate, float audio_rate) : receiver_base_cf("WFMRX"), d_running(false), d_quad_rate(quad_rate), d_audio_rate(audio_rate), d_demod(WFMRX_DEMOD_MONO) { iq_resamp = make_resampler_cc(PREF_QUAD_RATE/d_quad_rate); filter = make_rx_filter(PREF_QUAD_RATE, -80000.0, 80000.0, 20000.0); sql = gr::analog::simple_squelch_cc::make(-150.0, 0.001); meter = make_rx_meter_c(DETECTOR_TYPE_RMS); demod_fm = make_rx_demod_fm(PREF_QUAD_RATE, PREF_MIDLE_RATE, 75000.0, 50.0e-6); midle_rr = make_resampler_ff(PREF_MIDLE_RATE/PREF_QUAD_RATE); stereo = make_stereo_demod(PREF_MIDLE_RATE, d_audio_rate, true); mono = make_stereo_demod(PREF_MIDLE_RATE, d_audio_rate, false); connect(self(), 0, iq_resamp, 0); connect(iq_resamp, 0, filter, 0); connect(filter, 0, meter, 0); connect(filter, 0, sql, 0); connect(sql, 0, demod_fm, 0); connect(demod_fm, 0, midle_rr, 0); connect(midle_rr, 0, mono, 0); connect(mono, 0, self(), 0); // left channel connect(mono, 1, self(), 1); // right channel } wfmrx::~wfmrx() { } bool wfmrx::start() { d_running = true; return true; } bool wfmrx::stop() { d_running = false; return true; } void wfmrx::set_quad_rate(float quad_rate) { if (abs(d_quad_rate-quad_rate) > 0.5) { #ifndef QT_NO_DEBUG_OUTPUT std::cout << "Changing NB_RX quad rate: " << d_quad_rate << " -> " << quad_rate << std::endl; #endif d_quad_rate = quad_rate; lock(); iq_resamp->set_rate(PREF_QUAD_RATE/d_quad_rate); unlock(); } } void wfmrx::set_audio_rate(float audio_rate) { (void) audio_rate; } void wfmrx::set_filter(double low, double high, double tw) { filter->set_param(low, high, tw); } float wfmrx::get_signal_level(bool dbfs) { if (dbfs) return meter->get_level_db(); else return meter->get_level(); } /* void nbrx::set_nb_on(int nbid, bool on) { if (nbid == 1) nb->set_nb1_on(on); else if (nbid == 2) nb->set_nb2_on(on); } void nbrx::set_nb_threshold(int nbid, float threshold) { if (nbid == 1) nb->set_threshold1(threshold); else if (nbid == 2) nb->set_threshold2(threshold); } */ void wfmrx::set_sql_level(double level_db) { sql->set_threshold(level_db); } void wfmrx::set_sql_alpha(double alpha) { sql->set_alpha(alpha); } /* void nbrx::set_agc_on(bool agc_on) { agc->set_agc_on(agc_on); } void nbrx::set_agc_hang(bool use_hang) { agc->set_use_hang(use_hang); } void nbrx::set_agc_threshold(int threshold) { agc->set_threshold(threshold); } void nbrx::set_agc_slope(int slope) { agc->set_slope(slope); } void nbrx::set_agc_decay(int decay_ms) { agc->set_decay(decay_ms); } void nbrx::set_agc_manual_gain(int gain) { agc->set_manual_gain(gain); } */ void wfmrx::set_demod(int demod) { /* check if new demodulator selection is valid */ if ((demod < WFMRX_DEMOD_MONO) || (demod >= WFMRX_DEMOD_NUM)) return; if (demod == d_demod) { /* nothing to do */ return; } /* lock graph while we reconfigure */ lock(); /* disconnect current demodulator */ switch (d_demod) { case WFMRX_DEMOD_MONO: default: disconnect(midle_rr, 0, mono, 0); disconnect(mono, 0, self(), 0); // left channel disconnect(mono, 1, self(), 1); // right channel break; case WFMRX_DEMOD_STEREO: case WFMRX_DEMOD_STEREO_UKW: /** FIXME! **/ disconnect(midle_rr, 0, stereo, 0); disconnect(stereo, 0, self(), 0); // left channel disconnect(stereo, 1, self(), 1); // right channel } switch (demod) { case WFMRX_DEMOD_MONO: default: connect(midle_rr, 0, mono, 0); connect(mono, 0, self(), 0); // left channel connect(mono, 1, self(), 1); // right channel break; case WFMRX_DEMOD_STEREO: case WFMRX_DEMOD_STEREO_UKW: /** FIXME! **/ connect(midle_rr, 0, stereo, 0); connect(stereo, 0, self(), 0); // left channel connect(stereo, 1, self(), 1); // right channel } d_demod = (wfmrx_demod) demod; /* continue processing */ unlock(); } void wfmrx::set_fm_maxdev(float maxdev_hz) { demod_fm->set_max_dev(maxdev_hz); } void wfmrx::set_fm_deemph(double tau) { demod_fm->set_tau(tau); } gqrx-sdr-2.2.0.74.d97bd7/receivers/wfmrx.h000066400000000000000000000072561226036373100200320ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2012 Alexandru Csete OZ9AEC. * FM stereo implementation by Alex Grinkov a.grinkov(at)gmail.com. * * Gqrx 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, or (at your option) * any later version. * * Gqrx 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 Gqrx; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef WFMRX_H #define WFMRX_H #include #include "receivers/receiver_base.h" #include "dsp/rx_noise_blanker_cc.h" #include "dsp/rx_filter.h" #include "dsp/rx_meter.h" #include "dsp/rx_demod_fm.h" #include "dsp/stereo_demod.h" #include "dsp/resampler_xx.h" class wfmrx; typedef boost::shared_ptr wfmrx_sptr; /*! \brief Public constructor of wfm_rx. */ wfmrx_sptr make_wfmrx(float quad_rate, float audio_rate); /*! \brief Wide band FM receiver. * \ingroup RX * * This block provides receiver for broadcast FM transmissions. */ class wfmrx : public receiver_base_cf { public: /*! \brief Available demodulators. */ enum wfmrx_demod { WFMRX_DEMOD_MONO = 0, /*!< Mono. */ WFMRX_DEMOD_STEREO = 1, /*!< FM stereo. */ WFMRX_DEMOD_STEREO_UKW = 2, /*!< UKW stereo. */ WFMRX_DEMOD_NUM = 3 /*!< Included for convenience. */ }; wfmrx(float quad_rate, float audio_rate); ~wfmrx(); bool start(); bool stop(); void set_quad_rate(float quad_rate); void set_audio_rate(float audio_rate); void set_filter(double low, double high, double tw); float get_signal_level(bool dbfs); /* Noise blanker */ bool has_nb() { return false; } //void set_nb_on(int nbid, bool on); //void set_nb_threshold(int nbid, float threshold); /* Squelch parameter */ bool has_sql() { return true; } void set_sql_level(double level_db); void set_sql_alpha(double alpha); /* AGC */ bool has_agc() { return false; } /*void set_agc_on(bool agc_on); void set_agc_hang(bool use_hang); void set_agc_threshold(int threshold); void set_agc_slope(int slope); void set_agc_decay(int decay_ms); void set_agc_manual_gain(int gain);*/ void set_demod(int demod); /* FM parameters */ bool has_fm() {return true; } void set_fm_maxdev(float maxdev_hz); void set_fm_deemph(double tau); private: bool d_running; /*!< Whether receiver is running or not. */ float d_quad_rate; /*!< Input sample rate. */ int d_audio_rate; /*!< Audio output rate. */ wfmrx_demod d_demod; /*!< Current demodulator. */ resampler_cc_sptr iq_resamp; /*!< Baseband resampler. */ rx_filter_sptr filter; /*!< Non-translating bandpass filter.*/ rx_meter_c_sptr meter; /*!< Signal strength. */ gr::analog::simple_squelch_cc::sptr sql; /*!< Squelch. */ rx_demod_fm_sptr demod_fm; /*!< FM demodulator. */ resampler_ff_sptr midle_rr; /*!< Resampler. */ stereo_demod_sptr stereo; /*!< FM stereo demodulator. */ stereo_demod_sptr mono; /*!< FM stereo demodulator OFF. */ }; #endif // WFMRX_H