pax_global_header00006660000000000000000000000064132014214550014507gustar00rootroot0000000000000052 comment=1503682202ea11afebeccba25d2f345d849e83c2 gqrx-2.9/000077500000000000000000000000001320142145500123425ustar00rootroot00000000000000gqrx-2.9/.gitignore000066400000000000000000000001211320142145500143240ustar00rootroot00000000000000build/ builds/ Makefile gqrx.pro.user* *.o moc_* qrc_* ui_* .DS_Store Info.plist gqrx-2.9/.travis.yml000066400000000000000000000012541320142145500144550ustar00rootroot00000000000000sudo: required dist: trusty language: cpp compiler: - gcc - clang env: - AUDIO_BACKEND="Pulseaudio" - AUDIO_BACKEND="Portaudio" - AUDIO_BACKEND="Gr-audio" before_install: - sudo add-apt-repository -y ppa:bladerf/bladerf - sudo add-apt-repository -y ppa:ettusresearch/uhd - sudo add-apt-repository -y ppa:myriadrf/drivers - sudo add-apt-repository -y ppa:myriadrf/gnuradio - sudo apt-get update -qq - sudo apt-get install -y cmake qt5-default libqt5svg5-dev libboost-dev libpulse-dev portaudio19-dev liblog4cpp5-dev gnuradio-dev gr-osmosdr gr-fcdproplus script: - mkdir build - cd build - cmake -DLINUX_AUDIO_BACKEND:STRING=$AUDIO_BACKEND .. - make gqrx-2.9/CMakeLists.txt000066400000000000000000000137701320142145500151120ustar00rootroot00000000000000cmake_minimum_required(VERSION 2.8.0) # Project name project(gqrx) set(${PROJECT_NAME}_MAJOR "2") set(${PROJECT_NAME}_MINOR "9") set(${PROJECT_NAME}_PATCH "0") ##set(VERSION "${${PROJECT_NAME}_MAJOR}.${${PROJECT_NAME}_MINOR}.${${PROJECT_NAME}_PATCH}") set(VERSION "${${PROJECT_NAME}_MAJOR}.${${PROJECT_NAME}_MINOR}") add_definitions(-DVERSION="${VERSION}") # development version execute_process( COMMAND git describe --long --dirty WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE GITVERSION OUTPUT_STRIP_TRAILING_WHITESPACE ) ##add_definitions(-DVERSION="${GITVERSION}") set(PACKAGE ${PROJECT_NAME}) ########### Main global variables ########### if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build, options are: Debug GProf Valgrind Release" FORCE) endif() set(BUILDTYPE ${CMAKE_BUILD_TYPE}) string(TOUPPER ${BUILDTYPE} BUILDTYPE) add_definitions(-D${BUILDTYPE}) # We have some custom .cmake scripts not in the official distribution. set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules) # Add valgrind build options if necessary if(${CMAKE_BUILD_TYPE} MATCHES "Valgrind") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0") endif() # Disable debug outputs in release builds if(${CMAKE_BUILD_TYPE} MATCHES "Release" OR ${CMAKE_BUILD_TYPE} MATCHES "RelWithDebInfo") add_definitions(-DQT_NO_DEBUG_OUTPUT) endif() if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") # using regular Clang or AppleClang set(CMAKE_COMPILER_IS_CLANGXX 1) endif() if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANGXX) add_definitions(-Wall) add_definitions(-Wextra) add_definitions(-Wno-unused-parameter) add_definitions(-Wsign-compare) endif() if(MSVC) #get math definitions like M_PI add_definitions(-D_USE_MATH_DEFINES) #use std::min()/std::max() add_definitions(-DNOMINMAX) #needed to dynamically link boost add_definitions(-DBOOST_ALL_DYN_LINK) #export gr-rds symbols add_definitions(-Dgnuradio_RDS_EXPORTS) if ("${MSVC_VERSION}" VERSION_LESS "1900") add_definitions(-D__func__=__FUNCTION__) endif() endif() # Functions & macros. These must be defined before including subdirectories. # function to collect all the sources from sub-directories # into a single list function(add_source_files list) get_property(is_defined GLOBAL PROPERTY SRCS_LIST DEFINED) if(NOT is_defined) define_property(GLOBAL PROPERTY ${list} BRIEF_DOCS "List of source files" FULL_DOCS "List of source files to be compiled in one library") endif() # make absolute paths set(SRCS) foreach(s IN LISTS ARGN) if(NOT IS_ABSOLUTE "${s}") get_filename_component(s "${s}" ABSOLUTE) endif() list(APPEND SRCS "${s}") endforeach() # append to global list set_property(GLOBAL APPEND PROPERTY ${list} "${SRCS}") endfunction(add_source_files) # 3rd Party Dependency Stuff find_package(Qt5 COMPONENTS Core Network Widgets Svg REQUIRED) find_package(Boost COMPONENTS system program_options REQUIRED) set(GR_REQUIRED_COMPONENTS RUNTIME ANALOG AUDIO BLOCKS DIGITAL FILTER FFT PMT) find_package(Gnuradio REQUIRED) find_package(Gnuradio-osmosdr REQUIRED) if(NOT GNURADIO_RUNTIME_FOUND) message(FATAL_ERROR "GnuRadio Runtime required to compile gqrx") endif() if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") if(NOT LINUX_AUDIO_BACKEND) set(LINUX_AUDIO_BACKEND Pulseaudio CACHE STRING "Choose the audio backend, options are: Pulseaudio, Gr-audio" FORCE) endif() if(${LINUX_AUDIO_BACKEND} MATCHES "Pulseaudio") find_package(PulseAudio REQUIRED) # there is a defect in the pulse audio cmake file that does not include this library. So we add it here. find_library(PULSE-SIMPLE NAMES pulse-simple REQUIRED) add_definitions(-DWITH_PULSEAUDIO) unset(PORTAUDIO_INCLUDE_DIRS CACHE) unset(PORTAUDIO_LIBRARIES CACHE) elseif(${LINUX_AUDIO_BACKEND} MATCHES "Portaudio") find_package(Portaudio REQUIRED) add_definitions(-DWITH_PORTAUDIO) unset(PULSEAUDIO_FOUND CACHE) unset(PULSEAUDIO_INCLUDE_DIR CACHE) unset(PULSEAUDIO_LIBRARY CACHE) unset(PulseAudio_DIR CACHE) unset(PULSE-SIMPLE CACHE) unset(PULSEAUDIO_INCLUDE_DIR CACHE) unset(PULSEAUDIO_MAINLOOP_LIBRARY CACHE) elseif(${LINUX_AUDIO_BACKEND} MATCHES "Gr-audio") unset(PULSEAUDIO_FOUND CACHE) unset(PULSEAUDIO_INCLUDE_DIR CACHE) unset(PULSEAUDIO_LIBRARY CACHE) unset(PulseAudio_DIR CACHE) unset(PULSE-SIMPLE CACHE) unset(PULSEAUDIO_INCLUDE_DIR CACHE) unset(PULSEAUDIO_MAINLOOP_LIBRARY CACHE) unset(PORTAUDIO_INCLUDE_DIRS CACHE) unset(PORTAUDIO_LIBRARIES CACHE) else() message(FATAL_ERROR "Invalid audio backend: should be either Pulseaudio, Portaudio or Gr-audio") endif() endif() # Airspy optimizations that require modified gr-osmosdr option(CUSTOM_AIRSPY_KERNELS "Enable non-standard Airspy optimizations" ON) if(CUSTOM_AIRSPY_KERNELS) add_definitions(-DCUSTOM_AIRSPY_KERNELS) endif(CUSTOM_AIRSPY_KERNELS) # Tell CMake to run moc when necessary: set(CMAKE_AUTOMOC ON) # As moc files are generated in the binary dir, tell CMake to always look for includes there: set(CMAKE_INCLUDE_CURRENT_DIR ON) # Finish configuring compiler / linker settings & flags include_directories( ${CMAKE_SOURCE_DIR}/include ${Boost_INCLUDE_DIRS} ${GNURADIO_RUNTIME_INCLUDE_DIRS} ${GNURADIO_OSMOSDR_INCLUDE_DIRS} ) link_directories( ${Boost_LIBRARY_DIRS} ${GNURADIO_RUNTIME_LIBRARY_DIRS} ) # Add subdirectories add_subdirectory(src) # uninstall target # https://cmake.org/Wiki/CMake_FAQ#Can_I_do_.22make_uninstall.22_with_CMake.3F configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" IMMEDIATE @ONLY) add_custom_target(uninstall ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake ) gqrx-2.9/COPYING000066400000000000000000001045131320142145500134010ustar00rootroot00000000000000 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-2.9/LICENSE-CTK000066400000000000000000000261471320142145500140000ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITION OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.gqrx-2.9/README.md000066400000000000000000000166341320142145500136330ustar00rootroot00000000000000Gqrx ==== Gqrx is an open source software defined radio (SDR) receiver implemented using [GNU Radio](http://gnuradio.org) and the [Qt GUI toolkit](https://www.qt.io/). Currently it works on Linux and Mac with hardware supported by gr-osmosdr, including Funcube Dongle, RTL-SDR, Airspy, HackRF, BladeRF, RFSpace, USRP and SoapySDR. Gqrx can operate as an AM/FM/SSB receiver with audio output or as an FFT-only instrument. There are also various hooks for interacting with external application using nertwork sockets. Download -------- Gqrx is distributed as source code package and binaries for Linux and Mac. Alternate Mac support is available through macports and homebrew. Please see http://gqrx.dk/download for a list of download resources. Usage ----- It is strongly recommended to run the "volk_profile" gnuradio utility before running gqrx. This will detect and enable processor specific optimisations and will in many cases give a significant performance boost. 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 been included in a binary distribution - The udev rule has not been properly configured - Linux kernel driver is blocking access to the device You can test your device using device specific tools, such as rtl_test, airspy_rx, hackrf_transfer, qthid, etc. 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 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. Please stick around and help others with their problems. Otherwise, if only developers provide user support there will be no more time for further development. Installation from source ------------------------ Gqrx can be compiled using qmake or cmake. 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-digital - gnuradio-blocks - gnuradio-filter - gnuradio-fft - gnuradio-audio - gnuradio-pmt - The gr-iqbalance library (optional) - Drivers for the hardware you want to have support for: - Funcube Dongle Pro driver via gr-fcd - UHD driver via gr-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/ - Airspy driver from https://github.com/airspy/host - SoapySDR from https://github.com/pothosware/SoapySDR - RFSpace driver is bult in - gnuradio-osmosdr from http://cgit.osmocom.org/cgit/gr-osmosdr/ - pulseaudio or portaudio (Linux only and optional) - Qt 5 with the following components: - Core - GUI - Network - Widgets - Svg (runtime only) - cmake version >= 3.2.0 if you wish to build using cmake. To build using qmake, you can either open the gqrx.pro file in Qt Creator and build, or on the command line:
$ git clone https://github.com/csete/gqrx.git gqrx.git
$ cd gqrx.git
$ mkdir build
$ cd build
$ qmake ..
$ make
Using cmake, gqrx can be compiled from within Qt Creator or in a terminal: For command line builds:
$ git clone https://github.com/csete/gqrx.git gqrx.git
$ cd gqrx.git
$ mkdir build
$ cd build
$ cmake ..
$ make
On some systems, the default cmake release builds are "over optimized" and perform poorly. In that case try forcing -O2 using
export CXXFLAGS=-O2
before the cmake step. For Qt Creator builds:
$ git clone https://github.com/csete/gqrx.git gqrx.git
$ cd gqrx.git
$ mkdir build
Start Qt Creator
Open gqrx.git/CMakeLists.txt file
At the dialog asking for build location, select gqrx.git/build
click continue
If asked to choose cmake executable, do so
click continue
click the run cmake button
click done
optionally, on the Projects page, under Build Steps/Make/Additional arguments,
	enter -j4 (replacing 4 with the number of cores in your CPU).
Use Qt Creator as before
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 Simplified BSD license. Following people and organisations have contributed to gqrx: Alex Grinkov: - FM stereo demodulator. Alexander Fasching: - Bug fixes. Andy Sloane: - Bug fixes and improvements. Andrea Merello: - Cmake build option to build using gr-audio. Anthony Willard: - Various fixes and improvements Bastian Bloessl: Pavel Stano: - RDS support via gr-rds. Chris Kuethe: - Fractional PPM correction. Christian Lindner DL2VCL: charlylima: Stefano Leucci: - Bookmarks implementation. Daniil Cherednik: - FM OIRT stereo. Dominic Chen: - Bug fixes. Elias Önal: - Building Gqrx on Mac OS X. - Crash recovery dialog. Frank Brickle, AB2KT: Bob McGwier, N4HY: - Noise blanker (from dttsp). Göran Weinholt, SA6CJK: - Various GUI improvements. Grigory Shipunov: - Initial .desktop file. Jiří Pinkava: - Port to gnuradio 3.7 API. Josh Blum - Windows build and MSVC tweaks. Kate Adams: - Auto squelch. Kitware Inc.: - Widgets from the CTK library (http://commontk.org/). Michael Dickens: - Bugfixes and audio on OSX. Michael Lass: - Improved tuning ranges at hardware limits. Michael Tatarinov: - Documentation and bugfixes. Moe Weatley: - FFT plotter and waterfall. - Frequency selector. - Signal strength indicator. - AGC Nadeem Hasan: - Bug fixes. Nokia: - QtColorPicker widget. Phil Vachon: - Bug fixes. Rob Frohne: - Initial Qt5 support. Stefano Leucci: - Peak detection and hold for the FFT plot. Timothy Reaves: - UI layout fixes for Mac. - cmake build files Valentin Ochs: - ALSA support improvement. - Various bugfixes. Vesa Solonen: - DC removal in AM demodulator. Vincent Pelletier - Initial work on the horizontal zooming / scrolling. Will Scales - Bug fixes. Wolfgang Fritz DK7OB - SDRPlay integration. - 1-2-5 scaling on FFT plot. - Various UI improvements. Youssef Touil - Two-stage FIR decimator design. - FIRCalc design tool for optimizing previous input decimator. Some of the icons are from: - The GNOME icon theme CC-SA 3.0 by GNOME icon artists - Tango icon theme, Public Domain by The people from the Tango! project - Mint-X icon theme, GPL by Clement Lefebvre Also thanks to Volker Schroer and Alexey Bazhin for bringing Funcube Dongle Pro+ support to GNU Radio and Gqrx. Let me know if somebody or someting is missing from the list! Alex OZ9AEC gqrx-2.9/cmake/000077500000000000000000000000001320142145500134225ustar00rootroot00000000000000gqrx-2.9/cmake/Modules/000077500000000000000000000000001320142145500150325ustar00rootroot00000000000000gqrx-2.9/cmake/Modules/FindGnuradio-osmosdr.cmake000066400000000000000000000016201320142145500220700ustar00rootroot00000000000000INCLUDE(FindPkgConfig) PKG_CHECK_MODULES(PC_GNURADIO_OSMOSDR gnuradio-osmosdr) FIND_PATH( GNURADIO_OSMOSDR_INCLUDE_DIRS NAMES osmosdr/source.h HINTS $ENV{GNURADIO_OSMOSDR_DIR}/include ${PC_GNURADIO_OSMOSDR_INCLUDEDIR} ${CMAKE_INSTALL_PREFIX}/include/osmosdr PATHS /usr/local/include/osmosdr /usr/include/osmosdr ) FIND_LIBRARY( GNURADIO_OSMOSDR_LIBRARIES NAMES gnuradio-osmosdr HINTS $ENV{GNURADIO_OSMOSDR_DIR}/lib ${PC_GNURADIO_OSMOSDR_LIBDIR} ${CMAKE_INSTALL_PREFIX}/lib ${CMAKE_INSTALL_PREFIX}/lib64 PATHS /usr/local/lib /usr/local/lib64 /usr/lib /usr/lib64 ) INCLUDE(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(GNURADIO_OSMOSDR DEFAULT_MSG GNURADIO_OSMOSDR_LIBRARIES GNURADIO_OSMOSDR_INCLUDE_DIRS) MARK_AS_ADVANCED(GNURADIO_OSMOSDR_LIBRARIES GNURADIO_OSMOSDR_INCLUDE_DIRS)gqrx-2.9/cmake/cmake_uninstall.cmake.in000066400000000000000000000025311320142145500202030ustar00rootroot00000000000000# https://cmake.org/Wiki/CMake_FAQ#Can_I_do_.22make_uninstall.22_with_CMake.3F IF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") MESSAGE(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"") ENDIF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") FILE(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) STRING(REGEX REPLACE "\n" ";" files "${files}") FOREACH(file ${files}) MESSAGE(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"") IF(EXISTS "$ENV{DESTDIR}${file}") EXEC_PROGRAM( "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" OUTPUT_VARIABLE rm_out RETURN_VALUE rm_retval ) IF(NOT "${rm_retval}" STREQUAL 0) MESSAGE(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"") ENDIF(NOT "${rm_retval}" STREQUAL 0) ELSEIF(IS_SYMLINK "$ENV{DESTDIR}${file}") EXEC_PROGRAM( "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" OUTPUT_VARIABLE rm_out RETURN_VALUE rm_retval ) IF(NOT "${rm_retval}" STREQUAL 0) MESSAGE(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"") ENDIF(NOT "${rm_retval}" STREQUAL 0) ELSE(EXISTS "$ENV{DESTDIR}${file}") MESSAGE(STATUS "File \"$ENV{DESTDIR}${file}\" does not exist.") ENDIF(EXISTS "$ENV{DESTDIR}${file}") ENDFOREACH(file) gqrx-2.9/gqrx.appdata.xml000066400000000000000000000030531320142145500154570ustar00rootroot00000000000000 gqrx.desktop Gqrx Software defined radio receiver implemented using GNU Radio and the Qt GUI toolkit Software defined radio ontvanger geïmplementeerd met GNU Radio en de Qt GUI toolkit Приемник для программно-определенного радио (SDR) использующий GNU Radio и библиотеку Qt. Alexandru Csete

Gqrx is an open source software defined radio receiver (SDR) powered by the GNU Radio and the Qt graphical toolkit.

Gqrx supports many of the SDR hardware available, including Airspy, Funcube Dongles, rtl-sdr, HackRF and USRP devices.

CC-BY-3.0 GPL-3.0 https://github.com/csete/gqrx/issues http://gqrx.dk/ https://c2.staticflickr.com/2/1567/23593127703_11fc1ac026_b.jpg daveo@fedoraproject.org
gqrx-2.9/gqrx.desktop000066400000000000000000000013741320142145500147230ustar00rootroot00000000000000[Desktop Entry] Type=Application Name=Gqrx GenericName=Software Defined Radio Comment=Software defined radio receiver implemented using GNU Radio and the Qt GUI toolkit # FIXME add comments in other languages GenericName[ru]=Программно-определённое радио Comment[ru]=Приемник для программно-определенного радио (SDR) использующий GNU Radio и библиотеку Qt. GenericName[nl]=Software Defined Radio Comment[nl]=Software defined radio ontvanger geïmplementeerd met GNU Radio en de Qt GUI toolkit Comment[de]=Software defined Radio auf Basis von GNU Radio und dem Qt GUI Toolkit Exec=gqrx Terminal=false Icon=gqrx Categories=Network;HamRadio;Accessories; Keywords=SDR;Radio;HAM; gqrx-2.9/gqrx.pro000066400000000000000000000171521320142145500140530ustar00rootroot00000000000000#-------------------------------------------------------------------------------- # # Qmake project file for gqrx - http://gqrx.dk # # Common options you may want to passs to qmake: # # AUDIO_BACKEND=portaudio Use portaudio backend # CONFIG+=debug Enable debug mode # PREFIX=/some/prefix Installation prefix # BOOST_SUFFIX=-mt To link against libboost-xyz-mt (needed for pybombs) #-------------------------------------------------------------------------------- QT += core gui network widgets svg lessThan(QT_MAJOR_VERSION,5) { error("Gqrx requires Qt 5.") } TEMPLATE = app macx { TARGET = Gqrx ICON = resources/icons/gqrx.icns DEFINES += GQRX_OS_MACX } else { TARGET = gqrx } # enable pkg-config to find dependencies CONFIG += link_pkgconfig unix:!macx { equals(AUDIO_BACKEND, "portaudio") { !packagesExist(portaudio-2.0) { error("Portaudio backend requires portaudio19-dev package.") } } isEmpty(AUDIO_BACKEND) { packagesExist(libpulse libpulse-simple) { # Comment out to use gr-audio AUDIO_BACKEND = pulseaudio } } } RESOURCES += \ resources/icons.qrc \ resources/textfiles.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) VER = 2.9 } else { DEFINES += QT_NO_DEBUG DEFINES += QT_NO_DEBUG_OUTPUT ##VER = $$system(git describe --abbrev=1) VER = 2.9 # 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 += \ src/applications/gqrx/main.cpp \ src/applications/gqrx/mainwindow.cpp \ src/applications/gqrx/receiver.cpp \ src/applications/gqrx/file_resources.cpp \ src/applications/gqrx/remote_control.cpp \ src/applications/gqrx/remote_control_settings.cpp \ src/dsp/afsk1200/cafsk12.cpp \ src/dsp/afsk1200/costabf.c \ src/dsp/agc_impl.cpp \ src/dsp/correct_iq_cc.cpp \ src/dsp/filter/fir_decim.cpp \ src/dsp/lpf.cpp \ src/dsp/rds/decoder_impl.cc \ src/dsp/rds/parser_impl.cc \ src/dsp/resampler_xx.cpp \ src/dsp/rx_agc_xx.cpp \ src/dsp/rx_demod_am.cpp \ src/dsp/rx_demod_fm.cpp \ src/dsp/rx_fft.cpp \ src/dsp/rx_filter.cpp \ src/dsp/rx_meter.cpp \ src/dsp/rx_noise_blanker_cc.cpp \ src/dsp/rx_rds.cpp \ src/dsp/sniffer_f.cpp \ src/dsp/stereo_demod.cpp \ src/interfaces/udp_sink_f.cpp \ src/qtgui/afsk1200win.cpp \ src/qtgui/agc_options.cpp \ src/qtgui/audio_options.cpp \ src/qtgui/bookmarks.cpp \ src/qtgui/bookmarkstablemodel.cpp \ src/qtgui/bookmarkstaglist.cpp \ src/qtgui/ctk/ctkRangeSlider.cpp \ src/qtgui/demod_options.cpp \ src/qtgui/dockaudio.cpp \ src/qtgui/dockbookmarks.cpp \ src/qtgui/dockinputctl.cpp \ src/qtgui/dockrds.cpp \ src/qtgui/dockrxopt.cpp \ src/qtgui/dockfft.cpp \ src/qtgui/freqctrl.cpp \ src/qtgui/ioconfig.cpp \ src/qtgui/iq_tool.cpp \ src/qtgui/meter.cpp \ src/qtgui/nb_options.cpp \ src/qtgui/plotter.cpp \ src/qtgui/qtcolorpicker.cpp \ src/receivers/nbrx.cpp \ src/receivers/receiver_base.cpp \ src/receivers/wfmrx.cpp HEADERS += \ src/applications/gqrx/gqrx.h \ src/applications/gqrx/mainwindow.h \ src/applications/gqrx/receiver.h \ src/applications/gqrx/remote_control.h \ src/applications/gqrx/remote_control_settings.h \ src/dsp/afsk1200/cafsk12.h \ src/dsp/afsk1200/filter.h \ src/dsp/afsk1200/filter-i386.h \ src/dsp/agc_impl.h \ src/dsp/correct_iq_cc.h \ src/dsp/filter/fir_decim.h \ src/dsp/filter/fir_decim_coef.h \ src/dsp/lpf.h \ src/dsp/rds/api.h \ src/dsp/rds/parser.h \ src/dsp/rds/decoder.h \ src/dsp/rds/decoder_impl.h \ src/dsp/rds/parser_impl.h \ src/dsp/rds/constants.h \ src/dsp/resampler_xx.h \ src/dsp/rx_agc_xx.h \ src/dsp/rx_demod_am.h \ src/dsp/rx_demod_fm.h \ src/dsp/rx_fft.h \ src/dsp/rx_filter.h \ src/dsp/rx_meter.h \ src/dsp/rx_noise_blanker_cc.h \ src/dsp/rx_rds.h \ src/dsp/sniffer_f.h \ src/dsp/stereo_demod.h \ src/interfaces/udp_sink_f.h \ src/qtgui/afsk1200win.h \ src/qtgui/agc_options.h \ src/qtgui/audio_options.h \ src/qtgui/bookmarks.h \ src/qtgui/bookmarkstablemodel.h \ src/qtgui/bookmarkstaglist.h \ src/qtgui/ctk/ctkPimpl.h \ src/qtgui/ctk/ctkRangeSlider.h \ src/qtgui/demod_options.h \ src/qtgui/dockaudio.h \ src/qtgui/dockbookmarks.h \ src/qtgui/dockfft.h \ src/qtgui/dockinputctl.h \ src/qtgui/dockrds.h \ src/qtgui/dockrxopt.h \ src/qtgui/freqctrl.h \ src/qtgui/ioconfig.h \ src/qtgui/iq_tool.h \ src/qtgui/meter.h \ src/qtgui/nb_options.h \ src/qtgui/plotter.h \ src/qtgui/qtcolorpicker.h \ src/receivers/nbrx.h \ src/receivers/receiver_base.h \ src/receivers/wfmrx.h FORMS += \ src/applications/gqrx/mainwindow.ui \ src/applications/gqrx/remote_control_settings.ui \ src/qtgui/afsk1200win.ui \ src/qtgui/agc_options.ui \ src/qtgui/audio_options.ui \ src/qtgui/demod_options.ui \ src/qtgui/dockaudio.ui \ src/qtgui/dockbookmarks.ui \ src/qtgui/dockfft.ui \ src/qtgui/dockinputctl.ui \ src/qtgui/dockrds.ui \ src/qtgui/iq_tool.ui \ src/qtgui/dockrxopt.ui \ src/qtgui/ioconfig.ui \ src/qtgui/nb_options.ui # Use pulseaudio (ps: could use equals? undocumented) equals(AUDIO_BACKEND, "pulseaudio"): { message("Gqrx configured with pulseaudio backend.") PKGCONFIG += libpulse libpulse-simple DEFINES += WITH_PULSEAUDIO HEADERS += \ src/pulseaudio/pa_device_list.h \ src/pulseaudio/pa_sink.h \ src/pulseaudio/pa_source.h SOURCES += \ src/pulseaudio/pa_device_list.cc \ src/pulseaudio/pa_sink.cc \ src/pulseaudio/pa_source.cc } else { equals(AUDIO_BACKEND, "portaudio"): { message("Gqrx configured with portaudio backend.") PKGCONFIG += portaudio-2.0 DEFINES += WITH_PORTAUDIO HEADERS += \ src/portaudio/device_list.h \ src/portaudio/portaudio_sink.h SOURCES += \ src/portaudio/device_list.cpp \ src/portaudio/portaudio_sink.cpp } else { message("Gqrx configured with gnuradio-audio backend.") PKGCONFIG += gnuradio-audio } } macx { # FIXME: Merge into previous one HEADERS += src/osxaudio/device_list.h SOURCES += src/osxaudio/device_list.cpp } PKGCONFIG += gnuradio-analog \ gnuradio-blocks \ gnuradio-digital \ gnuradio-filter \ gnuradio-fft \ gnuradio-runtime \ gnuradio-osmosdr INCPATH += src/ unix:!macx { LIBS += -lboost_system$$BOOST_SUFFIX -lboost_program_options$$BOOST_SUFFIX LIBS += -lrt # need to include on some distros } macx { LIBS += -lboost_system-mt -lboost_program_options-mt } OTHER_FILES += \ bookmarks.csv \ gqrx.desktop \ README.md \ COPYING \ news.txt gqrx-2.9/new_logo/000077500000000000000000000000001320142145500141535ustar00rootroot00000000000000gqrx-2.9/new_logo/PNG/000077500000000000000000000000001320142145500145775ustar00rootroot00000000000000gqrx-2.9/new_logo/PNG/horizontal color.png000066400000000000000000000674401320142145500206100ustar00rootroot00000000000000PNG  IHDRWsBIT|d pHYsDtEXtSoftwarewww.inkscape.org< IDATxyxT?}$$[ZV^_7Zc2/xL¢RVjQLT@[[vq_? Y(H9E$d;<3z]%9[ Is~ZADDa \!vrl """""""96DDDDDDDy?QcODDDDDD96DDDDDDDy?QcODDDDDD96DDDDDDDy?QcODDDDDD96DDDDDDDy?QcODDDDDD96m6xv"""""|柲"@n(Ȗg u 65$5k7o]6CqyDDDDDDoM>PWx^\urX 5]DC ~j0Im t]`"""""!;:Zn:s,i*um/Q\VO=8du Uo'*o2m/hU2)m p ʒRS0 Mb$Pt]d6uFfBh8C:"noY\Zs}9zUDR踘ର/Ӛ[/$΁\ݝ6//7](̞={֭Q>"}.9D+6SE7!xCUA 9bj0o DW ғq&j9HEAߡi@[xK<ظ?|_+ )`b'~t+6gy訾y/Zm[Da/*3;k[M\{lot&Q(3q5]mu&+Ϙ 4ty_D?ZSP3V#GmEz`t!AW[k;)SDQ*(NPha (D RM&<_1p,e3/EWG}Ns"B4u]w`:S00~x\o /_|]%ʨ\iWt՝lGuȺt +o0w/R|mj0w}4.3Y#pɮud@L x< 8u(> `(GX XH$a)eXyy"C0go)ʿ<(L$m8*@@hDF\ht+<Š˪ktl muKMPNv{Xh-N0V#)܌qxaO'2KU_S$X,v6mRԫdr6E++?Z!"(0v=xRERۖ'MyYSKU{ēY^^~d`ADal)1t|o?ʕh4AAQÙJIIJY_|I4}׏e\6 Bgѳo,ph^d$8XdҗҚX,R6~"%ͿDT~e>QȢhSP|VDp 7ڊXe< Y*G8TNAn#fϞ}@OOLadЇDV7RX[Qoq-]457R~Dc:P ) MwIRњ [ym,+R8KT^ӿ+מ}g+F޷lٲN6vm[~7_F'$IslŪПZ:4?xwX ^d 7\A!fqYLl]&kd/=n5?U( zkHˢ` g;p}"^\^^~@48X츊noö3 ͷ6+zz垊xUrg϶gG>W.[G? @o#Dt NaZ<?Pw#˾pf/s&2NXX~ Gf 7\`){<(fӓzX!2|HUTT>xzG2-"~FĄXNcHD/ki=}*[gG\Ji?' v.0:9wbw4g]ÿ8L1+:kn;Ng')?bi=ROxohlRpVV+~v#'ECcοǠG***ϰ̊bEtkrw&`f癊x?y^GtoZH6\HmC-:kv!YsS]}}2Wy{aچ ;ElBK櫿KhIiL:V>_WG}v]UPk;Q_J*b g%ΓQYl}|mG<^u@B8gg]v2#  U?{wl[H._2D/mbc3e;z8X]?ꈞ #GI NNPc;ݕH4<ߋ _iްcWo,^ŷ7XiDQY (hƀ^wԋF,Ứ`D[Tl/]?gsUVV~()|TFo8r ,U.qt$'`aEEe4|啕'AqV]Ѫv~i@΀fWbmiGmPh;Q+~Zv=l|v!Z~hh5ͣ? &kؠwf3q2?rw L%ٚ***㺅8v,#0vYf?@?E l$'D/ Tpc*'.3nˍc҂j=5gKYDpߞsvA=CKǞͲA,*&LeZ[M0_/rnsgkPYj;LXUa@N %K Iq X#,[]CG\_ bs9'nW~U\ D"ǁ>Kv[^xmN2/yC^:FUqoLI/2Y`͏S*K׵v:5^FWݤȚ43p,YMpT sd˖-@ron~;gobC2Jwl`ސ ȝC DʺUa~p^XD<~S.pC"s9FpiAj-rƛ8}Z^A6m7<F݊z,42UgΒ#F@'f LKK9zıjMyyeNNR8(coT.]tP`m7q&]duYDt%?nE9&kd6&ߤ%&kEX책+W*cp42bU@pF2hCS~W/&/r&ŧ_cwSL* k;l1ćU_Ykel *9Wq.".eN$'̘5 ܢ&kZY%^ go`Pv*/P^)'DiK2y_@^x@ x *.0l䲮YjtFm;nn R2}9dP Fcvݚ[Ed ULC85ڽTn5hd1՟(b1J8( Š4Ǡ;C? ys ͍Ú_5LJ.Z"GxɹI8}ɞѽd]ڼWLV8.|G.I$U `:*++?W# qƾzRFD!=חD"Ă^F(fbUΚ5k4(e/g _⍦zLv2Q?"rsGL(. 3Y7H:5Gn4])G pi{MJ.Z kPn~ZF}֤lg!"D}ZQKKCPDhtv ޤd({=p>Ϳ'm._V}Zx!bcAl3y"JP>;dgxdP^ֱ{d,\V5Fݴm:dp 42=s!8v"2O-[B0ZvI-ñX!| oj L6o{^& );(&[6;fMWT?*ͳaï5Z!jvp j~pFrB*&ɚO&^js IDATl?h***EK9bD Zjҷ@R!G2NUTThP9m meqܵ+5˔;fou"_Mq:52d>m@W{da߽te՟jKx;+ 0hOf͚5>T" :p+[Z\jN3Y҉-C"x[4L&a]2SNtOX6w.zyq)kލ=vy/[h s[ǻ'WJ-h T0z*Q_R~m c>j,ϰʲh4XչZaŕD0>΄ 5r }M{ƒnSP>m6Yߜs D A_V:[`C+)7N}}>,.Fm25(8.`tEQ_++? 9sQ0aD 4]H@ssUD1hUuORT6KTIӷ]ꞄdnϞ/r+dF90q0Kֶ.M^b쳏oD:ۢŢ[(EB'ݰq2Y,ke>fSLiT`_S5 qd6 hspO0GTbo͜3gdS d׵d&kء+QSjBNJ':#eNP2DW>[M֠FRvHe<֏h tOwJ$~ g D{gϞmd8{Ijcڦ &mW=e%AĿPCa01p^$p2]] %H&/)^8]<o:bdɬY 0v"< >Ap>0 |`۶T+*f}Ls@Ԕ4u}cͿwuw_V}<凸)2pCW T NUƎ][ghrIX|;Y+DWk#h 7a03#a L7('B2"3R).ǡ~"Άe)pyEEՔӧ(^v_5*2v4:1_+)]?izՍxF>D Mpŭ6Yc/Qw>jf@AM_͸d =1@5$ڛYBL9Ȋ 75\a_7@~M)HpjccyU;՟,hU{̥>:͇5" B^C*ɢ#bd SL@$eD8Z #\QvիWy(汗 R#li.< Dw˘h^o|sycXח{MUqt G7\I- Z{!prX̟0d wd=xiez+4Qhʺ]n厒?'ȀY*9Ȏ ݪUKJ)S5LYU3L#㸁 {RqOsuXyg0% MVRVVT䰒:nH:Vy"ߐnvҼ"rcjAMס~7'Q~'Fo#:F755=UC+xq'Rp;ףL&{3Q,>s;5J:"xfԯ= p.N:"qQPGs9,zo_uշ.G0p>d7!H}ڰ5(9Q&A#S<?@v"Gl%nAᭃ}S,Vy@N64jiYL tE#IJ+.A{^:&%[| fqkױ{;Tzڿ961)0:]+h mҪt3}ZXHq)ز),Y^98:p.#dM@۱59,4BcnNWN;qC(obC{Dm \ƇxcS}0x]?ܠvEn=.&z?rE\VT}[g(uo!-innce' -B(gz_aoP`vL?x4F8 pcLlo@i5*zٲe3>6l_ eUUU%_UW&MNyGߩ# 5QLI"xn}'l'!]_MMKcU' tZj7xPP Ds[\Z=_}{byZzS%eU t#]muS Fa'͛8xf5Eh+zX3HCITqvw{](%o9Y) rJWkm3Kьʅ]]mu<+DCH`"_6YhǍizd7^t{U*E ȝ~jw& ͞={)PTL$J$dXU A>ݛ7ЪUKC&>'Ap>icsafϬYƧ?PѪ=֜:u?_\%lUM78{E1 SUgk"?: #h_lQ&k|;"L5zhc^gUm2YT!WnI4^md25hsbF~4[!R'575xXlYOKS}-E1]rJ *&577NK$n3hxt?[,>xcȒ]-_|@NCyoϞ={+?KrEWdbއ!!M2:#gvw d2KIc2d\"7: {0qWQ\lzkH *8d Gl^CYBٶ# j onj&k"*&zFvG!\jsA֐Қ|t-PtH^5]UrRH$favL&_onj< [mf;s@3^2d '777mj +D sHK^|{Ȳe͍J$ xlO /_E9 @*wxn3P(D:{Cmjz ̜w8 :S+ťDζç_h,e#ovj%p/k~I dݩ⌒j7$o 5d@NaTƬ^~›h}QWUoCVQ. 3dl:Yөo;$GO@,}ן? `_b%rln885LN2 oI@62Qd};Rx^%U,^raɺťQ2Y#)/6匿. p8fg#LٍERy#kDV1 Jy#L9GTJm/ONa; Y9eyN|#8_⍁ljs)<_9+#r6&*5bJɌ254>P4R8U_?M͍w1``'/kW XuKz(5ֵ=mܗc3MtͶaI$@%Β-ĕo0X #9"6BMMM?c8ZZlI4m;N--=s n*^]8>J |Dn0tQTD/>w`e&w:FqgF>qHG;O?#tBټևosY+~60SUUUʼns :Ɠ9kJ"SX90NaP ҽ؎1Ƨ4@ @=wx)N y$(~l;G>yYiͿnX=a2 O"t|;aBw˰3:IW(2Y#&ΟaBGxpv<}mh_OI:wrI"`;p5777b!>f; a p4>mfi'{`t؈%Xb;U@7jtâw!~YAf>J琙הp( hpw>,{[6{9WM}^()bep0`' 17Ksr,}fz ѐqFhz=v<ƁyM_Sw :Y/,.yps⸒'x'`D oť?cTnL^ot?{9^٣Kpvьwa oUݦjP(s3 PJ5}2G>͚my~ta$ǹG( hijjgΒ'@榦Iܑ?0OVz2Tϟ,s*ꎋz&kmuk28tAr3,xw Cf `r_^=qT?o;@ dn6AF%m;HQP xv|/Gw0~0MHC :r=9li͓s]ǧUB[CU5KE3jyAȢɬ4x9qCPqƾNc xjZm z{lGI/_E'nj*0Mu8/Is12䢲 !hO*0v\E:{W,!oͦW`k_+L T>m;€(^5v֏pmED)c,i!Xd;Gm 7%3e{}4カc ,A RM%_`@+LJ;㗊KO0ta:^ EV,j2[3 εͶc>L&N1uumEH$a;G78g;`pȑflL|v!sTo?5 RuoM۰b)E/z5¢@̈́iљݫ_&kSNqs ӉD⏶CP$WV3zի/L4hlGV^]Cɓߟ3ZmKj*d3P)ѯhUz1LJ.Z/vq=CI`:!90pCek0[C "w@f@U}du3dɆv\~=#WOYkzMATn(*>h;ޔ9so(87x{ 3+L0|6-dz7CPN:JlLPnll#]"x;ΑDO&{vSmMߋsS zXUΑ +Mx{]?5Yqn`tͦtd-8g$ )XiKOT').o;Ş1(#opL0%Z1y}(*%'c_..jH2w.1Z¥Ϳ8| n>@.f{X|:=v,F$vx}܅7 kf*=mkѾϳcXQWOύRꛮ n 0Y.ĕv٤]?# 紦 VNyȉpi84ꝰ>etT`v"=3Ǘ_>'*JM1,cQwX&%Lv>>URcsv(TI"T:,r{[ƈܔhZk1"އL̠#Tpd}l! e;`JOϐډesk[TUޫx&{ߖIc7 k2m SdRoY]m;ўlgwZ| rayO.T1T¡Ϭ#ǗUo;`BYN?dfנ_mg ڃd2n sاA|ٲe= UƚTY"GMqO@ssx[sD1KD5Vpa seN?*?O}9O,ů,q-a¹&H\4yUL{Eȧa9Bl7>[K"dx7'"ۿ*ԟvիjf IDATӂ4޶Œ;[l4`\&jeQVWenьi麴8u3 DQiM l0dZɌA` {R)Ȕ" o>Qʹ#M"xF!Wa&?Vy2\ƛqL6 V2s^E8(c]rQ2a NT&L2~ eUXv5yf}/lg #AIZu\@O&{v0mzCrx^FVW qE}m\@a@ɁBH$shcN)a1Шâhٖ>^4}~Vt):{@/#TQ;;vA`oT5f'p]6T?W477wd$zþIJ:"od1`߃X'h;Nj"sd[N\S"}+Yo;`,ͧv<{/t@p,a-_y!Kzڿ@O"-tKãuE>6ŋ8iދ+t\3m3f̾,W6Lt_F~QhS*+g;As:e}8ZukBDפYKO]:~CwR ښ+ /N\ny֬Y3)f6яgo13{җ\4G\4twIc.N1:\G]&=n֝:aOx:Io1F惕J% cf%+.XZxF6i[{cZ.dxO;)8* (-_|h4^.yqfc0T|n4:wY>{.wX=G܄f0ͭ>͒V75<̣rׁjWC7L'tdtrqrocsj]N;KLzԛ'CN/5?{io"4e? Ƌ(كo&'|a]E)O:>?6Փ] 7mpxɊ.}V/iVf#青2xxf]%~Ƥ^ܶ޴?g-[HMb^Uֻ;/>hjaIޖfX*:aNJ p~/sc1 IZn6I;g2 ݰ'&1K>K{n \ա3f!G$:\~4߲/Z)Y :(C' 0Vqho#$}(=0l3'po[kF\'t;a]zDž^. Cw皟y_Dez}Ԝ$u#@#<ÿ;쏡&MC7hCCCs-%nI6nܶOb4^A<`Eifж̒>@sR/3Su|C#I\  @`.={ MSQ#Q+WC79(#tdܬW: zmrE+:6?d4o[zS$C d0ȟ:1::z;Lr.:vMrl @2Tk9&1*hG.aRn %mI1{]%1;|U6=5tt0 f⭻]>tofIG0 h;{3C'~:a=ﱡ#$W63BwWSlvi莩bc獏%+.Xj[j;W]%cCס& `%tG\њS$e[(}mcS Z̺^aB7L 'GX#x*f1 @,߰ l TN|ҭ;&f'n@sđ~a2&@|-I:!tG;r}}+{.?4?w,$QviHvsɘġ;x>uS)ޛN 91rˇhc{YT-Z;tȞ03J/IKT;Kr6=N/i!SS\;6LGn s\$^{Cmz[=a|O3)@IIVn@Sn 1)3xIgѾJ읙`b7UedZ:h7~aJL/na\ S…:@XLMz}%ţtzAG$IB7mi||͡3͆BG"Lm;5ZHkLX#R=x4F:JRۛhjn0.8*t0nRdžHf2:cW n]]ssJ/tD]5]:@e29IdvpppI萝1:a\:/Xo g@,GCwCLӒj;ctTJ0u~CmzC 4ևnw|>~š[6:bLOl;8B26tTE;8n2W_6; |d'.oӦ.-GGK{j (+WOBwtG(SN=u#EO4{Bwk~æܧdAt::Й\_ 0ML&7:bVZ=>QVҬ|,Ӻp؂.6z\/ $I:I*Jj'̘+ʸ$uG$~i3m en}7_{pqIi3 eod_c&R;^=c\7}䑟~?|l׫}$&d?#)';i/kG_v&6tGR/J\.w\zd2csxzl6\5|>R 5t:l?sw:1?L:ngWk\.)fGzWݒdR^N-/?3tȮ2̱TϷd6IHL&sL Q_^*&eRˣWH'ZJbL:(vz&7paoo1tzA&+^NHvu膺4:U]}+ddzv -V՛ՉSry{|Cf2Vo34W^hgA}uY[Zv|Vt }6] S]=2e@d3uʣJ2zfE5VnժM͟7>jjq(u\@=kG%bj)?2RWZU6:RoSkKW>]UM=ǣVH֞#)\%ˇi]W27~tzTwu@nV;'źueK;vXnf?Z_STl|aÆgW{_tM_\Cwä#~vdddCQ^\+Jg qZ}e]ؔF)Jz>winjtOmM-ղ_޷k]uXQx1QI֬?=Hñ"uΊe|Q6_:,z[n9IEIr_phuHHu\җ͕#ujkJe߿ ݺ| *EQ 'pBY|se֍k ogt^#Zﺺz(M@3/-击v)zn;fMwqw];g%_١Q#ˎyíK~VhllglW\zAZ`]vvgLf w&]:@gWHz|:luP7+˴lrF.wb5zRiIwU!tЗ˽DY;b~NetOtrŵ%ׄc7[]78(I422Z.;;p/t788>.ϕf\.!+u&;8vf*]]t(D)IKCwnU>I\O}&d}PBw`Z~}5un+LGzETc+U#VtU&yL#uS26VXCwh?}}}u1]5V(|UT*6$ IRk0fJ2_[-&Bwh'pBE]I BᶇykOZ.?_6K/dr4x YiXoU*~.C;eZҳBwԡ[vcu!=cfr#WJ-tfZub~@rjͤK `0Csk]N{fz U g?STl8t :UcWQRwi3pzO茱_ynǦRQܹ=H)tfRi2H5$;2tG+׬YMI Qgٿ!Vm@xv;_@#khGcc?E;:fV ݰumKE> isɑf܆ǽO\2tŮJҽkHR^ӡq H:#?(@kʼn%P( 9\%- 1].= _cc/ɔ&e/|.?|\Nd$R"?XXr0׫Bwܞs]izەNءRﱽTC"9K]w-.}1tp҃ۿi\.ΘVڙj=>iNFo43j[<-]q磮j<ޡ[HSU.Զ{^ԗ˽z:.Jr.txή[vc\M"oCh6zK QY,:d:>0هGMxr*. ݁fI%#*kI@k <ݤҿJ#_ 1]|>"V/ZH\S5/Z{*nJ%/t/w666D3L\jI=[ˤ_̛ӽ:tG ZNmܸyJ AT䗅@c%}Ί⭦$n:֭r[CD\Lp邾gOq LT*E*+F]jwRts%M<:lv20Ecc7hk%]⬳I$?SP.fCJ1=(j\.onIȜu6rbJ|l["]8tFT*6|aО>)+^B`Sm7KRT;p?%W]i%t 0UUj]6TTqPޭryrX櫲쩻{7?pmey<}bɷؓs Z~}\.c?Kj{l쏑/;t0U}Qs* 3BnQWSlv飽i*˿ϑ>0ـ}3U*4ϐ- p{-e_,o LU~y莺>W.Cg4˚5k&ܣ3ez8thͣ:T*-k= I\idFRieϬmT*V?ޥOh3y9 CQ-ֈ4yIDATš[pY666rN+فw}#?30V(|>2?Z+$š{{ BL.SFT*JW$:r |32^\#t07ryNQR0V Q_brS|i9ԪO+? 2Ehryt}6srdz~Xs1rˇ骱B3ZȻ${nW#o`AJk앒:JKr*N5)e~;J])QFՙQ~/wYGoqn>Umb622rmǧR? 4PRJy^w[$!tSLc){b4o-n+ŅBRa&+3^%N=\]:OC#T*B0V,~N'Cw읹\NtktYoo.wLotLs}^]T.M%U |5DKyR'Sz=OoQF:#Mj< 2M]qlt:},?Mnݺm~lR.yӦ#%y/G7I/V 666r e[,\ғBwM3ťfbhhhkY03Bٜ;{kۣ2)Jߒg[WXlP9v6N_],o'rű+5Gan.tY&9VQנOtHزK]* ? )`wwwbl L7ovww( ]2}fv/klW,N<wk"rZ캵k7J*H* 5'r=_ۗ0F+bq:\[52:َ3HқSRܸ֯d_uqu[eWLן߃P\Ӷ{0fIC+Wobb9R,KUs^Ӥ"ٷ\s}3=G#̓{y+Y{wvKIYpgnz\O}2o(җ*J1|_$ipppjzHEF~|_+HE>+6Ӄ6wQt~O>>~y<_ޔ6ƖRtw$۱O7w$(wtX,7~MW_ҸI](^Q˔'&&6T*[َ۱:;~{Vw IURRKe@j&@bp ]&^q?h˗?CB7?h$j['h,tGfݱ'5n T$- ݱ'>1HcwU*{BG?nkzz=r}/t0Iq -N &- 20{umR]=k$:d jq<βŕPd+!SaRy0tpLKooEqie薩2WB7?L&s֥ 2qn $JY'%m7#+`2̱+-p $sݣ[zsY:^uy^mG?&+t10˭ZMN$.B75U*CG?P:=,ꮾ(r/dB75k$tI@Kө'܏WlǛxQrɥXٿ[=_@0A'ғyTM-ޒI} \Ժu붅 h3sg0)>\nGd:$G.o?[]k@R0 s,8`m{kAr-qK|?׏˿ @R0b}}ORLK}ɖH:e&-jR,\2Lp?e)B7$ XO~=Qՙrhl .BX$I:fddd^ٳ^+yw iT}2tG1XX?` n{Bwt-fCGD sX=tI@@,o ~1wnCwT Ggo @R1gƥ H:$? iyT*!$? iy(2_Q* @;` AX?%+],:v@¤R~$Wjr :v@ LYbj& ˅!T*wLR0)w=@BgKφfV7{ܹ'0tnddds6;JWeI'[=NR+:fK<? 6˗fg]vyyrRtĕ?0#l`an"H+/{>i@k[%Tf7F<11Jr t]pҚ5k&B@1\_V7mpŷ2o^/M@?a/t;S;N)#nKYV_T 4&~ t:ϕJա;'$]$ RB7 ~?@c1p t8:?@c1p t8:?@c1p t8:?@c1p{a.IENDB`gqrx-2.9/new_logo/PNG/horizontal white.png000066400000000000000000000524671320142145500206150ustar00rootroot00000000000000PNG  IHDRWsBIT|d pHYsDtEXtSoftwarewww.inkscape.org< IDATxgdU3$ IE ("f0GDrDr!gAr𨈊JN*Q$kgkw]s]{LWwճZ I4="(l)Y^.̆333333!lȹ7333333r.̆333333!lȹ7333333r.̆333333!lȹ7333333r.̆333333!lȹ7333333r.̆k{afffff6L\[|t33333aT:Y6%e lzDQ: 6;"K133333+𚈘 i^:ES 3ffff*}Fcfffff64򘙙 %[)+F]|dVYxJ9 "~ ,c33333+v`xIzp)YR3ffff*??Ȑlyߚv rD<9ȃ%to\YyZ5mA pNy̆gIL"' {̲̿gI{q8k[Sbf5̿g Oy8k +ք" =O뚙 ۃ9.w_qm33333ar;&"y}஌733333<]q9I7l.-"2pk㘙u:&GÚ̬BJ^SI X1fczDQ: ~p`cug-u"⢦4#Jc3ffff*_xY!Jsou3pRc*̬46Y{EcCC&2!f'"nN+̬$7X7"~V:D$-W: 73333V̿ ]("v9J̿ b:zDt^IZxN,65q pk0'`970(yԻa"gbfffffa+{fD\sI+oGOr1bEĵ}QDLk 1\ o_AI_-^7Dg%M4\Ԅ.r1"E:^X҂uYSffff*4⬅eWRaJ[}Gr戈Qkfcfffffa4K}X:[`FSu |=:d<+fn52Ш\ޞ]3333ka)t`;"g_$|+1~_ZD_qAM>;֌1;fffffY w#}9K5Gۀ""['vIsW+?Hz )לRF擹y{Gj֘"~ct1 8?FQfO꼠uQu;!~Ye^VuB|vn#?sI+W'#YtxEDW%} Zԃ9HxcK[d$}z;w݆qvI:إ2po4/"Ez>D:F~s:[\$=Xx9R`iҤs?3<< [6"撛ѥq^ Wo.^-DsIO_H f{xsSoҪ뀥f.m`V XN~R˪?9$M!'z#鹸r9Ny-Sg#bzqlHX x?6j'?V^wp]Fu^D|$V~̕s4@7!m""kFIkFTvW&./^ |x7ZP餛?_DPkIϿn<=gQAR^ l l,I7|7+PxeDܚ{ I[GI9hpzD)._WNi֩K\ۄ$-|}| 85"灒^ ̛%U=.{I%ms%wfՈg^%U=+꧳Hc鮈HZ O]OV|Q+WV,FiX^dD\1Po'61P5S+`&+ZUd$-H\cG^$}N1Z>`Ro%CZ %.mL&[o;<CjVI΂r"!&i`c`s{iVI5\n7t&SM%-|v58tjUێOw~gv# @; iWS#m; ̪bxCR#ag¿+c|4m5XDQ i1I{7 Emk6.j:Dҫ$Bjd6na,I1mH V,ij㘪N#JMҢ)y֋ˁf`x<ltӗؽ1v)Ps1UrRW_-!%鹒!4Y|J4Id' ~@ھ#W8_%6B$+ lڽ}I1U+6fIzOH!`x{6GU>iܵn#YһrvG"Zq¬'՛3v~0l^&\I-%C! Ikbͨ&.6*O+?jX.n0S]MIPh爸nOKIxKKe伈u$DGX¡99mG[L+I3:7e)Δ4l78:I_bXXR֐h9aI[j&] p|u(ic>.]#!?&g2+JedAs'˗2$Wu>?t0I(&Ozvsd6/pg|9w-bP'$1w,t2քT("W:X"WKпIgfSazh1"`;I3}lg|Tokj#[/CZ^{1|[IZt\dʯ&mS2CwO8:ZRW_^V:6z!-si1#"wh_#};x9N]I'vtْ:uW;"ⷽ~q[=#["1`cYp9棝{cv9@Dp'ECa!ltTԏ#h맍I~,V:K-Q:O#*guXntau37%N+M.mxr)!zyu3"rx~1 zZfrΛ8㊈{hvbJgT5if&6o6f6DDqu# N Mv#mǁ#K 7v IȦS[1&i2Q_9FCK Y'isDe.!6{I=fNjpkU7U?Z:ǀv1WNԙՍMhl ޟyIR8n/?Uu7cN>Y:&I[V~f[M5dUwlJ5/+b<^ W:ǀ.d%_H#꜈S(HZ*5Y 9Ff;DDg-E`!l4I8(-6%^ꎮn9QһJ9I+*ew :[8-;!"N_cxt2An40NEip[1rvD\T:D v"ubVIqx$8 {ˆ8tEpJ=!mD9K{f!*bgDD֣A$x1j6/E#~]{A$E҂9LjSraE{I]'t&>go&eIZA⿋NIt+QI33U޶.c&SqSI}tbW9I{U8ri?Ʊ!'ie\\ a"YV}5k3oBZA51%il`YPqoA$-{ 8t6d|!"n9Nsj>wp1jt QanVvœU7ZXƺ* Xt=^Q:eiE YI+8ॅ3 b:i7]("{\*ǪX㹗\'$&gWe;G9NYj>1 qlUO%u(6+a/`!̬}Vu-iq굶qMw5d`\=XD9nM mYM5b60liC=2/Y94싈VUN4'i;BnRwڏubs t4?poⰩ9C3F@ҋ9@9ßg*t_{Gs  _.FX?Q4"@6pFtܶ;r\8g/`ۈhb =| M/92I38%~H߫>_DOb>^܎nl` IoIK^#l}T߶5_ YW+ v,Og>V!5EcKFUg6mJy)zƞ70qשӒޔ+ <݇d1 b͚t0<FGDM:ve 9{OO̧r*更YSqy I˫s;߇xP2'31GhΧaf\D *I"m1k\Rj9̬ǁ}ш kJko4yzSF_Un7ӵgk|p\5Fۀ[b92 3s1ED/03n IuM5ώc@ef}h+3k""פԿ ˓VN$oGFįsqDd_\5_xe8R2k֘خ1}9^{/iyz&6yf o~oxˠ%-MN}µQ_WV=KjyϋIu^l4UDIoj̚,Ni}QUnHmt_Gk\Ko#a~DarG=ݸ:f+aiE$[FD]%TF4c#ۙ דf#o ܥAqs΁NhGNSi{'"➜It:Nkr0)TºV7H!8o#⍵끤%I+qlrܚlc֬V XH83"6.1d 6w_J޽_xE=~H3 HKz ?e8zMD\Jѱ!/\)"]D#qnDEV`}jX."."ш83"VF1e6cEq^XqGD|7"6%MެEj}_ʾVv֍fc-c<,Pj_ob|DHji@/wZPw.\Rn'uqc!^ls &~ QѰIt-FqGXQy} P WErE/5Im%2XxyDl65hDܞ4y)wo|x"MP>CN6 0Nқ笪3O%5h‰{Ƥfytpn0z݇ggF6Yxɳ#W5^o<l"6;9ZLK瘄FKU:KTcXA7JEę&Z;=J:9qc pDkzzr4>? ?$1@nb3sM'6TnQTI?Kl䄈!gIo!5vg{ u䞬i`k=Ne!9}ۆ[W /FĆ`03{"b#6 g׵x "6"6_ox+6XN7&%SH iEAn EM{MEYc|జcW78 G2wͯORVT{96flGhfu0c+OWD`&z>e֋J8tzKcԭ;"ץND\ lA:i餆T[Mқ?JK6V5Hٺxk%!u~tr|RI#r۝ISHs; 7ZV#o\/ы7PJ"i 9(S{UmYpAͨJEDLSHۗPL= d/I&m#⯥C`^J҆4J8[R.=*ʈjdNةc6O*KX[҇rP}>O7IJ>29쌈ԉo:[/3?* 4~ne7dD"؜gDēFt{H',tO=l$-F;qNC$<ڗ6K21 ")ƈiD`w,l"ⷌƺk=Mr7&ZpM,-p\D|tAT} _:QDĖsѧ{7S6kS~'I%V9Xqz&spzA$-s!sx w Z*,4sn=6$#V-w4:Eu=@[=wޥCLFS@gnY1䶉VF?חѮ3ΐ4"aCr iy-Tk.!_kTǓw{_MO漛v<{tɪLjL8+6lCCɪV}tm;dl16ΪʜYS|ɴH{?{GT3a;cH"?[/RܶVi`^,D:eA:>fX3Y:@~Q[IͮFZLHVjq,"-ik s/fΈi39ve^.J]J`,+"g(:[jD< i_\cX*GONpz?l"0 "|I2$t掴W7?|]sKTutfst<ƾ8#sH'A,8f\8eKљC6j[J^-9l+#GĎ6yDEC z pRp$i f92<5Χl֐W[c֑~]וY'JzIZxM= 8tYZ:(N7ʜ+IT[?vW|D'K藤51occ|]!5kM/^s 숸tjJ]pxf}zf:9:1҉=^EU"ڷx"'Hza}:5R좥r+L$}X;8HݯrC˫l|У*l;KQQ{F_JH4˸`}Z8k!J"{JzN1&OwDd=7:mȜchl"fK9ǰ<$CjvWW3-6|r6OJU ؟ёEI&Eۗ1!s ŀs&f~Ik̞C>tϫ9,JhY.,`E st}f]>UWj%UCL`KC ]$-y{#b`לcdmx7n1uњoaVX:G~S&k=;QmDt^?]<6/pVR i|Fj. g w#úAʝ)@p3tq{Jtٟo>Y1"|Na`3_ѩ)EIk;Hg.us8$|$8+=9jׅ3*ë ?.l qu-v+Cjpu߻:J5$-A*-y}J'D9{MD\s JA+z.<k)-BӁ#AU,tc/\?!5іS 6tLDmGje/#ݙ>~/{%HhQaټtxYvL7~5师yhj`&l mM?>[^ lQ:Ljt3sj9,tNDof.>$6+(l`I%gە~C 7H'aA: \ $R:#[8 7Y/*`Yu9#k uDģ ّ748^ p㒴mUSH!5 %t+nzHUG)c k~_:Da{GDR4RKz!pPcp5S1|K(Šk~?`,Kzl|$1p,Ӛ?t. T:MkO;t^H92Y_һJ-`T='kuN5q GlKſ sBKjl_udMgcZWRoTЇyr'X;vգoutqtv w\UZWĻ~Ϊ6a e;BR]M7QM+~ֻח`Y-Q:@\[[=V:M˰%턥nDS:D]r7b9Ejt#ۺutKu.s4 IaE,^:\x .s3Q5 ۀJS_ڤpI+g 2a;8t,S:W7n-`FU[[yC"sd4("9%}-ӵߍ!i͈xKZlf>]: |t}N"³\#Bܴ׿KS:,l6{X:H"CmNl)CKJZc&r?Rk%}; ~U+bYtnmgf%m\L9UCƥjOIo󂒖VIM`jQjՃ^kص9矙*"..FD#9S%0ҶkfHZؿd8: ӆ߂`T#?]Y(&FCY˓MUOq-f`F*CX#/C9`O+z÷`f'" 5zkܯc]7oKIuw u^4Y[PI1,f=*`~Yq5gp?Ɠ.8ER>Xһukc,N:QgXn YoPojY9矙D ~CͅAKXXq,]%5}Z v;Kƣh_îE3,$@:eN&Іr>Pѯ IGZw,`hj8t˪텗}6~ͬvձgYtJr_֗4/pP<X ڳ}#7>`l( t%i!rq_ւzxQ<ϼ޹\{Vmd=t˪A?̬VV 8CP<IZ7,--؈΋VKy!4y"No -j{3w[Α4IS/oWÀbL!sK3\׬Ku-zt _:uŁiZ!\s.JZ{Hz5x?f][: /jspl(v> :l<XuU /p?tof"i ࣥs4쫒..*g9%*w cټC:Hu;כ%_ſ -IǕQ;;X^rL %R=+ x xt 2Qupof4QJy'M墡V/²Zͷ {J XVw0$ yf֌Q1:`{8phu'/ϐǬ]:\JJ0n_-0?pvo7+ke^:GG"i!z߬=>?'_*{5Ǭ> BI?n./I/!K'+fscAw=.tt3kISJӟKڢt^7kf@uȆeuKDL/*t+i!̬uv.Ij\f9Iw`NftMҼCXWЃ|3WSK%U:7k`fB Щֳ[JzY;T'|My-G߬A: ?FX(fV9xuCIm߬.W:YtKY_.+,ʒvss 9oHZt7kEJ0kJ&CXУNt6<$-kºHZM:FPN+jzaf|xaCj;I+bV.lt--SJ0ITC,S%-Y:\0]}حGY"b:9zII//̚#s @iIpoff藥a`!v(Gsaf͐48Xt>Y:>"i!fpoffC'"nY:GlVK'$t3kuJ#o)dKZtpoffëK GQIeWwч%_:D52+M+CJ0S p9Jqoff)I)b29bҬ0xgfm"in@OW~pY0zwSQ.lX]@0צ%9t>$飥CL͙MV)bNՍѽw%dff6"/si\I2I;ە"OчΔz *p>3f"Υs )`ZD¬kΐTl33f]:^\ iA!iW9$"[ - I?:y,I o>Fğ#MˁJ ̆JК%[:H/$5,Yg~ٖ~t2&|357ɈIQ{J ̆o@o.t 43tt`ӈx`; ,լ$-,KkcF8nz o_ݛA73ak.8"UK7#1Yѥ|tIAyፒtyYSU:6~ܐ!Od9o^`3]wu{

&[E1/ o>RkA\٨WlI՟g00[}i W?UAғGv-WJI7hbXm@ ٵHzmDQh#E-TPBnh$E`TV5FL\hTĸ fpCTMD" DeqfqXvf۷=V7<4S<߹v-R;G!{J){L&] !& h) $IiI\;ݵl,Pw";J)%C̕Fu"(v;[%IjK͆N$!SNbu;'ətĈ^VJo/K$YOb _'ΟΕ49$WJ9xI:"Ok끋kJ),$^ Y;]'H˵CH9&tJkOg9RXA,$m$Ʌ4jgѢloGpA,^)e9U2k1$ߧ;1X%I# ojТܙ%YvIK(`!&XAt,!&R.~_MHr"9h:{$E(<މ~O1vA@.Z8rQ>/If$9 8v-Η$WϦϤ4Rʽ{2ˀbRIcBOfğ_-HF94uX+=I V;.$>Y;DŽXJy|d$iItyŚH;'xpy(}qk8J)^U;DŽ.W]+[ьoqI_$d\\]9lY:zrXmobZ H?3-}_%8'MhzWH_kg騫?V"DaCLvIX7+9S;R>_1$9 x >}KӜ[jTfcC,$kLI_ $Yw?MAۻs%x^#kFUJYܿv 8vŖrjоC_HrhxqYS;_d70MInO PGp7`$?DhG'yKk{K),$-Pxpi<$߯dD|x<x;$+ v ^ I r"} pr)~/IҔkwP`Dϓ{4GgiS4ʵ4sRoR> ܻv \E30S%}= `$iKo#*GZ 8xl)Y<$&9xpø͔ٕH:fBoHr{ 5$9&RS/IҢHrgG1gONy,U TI.K4KzLINrS0$J){cBG'N-Ţ[Rk$i%YCIv8X]9ָ_d"gZԒ] P+Gd$#M-Q>k}D_<Ҷo c)$II|r?G<غjͻ8895~Q)=zfo8R;5m'h"EHwq`MD>$+Dw%/IKfR64!|\` . i7SM5}#_$+<x!Ũ+i}+^LLj7$c$vE)4qnN,iv\| 8{J9ﱘf#oo?fKkƟ?As$%IsX%Iꐶ|h_;\x8`ﶯe}hmw= 4w/wWI/J)vvn_ܮ}h nnyL܊$iIY%Iv:RĀ?(LKrpe$PMJ*lU;8/III4MO~v-vIjKiڧvy$ISe$IS.;vy\R;$I5X%IҴxPKf_$-X)epd!/k$˿$IWOb IR-I 큷1j$˿$IXT`YF`$,˿$IH)%Isjg:$0˿$I[[2Z;$IlU;$IR4we _@,$ida,cz$ؿ$IW)%_f$IE'{2/'U$II=K)+$I;$J)^ J wB,$͸R=/h>de$f$iRxmD$I˿$IWJXvji|vI/IҀg <~5sU$kj$ ,$L)ek`H1@u CHI:-~z8ڷ-[ǵCHIX)e7}C]Ԏ@.KXN3B!IR,@Y2@!$I˿$I$ 9tK,$:!d5CHEI*qޙ!$I"˿$I9?5BKT s7<ɯj$,$U-!IRY%I'U!$I:˿$Ix~ d] $u_p,&vI/IR8?~I.DK=o|^Lrf $_Irrk$"IRX%I$_^HSp[f_r&`$DZ+ϮDKQC+=|vi }I~U;c$f|3S\Q;4ӓdEY'2˿$I7k&/-Hc89>IέH@$mY;J)ggHc9p"p|H\$f`% iW\ I]e$?88 xtz&Z=aINrKc䗵I#ذi.7Iz$Y{=i`$'v ih.n@4.I?vd +OYQ|x_kK3IVRΤCs$kFt~4Iz(Yhvt5iG\Q;4o,K_o^@|O`E *Ʊ,pljb$*|}})uKK ($?F_[+siE?I3/IRMsnJbB;IVN{K5]|*a$iY%I W?)?l^\vs K$ ˿$Iˀy{uInUZw%~kIUIB&9vIeHHv d$I$i,$I$ _$IK$I4pI$I/I$IY%I$I8˿$I$Ig$I$i,$I$ _$IK$I4pI$I/I$IY%I$I8˿$I$Ig$I$i,$I$ _$IK$I4pI$I/I$IY%I$I8˿$I$Ig$I$i,$I$ _$IK$I4pI$I/I$IBgIENDB`gqrx-2.9/new_logo/PNG/icon color.png000066400000000000000000000253521320142145500173430ustar00rootroot00000000000000PNG  IHDRWW1 sBIT|d pHYsDtEXtSoftwarewww.inkscape.org< IDATx{\ey?sf#,df7bbJRZ[+jڪ(&F ȥ@ &ـ(-Zo@-rUSLvg@9O؅_ٝsf/yH3g}$Ԧ "P9q7@D41\ 0\ 0\ 0\ 0\ 0\ 0\ 0\ 0\ 0\ 0\ 0\ 0\ 0\ 0\ 0\ 0\ 0\)Q ๸ *ÕEUr1\)1T5vQ9i꽸ڸ!*ÕԣwM~c?DeaRBbtw/jFqvDT+%sU!*ÕKFn_C +KTCQD+iW툏VDQhQܼ{j:(Bp<﫿r`D pX蚝}+}@_׿ =˞p8/gR~;DcRT"}@_*Ţ'1\)js7d " WxR_'" W`dYUyW`00\)2,z۠#kYaRT6oaX+~;úQykvo^9߇yM01\)r@Oa_Ea5߅}]00\>196pGϪg0CJ$+kvu{9U:p!%Õ,,,Ֆ5Jp%C[uclCT +Yy"7{痣(mw@]Q"(+2l0UY*zDa@oHKn0$iM#`R,A ~]K4+KЗkXj{^ P/DaR|,{;{2n0\)4"3㑸@HoJa/A`͵XJPkt6>^ )V W ꃉڂ3ʞz@T*+M{<w 0C Õ5&FGU!>hjbRYTn8wc a +LOuwG3>haR+yRTtwqAS ÕJ8Znp!EJ%Яu<w0CÕ8qQ 3(1\8zOF)vu{ .>_1\{RU(G1aQRQ-w3QbRk{eLwmҲJhΟpڞz- uͭo32 +MU@ER*Õ&D ˶woY㙡¥ AU3<иkm^_PYKpiݖ%NhjY:ˏdZnYo4n34]wovܭP&e`G=L&8/ϲRcJL'c}?D(RA]s?2%Y3-r )}XG({$ Wm~Y=4YpMT+RN'׼JuAEnHA 8ELp}|| Sa,k9+A;{ X֘Ru>>vl+pm-e2kHC p8`>]p|U<#058O!LzKOMwD T4igoZLs۟8kFޚyeޮDzYj[{[f@ Qo^c"=Pq A ',QE 8̰;jOy ݤWmޱ|_S VYetsG! >cOZ ~ò$|I9J{afһ8ȧX]:TՍ5& U}] Ł0JWQ]4|oS9e=S}blXt(\QlXtt=ה8E+ʽL~koxvJVS|O 39r% %\Y ]g2m9h '{obCzp%:)*5\k\\Ҋ#oZՋee]Dpܬw\H-^aQ]0%ݷyɳ4BUWx,!i5]RW8kP/Ҩz#-; J%5G ob1AL}CI8/kb Zc9X=x-hl 5?" #GuF܋1NFÐƁ+kԠtndZJv=z;PARa*ŵTzz&"ܺkS瓖[ ECTgZ_5WS^JAwyǽ@hsPI:P%{ge_~knf1V1**?r: xɦkw[VkZ~ets͇ _q?*(rQ [-f`m8~SkB0h^JEms-* h7Q BݷyqP$+!\:Ķs?"'.ݷy…LYز@t.e#G3Fj m>*;6{|㮜"3x.ueYYoTX.A[;fXBBmrwXj)S {+|j.1:\ZC|pm3=|S%`Ůn/{0äJrŵdç(kF'[, ـ^nYU"fD0&)&BTҬ*gk9r@a>.WK `zzի[?`Y~ղF҈ذ!/zwvJE>>p}dmDi/$@o0F3{#1Qb[$X)DށK`w}LT5-ڽy倨\g!EG3jW332g[|R~9MubUD liQஸ|6K[HW a3TjmѥU=v3wp@b V ^LSدg5?Z[:~7-kI;dNygG$Q*Xmq-՞5ωT;YgV8ş[(g=Orbreu/X &pU;0qt|)>JkδB6++Mku՚/ߵIlG=;{9]=);u?wGr=K">Jn}Qz8ŲF feܡf VCo䖸8.jkNs K~}|مE9hS/, jY#R }]wU=4 ^.pITExPr@d#ݞp~߲F_%Zp} 81P]GÖA|k9򧻫!oq1?˲@퇺3a)i[6Ŋ>\iZ\]*Ɗqeos-BΫ?;ٲFn ~mY#\6mpJ֫VmU4UC+HUD.y{dk@} :\׿뷖5FL}e #n< 6 ARV_|>3.~MWEebD{z7YKkvs۩ֵr/T`[ھXj=Ma3| ϺH\:֟_qe;{:V;,k'p5QlqM7O)\gv<AdZ|+p VU8βFJ/'s ;E[ٰh{Q̑s9Vϙu̿]FT6H/3r<Ai7T7uPeQlY`>|B M/O95+M+淼β|S ȶ)*8En2 t`yHu82 e44f,EF^<ؿrEL$Uۢ:T(L3!#"]Apݿ [e97oݖ5Ƨv>`]uQy ]}s˻ [# Tcjܴ!n٬(nPUijyZ:TnKX'T7\FRZ\e>E;Td8>Ӎ8ȧI:mVg0pH>OU: -Sp/"U&t{C Of'9MO +5Ʀ6w?%UhC04w64 pvnK-+[\_l Qov#E ue%vbl^cYBT;ӵȣxSX=T"7|^ $palq gr{3=xr$k7z:_Z"rG*ӡlu?>T)\jYDdY)o4yYU?Up e~BZ]fm]gČTZO@ꅵv\OI?=g]p U5f_Qy_}#D'.N| !參QhgV$ ^rYN'>ZIJ0Y~XJs#ZYWzR> UzgʼeLS[;-kTy)2.鵩54^@$ ~i@oݪa-:\4m 5}kGE_z{OҏmW.\tQ*#U$(ݫzΜ\U״|q\'|#з̮t$9ǵ 졊י} Ngv,00H:|߿UGЫo@CYLFk)ڬ+ ?n3+&vs > g!@wAQ:xi6R2I-Wl"Ik.SP+֋bTz֑3/`~r?k\dYs|{W=x!PVw;6{6TuByjjS{qfN/ mq- Vl5=s: `gQG;Y~rsw Yhgȿ g,Uwr4._ +c ȇ[K7*EqQɑsf2Mmp]* qeВc0ӲNqf=3v;˽NbEŵdfnT ' T m?%U[pg<=yjG@}Cܯ^)ͺ;5!CMwd5͵'<3T _]`}(@$htF(siV"µp@rogTHaQziΞ(kjZ7uMη?EӢ~֏TH?||7hnq-BªqywwՅNra>߲1;%*L~gzO87\Wc ׺G[\K_Z/qerk\nY ׽jnfk~{aNj7;W(幱ܬw`76%>pw~1"0CG7Gy_fr޻}A#׹مIr緼#6.cRkrrdY7܄J~=z\o*)9e+ u]c) fevu{n0Xh 7|<^J:bo2ޕ8\oY *ζa [IDATw&HZn8";J$7z?w鳽?VMwt~">57Wp|ܽ`+7YL܍$8 &m~H52"wNb.L`c /o# ׺ 05 zΑdZq՟v@ >vJ}$pVEl'i&l%*TJmAԿ >?D)8zB" +nk9D<2`1R0RcG0!P]sn \Ӎm0ϺN(pcEV0uE*zjM7-_m風Rk4\fAPh| ɧЛ$[q&Ќe7e n_Aoq-{F357p@WXס y[yyCM~ZLI AGSCux+ε~PUևK um4qAgTXO1N C~$v\:P΃Wx skW T:+>^nZ>~/>"6]:x@#q4* QME;K Guʗ4Q>r7I4{PhC1\1uҖy*+CLQVq$p٬[U1kLh4W2pzʨ^@WT*HGw5^7j&<эKKFQJݠ3Hn1\5# ;ǿЍIP1g{q'+5f"xWpҸiB$ȈMhZ"CPcY tdsO&IIcvݝ},LJ s,.^׸U5'r%42ijS4auض;pp˜Bypwt<URcH +T9+`,Qxa_4S[X`Mk\1rҍmEȎM-zkhIpM;.)Ziq#k c'/9p02AI.2Z޺32'~$5T4/.VȎFy EUn_ljĿR3nL-Z iƶs ڽ[ 5x Jf8ap%%>+%5m 0\b&pEU}D~'*Õ(~'LOx_o%Õ(X17p]x?Deb%C_qadjʀKp%JU,=0g aKT+Qr̬/f7,KC${T+Q(.ӼU:@,#4 Wd9ԢC#=PÕ(i_xy9_BygP DI#) qDdr݇muq7CBd*WjnJD `l W"" W"" W"" W"" W"" W"" W"" W"" W"" W"" W"" W"" W"" W"" W""9,[E'-gQDl=J+`#W뚀}Hu1kպ8s99Z͘ŷKwEe"IZ%vxj]:aE _ΨЏYklkc_Gֆ?F ]74I_sSn6\mSFF%iév!i|ݶSG6CUڙo3<4ŧ#'q5st\mPWG-Y$.0:nܟ@*%?wU%ڠޙfi3La|g4~a?Uyj8ج9*Ձˀ5(鶌%a.6~lIWM2{ekc_J 0t/kC's`#i ۀg E&#3 HZ m~3FW33kQ߉qtbx#!N;*9eorpPc.r|EK-\GD?}6"~Y@&NVY?"|?slD_EU+әizxZD!`nZhEE$ÜgoLQ_6QuND\US`|eZ~60M~l1'fJܙRдCzl 7"b%`&5zGӋuRߦ(i=WΰNWsI_^R?0#)VHJ { ?3X2\BY^KOd֘B7Βֽ){OI׽v$3.>Y@?k?0")hޘp=w_-2-|hx>2f8(?Om9pz*L=)s/四kxZfa_)3a."Nk^hޙԑ롣+@s+-3D%2NooY5NG_o;XLx8r t&#ř$m@-\ofµIj+9v`HYlk4i#kƮ)>sH V(}[25+̬1ޟCe]Ioj󂒶]W~|D>$dmxT93\?P1GRB3NIѧ*" u'/cu<`.|B:,3;X" :&iHЌP6θ~;", &]~ޟ3 `4t͜jkۤ\d3\q-Oě%Ǚ}7򏃹xof x[`$'!\^:8:?L6L"IKiʼF.G~n QiwkTG'o*aPtEv$Rf+mHz.j#3jk=l \A{em6N_ʬc*X"UGc-q]ծ[Y2IZ<{O, Y@I=t1is3֡l91Su؃/WmGWhFBgtː~hk)-i X%Fe'F9]72Iǒ|82rݧ Q澽^p+$B7keh :1Fe'MJ6\uCń_0ASA(<,ƨtp=M #"Ǜ-uIzXt8㮛 ,9%ucZi '~ӼIs?,$HO:G+9f$=9X25k֖RTY2a,yx}DTY,@۷#n6+Z]Ho O~M嶬֣lŦ"P&6·<){{LI?R͔5r}Kvg`8_î_6m:Bn+,fxJf8BcC0vf6*1r=-"kx34+jR?Bh\,sM;;mr6D* )dRFi @ҾwUOyK8Kk{z $*-omrfk̝`x0ɧFoACeu͕z~EK! zc{{%9OMk6+0CItcײkjk6UF~W͑ϨYZ݁STvO|م6wyWHx3Nr>Zr?M%ޱ5 ɝ'1aҵ}Z ;$=Rl祽FM*#2{[Mҟ[{W'MJ>P=^JQeY-Xzi}/S[Z7жE~6Bϩa)}N]kx]R?Uy~mXlgr|>^ lR锄 )yz;Gu^G i-e= ~Tr3|CDp%T6Adv|iD|>n<=ǽ]q>x%"bʦue?+$ofxD¥|,3H^c|SC.c$>fzq״ g+}s+JzT{>O-Xɛ7}o:?Z֒ts g*Uyqٵ"6}5;mQG4knFD&1YW҃3 Dd8F0(V~ ۪pSy}3YZm IONJ#n.j׿Zu8$F of?\ezLveԿQӂB-( wLIq kWxmm-JQf%?ԋz˯wAy_IQ=WW'I;7_rِ~VᝮO*(ᚺ{dHHrW39W+!G"aWJ|3Vx[9ÆYJ^%ԼeڕjgKPS3u n>~0B_CBG76fB q^ t ~[Eϲ ,ˆXXgU2 9"sCgI[- "¤%}vqdfYBii0SOvʮ3;7wI`lʲCf6:Y "+QNM:XSwq琴_{z%]躉qH?7~;msuIQE(̮,M=6Z2k 餈xCfS|!utdrU9 eG`mÃ)Gi3J4F^\ U-6u28Կf`6)sDD'aIJ"*?2u9r}?P9E6w/FcV,^1Y eSc*9zuj'*ikwavfė&"~y}IOv̮dIehV>%\MukNxJDu#w,^#sBW'.{|;2H2à_EAǩ\<6"nISg\xtDﺑQHڍ<8doc3 4R^_5\'d8N7u݄GRN뺗93񊮛VPyeäX/"뺑;Uڬ0o̾3#p2Iva:miqr'mHڂD,p"_=_$/&RnFȵY*z`M+`uC8 V(爮Su;H~NlﺁAHz)HgwTHz9:=te(L9xtz6m#{]71f*û%ŔԩY+:rmFXAGjհ >8V uD?I'S~=""L3[D|&fj}\LK_(S:;5$mT,q}e'a6I6}rpO,s#XBMyl`6 `ˌ$AYZ׶;In}Tv;eܥ]Icc;4"ZM*JQJ)d˴GSۼ]ۼnffBɱ5u%eM+H:xYײPf"mL2Q\D[XGu=NmֲkY'Ø E=r#\@w(xYr̍ K 8c\w-~DPG>d?#b?f!piM*M4\3Sf9fdI3SMS|[^ lu#l1`O-[Zpۀ{v݄>"67J8~%Wn]Hy0$(Kҕ\ͺh#|8X{#W]lw Œ֥x}ۨ  JD_vQm1G m JD n6"&>hhcP"lp}[on#vo AwuQMv.%"ehx&6ޭ{<uD441(@lK{uZD^{[GQ!`P"7tyUt!WۼwXɖ~  ']~n)pOj8w?2 @u(:싯oܦ ;8%DpSlvgYszًz]ދPhH).}#}ͦҽY  y<6s\ ^1]w)P|+uUʸ~6jǮ#Q\^aA: Ty%}]_\Vw{~7V>zu7MB[>mkx;*(nQ!bP!KXm=m O|T3٤i_.%֛[Dm\uug.J7/ᖈ  Uuշ 䍝Gns?tק-@i io3V*F<3zz+AagX(QaPh{y^'x^KK!VF+]pTc3}[)AZ]o"*F J4( /ds+.~UꩀgJֲQ\ L-UM~tw)u\XEw)OtOZ{~{*{]"[(juZV&} ?1^wH񺻡6]{u_dNd lY$#dt{=y=Ed >ڹ)|/ Z,TƶUKWVdf\\! :W=\pUECN:/7:b~[ TON{cyv8yf͑nxU]nnDv^uO ǣh|cl{A0nop16jCA¾<l'@3QtW ,%*&/my?#=?lPַ7%*ķ`\ ߢcwTq̲_Flbb700-  GNQA %*x"rmJrro#I(%i;JustTM}#}&^gUek솴/{oB8.;VFDUޟ!r <ѵYa8Ka,N<JܢWM׵ 'plk@6mߍA[_`oM`|OIMXUT ?tV+sʏ=gQ/G]dc7[ u`+Ѯ [\L8 \MY!k@OIϹƹz5WwZ6 i]zWڹa~anND.aq/(N`z=2j"PVqWK{ĂVx#}&Qبd?voqcV>t0Q~VC__F1ƚm xFm"S 6oeGo(ƚйW Xq \k|g{G@p =y֢۱0?j@\Xϭ,oP۾,{0q=L-/Ms$•?`e|SHbnxtPueޣoecJ[0> h(*jkz3g7z~y:)}mmq|o$(ʿ:;l~s3~kKNJf?QO8 -@%ރA""7Yۦdf|ə^oV*DkƤY>chZe~eްF3K \+N;W׵ SrŠDQx/sc* nFxekF;Eg{2ztoڷǐmgZһc8j?[(1bOgWD`PSsmmSR1z ne;J2eZSRˎܤ/.ڕ^AU,\%]_n:1g.Q(Tue4^&ƽrY2:W׿6J/٠"Yo ^1{S=tB(=;^v2lfL~pA A .Fml1(Q>'vn#+fWktխFvONmZ2WevOT̪mk6)x :z[M4>RmtyU`mdQ6(D.MI߮Ǜ)*זUz1YsW=;b^C[{4;*u :Zx5^x[2Xm jG?`s YD~C@ƹ?H0{qJe6zpZ aSߨ뷨Ն2hb+4TO{妋-@$|9<m~ۦdoS2j;ſ=mҞ-PgWOUu ŽRu'`⶝ۤd0BN&,EW[]V\0(Q>xxVNmh{$EU~7!xȗ^oco13 Kl'ce@ʚE$e6;/z>ڈ Jd]t$'oSNĸ7Fgd$wM!™Y5IG[ TQke'DNj-y`K;y(n8[َcM#\'%) gYܦ,)w޻^qw{LuX+Pڪ$[N2ǣ,}nx,8 'm/0.;ܓ(Y"7oWzB}t]GƮ}U9>`v,wg~ NFtoۿOq\ D{ DĜZKWAlSW^x ?x{f^:VM-BlR:,rAƟHmexidL*'Y"gYۦdvd@ foܵ /\'\/y9I6j3ߢ!۷8ke?k3 뾒q[={DyŠDQ};Ke}?,NEe욹w׭XNMVEjFaQ 販KmlKr#+3#3.Dal>Ad\yeMoFMޱ{9F|v=?_]R>![9={vY*je*m_M.U)MxFFHHoş'xSuG0}+7?"!kL͒+jg\@ߩjDSDf^:")U<'&"Ģ N4S :M [FDڨcgUF_/FmwcPsF\ }Qn ,-UѨOŬF}x6 xȼ匊ke?(P@{%WdyًQb~`c0cD"TY1u_mиk*=hs"Z~-b%o_hŠDmS]ܨ[Ǒ{[3|V7((<e`;q(u ||g!c`*^I߸ uލA Zx@=+f.|}i^ "y0ǝE/{o^܄|y@K+D`P5G 3/u#('s&GG1qF]">ҽF~Fm56e@|X8 JT%mJ`Q7N{cHV[i+6>[[~:. WpUX xs?I%*PejNpl}dKeՑ?ay!CT%\oqҫ2ĂoT̪Ž%ڨMT׳i^m;w<ϿJo, Xl4YkDǞ ?*߻t?u޻G& Zw^.7xCLt.X\*C`m\*3IſQ'$Ϝapg{MLፗިϮĨ,fW֞u|;GQwџY+Q= (%*e_x4E݇1璾KP\g 1Uڄ) FA亨bg="`^{-pP}4V ˰̲Y5PC*֗u})#g_uDDOzN}ǔL +F)pG@ IDATcWM䒨ҌD@0(Qy1(mJr'h4;e=d?3~n9`/Gў0(Q/pd}X4q{vg@[MPNEL\t x}^KDbPHM<"mx6JWd>|*6&Ϗ 㮪s+U>V3 #zǐGQ6Sc*hw{ߑ`ZG_*8yWl&&W2xmkQ5P>Sg{yoEp$eETUV{Y:;;u {;ฅb<(Q_!^z#ʯF|D3 OCO_OIIc?‰3ChwL!U̮=au7P7 ~G,yV:Q7A; JDP^yǗͨ>Сó1iv򺢋[1[=Pd%|Xi^LԿX"D >ְdM G7߀L%B?S~|q]~d^ـ|D!@7Ԃy /Ņワsq AD{)qo_\i%"2A8pp))]`{} >"j臈 bP"* hQR ۠hCmYeM,DD0(}?mof3DVCD4x&"2D &̼t@;81(3-@Xvw@/ Arɔqo'*| JDDfnr\6Xf!"a =7/Nb1-Qvb.FT$3NJoRp?7" DDdG]_p/`|T QlQ90yf͑8%ꖈ(; JDDr  FTtf|.F({ JDDv 8h1(`P"""" DDDDA(Q%""" JDDDD!B0(`P"""" DDDDA(QX  PWDc {fy8 `2yZ<5Xs8n<}ͶX`O_DCgB5WuM-ͲTqlq48cc5|ed{PEe"Ui0݌u7;峫kZDKoDDDD!B0(`P"""" DDDDA(Q%""" JDDDD!B0(`P"""" DDDDA(Q%""" JDDDD!B0(`P"""" DDDDA(Q%""" JDDDD!B0(`P"""" DDDDA(Q%""" JDDDD!B0(`P"""" DDDDA(Q%""" JDDDD!B0(`P"""" DDDDA(Q%""" JDDDD!B0(`P"""" DDDDA(Q%""" JDDDD!B0(`P"""" DDDDA(Q%""" JDDDD!B0(`P"""" DDDDA(Q%""" JDDDD!B0(`P"""" DDDDA(Q%""" JDDDD!B0(`P"""" DDDDA(Q%""" JDDDD!B0(`P"""" DDDDA(Q%""" JDDDD!B0(`P"""" DDDDA(Q%""" JDDDD!B0(`P"""" DDDDA(Q%""" JDDDD!B0(`P"""" *.^{~I{]=oI i޲elXXSZݑ\q Łr\qKL qKZ/[nuDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDfH ز`M""Lb?Z` oCMy*xAu|At:-3*&TA&:u$Q`xCg3SLt:տ mx|o))S2UUr&p lCy>89tSS P JRUUE(s(VFGQH͏[X̙3oHp4 G`o kg̎_3S#>3U1PZxBU~ v%uhҼy& ֪5AгD}pP/`(lWEqqW/[7>= p&':1k*𨈶 Z[ZZ^ǚDUTA)뺥IIzL+D"q8=J$z"Ent pO&.K9Ʃm56XA---sD">q&slfkksSUULW xD3@Vǎ:p?cƂƎa˽Y!"+W|%>%L&"##[DɂJr6D0"`o+|+96uW{@6P9%MBĹQo3f,k- gO/X ,rcsUuO$őm73qc}\[Rf6Db0tI,*G+p[ooY2'9 cY:AG5yO={_S(:GQb[29 }XNY_RXBM>}D29DqGH] BɧÉ!N"80Lݴט-Bp,p~L"QiӦ/EdI29wvOo@NG}Ondӧ~{19[yϔn֨mzU{w\  xTB#Q4h;*?M$J&G/^Uodi'{ bߦRnk%sU)-_! \>L=!?h+Vܛ)DjNMشNlqDi_($DUʍ>-*8C5 TUUMI$x\!'a舡@DQ7B1omDKV)5yXomnO |f L$Cɪ( o755=DP3gɹ`%Gݏe%P+/WT`oN͛7:Zx sVP4H&< }GPu/0-Pk"l߯9: 0륩PyRȉbuks}a_2J^'c% (PH=7U.ygyVshyc G݋ɧi"7j(+vO<':+.Lf=#TUU{⩧H !B}cS{ڈa4?C!~CoΜOFG.o9'8s恼9/?0NLAc=]͙3~? dvԽ(Q="zXԍPn\#OHCY,/hhbx&I&~"ߊԵ@n=(IZE_T(qb7%Q7AًϛwDDG(˞"zo%>얌XuL&'((qcZVR"Z *+(Dձ:eQRj ="9ѧþ< cT;1bm {; bU݄ւҜsr5œJ}D EC1/ A wRޥR!6*yv oXf!Dx|~y}'HoC1X`D v^nDCtz@߼u {>U&8(˪sx ]kSHPcbAL#ʆy[R 4гv"Z _MRFnj GW(綍q` JnI ZDzgr;ѰKMMMj(*7=$Fz=Q::wͽz֠R"[(sRY \uDd@x۝Pt?y0x<}'R_YQ=h +W>L&5&!V2,P ȮќҺu2UE9[X|/L&'8*Q7nզO>B67oS4${/x6Ѱ/ojWB]ӹ@A=y7 +YOOAi}=@?sR va2͍w)dPO@ECF(D29tZʅFO:/2w ;JDEi 2;NL}{ƳH*:^nnn4>;+yp3 @d|MQADye,A:4GJd2imibJcma S2g|6@R"3~4@5 i߯9}3O=J) wr+eϟ?Io;F_”5Fp^v`l*:JjuAwY=E Q&O9 aQADfҊ+@ݢ 9UU0Ulƌ{J%jFJD@k0x|(j1jT3k#TdrZnAg>T晘ݟ%Dqb:1>(˗/Fᮮ:Q۲rUgY͙S(4Pީ^PPJRWHfvg޼yb*.1[.A f[kX#HTr=PP T(wy6hSA Z[[2kآ۪t,Tn>VߑcPOy=,XPG4ӚWC1M~;=LԉZ/ogAu3`xMz @i_4laoKE*v~y@'ME6͒|xb%kqsd>,LOA)QP# Ly(l^bOODH$|PZ25 -Nrߞxd ǾQsWv{~"8o _skkkm,G,NRG:(4p@N};mN6cfx0|*g eaEC?UޭoϵkmͥH:}SUUոLA1/wl5򼽶1O>nW,XP[D1`W,omnJGt9s}D/l D yr{u!DODX577XH7gܕmfʕo{9sN:T1o{m 4M 9/kA*?}.|-L&8͍3-[@(_vߊ2=g^nM Q|v؇ CqzKʶHX3UU7>QRl؈B=$ HT 4U/T׾;~Y}|ڴi^EŔo(L_nD[KK&k rE¸L8iw-Ÿ[ res$! 9%?mڴIoяDv^sB.-~&aPG(9l䚛Wb%^zr(BҮ֭[innloi^9]B͹7FWWǕ~9S;1/9zFQT677w-6(UTLbqF饨{U:[+>(D2ŗ#G>TkiYy˷G̮v~8_4|-- tuQ^,-uYq#$_ ^.9,magzqJJdD}dC!?:Ы#tzSi (2G +W6F`455`mii⌨^qP=N<3I 6\DppŬ, "1bv\Ttv/(B0twuS@PѨ "l (;Lw=$n3=}%t2gz A.|4(1pDH,M $R/_9[Yڂ)J? &Z |z\xR@: /Έo * J;`D ·={P:c*ccQ:J7Ԃ@|( HC5e J|^mЭl7(9]SB'\*(%!^+Q-=/GZ!ݠm+"voz5sD7k/1KfL|= P&vtC U*Jm@W.\"umĂP(ٕ%D /rQB5 tRxqʔ=Sq9h L'4>( *S5+_AdhsoPj;L}Q|7ϟ_f6g#v d*'J`Y#TcGHwT[aD:Q'i6ʶ a U#;Q~[Lx>(8 Rqh@:B5`3>˭ k(~Z*B}.V[%M%`ܓ^PM`"@dډŏ:YX\;51hH$pK5PMN_2e}J`_IGtRpê~32`9Z: rfKw.}Hɫ*tG(_*"f:J: PdɒCL Q7l!ׂk(xI:B51f?1Y:A]!@*X=˵`h@!DЫImل3B}8"|_k="5mVK+Jzb[dn 3?%ݠ.JQrGK}]@ۓb,,MnAtRgEի.(ҏy1$!W;!_?s)9旤nPJEK2ׯJG(d8N .іd,,}OK'c!*)'|^voe2;?c#XjN-ݡnG/]d\C):{?T^mAQtj,6X(P-_*)1ȉCj,.éd\鈭[>Bj89|D:C5W;4kKG(om8K5Ay:IԊ13(`Μ;wtG#0ht$3pE*ztGޤa \+Qm%1udp~A˘!")y(k;1CC1H,[]׍c[4tRa2{X-5c\&tRo^nvR.{cB-atO&gptG0?n ĺ#}Sʺ9'TuM~?o'7;kf6=ߗ|tG]X|C<pm2JU7>R)x28ohEH +ʼnTX߈G%)N[azYg;7jS2='UǕ}et<ݟ Nfq&qfҥoc a-81 GU`% nëf7t}#ߑ=Hrbd7LxEX XS.maRiIA,6=X: #o ,#po /7%[Fچ!D2?&`X 8vl}at0Gq@,wt)%h sl)׷y`tG,= xY${ܘHЈiP+V\xsQ*KC)~d0e;|yzl6ks6WơP̓… +BVO"yR-riM`8[jG U<\ mb^yPB[57W^bqHw(k IwԎgo{0Rp(۳pa29uH(ĺx:T پGzR5m3; ^(Za3}閈=JN;|UPc;ظ~,1LTjQR3 ܯ!BZZm~.H&/#JU+NO59tKW#{j-͓:bF M7F . hFn2z>`-.QZFNQ< dJZ H-9͍Z abVK| -!-^(K(Ud2!͑ Jmy>QG+BL9HfWe?\,3"t_d3Fcw $P%^JCV"Hwԃ­͒fm"go0 d;kʥB!Ѓ~ X,\Aġ~IJ8m1z? lAx`Y,~-T )v+}C7[E4?0?[#N2-X,e֑WQ:]A+J`:m w< jOe#;8"9c&Nvәmem|9jL&gZ:-(wJG҄ s#gDrvoAi\.B˧ ۣzX z* Om%UlJG4 bPaNb!n7Sg1=[jx1tFxs,}*>(mU(~[r t_I4B}寜?ơo*yC6RPqn"3V:X"|>aubҳR &-KQ%dpBYX?0Ob`n ߿ ]׽ĺ |l4~y=NEV2|qI yO7p4S+^ĠE ~0=u ԱWvg``% 79ҩ;Fo֠cπonE)TDuPΖVdfs0b`_1},xٲ͌ID`&īz_f/5+K BSa^s{jrK,\םuh0 t V3c1"2ˬnZ:00Й+vs ^tX[T_2J(]XnDxL8Ya;uRMƉxj쟢RJB`hhx /nPJ)$tRJ)%I{%~$#/tRJ)%-_äw0G 40Hi.RJJur]q 'l* M\cۥ#RJ)i:(ՠg x7tnj BhO)Jf͒%>~;A{4EUm,́3 RJJ pt,`ecX-d0ueι?-IW(Ra1ROOdk묥,:pNvBRJhAu=1aL& `l8ה+5SGy#RJ̠;mhidt`L0 cd."jø&Z R*,"s(n__0gxۖcA&Iv 3A0|tRJ)&RJ鎶d%3RJ0Ԡ+;OxoPJ)&Rxw/Z|j…R*l"7(PJ3RJ0䠤KpJ)TXErPt !Ig(RaA %qT(蠩RJ"҃.myP>G:B) HJ.a38V:D) Je..U##U,(RJEA[ Jt nl|X-ݡRJEE[ Jbz^F R*JfP]x^#tb* R*jjP[C;BT[ ${HÀW!IS:<"BñvIP$IBI$i %I,$IFP$IBI$i %I,$IFP$IBI$i %I,$IFP$IBI$i %I,$p5!$i1Jjx<`e  %IvpPD|@0 %IvTD}inIR/Y(I#VGD^IZY:0".^sFc$iV޲'#}^BIҬf<04 %IM?FaDxHx,$^|?"$BIRi/Eq!䑤Y(I*i%_4BY$ibJJ:5"N#/HD,$r9)xVTRs&8@I._ ,45 %I%Sokh6,$uly/ƑY(Iھ U2A䑤Y(Iww5XD| RWIҤ,$uiXv<$BIRWzЈ24 %I]H`^CfBIR.5x;J/IX(IZ+I \=H_Y(IZc#/?u4 %IKq^43X4JⰈ8V_o$BIҴ~ SVeBIҴ,;MBI4NNxÀ *^_2a$i{DDqptKZ>,$MK!!iY(ID{!iY(I#⫵C8WCH_JuOiHRJ8v8 i擅q\BOgn"9$' %Ix]Dv8vIBIb~vDJ`9$ %I9݊k@v ٟ]kBIBV;zio\;TWu=hf><3o%s] 8]HY(I43WG{w=vk/.q@D|xGR#JZn` >_zܐy_h퇷&u%5?3Y(i9&D-k(w[_[;w}!ǝsGG#HYBI 's'Eď437ڻ^Z 2#2RY(ix]0 }<.*4>k̿bKڠWFE|Ϳ30RX(i98)b:*"~ys#8(7t piŷw.Eֵ %ͻ^:*.P]y>h?tf>Fw^:̒lX(iޝfF]w5ƗL0kNߘ{^X(iGVq5-F}2rBckq>=?Է?NM٣#ٿFy }ADNxsU7.5vڑ[8 80R_Y(i^}2"3׾B0=M}]ASLnPn ;7kJ5 zBIRFﰺv!s %귽 UtS|Ana7]:A|0&ip,4& Byh NEkw>\ mq+ МPҼ&"{k+)>%m w(jFm>PY+]BICr/GvEw=hfށPA󀣻wFkg4gަ@ꊅ|ZW?bP͡zƞk33 {cnHVd*mQlY/Eǻ43סzMfnP`ܷ}8vqEQ@Z,Wc@k;{jwCN<`Bc/խhT{NZv-yniWu}ZY(VS2'OT#]QwW46}/0"~{jPRdufnYbvk *`BcyrS_]Wv2 Y4Z0hPRߜM88PU`؋8^yC~QCgf^^`ܥx]DG~xfn"[TZ+ %͎QjQ9-Yr/:+4v6!Lan=B}2cV̀J\}_Jt2,Ԃovd ^zyv̭2 ]sf޸ S |gOrK3󖅮94X|`K3uwef8Ϧ9Zs)w6!@m%mB݇}hޕ/՚6*qcK]PR_<"-1pf>qo`/Siy];#;e-4"Gv.q[6p{/%^AU% %QhMOf^f(eJ\'BcG2 )7g GysD|دchexkJ]ǥ z~8 om_f\Z(ICG1[E8ϭ},QMEDxiY(Sh!rfۙRx{PY{z֪Smw=~DI݂rt LZ* %r ǔdgܼDXIs^Z ?_`&y(4vn^Tfnt̛v'"yҵX( SrG;vKʽs'"~[ \CJ^S?g4"a6畽7"Xhh~&jp6kl%i&M =D2P(3%[ζ̋ 43oU(siDfxlw̫;bIM%հscJv9d)B[0]F4TEn;3].@.'밈8odiDWP]Wf]m7eMhM/fI%^//[_w'3oq{B?ٺߵshzYu.3bY{#.\Thli"Jir,XaSA4GlTh쾸m)Qм<".zlvv8:"3;?-2fiǔ<xRþ.3{1y7Y]Se:=}}!">x CإVzgƖfY"pj9 gs/[t"_zhX./ԏu )joMOt1cwmsL,'Hue)9)Y{=NeCGu=n~ /:[`M)tJDnJRY9]{Wsl*tUf}aވk# ,BΦ· ]>̼Al̕K͍vu6qKdgvwܵO3J*,>c2i 3 ~j}&pZxB3q]`!?|*p80dsi:#jJ,r^JZ 1%bcJ4yqw}J&uS CkWOaսoWlZ'#Ied^_02DZ2u~Zd]:[&ߞ"?ts]E, h:7M%rYs<ͮY ,ds/u}lBݫq&t#Ԃw-ඔk^z$PS4JyqcJ^<؋)e=崷ppBcn""> |y/ -e杨q}̼a׃FeX %MO4۠xKbѹlԞ:bf>Uf斔k8D{{1Ҵ(.xBcKe:ZTfكs;'eٜuWE,r83X7PWlZ,A7Zg4ɵsG2`2sk๵sІ?3=iv~[D%ĒdǝPv_]blim$/e1%K`چ~Pdm4":ᮯ}'m榄WѬPPܢv8,3Z;B2j(f5>w5s9esQYĈ .88 %^fS#l@spnú⚌=33q5>oî:ظ(rd s -z-3ץ9ptYpO Z|29`fh\ uEO+1& kҚ,w{E:Twl,]yZm*q6{kk ynӺ> w(4X(2ۑԵxp!栂~ x[wSh.43;?턾+Ph PROecJ6difnGi13;iTf^r^W֥Z*%ƖBI`!cfayrX[w*"Jmg !<.3Uh=K eesLɏi6 Fj\<~6<>">Z;b2i9d?kLXx_ވxkPRd{αD'ԸpfO-i^;U֥49&8vi\J'stdx,/wy C=+"z{TF}sLJJPRodjg43fu93qU k̍hncݺri{!C" ;2s&23~gqevj HxY;)3J̼/U݁{Z{DDeA]~B*Ƈ#^猒1%Kqpfޭ2X$%;g(_$A CHqFIekk(G}"no}8mD\\;Hf>nHsLFUmA&H;]&N3.{Cn9g"R9*_+@f8 M3vX(i"y9,](dy񺈨os;aU2"b!֛4[[b̼!W;j8-^BZ %i^qդGfn8!CHkc$q|{T2s+ų^5;k!5Y(IulA;jZtE[f&o&2Cgu8:3}]R|ߔZ vXQynNxn,bacugp=yn:,3]{ްP8e܃s31e#i Oj{v/5~wsG,/ڍy?eHJ*Nn̼UX(I}q`f.=fFcydf>A3@#l# Rua˩4Fkafnfif;Gw5==3xOG;SsBIꏻҼm}ˀ[6|lD|vyٰ}q/,46oI5o|y^Exuy$uBImq>hqw87`IP;^Z7pPh*y`HꈅOgShcUn 4M#KI=c$ jow'N$3<@IPwtHn hoI#|쓙N{vzBIMϫEemƑTJԭ=ڞH9 Kg$IݺrOMIKa$I53o?h-ay$MBIsBIxQ`2Fx4(JTk|fH$̼@fx^<&d$I&3x#& $dH$؁[, %I,$IFP$IBI$i %I,$IFP$IBI$i %I,$IFP$IBI$i %I֭@ӕgt-M;, '70C)WZ76ѵj%)o3`np.pIq4,4?mfq<ء!WMf(AY\(3?l;kuߔȳ[DגZ,xM$i %I,$IFP$IBI$i %I,$IFP$IBI$i %I,$IFP$IBI$i %I,$IFP$IBI$i %I,$IFP$IBI$i %I,$IFP$IBI$i %I,$IFP$IBI$i %I,$IFP$IBI$i %I,$IFP$IBI$i %I,$IFP$IBI$i %I,$IFP$IBI$i %I,$IFP$IBI$i %I,$IFP$IBI$i %I,$IFP$IBI$i %I,$IFP$IBI$i %I,$IFP$IBI$i %I,$IFP$IBI$i %I,$IFP$IBI$i %I,$IFP$IBI$i %I,$ICuI IDATFP$IBI$i %I,$IFP$IBI$i %I,$IFP$IBI$i %I,$IFP$IBI$i %I,$IFP$IBI$i %I,$IFP$IBI$i %I,$IFP$IBI$i %I,$IFP$IBI$i %I,$IFP$IBI$i %I,$IFP$I!jаd̀TY\(3oܰ!9a-[vDĕPft5qڤߔ7,笈SqЕGoj$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I.j(%3nܦ5pC?6 \ \lykW?Ofsh2VV-i[<6o?6mtC`#rO_Dĕ3!d{[7nXHR"_h?~~:Ե(23 ~] m#i[Gx^]|<wM+3 0n.N~|)"`\i[(e捀KS ݠbs> |wCf'ChfjoG"J94 y;Ev4p]SZAJ9tk'̼5u Ŝܟ26Ӂwgm~%"Ι3틤ʈ,/75'taD|o̻D׀GS4Ugu; V"׵CJfnC`h]̌@f^ñ23?O̱ |[ȋz[}gW+'qef!3?ʙ3ۋytzgAfTμ_e٬ZgX_wjGd8D{qF 3w|uSM$mG[gohfirI /ukR=B;пWfX <xV:%!Jh/uǪk~oa3s'>',{Ofj2Ιv ``ʑ4O_涡\/3>!Mz4/RgS q-q͛!zFf>v ܢv)>"YzS(es4/N1_b |+3~d3Y̋{'hxI, x/Q}D ol־͝j!qRf .V㔶p e#37Ciiẓ1i"43U#/~7H 8iԌPeV1sL#mVU,3s<Y*{wCBf>/֞4 ]{tVs>e4vlFta "A74^\;DWBd7cCs.MJ31g Mfn>F1{yl jNhEΓ̇Ɉ p|HKvdfޡv$B_4B] E`^$Ky&2<8pse_QCry~R)R\xw6m Vf8v)_f7>EOPdӃ@v7'IfNS;Ga4˼ߪODĥ!poCL+37Nqaf&lQ3+څCs5#du~dC읙OB˦Y;Y>!Ӎ<"\)RbJG0>WώK♼`d检owemi1~B,=4 ⶩDS;f[rpADq--Yx{fj3If>xiS:0">/24Ɩ8E]ZPfޛf&V 8vMݖc3ӵ}rmN܉̼!v}[4;)VPiz#I3lq(C1#ekDuƨ?_a.풙bL7b 5!̫+2sgVe?)݀嶾ldo?,]ZA u샬kOcJFĂ)RĴm04pLҧhTIv,ٵz[NK)j|h[DOB=sRd}xbfnIkSU ,Up_w"MT(ef%HivIUta{W:,fh+ਮ{v=5"_jIgnP"4ܧtZ(E_nflu5 樚b.2H_;jzMg!:uԅ;v4,]< dFi왤zvBR } Bct:Xg^8U["cJy+)Ji2sc OJ굮'j]/)uϜ};Aи3J0̓5Mb@DxUktlf|nexfݷh7AZ2s=\'-w؝v<\k6H;+qF5t1Mz)pAMi~[Мy0!Z \~\\ADxzIQ33$;DXk2ss 3Y^pB)37bL#ӁFğv-;[iuQP_y,ED\7e4;y .74*J>744o 7f6ELEĹ"ó^ǎjo#.HUBQzþ:NOFo&Oڏ{t.\2j{kOsmeal\|"p8ؾ u{ӼܚU4rf"">'ϛ5;1pJf>d3#PYn$"~7.=3 gFYJ8xSD|/))}:9"?e^aѬ#8TU2sqN#Ū.fy<eלkOo>v9xf^{4ꌈiӀ9"]k>OS)Qxwa>ϢHoGijӜd|,;OсkcF~$&">E\j$6`E@D\]"b+kifC'1E CwhQ;vfEYT|ò;,E߯ "ΊDs,\0j?dA5,aW5fp 8vյC^Ob5Cv}xS%"1x H-@%?G -!"Oٹ_VeF ڵ{{U0^S;ĨB)3Mѝwͪ[4"G4cOd_}xADS;B" 4 P;xhD|vIwgN{g?=^Nz~_Yص *dY.փ^M&k{rSHAn}:*ͧ+hiqD|4b>Q;Ѵ ="⇵Cgn}iM{,}v{HS?"Nbq>-n4W;DkTD\{>R;4]2ӴB13O4OQٯ6bBbqMϥ΢N+"Y;RD7"7猶^_X;6q!MߛJ4[ <+"έdZmYZeH?m$ڙӜ/!Q9܋ ?+S(e-GWjXfOeыIpmak챗 l)FD9*)+`!j5ei {"bԖ!cLݿ*$".iCkzfmBURLgͼ?0k cYpA kf43 P?*"~S;?~!}ޡB)3olU1$B$Ml(!!w'gP-dW;P;ǘSjXnڮsV+hEWᄑ TK1y+F'CJCHkaDkcO1P4BjC!<>BZCrgmc^>GSd w}*͟!<]wc@.9@7ݷjFVkbjp%y(">=ƵSDS;ĸVJwb"P;4VJ}}>٭k×jFU[qLڽʏh|>pAt~[;-"~L3]<3".dZ+[rqJ_.?yC{GkXE 6nY;"~6Zlcq[k1ebqV7b g22s=`9aeJEoX]/=gb ?@lA=viXN"1c꾽!JGĹC0 `4;sQ817 %g[cPnXkoE-bn5Pr|AcEJ_X;GA 0o;1W׭b@EmZ;"C)6<{.#SCtmPo}]W!i>ֵ{֗اvV BǟNEďkЪsyd שkPW/" `!qi*?Ij]+k(inp}|]E_/c VuߞMJǟl`]`!JB;ǟ"2s+}jc\vP(]U;_0 uIbY;DI+hيTTĕR;DG̿յC,b.s>$-+yO櫏Һ3:0Bi ݴ?,$u&37Ne[y{0oPPԥwfcj XzTe!3\;GA7K+|y}$ _fno`8v.\;"2յ\R;"@\x3p!f䉙!PݰvBBIҒds1cdkf,ŵ,by:+̼99*yxBe*:k4vI jYM xfq\f޲vqmkP1g0 2fAmm{Bjovlf޴vխdu k3εCs_`LwV;̛0+k iv_ A!Թ/0]$-[i^ćW~CLNC/WI1m3N}v1m V;؉a6= 8f .^х\wي+cp̼}r̼-pxS=pra!M3kJ_`8n0͑kO0"wڝ&/C\x7I,SxMD\y}{у[S(EW2e栟3s;`9zL! sH=?wCL\{_+dY}2jXO<ҽ13`&3 v3#uVyef>vidt{1K#??NiIVuf^ Og=kDf>듓+pS8)3V;$2s+-kg$37V}}NQ=g}tZ_[Yf'3󎵃#3DS$yﵽv)l|<3Y;8کE7Cܨk`Q.N^Xk aẋ2JfFf IZ8angH[Vf|մ!vc ".\뎣y4DǷ?gj1 in݀6\Cf46LiCyd.3an " \}65 # &í6_f2iy.n_dҴ?K/cΫ#̏g 2] f3x]/ߠYQK,3WL 8ifnyMgifkf}[y;y MAGo W5jmF³)pkv7O DeymyxLAffOiְ8=V,d܂螻qG\-Mk/j)]<;"._8G2Ypxf^+.ȵ#`fn lpp pAz,Yf 2kNQ<{ѼQ˾.jϞ\fg=#RXhq1VicTUDX0H Sf4mf:g1FoGYPjͪzC1([G1ۯyRE KE,Z(۴?P*8;$c%sLi׈8/~Uhx[fY+<Ό4}GǠ%=خ9V#_}d{~]BIңY$=Gٙaf֩OQbʺgOIqM?.[qs:$̚/"I%;QY78x<+CVΎw](IZD9p[-̆$"VVR-4jm9jDf(!&^onVWu!9̬N6/.y0v&Iho{#"b^.PPC9,)g5JDD4^J=;R>A9mֽ@BI@#*^E^fe,W>F;)n |E$ȒJm+d)oEԍ#Z5S|`նogzkFJM``fc."Q'$5OܱM!j:5".5,9 Oײj܈I_afy")d$bERÛsK$5+%l,E3ڋv=LK55m[k5N TCdg1;ixԪp\#}P ׵hۄ_W%G1!Iײs4:meaUi߷NJ/ʃi҄j*x/,f6X;=UWgC%1 ʨXۼ;DŽ+#JC$wbfuUvQozPAe9j::"޷wX3.IO 6tl,UfgiV0Oeq`S5|?"޼wI?v:TvnIzM"sòCu*"ސ)ЍjZto.]ҝ6xq(hFI$|v~#c f] l瀏IjQ $Wst@D췬?98Ղ]io#ay&; P ʺ4ȳb֩8 ;GM4S\"⭯C;0Pҋrnp8߳C@ %IMY, 8u*"64lˁ3C JuFAuOVt5þwCL%r$ \ISb֩!v^v~Crk=t/6ڙr$۫ PtёxA: k!p;HUv.M>؜k.Q?DĖKFJeN); hhJl]hŌ`Q[+q؝ygb֍8);GMߓC6vpvH,&H+i 0.Ƌ;Jg?[%!OGN5t!p,_T_|CX8xcv1lCvlM9dh@4AMK9Xgq*(ұ^85#8TI_hL+$7޴:xwvIjUgۣ>;A .#p1-:6c9/}䟖0,KGrv46~6Z"b[a$ifJm!jX 8+"j\4A5A;:NҕG%lFc.Ŧ9%|0VDArkc[)Ϯ&J6Nk4U-t;`?``D488K]|`DIYD'JjOu}o,ޗ2Д6l8]\ "vyi% x#0٥ՙ#)"VQӂ%"b-`uPZRU4dFc NkX[fSC4IDl@9cW`'`!Gxuw9pv%P23{VJKvDll[}0 Xk{RASIzш^3@{Hr7刈?4T݊R̯ӯS?gS]\(V+&i? xyiCM('[y)+MP 4_2vk%i6yYDO ؘ5>&J!2-DP6.,ͯ^skΖ4]ҌDz6sq:19; 4ynbffkBTOk33o. "Vk;JͰ3eTS-Ir Ӳf033B)YDlNQj2Jff6\(;;2;Y?GZD88; <Ul\PJRu??G?Rheq "gu̲PBMj7d033BiH"Bq ptJz!;Y=I"b-4`Jv.4;Y&( XDNiض" fff\( HDlF)66SýnaffSo}[ݟ`ffƈՀ=MesTSD(/D}upMv33l.vM40Jfff\(-CDL&Vo'z233{.XRmO3s%9!̚`l X |[oK435HJ&1կ7/ڨ =C5Ek X8R lįWO6JN8;YSPdDl |$;ˈ8;;Y#꫒^aff$*$=|<;fffMӪB @%y9FQe033kJO,I̬ZY(y oaffT,Sp}rC5Uk $fffBSp ̬Z](jxOSA̚R<׉)nbff#Q(I:qCHJ9l!dd 4 4CҗCHJ |EGB @ ĬGbfg2s` ItCv\ L90f6FnD ~UHZ \ l+iIH2L#Y(X{ _[< |x=]YSۄq8TҍA̺(`f5lf(#;/OM1@ S\$YL,T IfT#]( "`$-d֡k?tف̖g tF,ˁm$}X}a:K$Ev(3NE4,i7Id/63k8/85"&elE.}3"b쿓u!"֋}z ҭ1-" 9qDDw13"brD[gl\n!0r/ۀ@p7p pØ J)gn͡ image/svg+xml gqrx-2.9/new_logo/SVG/horizontal white.svg000066400000000000000000000123461320142145500206330ustar00rootroot00000000000000 image/svg+xml gqrx-2.9/new_logo/SVG/icon color.svg000066400000000000000000000055321320142145500173670ustar00rootroot00000000000000 image/svg+xml gqrx-2.9/new_logo/SVG/icon white.svg000066400000000000000000000056161320142145500173740ustar00rootroot00000000000000 image/svg+xml gqrx-2.9/new_logo/SVG/vertical color.svg000066400000000000000000000130651320142145500202500ustar00rootroot00000000000000 image/svg+xml gqrx-2.9/new_logo/SVG/vertical white.svg000066400000000000000000000130631320142145500202500ustar00rootroot00000000000000 image/svg+xml gqrx-2.9/resources/000077500000000000000000000000001320142145500143545ustar00rootroot00000000000000gqrx-2.9/resources/icons.qrc000066400000000000000000000015651320142145500162050ustar00rootroot00000000000000 icons/audio-card.svg icons/bookmark-new.svg icons/clear.svg icons/clock.svg icons/close.svg icons/document.svg icons/flash.svg icons/folder.svg icons/fullscreen.svg icons/floppy.svg icons/gqrx.svg icons/help.svg icons/info.svg icons/play.svg icons/power-off.svg icons/record.svg icons/refresh.svg icons/settings.svg icons/signal.svg icons/tangeo-network-idle.svg icons/terminal.svg gqrx-2.9/resources/icons/000077500000000000000000000000001320142145500154675ustar00rootroot00000000000000gqrx-2.9/resources/icons/audio-card.svg000066400000000000000000001446111320142145500202270ustar00rootroot00000000000000 image/svg+xml Jakub Steiner http://jimmac.musichall.cz Audio Card hardware audio card soundcard gqrx-2.9/resources/icons/bookmark-new.svg000066400000000000000000000714621320142145500206160ustar00rootroot00000000000000 image/svg+xml New Bookmark bookmark remember favorite Andreas Nilsson Jakub Steiner create bookmark action gqrx-2.9/resources/icons/clear.svg000066400000000000000000000460611320142145500173050ustar00rootroot00000000000000 image/svg+xml Edit Clear Andreas Nilsson http://www.tango-project.org clear reset blank edit Jakub Steiner (although minimally ;) gqrx-2.9/resources/icons/clock.svg000066400000000000000000000404761320142145500173160ustar00rootroot00000000000000 image/svg+xml Jakub Steiner http://jimmac.musichall.cz New Appointment appointment new meeting rvsp gqrx-2.9/resources/icons/close.svg000066400000000000000000000274721320142145500173310ustar00rootroot00000000000000 image/svg+xml gqrx-2.9/resources/icons/document.svg000066400000000000000000000563541320142145500200430ustar00rootroot00000000000000 image/svg+xml Folder Drag Accept Jakub Steiner http://jimmac.musichall.cz/ folder directory storage drag accept gqrx-2.9/resources/icons/flash.svg000066400000000000000000002724061320142145500173200ustar00rootroot00000000000000 image/svg+xml Jakub Steiner http://jimmac.musichall.cz Flash Memory memory flash gqrx-2.9/resources/icons/floppy.svg000066400000000000000000000531511320142145500175260ustar00rootroot00000000000000 image/svg+xml Lapo Calamandrei Floppy 2006-04-14 store floppy media gqrx-2.9/resources/icons/folder.svg000066400000000000000000000345161320142145500174740ustar00rootroot00000000000000 image/svg+xml Folder Open Jakub Steiner http://jimmac.musichall.cz/ folder directory storage open active gqrx-2.9/resources/icons/fullscreen.svg000066400000000000000000000264321320142145500203610ustar00rootroot00000000000000 image/svg+xml View Fullscreen http://jimmac.musichall.cz Jakub Steiner window maximize fullscreen view gqrx-2.9/resources/icons/gqrx.icns000066400000000000000000022156531320142145500173440ustar00rootroot00000000000000icns 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-2.9/resources/icons/gqrx.ico000066400000000000000000001122761320142145500171550ustar00rootroot00000000000000`` (`   #)+,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,+)# &3COUVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUOC3& ,DYppYD+ ,KgfK+&DfTTTڊ򠠠TTTeC&3Z󦦦Y3 #Bo󦦦nA# 'M}XXXӦWWW}L')Q𦦦P)*SS**SS**SS**Rlqs"%     ##          ''           ) )          '&        #"     "%mrtR**Rkpr   !!#$$*6*6'( )!*!*!+!,","-#.0=1=$/%0%1&1&2&2'3'3(4(44B3B'3&2&2&1%1%0$/$.#.#./</;!,!+!*!* ))'&&%)5'4"!   lqsR*)Q!$ &!)!+#-#.$/%1&13B4B)5)6+7,8,8,9-:.;.</= ;L!0=/=9K9J-:,9,8,8+7*6)5(4'3&22@1?$.#-", * (% #&Q))Q  (",&1(4)6+7,8,99I :K0=0>1?1A3B3C4D5E5F6G$BU%CV8I 9J :K :L!;M!P">Q(H](G]"=O!0=/<.;-97G6F*6(5(2%0!* '  Q))P  )%0(2(4*6+6-8-99I ;K0>0?1@2A2B3C4D5E5F6G$AV%AW8J9J :K :L!;M!1?2@3A3B4B5C5D6E7G$BU$BV 9J :K!:L!;M!O'H\d|!N1?2?2A3B4C5D5E6F7G7H%DW%EX9J :K :L :M!:M!?S DX EY!?R!2@3A4B4C5D6E6F8J@P'Wm(WmAS;M9J:K:L;L;L ;M ;M ;M(]u%k=M=MjCW8I'K_&K^6E5Dt|@OMan"DS.=y1>(2!+ O('N~!&0.:3B5D5D7E;I!@P"FV*Sg+Ti$HZ$DU"AQ!?P"@P"AQ#AR$DT'M`(Zn.p.r)[q(Pd&GY%EX&FY&FY&FY'H[)L`*Pe*w"~$l#Vj~!j"v/Zm/Zm'K]%HX#BR!?P!?Oo L^\r!&d{\pBR2A.9&/!~N''N~"'1/;3B5D6D8F:J!@P"FW+Sh,Th$IY#EU"@Q!?P"@Q"@R"AS$CU&Oa'\s-|,(ax(Rf&GZ%DW%EX%EX&EX'G[)L`*Pe/\t%$L`%DW o&Nb.Ym.Xm&K^%FX#BS!?P!>Oo L] ]rg4C2A.:'0"~N''M} ",)2)4*6,7,9.:/;#GW#GX2?2@3A4B4B5C5D6F?OOb&~& UgCT;L9I9J9J:J:K:K:K)Od)Od:J9J9J9I8I8H8G7G7G6F'K^&K^5D5C4B4B3A2@2?1>0=0;"EU!DS,8+6)5)3(2!+ }M''M|"+(2*4+6+8-9.:/;"BR"CR1>2?2@3A4B4C5C8GBQUg%% YnEV=L9H9I:I:I:I:J:J(K^(K]:I:I9I9H9H8G8G7G7F6F%HY%GX6C4C4B3A2@2?1>1=0=/2@3@3A3B4C4D:HBSYn$$^sGX=N8H9I9I9I9I9I9J%EX%EX9I9I9I8H8H8G7G7F6E6E#BT#BS4D4C3B3A3@2@2?1=0</;;J :I,8*7*5)3'2!+ |M''M| "+(2(4*6,7,9.9/;:I2?3@3A3A3B4C9JDT]q$$axHY>N8G8H8H8H8H8H9H$BU$BU8H8H8H8G7G7F7F6E6E5D"AR!@R4C3B3A3A3@2?1>1=0;/;9H8G,8+6)5(3'1"*|M'&Kz "+(2)4*6+6,8.9.:;I;K1>2>2?3@3A3A3B;JFW`v$$g}K]?O7G7G7G8G8G8G8G#BT#BT8G7G7G7F7F6E6E6D5D5D!@R!@Q3B3A3A3@2?2>1>0=0</:9G8G+7+6)5)3(1"*zK&&Kz"*(1)3)5+7,8-9.9:I;J0=1>2>2?3@3A4B1>0=0</;/:8G7G,7*6)4)2'0")zK&&Kz"*(1)3*4*6,7-8.::I:I0<1>1?1?1@2A4C>MM` t$$!Rf@Q8I6E6F6F6F6F6F"AT"AT6F6F6E5E5E5D5D4C4C3B!?P ?P2B2A1@1?1?1>0=/;/;.:9H7G+7*5)4)2'0!)zK&&Kz"*'1(3)4*6,7,7.9;J2?2?2@6E@NRe $$!WjAS:I6D6E6E6E6E6E#CU#CU6E8H:H9F5D5C5C5B4B4B!@Q!@P2A2@2?2?2>1=0<0<.:.9:I9G+7)5)3'2'0!)zK&&Kz!*'1'2)4+5+7-7-8!@P!@P0<0<1=1>2?2?7E@PVi!$$"ZnDT;I5C5D5D5D6D6D%HY%GY;J>O@Q?N:I4B4B4B3B3B#EU#DT2@2?2?1>1=0<0</;/:-8 ?O >M+6*4(3'2&0 )zK&&Ky")'0'2(4*4+6,7-8"CT"EU/;0<0<1=1>2>8FBRZm"$$#]qEU;J4B4B5C5C5C5C%K]%M^?NGYSeI\>O7E3B3B3A2A$IZ#IZ2?2>1>1=0<0</;/:.9-9!BS!BR*5)4(3'1'/!) yK&&Ky#'/-93?4@5B6D9F >M!BS)Qd)Qd#EU"@P=L;JM;J;J:I:I ;J @P!CS)Qc(Qb CP-9'/"yK&&Ky"'/-92?4?4A5C9F >L!CR)Qd*Qd#DU"@Q O!>O"@R$EW%J\,Vl,Zr%Wk%i$#o!Th!GZ@R!?P#CT$HZ+Uh+Th#GX"BS =M;J:J:J9I;K!?O!CR)Qc(Qb BP;I7B3?2>,8'/"yK&&Kx  (&.'1)2*4+5,6-7"CS"DU/;0;0;0;0<2?;IK]q%%%%tM_>L5C3@3@3@3A3A%K\%QcIYcw$!Wj@Q9G2@2?1?#HX#HX1=1=0<0;0;/:/:-9-8,6 CQ BP*4(2'1'/%- ' xK&&Jw ")'0)2*4+7.8/;2= CS!DT5@4A5A1<0;2@=JOa %%%%!Qc>M5C2@2@3@3@3@#IX#O`Oby$$cwHZ:J7F8E8D"GY"GX6B4B4@3@2?1>1>1</;09!@O?M,6)5)2'0&- ( wJ&$Hu  $-+5-80:2=4@8D:GGXJZ>M?NLSf!%$$%!Vh>N6C2?2?2?2?2?"ET#L^Wj"$%SfBP?O?N?M!IZ!HZJ.9-7+5(4'0!) uH$$Hu#(21<6A:F>LBPGUK[WkZnVgWjGV7E1=4@>MXj"%$$%"XlAP7D1>1>1>1>2? DT!M_]s$$%#^tXlWjUhTf!Zp YmN^L_KZIZHVETCQAO>M=KETAQ5B3>1;,9)3#+ uH$$Hu,6APSe`vnz!"##M_;I2>4B@OZp$%"~#%$\pAR7F2>2?1?1?3A!EW!Sgg}%$#$#""""$#!" {vqllfzXjObIWBP:F.9 (uH$$Gt+6BPSd`unz!"## RdK4@7DHVk%%!e|!f}%%g|JZGWGWGWGWGWGW!Oa"bv"% ^pbv`u_s\pZoXl!`u^qTcRbO_M^JZIXGUET@O>MGWCR7C4>1;.8)3$+  tG$$Gt #*)1,5/70:2>6A8DHYcv#$Zn@N4@9FJZ}%$!cw!ez$%{fzgzg{g{g{g{fz!dz"h~$%uL]EUEUETDRAQANK]K[=K?LK>J*3&2&/%-#)$ sG$$Fr #")#,%-&/'0(1-7!JZ!_q $|JZ7DK:G:F;H?M DR'Rb'Rb CQ>K;F7C7C7B6B8C;I?L&M]&L\=K8D3=.9-9)3$,"rF##Fr"$,)3-8.90;1<5@:F BO&Ue%aw"$#^oFUJYdw$$"v'dx'^r"Sc!Q`M\K[K[L[L[ Sc#s$$$$"tScBQ;I9F9E;G>L CQ'Qa'Qa BP=J:E7B6B6A6A8B;H?L&M]&L[>J8C2<.8,8)2#+"rF##Fr " ("(#*$+%-%/)2FU!Sdh{%$Yk=JDRgz%%\l#Te"IY3=4>4?5?5?5?5?>KZk$%%%\lAM5?-7,5,5,5,5,4!BP!BP+3*3*3*2*2)1)1(1(1'0>K=K$,$+#)!( '! rF#"Do ! '"'")$+$,&.'/?NL[Ve$$]p@OHWx%$Xi N^ DR+5,5,6-7-7-70<:GO` %%!SdK+4+4+5+5+5+5.85AFTh~%%xJZ8D.:+5+4+4*4*4*39D9D)2)2)2(1(1(1'1'0'/&.4?4>$+#*"(!' &! oD""Do ! &!'"($*#+$,%-7C?MLZ $IYP`"%M]BR:G*3*3*3*4*4*4*52==KYk$%^rBP3?,7*3*3*3*3)3)25A5A)1(1(1(1'1'0'0&/&/&-0=0;#+#)"(!' &! oD""Do ! '!'"(#)$*$+%,4A=IGU$ O^Uf#%{HVAN9E*2*2*2*3+3+3+30;:EP`"$XhL7D)2)2*2*2*3*3*3-75@FU s"N^8C.9*2*2)2)2)2)1)15A4A(0(0(0'0'0'/&/&-%-$,0;/;$*#)"(!' '! nC"!Bl " '!'"(#)$*#+$+2=:D=Jbw$$Ykbu%%Zl>K;I5B(1)1)1)1)1)2)2*31;>JaukDT3>,5)1)1)1(1(1(0(04@4@'0'0'0'/&/&.&.%-%,$,0;/;$*#)"(!' '" lB!!Bl " '!'"(#)$*$+$,5?:F8DSe$%_pu%"Vf9F3=$*#)"(!' '" lB!!Bl " '!'")#*$*$+$,9C>I5?Oa"%r%!P`7C@L:F'0'0'0'0'0'0(0(0*2/:FSGU2;+4'0'0'0'0'0'/'/&/9F9E&.&.%-%-#*")!' '" lB!!Ak " '"(#)$*#+$+$,=IBN3=L[% %LZ6@DO?J'/'/'/(/(/(/(/(/(/)0CODP*1(/(/(/'/'/'/'.'.'.>J>I&-&-&-&-$*#)"( '" kA!!Ak#$,)2-7.8/90:2=8C=H%K[%N^HUVh!%!#% VeJW%N_%K\>K:E6@3>3>3>3>6@:E?K&L]&L]?K:E6@3>3>3>2>5@9D>J%K\%K\>J9D5@2=2=2=2=5?8D=I%K[%J[;H7A1<.8-7)2$,#kA! @j#$,)2-7.8/90:3=8C=H%K[$N]GURc~%$$%rQaGU%M^%K\>J9D5@2=2=2=2=5@9D>J%K\%K\>J9D5@2=2=2=2=5@9D>J%K\%K\>J9D5@2=2=2=2=5@9D=I%K[%J[Jav%%%%Zl=H/9?K>J&.&.&.&.&.&.&.&.&.&.>J>J&.&.&.&.&.&.&.&.&.&.>J>J&.&.&.&.$*#)"(!'" i@  ?h "!'"(#)$*$+%,%-9D9D-68DVf%&&$Vf9E.79D9D&.&.&.&.&.&.&.&.&.&.9D9D&.&.&.&.&.&.&.&.&.&.9D9D&.&.&.&.$*#)"(!'" h? ?h "!'"(#)$*$+%,%-5@5@-57ASc#&&"R`7C-76A6A'/'/'/'/'/'/'/'/'/'/6A6A'/'/'/'/'/'/'/'/'/'/6A6A'/'/'/'/'/'/'.&.&.&.4?4>$+$*#)"(!'" h??h "!'"(#)#+$,%,%-2=2=,35@N]!&& M[7@,53>3>'/'/'/'/'/'/'/'/'/'/3>3>'/'/'/'/'/'/'/'/'/'/3>3>'/'/'/'/'/'/'/'.&.&.1<1<$,#+#)"(!'" h??h "!'"(#)#+$,%-%-2=2=,23=IW&&HW4>,33>3>'/'/'/'/'/'/'/'/'/'/3>3>'/'/'/'/'/'/'/'/'/'/3>3>'/'/'/'/jxxxww||h~#+#)"(!'" h?>f "!'"(#)#+$,%-&-2=2=*11:CQs&&lDP2;*13>3>'/'/'/'/'/'/'/'/'/'/3>3>'/'/'/'/'/'/'/'/'/'/3>3>'/'/'/'/jxxxww}|h~#+#)"(!'" f>>f "!'"(#)$*$+%,%-2=2=(0/9@L_p&&]m?K0:(03>3>'/'/'/'/'/'/'/'/'/'/3>3>'/'/'/'/'/'/'/'/'/'/3>3>'/'/'/'/'/'/'/'/&.&.1<1<$+$*#)"(!'" f>>f " '"(#)$*$+%,%-5@5@'/.89EXi%$Wg:E.8'/6A6A'/'/'/'/'/'/'/'/'/'/6A6A'/'/'/'/'/'/'/'/'/'/6A6A'/'/'/'/'/'/'/'/&.&.4?4>$+$*#)"( '" f>>e ! '!'")$*$+$,%-9D9D&.-67BRa"!R`7B-6'/:E:E'/'/'/'/'/'/'/'/'/'/:E:E'/'/'/'/'/'/'/'/'/'/:E:E'/'/'/'/jxxwwxi}$*")!' '! e><d ! &!'"(#)$*$+%,=I>J&.*36@L[KZ5@,4'/?K?K'/'/'/'/'/'/'/'/'/'/?K?K'/'/'/'/'/'/'/'/'/'/?K?K'/'/'/'/jxwwwwh}#)"(!' &! d<;c!"+)0+6,7-7/92=7BIJ9D5@2=2=2=2=5?8D=H%J[%IZJJYi|h|JY=K;F>J&K\&K\>J9D5@2=2=2=2=5@9D>J&K\&K\>J9D5@2=2=2=2=5@9D>J&K\&K\>J9D5@2=2=2<1<4?9C -20 dB gqrx-2.9/resources/icons/help.svg000066400000000000000000000317131320142145500171450ustar00rootroot00000000000000 image/svg+xml Help Browser 2005-11-06 Tuomas Kuosmanen help browser documentation docs man info Jakub Steiner, Andreas Nilsson http://tigert.com gqrx-2.9/resources/icons/info.svg000066400000000000000000001113151320142145500171450ustar00rootroot00000000000000 image/svg+xml Rodney Dawes Jakub Steiner, Garrett LeSage gqrx-2.9/resources/icons/play.svg000066400000000000000000000233641320142145500171650ustar00rootroot00000000000000 image/svg+xml Lapo Calamandrei Play play playback start begin gqrx-2.9/resources/icons/power-off.svg000066400000000000000000000235661320142145500201300ustar00rootroot00000000000000 image/svg+xml Shutdown Jakub Steiner http://jimmac.musichall.cz lock key secure gqrx-2.9/resources/icons/radio.svg000066400000000000000000013747621320142145500173320ustar00rootroot00000000000000 image/svg+xml gqrx-2.9/resources/icons/record.svg000066400000000000000000000244011320142145500174670ustar00rootroot00000000000000 image/svg+xml Lapo Calamandrei Record record media Jakub Steiner gqrx-2.9/resources/icons/refresh.svg000066400000000000000000000423101320142145500176460ustar00rootroot00000000000000 image/svg+xml Jakub Steiner http://jimmac.musichall.cz View Refresh reload refresh view Ricardo 'Rick' González gqrx-2.9/resources/icons/settings.svg000066400000000000000000000650041320142145500200550ustar00rootroot00000000000000 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-2.9/resources/icons/signal.svg000066400000000000000000000414471320142145500174770ustar00rootroot00000000000000 image/svg+xml System Monitor 2005-10-10 Andreas Nilsson system monitor performance Jakub Steiner gqrx-2.9/resources/icons/tangeo-network-idle.svg000066400000000000000000002131651320142145500220770ustar00rootroot00000000000000 image/svg+xml Computer 2005-03-08 Jakub Steiner workstation computer node client http://jimmac.musichall.cz/ gqrx-2.9/resources/icons/terminal.svg000066400000000000000000000434151320142145500200320ustar00rootroot00000000000000 image/svg+xml Terminal 2005-10-15 Andreas Nilsson terminal emulator term command line Jakub Steiner gqrx-2.9/resources/news.txt000066400000000000000000000253031320142145500160740ustar00rootroot00000000000000 2.9: Released November 11, 2017 NEW: Airspy HF+ support. FIXED: Always save TCP and UDP host settings. FIXED: Application hangs when pulseaudio server is not running. 2.8: Released September 16, 2017 NEW: Remote control command to set LNB LO (LNB_LO). FIXED: Inactive Start DSP and Config buttons in toolbar. FIXED: Frequency controller digit not redrawn after a resize. FIXED: Set default mode to AM instead of none. IMPROVED: Detection of gr-osmosdr when installed in custom directory. IMPROVED: Remote frequency handling. 2.7: Released August 19, 2017 NEW: Airspy HF+ integration. NEW: PlutoSDR integration. NEW: Restore filter low cut and high cut between sessions. NEW: Restore FM parameters between sessions. NEW: Restore remote control state between sessions. NEW: Support for passband when setting mode through remote. NEW: Widget to enter receiver frequency. NEW: Button to reset squelch to its default (-150 dB) value. FIXED: Keep waterfall zoom level and zoom slider synchronised. FIXED: RDS status is not kept while jumping through bookmark. FIXED: .conf files are deleted when changes are saved. FIXED: Remote control not working if $http_proxy is set. FIXED: Audio rec does not work after trying to play deleted file. FIXED: QtSvg dependency. IMPROVED: Tuning through the remote control interface. IMPROVED: Increase narrow band receiver rate to 96 ksps (for APT). IMPROVED: LimeSDR integration. IMPROVED: Voiceover interface. IMPROVED: Input decimator performance. IMPROVED: Increased frequency limit to 999 GHz (500 GHz LO). IMPROVED: Hamradio style frequency display. IMPROVED: More short waterfall time spans. 2.6.1: Released February 16, 2017 IMPROVED: Option for reverting the behavior of the frequency controller. 2.6: Released October 3, 2016 NEW: 1-2-5 scaling on FFT axis. NEW: Audio waterfall. NEW: Remember AGC settings between sessions. NEW: Right-click on FFT resets frequency zoom. NEW: Separate dB ranges for pandapter and waterfall. NEW: Raw I/Q mode. NEW: Portaudio support. NEW: Command line option to set Qt style (fusion, windows, ...) FIXED: Stuttering audio with Pulseaudio backend. FIXED: Use system font on FFT plot (too small font on high res displays). FIXED: Broken FUNcube Dongle Pro+ support on Mac OS X 10.11.4. FIXED: Correct display of negative offsets between -1 and 0 kHz. FIXED: Reset frequency digits below the one that is being changed. FIXED: LNB LO could not be set from I/O configuration dialog. FIXED: Update squelch level when switching between demodulators. FIXED: Set correct filter range when loading bookmark. FIXED: White area on waterfall. FIXED: RFSpace Cloud-IQ support on Mac OS X and in PPA IMPROVED: Input decimator performance. IMPROVED: SDRPlay integration. IMPROVED: Only probe for devices when the program is started. IMPROVED: 1-2-5 scaling on FFT axis. IMPROVED: Allow user to enter ALSA device name. IMPROVED: Set default audio FFT range to -70...0 dB. IMPROVED: Restore audio FFT dB scaling between sessions. 2.5.3: Released February 3, 2016. NEW: Set and read squelch via remote control socket. FIXED: Toolbar appearance on Mac OS X. IMPROVED: Reworked CW reception so that channel is now centered on carrier. IMPROVED: Shortcuts to view/hide some of the dock widgets. IMPROVED: Respond to \dump_state remote command (needed by some clients). IMPROVED: Read remote frequency as double for rigctl compatibility. 2.5.2: Released January 21, 2016. NEW: Support for Red Pitaya (RX only). NEW: Build with GNU Radio 3.7.9. IMPROVED: Increased spacing between frequency divisions to prevent overlap. IMPROVED: Reduced minimum window size requirement to around 800x600 pixels. 2.5 Released January 7, 2016 NEW: Read out signal time and on the waterfall (disabled on OS X). NEW: Save waterfall to graphics file. NEW: Allow user settable waterfall time span (up to 24 hours). NEW: Cmake option to build using gr-audio backend (Andrea Merello). FIXED: FM stereo (revert to pre-2.4 behavior). FIXED: Mode selector not updated when selecting a WFM OIRT bookmark. FIXED: Show correct filter preset when filter LO/HI corresponds to one. FIXED: Prevent RDS panel from growing by making it scrollable. FIXED: Use correct audio backend on OS X (app bundle only). IMPROVED: Button layouts (mostly on Mac OS X). 2.4 Released December 14, 2015 NEW: Bookmarks. NEW: FM RDS decoder using gr-rds. NEW: Frequency tooltips on the FFT area (disabled on Mac). NEW: Filter shape selector (soft, normal, sharp). NEW: Slider to aid zooming on the frequency and dB axes. NEW: RFSpace Cloud-IQ support. NEW: OIRT stereo decoder (Daniil Cherednik). NEW: Input decimator. FIXED: Prevent crash due to device errors while starting the application. FIXED: Use hardware frequency in IQ file names. FIXED: FFT averaging (it can now be disabled). FIXED: "Could not resolve pattern10600" error messages in the terminal. FIXED: Antenna selection error with USRP B210 (Ethan Trewhitt). FIXED: LNB LO value not updated in I/O config dialog. FIXED: Initial gain of rtlsdr devices is no longer 0 dB. FIXED: DSP freeze when setting FM de-emphasis time constant to 0. FIXED: Use correct main category in desktop entry file. IMPROVED: Support up to 1M point FFT. IMPROVED: Fractional PPM correction. IMPROVED: AGC peformance. IMPROVED: FFT performance. IMPROVED: Right click on frequency digit to clear digits. IMPROVED: Robustness against malformed remote control commands. 2.3.2 Released November 28, 2014 FIXED: Pandapter and waterfall widths are limited to 4096 pixels. IMPROVED: Add 2.5 Msps preset for Airspy. 2.3.1 Released August 8, 2014 FIXED: Use correct categories in .desktop file. 2.3.0 Released August 5, 2014 NEW: Support for setting analog bandwidth. 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: Airspy support. NEW: Peak detection on the FFT plot. NEW: Max hold on the FFT plot. NEW: Audio streaming over UDP socket. NEW: Remote control through TCP socket. NEW: Set squelch level from current signal/noise level. NEW: Command line option to list existing configurations. NEW: I/Q recording and playback. 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. FIXED: Ensure DSP is stopped when we exit. FIXED: Freeze when switching modes after audio recording. FIXED: Toggling "ignore limits" changes frequency. FIXED: Include gqrx.dekstop file. FIXED: Rename scope.svg to gqrx.svg to avoid confusion. 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. IMPROVED: Make release news available through the Help menu. 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-2.9/resources/remote-control.txt000066400000000000000000000024051320142145500200670ustar00rootroot00000000000000Remote control protocol. Supported commands: f Get frequency [Hz] F Set frequency [Hz] m Get demodulator mode and passband M [passband] Set demodulator mode and passband [Hz] Passing a '?' as the first argument instead of 'mode' will return a space separated list of radio backend supported modes. l STRENGTH Get signal strength [dBFS] l SQL Get squelch threshold [dBFS] L SQL Set squelch threshold to [dBFS] u RECORD Get status of audio recorder U RECORD Set status of audio recorder to q|Q Close connection AOS Acquisition of signal (AOS) event, start audio recording LOS Loss of signal (LOS) event, stop audio recording LNB_LO [frequency] If frequency [Hz] is specified set the LNB LO frequency used for display. Otherwise print the current LNB LO frequency [Hz]. \dump_state Dump state (only usable for hamlib compatibility) v Get 'VFO' (only usable for hamlib compatibility) V Set 'VFO' (only usable for hamlib compatibility) s Get 'Split' mode (only usable for hamlib compatibility) S Set 'Split' mode (only usable for hamlib compatibility) _ Get version Reply: RPRT 0 Command successful RPRT 1 Command failed gqrx-2.9/resources/textfiles.qrc000066400000000000000000000002101320142145500170630ustar00rootroot00000000000000 news.txt remote-control.txt gqrx-2.9/src/000077500000000000000000000000001320142145500131315ustar00rootroot00000000000000gqrx-2.9/src/CMakeLists.txt000066400000000000000000000057631320142145500157040ustar00rootroot00000000000000####################################################################################################################### # Add the source subdirectories add_subdirectory(applications) add_subdirectory(dsp) add_subdirectory(interfaces) add_subdirectory(qtgui) add_subdirectory(receivers) if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") add_subdirectory(osxaudio) add_definitions(-DGQRX_OS_MACX) set(CMAKE_OSX_DEPLOYMENT_TARGET 10.9) elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux") if(${LINUX_AUDIO_BACKEND} MATCHES "Pulseaudio") add_subdirectory(pulseaudio) elseif(${LINUX_AUDIO_BACKEND} MATCHES "Portaudio") add_subdirectory(portaudio) endif() elseif(${CMAKE_SYSTEM_NAME} MATCHES "Windows") endif() ####################################################################################################################### # bring in the global properties get_property(${PROJECT_NAME}_SOURCE GLOBAL PROPERTY SRCS_LIST) get_property(${PROJECT_NAME}_UI_SOURCE GLOBAL PROPERTY UI_SRCS_LIST) ####################################################################################################################### # Process the UI files QT5_WRAP_UI(UIS_HDRS ${${PROJECT_NAME}_UI_SOURCE} ) ####################################################################################################################### # Process the resources QT5_ADD_RESOURCES(RESOURCES_LIST ../resources/icons.qrc ../resources/textfiles.qrc ) ######################################################################## # Resource file - adds an icon to GQRX executable if (WIN32) set(ICON_SOURCE ${CMAKE_SOURCE_DIR}/resources/icons/gqrx.ico) set(RES_FILES "${CMAKE_CURRENT_BINARY_DIR}/gqrx.rc") file(WRITE "${RES_FILES}" "id ICON \"${ICON_SOURCE}\"") set(CMAKE_RC_COMPILER_INIT windres) enable_language(RC) set(CMAKE_RC_COMPILE_OBJECT " -O coff -i -o ") list(APPEND RESOURCES_LIST ${RES_FILES}) endif(WIN32) ####################################################################################################################### # Build the program add_executable(${PROJECT_NAME} ${${PROJECT_NAME}_SOURCE} ${UIS_HDRS} ${RESOURCES_LIST}) qt5_use_modules(${PROJECT_NAME} Core Network Widgets Svg) set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) # The pulse libraries are only needed on Linux. On other platforms they will not be found, so having them here is fine. target_link_libraries(${PROJECT_NAME} ${Boost_LIBRARIES} ${GNURADIO_ALL_LIBRARIES} ${GNURADIO_OSMOSDR_LIBRARIES} ${PULSEAUDIO_LIBRARY} ${PULSE-SIMPLE} ${PORTAUDIO_LIBRARIES} ) #build a win32 app, not a console app if (WIN32) set(CMAKE_EXE_LINKER_FLAGS "/entry:mainCRTStartup ${CMAKE_EXE_LINKER_FLAGS}") set_target_properties(${PROJECT_NAME} PROPERTIES WIN32_EXECUTABLE ON) endif (WIN32) set(INSTALL_DEFAULT_BINDIR "bin" CACHE STRING "Appended to CMAKE_INSTALL_PREFIX") install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION ${INSTALL_DEFAULT_BINDIR}) gqrx-2.9/src/applications/000077500000000000000000000000001320142145500156175ustar00rootroot00000000000000gqrx-2.9/src/applications/CMakeLists.txt000066400000000000000000000012341320142145500203570ustar00rootroot00000000000000####################################################################################################################### # Add the source files to SRCS_LIST add_source_files(SRCS_LIST gqrx/gqrx.h gqrx/main.cpp gqrx/mainwindow.cpp gqrx/mainwindow.h gqrx/receiver.cpp gqrx/receiver.h gqrx/remote_control_settings.cpp gqrx/remote_control_settings.h gqrx/remote_control.cpp gqrx/remote_control.h gqrx/file_resources.cpp ) ####################################################################################################################### # Add the UI files to UI_SRCS_LIST add_source_files(UI_SRCS_LIST gqrx/mainwindow.ui gqrx/remote_control_settings.ui )gqrx-2.9/src/applications/gqrx/000077500000000000000000000000001320142145500166005ustar00rootroot00000000000000gqrx-2.9/src/applications/gqrx/file_resources.cpp000066400000000000000000000044721320142145500223240ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright (c) 2016 Josh Blum * * 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 "applications/gqrx/receiver.h" #include #include #include #include std::string receiver::get_random_file(void) { static std::string path; if (path.empty()) { path = "/dev/random"; QFileInfo checkFile(QString::fromStdString(path)); if (!checkFile.exists()) { //static temp file persists until process end static QTemporaryFile temp_file; temp_file.open(); path = temp_file.fileName().toStdString(); { QDataStream stream(&temp_file); for (size_t i = 0; i < 1024*8; i++) stream << qint8(rand()); } std::cout << "Created random file " << path << std::endl; } } return path; } std::string receiver::get_null_file(void) { static std::string path; if (path.empty()) { path = "/dev/null"; QFileInfo checkFile(QString::fromStdString(path)); if (!checkFile.exists()) { //static temp file persists until process end static QTemporaryFile temp_file; temp_file.open(); path = temp_file.fileName().toStdString(); { QDataStream stream(&temp_file); for (size_t i = 0; i < 1024*8; i++) stream << qint8(0); } std::cout << "Created null file " << path << std::endl; } } return path; } gqrx-2.9/src/applications/gqrx/gqrx.h000066400000000000000000000021061320142145500177310ustar00rootroot00000000000000/* -*- 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-2.9/src/applications/gqrx/main.cpp000066400000000000000000000156421320142145500202400ustar00rootroot00000000000000/* -*- 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 #ifdef WITH_PORTAUDIO #include #endif #ifdef WITH_PULSEAUDIO #include #include #endif #include "mainwindow.h" #include "gqrx.h" #include #include namespace po = boost::program_options; static void reset_conf(const QString &file_name); static void list_conf(void); int main(int argc, char *argv[]) { QString cfg_file; std::string conf; std::string style; bool clierr = false; bool edit_conf = false; int return_code; QApplication app(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"; // setup the program options po::options_description desc("Command line options"); desc.add_options() ("help,h", "This help message") ("style,s", po::value(&style), "Use the give style (fusion, windows)") ("list,l", "List existing configurations") ("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; std::cout << desc << std::endl; return 1; } if (vm.count("style")) QApplication::setStyle(QString::fromStdString(style)); if (vm.count("list")) { list_conf(); return 0; } // check whether audio backend is functional #ifdef WITH_PORTAUDIO PaError err = Pa_Initialize(); if (err != paNoError) { QString message = QString("Portaudio error: %1").arg(Pa_GetErrorText(err)); qCritical() << message; QMessageBox::critical(0, "Audio Error", message, QMessageBox::Abort, QMessageBox::NoButton); return 1; } #endif #ifdef WITH_PULSEAUDIO int error = 0; pa_simple *test_sink; pa_sample_spec ss; ss.format = PA_SAMPLE_FLOAT32LE; ss.rate = 48000; ss.channels = 2; test_sink = pa_simple_new(NULL, "Gqrx Test", PA_STREAM_PLAYBACK, NULL, "Test stream", &ss, NULL, NULL, &error); if (!test_sink) { QString message = QString("Pulseaudio error: %1").arg(pa_strerror(error)); qCritical() << message; QMessageBox::critical(0, "Audio Error", message, QMessageBox::Abort, QMessageBox::NoButton); return 1; } pa_simple_free(test_sink); #endif 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_code = app.exec(); } else { return_code = 1; } #ifdef WITH_PORTAUDIO Pa_Terminate(); #endif return return_code; } /** 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() << "Can not delete" << cfg_file << "- file does not exist!"; } } /** List available configurations. */ static void list_conf(void) { QString conf_path; QByteArray xdg_dir = qgetenv("XDG_CONFIG_HOME"); if (xdg_dir.isEmpty()) conf_path = QString("%1/.config/gqrx/").arg(QDir::homePath()); else conf_path = QString("%1/gqrx/").arg(xdg_dir.data()); QDir conf_dir = QDir(conf_path, "*.conf", QDir::Name, QDir::Files); QStringList conf_files = conf_dir.entryList(QStringList("*.conf")); std::cout << " Existing configuration files:" << std::endl; if (conf_files.isEmpty()) { std::cout << " *** NONE ***" << std::endl; } else { for (int i = 0; i < conf_files.count(); i++) { std::cout << " "; std::cout << conf_files.at(i).toLocal8Bit().constData() << std::endl; } } } gqrx-2.9/src/applications/gqrx/mainwindow.cpp000066400000000000000000002122341320142145500214640ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2014 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 #include #include #include #include #include #include #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" #include "qtgui/bookmarkstaglist.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_have_audio(true), dec_afsk1200(0) { ui->setupUi(this); Bookmarks::create(); /* Initialise default configuration directory */ QByteArray xdg_dir = qgetenv("XDG_CONFIG_HOME"); if (xdg_dir.isEmpty()) { // Qt takes care of conversion to native separators m_cfg_dir = QString("%1/.config/gqrx").arg(QDir::homePath()); } else { m_cfg_dir = QString("%1/gqrx").arg(xdg_dir.data()); } setWindowTitle(QString("Gqrx %1").arg(VERSION)); /* frequency control widget */ ui->freqCtrl->setup(0, 0, 9999e6, 1, FCTL_UNIT_NONE); ui->freqCtrl->setFrequency(144500000); d_filter_shape = receiver::FILTER_SHAPE_NORMAL; /* create receiver object */ rx = new receiver("", "", 1); 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 float[MAX_FFT_SIZE]; d_pwrFftData = new float[MAX_FFT_SIZE](); d_iirFftData = new float[MAX_FFT_SIZE]; for (int i = 0; i < MAX_FFT_SIZE; i++) d_iirFftData[i] = -140.0; // dBFS /* timer for data decoders */ dec_timer = new QTimer(this); connect(dec_timer, SIGNAL(timeout()), this, SLOT(decoderTimeout())); // create I/Q tool widget iq_tool = new CIqTool(this); /* create dock widgets */ uiDockRxOpt = new DockRxOpt(); uiDockRDS = new DockRDS(); uiDockAudio = new DockAudio(); uiDockInputCtl = new DockInputCtl(); uiDockFft = new DockFft(); Bookmarks::Get().setConfigDir(m_cfg_dir); uiDockBookmarks = new DockBookmarks(this); // setup some toggle view shortcuts uiDockInputCtl->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_J)); uiDockRxOpt->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_R)); uiDockFft->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_F)); uiDockAudio->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_A)); uiDockBookmarks->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_B)); ui->mainToolBar->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_T)); setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea); setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea); setCorner(Qt::BottomLeftCorner, Qt::BottomDockWidgetArea); setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea); /* 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); addDockWidget(Qt::RightDockWidgetArea, uiDockFft); tabifyDockWidget(uiDockInputCtl, uiDockRxOpt); tabifyDockWidget(uiDockRxOpt, uiDockFft); uiDockRxOpt->raise(); addDockWidget(Qt::RightDockWidgetArea, uiDockAudio); addDockWidget(Qt::RightDockWidgetArea, uiDockRDS); tabifyDockWidget(uiDockAudio, uiDockRDS); uiDockAudio->raise(); addDockWidget(Qt::BottomDockWidgetArea, uiDockBookmarks); /* hide docks that we don't want to show initially */ uiDockBookmarks->hide(); uiDockRDS->hide(); /* 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(uiDockRDS->toggleViewAction()); ui->menu_View->addAction(uiDockAudio->toggleViewAction()); ui->menu_View->addAction(uiDockFft->toggleViewAction()); ui->menu_View->addAction(uiDockBookmarks->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(ui->freqCtrl, SIGNAL(newFrequency(qint64)), uiDockAudio, SLOT(setRxFrequency(qint64))); connect(ui->freqCtrl, SIGNAL(newFrequency(qint64)), uiDockRxOpt, SLOT(setRxFreq(qint64))); connect(uiDockInputCtl, SIGNAL(lnbLoChanged(double)), this, SLOT(setLnbLo(double))); connect(uiDockInputCtl, SIGNAL(lnbLoChanged(double)), remote, 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(double)), this, SLOT(setFreqCorr(double))); 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(uiDockInputCtl, SIGNAL(freqCtrlResetChanged(bool)), this, SLOT(setFreqCtrlReset(bool))); connect(uiDockRxOpt, SIGNAL(rxFreqChanged(qint64)), ui->freqCtrl, SLOT(setFrequency(qint64))); 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(demodSelected(int)), remote, SLOT(setMode(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(cwOffsetChanged(int)), this, SLOT(setCwOffset(int))); 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(audioRecStarted(QString)), remote, SLOT(startAudioRecorder(QString))); connect(uiDockAudio, SIGNAL(audioRecStopped()), this, SLOT(stopAudioRec())); connect(uiDockAudio, SIGNAL(audioRecStopped()), remote, SLOT(stopAudioRecorder())); 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(wfSpanChanged(quint64)), this, SLOT(setWfTimeSpan(quint64))); connect(uiDockFft, SIGNAL(fftSplitChanged(int)), this, SLOT(setIqFftSplit(int))); connect(uiDockFft, SIGNAL(fftAvgChanged(float)), this, SLOT(setIqFftAvg(float))); connect(uiDockFft, SIGNAL(fftZoomChanged(float)), ui->plotter, SLOT(zoomOnXAxis(float))); 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(pandapterRangeChanged(float,float)), ui->plotter, SLOT(setPandapterRange(float,float))); connect(uiDockFft, SIGNAL(waterfallRangeChanged(float,float)), ui->plotter, SLOT(setWaterfallRange(float,float))); connect(ui->plotter, SIGNAL(pandapterRangeChanged(float,float)), uiDockFft, SLOT(setPandapterRange(float,float))); connect(ui->plotter, SIGNAL(newZoomLevel(float)), uiDockFft, SLOT(setZoomLevel(float))); 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(uiDockRDS, SIGNAL(rdsDecoderToggled(bool)), this, SLOT(setRdsDecoder(bool))); // Bookmarks connect(uiDockBookmarks, SIGNAL(newBookmarkActivated(qint64, QString, int)), this, SLOT(onBookmarkActivated(qint64, QString, int))); connect(uiDockBookmarks->actionAddBookmark, SIGNAL(triggered()), this, SLOT(on_actionAddBookmark_triggered())); // I/Q playback connect(iq_tool, SIGNAL(startRecording(QString)), this, SLOT(startIqRecording(QString))); connect(iq_tool, SIGNAL(stopRecording()), this, SLOT(stopIqRecording())); connect(iq_tool, SIGNAL(startPlayback(QString,float)), this, SLOT(startIqPlayback(QString,float))); connect(iq_tool, SIGNAL(stopPlayback()), this, SLOT(stopIqPlayback())); connect(iq_tool, SIGNAL(seek(qint64)), this,SLOT(seekIqFile(qint64))); // remote control connect(remote, SIGNAL(newFilterOffset(qint64)), this, SLOT(setFilterOffset(qint64))); connect(remote, SIGNAL(newFilterOffset(qint64)), uiDockRxOpt, SLOT(setFilterOffset(qint64))); connect(remote, SIGNAL(newFrequency(qint64)), ui->freqCtrl, SLOT(setFrequency(qint64))); connect(remote, SIGNAL(newLnbLo(double)), uiDockInputCtl, SLOT(setLnbLo(double))); connect(remote, SIGNAL(newLnbLo(double)), this, SLOT(setLnbLo(double))); connect(remote, SIGNAL(newMode(int)), this, SLOT(selectDemod(int))); connect(remote, SIGNAL(newMode(int)), uiDockRxOpt, SLOT(setCurrentDemod(int))); connect(remote, SIGNAL(newSquelchLevel(double)), this, SLOT(setSqlLevel(double))); connect(remote, SIGNAL(newSquelchLevel(double)), uiDockRxOpt, SLOT(setSquelchLevel(double))); connect(uiDockRxOpt, SIGNAL(sqlLevelChanged(double)), remote, SLOT(setSquelchLevel(double))); connect(remote, SIGNAL(startAudioRecorderEvent()), uiDockAudio, SLOT(startAudioRecorder())); connect(remote, SIGNAL(stopAudioRecorderEvent()), uiDockAudio, SLOT(stopAudioRecorder())); connect(ui->plotter, SIGNAL(newFilterFreq(int, int)), remote, SLOT(setPassband(int, int))); connect(remote, SIGNAL(newPassband(int)), this, SLOT(setPassband(int))); rds_timer = new QTimer(this); connect(rds_timer, SIGNAL(timeout()), this, SLOT(rdsTimeout())); // enable frequency tooltips on FFT plot #ifdef Q_OS_MAC ui->plotter->setTooltipsEnabled(false); #else ui->plotter->setTooltipsEnabled(true); #endif // Create list of input devices. This must be done before the configuration is // restored because device probing might change the device configuration CIoConfig::getDeviceList(devList); // restore last session if (!loadConfig(cfgfile, true, 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; } } qsvg_dummy = new QSvgWidget(); } MainWindow::~MainWindow() { on_actionDSP_triggered(false); /* 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 iq_tool; delete ui; delete uiDockRxOpt; delete uiDockAudio; delete uiDockBookmarks; delete uiDockFft; delete uiDockInputCtl; delete uiDockRDS; delete rx; delete remote; delete [] d_fftData; delete [] d_realFftData; delete [] d_iirFftData; delete [] d_pwrFftData; delete qsvg_dummy; } /** * 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 restore_mainwindow) { double actual_rate; qint64 int64_val; int int_val; bool bool_val; bool conf_ok = false; bool conv_ok; 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; // manual reconf (FIXME: check status) conv_ok = false; // hide toolbar bool_val = m_settings->value("gui/hide_toolbar", false).toBool(); if (bool_val) ui->mainToolBar->hide(); // main window settings if (restore_mainwindow) { 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. if (indev.contains("rtl", Qt::CaseInsensitive) && !m_settings->contains("input/gains")) { /* rtlsdr gain is 0 by default making users think their device is * deaf. Therefore, we don't read gain from the device, but initialize * it to max_gain. */ updateGainStages(false); } else updateGainStages(true); } QString outdev = m_settings->value("output/device", "").toString(); rx->set_output_device(outdev.toStdString()); int_val = m_settings->value("input/sample_rate", 0).toInt(&conv_ok); if (conv_ok && (int_val > 0)) { actual_rate = rx->set_input_rate(int_val); if (actual_rate == 0) { // There is an error with the device (perhaps not attached) // Warn user and use 100 ksps (rate used by gr-osmocom null_source) QMessageBox *dialog = new QMessageBox(QMessageBox::Warning, tr("Device Error"), tr("There was an error configuring the input device.\n" "Please make sure that a supported device is atached " "to the computer and restart gqrx."), QMessageBox::Ok); dialog->setModal(true); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); actual_rate = int_val; } qDebug() << "Requested sample rate:" << int_val; qDebug() << "Actual sample rate :" << QString("%1").arg(actual_rate, 0, 'f', 6); } else actual_rate = rx->get_input_rate(); if (actual_rate > 0.) { int_val = m_settings->value("input/decimation", 1).toInt(&conv_ok); if (conv_ok && int_val >= 2) { if (rx->set_input_decim(int_val) != (unsigned int)int_val) { qDebug() << "Failed to set decimation" << int_val; qDebug() << " actual decimation:" << rx->get_input_decim(); } else { // update actual rate actual_rate /= (double)int_val; qDebug() << "Input decimation:" << int_val; qDebug() << "Quadrature rate:" << QString("%1").arg(actual_rate, 0, 'f', 6); } } else rx->set_input_decim(1); // update various widgets that need a sample rate uiDockRxOpt->setFilterOffsetRange((qint64)(actual_rate)); uiDockFft->setSampleRate(actual_rate); ui->plotter->setSampleRate(actual_rate); ui->plotter->setSpanFreq((quint32)actual_rate); remote->setBandwidth((qint64)actual_rate); iq_tool->setSampleRate((qint64)actual_rate); } else qDebug() << "Error: Actual sample rate is" << actual_rate; int64_val = 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) int64_val); qDebug() << "Requested bandwidth:" << int64_val << "Hz"; qDebug() << "Actual bandwidth :" << actual_bw << "Hz"; } uiDockInputCtl->readSettings(m_settings); // this will also update freq range uiDockRxOpt->readSettings(m_settings); uiDockFft->readSettings(m_settings); uiDockAudio->readSettings(m_settings); { int64_val = m_settings->value("input/frequency", 14236000).toLongLong(&conv_ok); // If frequency is out of range set frequency to the center of the range. qint64 hw_freq = int64_val - d_lnb_lo - (qint64)(rx->get_filter_offset()); if (hw_freq < d_hw_freq_start || hw_freq > d_hw_freq_stop) { int64_val = (d_hw_freq_stop - d_hw_freq_start) / 2 + (qint64)(rx->get_filter_offset()) + d_lnb_lo; } ui->freqCtrl->setFrequency(int64_val); setNewFrequency(ui->freqCtrl->getFrequency()); // ensure all GUI and RF is updated } { int flo = m_settings->value("receiver/filter_low_cut", 0).toInt(&conv_ok); int fhi = m_settings->value("receiver/filter_high_cut", 0).toInt(&conv_ok); if (conv_ok && flo != fhi) { on_plotter_newFilterFreq(flo, fhi); } } iq_tool->readSettings(m_settings); /* * Initialization the remote control at the end. * We must be sure that all variables initialized before starting RC server. */ remote->readSettings(m_settings); bool_val = m_settings->value("remote_control/enabled", false).toBool(); if (bool_val) { remote->start_server(); ui->actionRemoteControl->setChecked(true); } 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 (newfile == oldfile) { qDebug() << "New file is equal to old file => SYNCING..."; return true; } 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, false); return true; } else { qDebug() << "Error saving configuration to" << newfile; return false; } } /** * 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); iq_tool->saveSettings(m_settings); { int flo, fhi; ui->plotter->getHiLowCutFrequencies(&flo, &fhi); if (flo != fhi) { m_settings->setValue("receiver/filter_low_cut", flo); m_settings->setValue("receiver/filter_high_cut", fhi); } } } } /** * @brief Update hardware RF frequency range. * @param ignore_limits Whether ignore the hardware specd and allow DC-to-light * range. * * This function fetches the frequency range of the receiver. Useful when we * read a new configuration with a new input device or when the ignore_limits * setting is changed. */ void MainWindow::updateHWFrequencyRange(bool ignore_limits) { double startd, stopd, stepd; if (ignore_limits) { d_hw_freq_start = (quint64) 0; d_hw_freq_stop = (quint64) 9999e6; } else if (rx->get_rf_range(&startd, &stopd, &stepd) == receiver::STATUS_OK) { d_hw_freq_start = (quint64) startd; d_hw_freq_stop = (quint64) stopd; } else { qDebug() << __func__ << "failed fetching new hardware frequency range"; d_hw_freq_start = (quint64) 0; d_hw_freq_stop = (quint64) 9999e6; } updateFrequencyRange(); // Also update the available frequency range } /** * @brief Update availble frequency range. * * This function sets the available frequency range based on the hardware * frequency range, the selected filter offset and the LNB LO. * * This function must therefore be called whenever the LNB LO or the filter * offset has changed. */ void MainWindow::updateFrequencyRange() { qint64 start = (qint64)(rx->get_filter_offset()) + d_hw_freq_start + d_lnb_lo; qint64 stop = (qint64)(rx->get_filter_offset()) + d_hw_freq_stop + d_lnb_lo; ui->freqCtrl->setup(0, start, stop, 1, FCTL_UNIT_NONE); uiDockRxOpt->setRxFreqRange(start, stop); } /** * @brief Update gain stages. * @param read_from_device If true, the gain value will be read from the device, * otherwise we set gain = max. * * This function fetches a list of available gain stages with their range * and sends them to the input control UI widget. */ void MainWindow::updateGainStages(bool read_from_device) { gain_list_t gain_list; std::vector gain_names = rx->get_gain_names(); gain_t gain; std::vector::iterator it; for (it = gain_names.begin(); it != gain_names.end(); ++it) { gain.name = *it; rx->get_gain_range(gain.name, &gain.start, &gain.stop, &gain.step); if (read_from_device) { gain.value = rx->get_gain(gain.name); } else { gain.value = gain.stop; rx->set_gain(gain.name, gain.value); } 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); ui->freqCtrl->setFrequency(rx_freq); uiDockBookmarks->setNewFrequency(rx_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(); ui->freqCtrl->setFrequency(d_lnb_lo + rf_freq); ui->plotter->setCenterFreq(d_lnb_lo + d_hw_freq); // update LNB LO in settings if (freq_mhz == 0.f) m_settings->remove("input/lnb_lo"); else m_settings->setValue("input/lnb_lo", d_lnb_lo); } /** 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); updateFrequencyRange(); qint64 rx_freq = d_hw_freq + d_lnb_lo + freq_hz; ui->freqCtrl->setFrequency(rx_freq); if (rx->is_rds_decoder_active()) { rx->reset_rds_parser(); } } /** * @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); } /** Enable / disable hardware AGC. */ void MainWindow::setAutoGain(bool enabled) { rx->set_auto_gain(enabled); if (!enabled) { uiDockInputCtl->restoreManualGains(m_settings); } } /** * @brief Set new frequency offset value. * @param ppm Frequency correction. * * The valid range is between -200 and 200. */ void MainWindow::setFreqCorr(double ppm) { if (ppm < -200.0) ppm = -200.0; else if (ppm > 200.0) ppm = 200.0; qDebug() << __FUNCTION__ << ":" << ppm << "ppm"; rx->set_freq_corr(ppm); } /** Enable/disable I/Q reversion. */ void MainWindow::setIqSwap(bool reversed) { rx->set_iq_swap(reversed); } /** Enable/disable automatic DC removal. */ void MainWindow::setDcCancel(bool enabled) { rx->set_dc_cancel(enabled); } /** 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) { updateHWFrequencyRange(ignore_limits); qint64 filter_offset = (qint64)rx->get_filter_offset(); qint64 freq = (qint64)rx->get_rf_freq(); ui->freqCtrl->setFrequency(d_lnb_lo + freq + filter_offset); // This will ensure that if frequency is clamped and that // the UI is updated with the correct frequency. freq = ui->freqCtrl->getFrequency(); setNewFrequency(freq); } /** Reset lower digits of main frequency control widget */ void MainWindow::setFreqCtrlReset(bool enabled) { ui->freqCtrl->setResetLowerDigits(enabled); } /** * @brief Select new demodulator. * @param demod New demodulator. */ void MainWindow::selectDemod(QString strModulation) { int iDemodIndex; iDemodIndex = DockRxOpt::GetEnumForModulationString(strModulation); qDebug() << "selectDemod(str):" << strModulation << "-> IDX:" << iDemodIndex; return selectDemod(iDemodIndex); } /** * @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 mode_idx) { double cwofs = 0.0; int filter_preset = uiDockRxOpt->currentFilter(); int flo=0, fhi=0, click_res=100; bool rds_enabled; // validate mode_idx if (mode_idx < DockRxOpt::MODE_OFF || mode_idx >= DockRxOpt::MODE_LAST) { qDebug() << "Invalid mode index:" << mode_idx; mode_idx = DockRxOpt::MODE_OFF; } qDebug() << "New mode index:" << mode_idx; uiDockRxOpt->getFilterPreset(mode_idx, filter_preset, &flo, &fhi); d_filter_shape = (receiver::filter_shape)uiDockRxOpt->currentFilterShape(); rds_enabled = rx->is_rds_decoder_active(); if (rds_enabled) setRdsDecoder(false); uiDockRDS->setDisabled(); switch (mode_idx) { case DockRxOpt::MODE_OFF: /* Spectrum analyzer only */ if (rx->is_recording_audio()) { stopAudioRec(); uiDockAudio->setAudioRecButtonState(false); } rx->set_demod(receiver::RX_DEMOD_OFF); click_res = 1000; break; case DockRxOpt::MODE_RAW: /* Raw I/Q; max 96 ksps*/ rx->set_demod(receiver::RX_DEMOD_NONE); ui->plotter->setDemodRanges(-40000, -200, 200, 40000, true); uiDockAudio->setFftRange(0,24000); click_res = 100; break; case DockRxOpt::MODE_AM: rx->set_demod(receiver::RX_DEMOD_AM); ui->plotter->setDemodRanges(-40000, -200, 200, 40000, true); uiDockAudio->setFftRange(0,6000); click_res = 100; break; case DockRxOpt::MODE_NFM: ui->plotter->setDemodRanges(-40000, -1000, 1000, 40000, true); uiDockAudio->setFftRange(0, 5000); rx->set_demod(receiver::RX_DEMOD_NFM); rx->set_fm_maxdev(uiDockRxOpt->currentMaxdev()); rx->set_fm_deemph(uiDockRxOpt->currentEmph()); click_res = 100; break; case DockRxOpt::MODE_WFM_MONO: case DockRxOpt::MODE_WFM_STEREO: case DockRxOpt::MODE_WFM_STEREO_OIRT: /* Broadcast FM */ ui->plotter->setDemodRanges(-120e3, -10000, 10000, 120e3, true); uiDockAudio->setFftRange(0,24000); /** FIXME: get audio rate from rx **/ click_res = 1000; if (mode_idx == DockRxOpt::MODE_WFM_MONO) rx->set_demod(receiver::RX_DEMOD_WFM_M); else if (mode_idx == DockRxOpt::MODE_WFM_STEREO_OIRT) rx->set_demod(receiver::RX_DEMOD_WFM_S_OIRT); else rx->set_demod(receiver::RX_DEMOD_WFM_S); uiDockRDS->setEnabled(); if (rds_enabled) setRdsDecoder(true); break; case DockRxOpt::MODE_LSB: /* LSB */ rx->set_demod(receiver::RX_DEMOD_SSB); ui->plotter->setDemodRanges(-40000, -100, -5000, 0, false); uiDockAudio->setFftRange(0,3000); click_res = 100; break; case DockRxOpt::MODE_USB: /* USB */ rx->set_demod(receiver::RX_DEMOD_SSB); ui->plotter->setDemodRanges(0, 5000, 100, 40000, false); uiDockAudio->setFftRange(0,3000); click_res = 100; break; case DockRxOpt::MODE_CWL: /* CW-L */ rx->set_demod(receiver::RX_DEMOD_SSB); cwofs = -uiDockRxOpt->getCwOffset(); ui->plotter->setDemodRanges(-5000, -100, 100, 5000, true); uiDockAudio->setFftRange(0,1500); click_res = 10; break; case DockRxOpt::MODE_CWU: /* CW-U */ rx->set_demod(receiver::RX_DEMOD_SSB); cwofs = uiDockRxOpt->getCwOffset(); ui->plotter->setDemodRanges(-5000, -100, 100, 5000, true); uiDockAudio->setFftRange(0,1500); click_res = 10; break; default: qDebug() << "Unsupported mode selection (can't happen!): " << mode_idx; flo = -5000; fhi = 5000; click_res = 100; break; } qDebug() << "Filter preset for mode" << mode_idx << "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, d_filter_shape); rx->set_cw_offset(cwofs); rx->set_sql_level(uiDockRxOpt->currentSquelchLevel()); remote->setMode(mode_idx); remote->setPassband(flo, fhi); d_have_audio = (mode_idx != DockRxOpt::MODE_OFF); uiDockRxOpt->setCurrentDemod(mode_idx); } /** * @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); } /** * @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); } void MainWindow::setCwOffset(int offset) { rx->set_cw_offset(offset); } /** * @brief Audio gain changed. * @param value The new audio gain in dB. */ void MainWindow::setAudioGain(float value) { rx->set_af_gain(value); } /** Set AGC ON/OFF. */ void MainWindow::setAgcOn(bool agc_on) { rx->set_agc_on(agc_on); } /** AGC hang ON/OFF. */ void MainWindow::setAgcHang(bool use_hang) { rx->set_agc_hang(use_hang); } /** AGC threshold changed. */ void MainWindow::setAgcThreshold(int threshold) { rx->set_agc_threshold(threshold); } /** AGC slope factor changed. */ void MainWindow::setAgcSlope(int factor) { rx->set_agc_slope(factor); } /** AGC manual gain changed. */ void MainWindow::setAgcGain(int gain) { rx->set_agc_manual_gain(gain); } /** AGC decay changed. */ void MainWindow::setAgcDecay(int msec) { rx->set_agc_decay(msec); } /** * @brief Noise 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 The new squelch level. */ double MainWindow::setSqlLevelAuto() { double level = rx->get_signal_pwr(true) + 1.0; setSqlLevel(level); return level; } /** Signal strength meter timeout. */ void MainWindow::meterTimeout() { float level; level = rx->get_signal_pwr(true); ui->sMeter->setLevel(level); remote->setSignalLevel(level); } /** Baseband FFT plot timeout. */ void MainWindow::iqFftTimeout() { unsigned int fftsize; unsigned int i; float pwr; float pwr_scale; std::complex pt; /* a single FFT point used in calculations */ // FIXME: fftsize is a reference rx->get_iq_fft_data(d_fftData, fftsize); if (fftsize == 0) { /* nothing to do, wait until next activation. */ return; } // NB: without cast to float the multiplication will overflow at 64k // and pwr_scale will be inf pwr_scale = 1.0 / ((float)fftsize * (float)fftsize); /* Normalize, calculate power and shift the FFT */ for (i = 0; i < fftsize; i++) { /* normalize and shift */ if (i < fftsize/2) { pt = d_fftData[fftsize/2+i]; } else { pt = d_fftData[i-fftsize/2]; } /* calculate power in dBFS */ pwr = pwr_scale * (pt.imag() * pt.imag() + pt.real() * pt.real()); d_realFftData[i] = 10.0 * log10f(pwr + 1.0e-20); /* FFT averaging */ d_iirFftData[i] += d_fftAvg * (d_realFftData[i] - d_iirFftData[i]); } ui->plotter->setNewFttData(d_iirFftData, d_realFftData, fftsize); } /** Audio FFT plot timeout. */ void MainWindow::audioFftTimeout() { unsigned int fftsize; unsigned int i; float pwr; float pwr_scale; std::complex pt; /* a single FFT point used in calculations */ if (!d_have_audio || !uiDockAudio->isVisible()) 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; } pwr_scale = 1.0 / (fftsize * 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]; } else { pt = d_fftData[i-fftsize/2]; } /* calculate power in dBFS */ pwr = pwr_scale * (pt.imag() * pt.imag() + pt.real() * pt.real()); d_realFftData[i] = 10.0 * log10f(pwr + 1.0e-20); } uiDockAudio->setNewFttData(d_realFftData, fftsize); } /** RDS message display timeout. */ void MainWindow::rdsTimeout() { std::string buffer; int num; rx->get_rds_data(buffer, num); while(num!=-1) { rx->get_rds_data(buffer, num); uiDockRDS->updateRDS(QString::fromStdString(buffer), num); } } /** * @brief Start audio recorder. * @param filename The file name into which audio should be recorded. */ void MainWindow::startAudioRec(const QString filename) { if (!d_have_audio) { QMessageBox msg_box; msg_box.setIcon(QMessageBox::Critical); msg_box.setText(tr("Recording audio requires a demodulator.\n" "Currently, demodulation is switched off " "(Mode->Demod off).")); msg_box.exec(); uiDockAudio->setAudioRecButtonState(false); } else 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)); } } /** 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); } } /** 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)); } } /** 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); } } /** Start streaming audio over UDP. */ void MainWindow::startAudioStream(const QString udp_host, int udp_port) { rx->start_udp_streaming(udp_host.toStdString(), udp_port); } /** Stop streaming audio over UDP. */ void MainWindow::stopAudioStreaming() { rx->stop_udp_streaming(); } /** Start I/Q recording. */ void MainWindow::startIqRecording(const QString recdir) { qDebug() << __func__; // generate file name using date, time, rf freq in kHz and BW in Hz // gqrx_iq_yyyymmdd_hhmmss_freq_bw_fc.raw qint64 freq = (qint64)(rx->get_rf_freq()); qint64 sr = (qint64)(rx->get_input_rate()); qint32 dec = (quint32)(rx->get_input_decim()); QString lastRec = QDateTime::currentDateTimeUtc(). toString("%1/gqrx_yyyyMMdd_hhmmss_%2_%3_fc.'raw'") .arg(recdir).arg(freq).arg(sr/dec); // start recorder; fails if recording already in progress if (rx->start_iq_recording(lastRec.toStdString())) { // reset action status ui->statusBar->showMessage(tr("Error starting I/Q recoder")); // show an error message to user QMessageBox msg_box; msg_box.setIcon(QMessageBox::Critical); msg_box.setText(tr("There was an error starting the I/Q recorder.\n" "Check write permissions for the selected location.")); msg_box.exec(); } else { ui->statusBar->showMessage(tr("Recording I/Q data to: %1").arg(lastRec), 5000); } } /** Stop current I/Q recording. */ void MainWindow::stopIqRecording() { qDebug() << __func__; 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); } void MainWindow::startIqPlayback(const QString filename, float samprate) { if (ui->actionDSP->isChecked()) { // suspend DSP while we reload settings on_actionDSP_triggered(false); } storeSession(); int sri = (int)samprate; QString devstr = QString("file=%1,rate=%2,throttle=true,repeat=false") .arg(filename).arg(sri); qDebug() << __func__ << ":" << devstr; rx->set_input_device(devstr.toStdString()); // sample rate double actual_rate = rx->set_input_rate(samprate); qDebug() << "Requested sample rate:" << samprate; qDebug() << "Actual sample rate :" << QString("%1") .arg(actual_rate, 0, 'f', 6); uiDockRxOpt->setFilterOffsetRange((qint64)(actual_rate)); ui->plotter->setSampleRate(actual_rate); ui->plotter->setSpanFreq((quint32)actual_rate); remote->setBandwidth(actual_rate); // FIXME: would be nice with good/bad status ui->statusBar->showMessage(tr("Playing %1").arg(filename)); if (ui->actionDSP->isChecked()) { // restsart DSP on_actionDSP_triggered(true); } } void MainWindow::stopIqPlayback() { if (ui->actionDSP->isChecked()) { // suspend DSP while we reload settings on_actionDSP_triggered(false); } ui->statusBar->showMessage(tr("I/Q playback stopped"), 5000); // restore original input device QString indev = m_settings->value("input/device", "").toString(); rx->set_input_device(indev.toStdString()); // restore sample rate bool conv_ok; 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)(actual_rate)); ui->plotter->setSampleRate(actual_rate); ui->plotter->setSpanFreq((quint32)actual_rate); remote->setBandwidth(sr); // not needed as long as we are not recording in iq_tool //iq_tool->setSampleRate(sr); } // restore frequency, gain, etc... uiDockInputCtl->readSettings(m_settings); if (ui->actionDSP->isChecked()) { // restsart DSP on_actionDSP_triggered(true); } } /** * Go to a specific offset in the IQ file. * @param seek_pos The byte offset from the begining of the file. */ void MainWindow::seekIqFile(qint64 seek_pos) { rx->seek_iq_file((long)seek_pos); } /** FFT size has changed. */ void MainWindow::setIqFftSize(int size) { qDebug() << "Changing baseband FFT size to" << size; rx->set_iq_fft_size(size); } /** 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; ui->plotter->setFftRate(fps); if (iq_fft_timer->isActive()) ui->plotter->setRunningState(true); } if (interval > 9 && iq_fft_timer->isActive()) iq_fft_timer->setInterval(interval); } /** Waterfall time span has changed. */ void MainWindow::setWfTimeSpan(quint64 span_ms) { // set new time span, then send back new resolution to be shown by GUI label ui->plotter->setWaterfallSpan(span_ms); uiDockFft->setWfResolution(ui->plotter->getWfTimeRes()); } /** * @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 >= 0) && (pct_wf <= 100)) ui->plotter->setPercent2DScreen(pct_wf); } void MainWindow::setIqFftAvg(float avg) { if ((avg >= 0) && (avg <= 1.0)) d_fftAvg = avg; } /** 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) { remote->setReceiverStatus(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(); rds_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, devList); int confres = ioconf->exec(); if (confres == QDialog::Accepted) { if (ui->actionDSP->isChecked()) // suspend DSP while we reload settings on_actionDSP_triggered(false); // Refresh LNB LO in dock widget, otherwise changes will be lost uiDockInputCtl->readLnbLoFromSettings(m_settings); storeSession(); loadConfig(m_settings->fileName(), false, false); if (ui->actionDSP->isChecked()) // restsart DSP on_actionDSP_triggered(true); } delete ioconf; return confres; } /** Run first time configurator. */ int MainWindow::firstTimeConfig() { qDebug() << __func__; CIoConfig *ioconf = new CIoConfig(m_settings, devList); int confres = ioconf->exec(); if (confres == QDialog::Accepted) loadConfig(m_settings->fileName(), false, false); delete ioconf; return confres; } /** Load configuration activated by user. */ void MainWindow::on_actionLoadSettings_triggered() { QString cfgfile; 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(), cfgfile != m_settings->fileName()); // store last dir QFileInfo fi(cfgfile); if (m_cfg_dir != fi.absolutePath()) m_last_dir = fi.absolutePath(); } /** Save configuration activated by user. */ void MainWindow::on_actionSaveSettings_triggered() { QString cfgfile; 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(); } void MainWindow::on_actionSaveWaterfall_triggered() { QDateTime dt(QDateTime::currentDateTimeUtc()); QString wffile; QString save_path; // previously used location save_path = m_settings->value("wf_save_dir", "").toString(); if (!save_path.isEmpty()) save_path += "/"; save_path += dt.toString("gqrx_wf_yyyyMMdd_hhmmss.png"); wffile = QFileDialog::getSaveFileName(this, tr("Save waterfall"), save_path, 0); if (wffile.isEmpty()) return; if (!ui->plotter->saveWaterfall(wffile)) { QMessageBox::critical(this, tr("Error"), tr("There was an error saving the waterfall")); } // store the location used for the waterfall file QFileInfo fi(wffile); m_settings->setValue("wf_save_dir", fi.absolutePath()); } /** Show I/Q player. */ void MainWindow::on_actionIqTool_triggered() { iq_tool->show(); } /* 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); if (rx->is_rds_decoder_active()) rx->reset_rds_parser(); } /* CPlotter::NewfilterFreq() is emitted or bookmark activated */ 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); /* Update filter range of plotter, in case this slot is triggered by * switching to a bookmark */ ui->plotter->setHiLowCutFrequencies(low, high); 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); } /** 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(); } } /** Remote control button (or menu item) toggled. */ void MainWindow::on_actionRemoteControl_triggered(bool checked) { if (checked) remote->start_server(); else remote->stop_server(); } /** 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 /** * 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); } } /** * 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; } /** * 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; rx->get_sniffer_data(&buffer[0], num); if (dec_afsk1200) dec_afsk1200->process_samples(&buffer[0], num); } void MainWindow::setRdsDecoder(bool checked) { if (checked == true) { qDebug() << "Starting RDS decoder."; uiDockRDS->showEnabled(); rx->start_rds_decoder(); rx->reset_rds_parser(); rds_timer->start(250); } else { qDebug() << "Stopping RDS decoder."; uiDockRDS->showDisabled(); rx->stop_rds_decoder(); rds_timer->stop(); } } void MainWindow::onBookmarkActivated(qint64 freq, QString demod, int bandwidth) { setNewFrequency(freq); selectDemod(demod); /* Check if filter is symmetric or not by checking the presets */ int mode = uiDockRxOpt->currentDemod(); int preset = uiDockRxOpt->currentFilterShape(); int lo, hi; uiDockRxOpt->getFilterPreset(mode, preset, &lo, &hi); if(lo + hi == 0) { lo = -bandwidth / 2; hi = bandwidth / 2; } else if(lo >= 0 && hi >= 0) { hi = lo + bandwidth; } else if(lo <= 0 && hi <= 0) { lo = hi - bandwidth; } on_plotter_newFilterFreq(lo, hi); } void MainWindow::setPassband(int bandwidth) { /* Check if filter is symmetric or not by checking the presets */ int mode = uiDockRxOpt->currentDemod(); int preset = uiDockRxOpt->currentFilterShape(); int lo, hi; uiDockRxOpt->getFilterPreset(mode, preset, &lo, &hi); if(lo + hi == 0) { lo = -bandwidth / 2; hi = bandwidth / 2; } else if(lo >= 0 && hi >= 0) { hi = lo + bandwidth; } else if(lo <= 0 && hi <= 0) { lo = hi - bandwidth; } remote->setPassband(lo, hi); on_plotter_newFilterFreq(lo, hi); } /** 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); } /** * Show news.txt in a dialog window. */ void MainWindow::on_actionNews_triggered() { showSimpleTextFile(":/textfiles/news.txt", tr("Release news")); } /** * Show remote-contol.txt in a dialog window. */ void MainWindow::on_actionRemoteProtocol_triggered() { showSimpleTextFile(":/textfiles/remote-control.txt", tr("Remote control protocol")); } /** * Show simple text file in a window. */ void MainWindow::showSimpleTextFile(const QString &resource_path, const QString &window_title) { QResource resource(resource_path); QFile news(resource.absoluteFilePath()); if (!news.open(QIODevice::ReadOnly | QIODevice::Text)) { qDebug() << "Unable to open file: " << news.fileName() << " besause of error " << news.errorString(); return; } QTextStream in(&news); QString content = in.readAll(); news.close(); QTextBrowser *browser = new QTextBrowser(); browser->setLineWrapMode(QTextEdit::NoWrap); browser->setFontFamily("monospace"); browser->append(content); browser->adjustSize(); // scroll to the beginning QTextCursor cursor = browser->textCursor(); cursor.setPosition(0); browser->setTextCursor(cursor); QVBoxLayout *layout = new QVBoxLayout(); layout->addWidget(browser); QDialog *dialog = new QDialog(this); dialog->setWindowTitle(window_title); dialog->setLayout(layout); dialog->resize(700, 400); dialog->exec(); delete dialog; // browser and layout deleted automatically } /** * @brief Action: About gqrx. * * 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-2017 Alexandru Csete & contributors.

    " "

    Gqrx is a software defined radio (SDR) 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 " "Funcube Dongles, RTL-SDR, Airspy, HackRF, RFSpace, BladeRF and USRP receivers." "

    " "

    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")); } void MainWindow::on_actionAddBookmark_triggered() { bool ok=false; QString name; QString tags; // list of tags separated by comma // Create and show the Dialog for a new Bookmark. // Write the result into variabe 'name'. { QDialog dialog(this); dialog.setWindowTitle("New bookmark"); QGroupBox* LabelAndTextfieldName = new QGroupBox(&dialog); QLabel* label1 = new QLabel("Bookmark name:", LabelAndTextfieldName); QLineEdit* textfield = new QLineEdit(LabelAndTextfieldName); QHBoxLayout *layout = new QHBoxLayout; layout->addWidget(label1); layout->addWidget(textfield); LabelAndTextfieldName->setLayout(layout); QPushButton* buttonCreateTag = new QPushButton("Create new Tag", &dialog); BookmarksTagList* taglist = new BookmarksTagList(&dialog, false); taglist->updateTags(); taglist->DeselectAll(); QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(buttonBox, SIGNAL(accepted()), &dialog, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), &dialog, SLOT(reject())); connect(buttonCreateTag, SIGNAL(clicked()), taglist, SLOT(AddNewTag())); QVBoxLayout *mainLayout = new QVBoxLayout(&dialog); mainLayout->addWidget(LabelAndTextfieldName); mainLayout->addWidget(buttonCreateTag); mainLayout->addWidget(taglist); mainLayout->addWidget(buttonBox); ok = dialog.exec(); if (ok) { name = textfield->text(); tags = taglist->getSelectedTagsAsString(); qDebug() << "Tags: " << tags; } else { name.clear(); tags.clear(); } } // Add new Bookmark to Bookmarks. if(ok) { int i; BookmarkInfo info; info.frequency = ui->freqCtrl->getFrequency(); info.bandwidth = ui->plotter->getFilterBw(); info.modulation = uiDockRxOpt->currentDemodAsString(); info.name=name; QStringList listTags = tags.split(",",QString::SkipEmptyParts); info.tags.clear(); if (listTags.size() == 0) info.tags.append(&Bookmarks::Get().findOrAddTag("")); for (i = 0; i < listTags.size(); ++i) info.tags.append(&Bookmarks::Get().findOrAddTag(listTags[i])); Bookmarks::Get().add(info); uiDockBookmarks->updateTags(); uiDockBookmarks->updateBookmarks(); ui->plotter->updateOverlay(); } } gqrx-2.9/src/applications/gqrx/mainwindow.h000066400000000000000000000157661320142145500211440ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2014 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 #include "qtgui/dockrxopt.h" #include "qtgui/dockaudio.h" #include "qtgui/dockinputctl.h" #include "qtgui/dockfft.h" #include "qtgui/dockbookmarks.h" #include "qtgui/dockrds.h" #include "qtgui/afsk1200win.h" #include "qtgui/iq_tool.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 restore_mainwindow); bool saveConfig(const QString cfgfile); void storeSession(); bool configOk; /*!< Main app uses this flag to know whether we should abort or continue. */ 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; qint64 d_hw_freq_start; qint64 d_hw_freq_stop; enum receiver::filter_shape d_filter_shape; std::complex* d_fftData; float *d_realFftData; float *d_iirFftData; float *d_pwrFftData; //double *d_audioFttData; float 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; DockFft *uiDockFft; DockBookmarks *uiDockBookmarks; DockRDS *uiDockRDS; CIqTool *iq_tool; /* data decoders */ Afsk1200Win *dec_afsk1200; bool dec_rds; QTimer *dec_timer; QTimer *meter_timer; QTimer *iq_fft_timer; QTimer *audio_fft_timer; QTimer *rds_timer; receiver *rx; RemoteControl *remote; std::map devList; // dummy widget to enforce linking to QtSvg QSvgWidget *qsvg_dummy; private: void updateHWFrequencyRange(bool ignore_limits); void updateFrequencyRange(); void updateGainStages(bool read_from_device); void showSimpleTextFile(const QString &resource_path, const QString &window_title); 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(double ppm); void setIqSwap(bool reversed); void setDcCancel(bool enabled); void setIqBalance(bool enabled); void setIgnoreLimits(bool ignore_limits); void setFreqCtrlReset(bool enabled); void selectDemod(QString demod); void selectDemod(int index); void setFmMaxdev(float max_dev); void setFmEmph(double tau); void setAmDcr(bool enabled); void setCwOffset(int offset); 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); void setPassband(int bandwidth); /* 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(); /* I/Q playback and recording*/ void startIqRecording(const QString recdir); void stopIqRecording(); void startIqPlayback(const QString filename, float samprate); void stopIqPlayback(); void seekIqFile(qint64 seek_pos); /* FFT settings */ void setIqFftSize(int size); void setIqFftRate(int fps); void setIqFftSplit(int pct_wf); void setIqFftAvg(float avg); void setAudioFftRate(int fps); void setFftColor(const QColor color); void setFftFill(bool enable); void setPeakDetection(bool enabled); void setFftPeakHold(bool enable); void setWfTimeSpan(quint64 span_ms); /* FFT plot */ 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); /* RDS */ void setRdsDecoder(bool checked); /* Bookmarks */ void onBookmarkActivated(qint64 freq, QString demod, int bandwidth); /* menu and toolbar actions */ void on_actionDSP_triggered(bool checked); int on_actionIoConfig_triggered(); void on_actionLoadSettings_triggered(); void on_actionSaveSettings_triggered(); void on_actionSaveWaterfall_triggered(); void on_actionIqTool_triggered(); 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_actionNews_triggered(); void on_actionRemoteProtocol_triggered(); void on_actionAbout_triggered(); void on_actionAboutQt_triggered(); void on_actionAddBookmark_triggered(); /* window close signals */ void afsk1200win_closed(); int firstTimeConfig(); /* cyclic processing */ void decoderTimeout(); void meterTimeout(); void iqFftTimeout(); void audioFftTimeout(); void rdsTimeout(); }; #endif // MAINWINDOW_H gqrx-2.9/src/applications/gqrx/mainwindow.ui000066400000000000000000000377731320142145500213340ustar00rootroot00000000000000 MainWindow 0 0 950 600 gqrx :/icons/icons/gqrx.svg:/icons/icons/gqrx.svg QMainWindow::AllowNestedDocks|QMainWindow::AllowTabbedDocks|QMainWindow::AnimatedDocks true background-color: rgb(31, 29, 29); 10 0 10 0 0 10 20 10 Qt::Horizontal 40 20 0 0 371 41 QFrame::StyledPanel QFrame::Raised Qt::Horizontal 40 20 0 0 171 41 QFrame::StyledPanel QFrame::Raised Qt::Horizontal 40 20 0 0 QFrame::StyledPanel QFrame::Raised 0 0 950 20 &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/play.svg:/icons/icons/play.svg Start DSP Start DSP Start/stop DSP processing Ctrl+D AFSK1200 Decoder Start AFSK1200 decoder true false :/icons/icons/clock.svg:/icons/icons/clock.svg Scheduler Schedule recordings and other actions true :/icons/icons/flash.svg:/icons/icons/flash.svg I/O Devices Configure I/O devices Opens the hardware configuration dialog 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 Load previously stored settings from a file Ctrl+L :/icons/icons/floppy.svg:/icons/icons/floppy.svg Save settings Save current configuration Save current configuration to a file Ctrl+S :/icons/icons/bookmark-new.svg:/icons/icons/bookmark-new.svg Add bookmark Bookmark the current frequency and mode (Ctrl+Shift+B) Ctrl+Shift+B true true :/icons/icons/tangeo-network-idle.svg:/icons/icons/tangeo-network-idle.svg &Remote control Remote control via TCP Activate the TCP interface :/icons/icons/settings.svg:/icons/icons/settings.svg Remote &control settings Configure remote control settings :/icons/icons/signal.svg:/icons/icons/signal.svg I/Q recorder Record and play I/Q data Open I/Q recorder dialog Ctrl+I :/icons/icons/info.svg:/icons/icons/info.svg News Show news about this release :/icons/icons/info.svg:/icons/icons/info.svg Remote control Remote control protocol Save waterfall Save the current waterfall to a graphics file Ctrl+W 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-2.9/src/applications/gqrx/receiver.cpp000066400000000000000000001035431320142145500211160ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2014 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 #ifndef _MSC_VER #include #endif #include #include #include #include #include #include #include "applications/gqrx/receiver.h" #include "dsp/correct_iq_cc.h" //#include "dsp/hbf_decim.h" #include "dsp/filter/fir_decim.h" #include "dsp/rx_fft.h" #include "receivers/nbrx.h" #include "receivers/wfmrx.h" #ifdef WITH_PULSEAUDIO #include "pulseaudio/pa_sink.h" #elif WITH_PORTAUDIO #include "portaudio/portaudio_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. */ receiver::receiver(const std::string input_device, const std::string audio_device, unsigned int decimation) : d_running(false), d_input_rate(96000.0), d_audio_rate(48000), d_decim(decimation), d_rf_freq(144800000.0), d_filter_offset(0.0), d_cw_offset(0.0), d_recording_iq(false), 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()) { src = osmosdr::source::make("file="+get_random_file()+",freq=428e6,rate=96000,repeat=true,throttle=true"); } else { input_devstr = input_device; src = osmosdr::source::make(input_device); } // input decimator if (d_decim >= 2) { try { input_decim = make_fir_decim_cc(d_decim); } catch (std::range_error &e) { std::cout << "Error creating input decimator " << d_decim << ": " << e.what() << std::endl << "Using decimation 1." << std::endl; d_decim = 1; } d_quad_rate = d_input_rate / (double)d_decim; } else { d_quad_rate = d_input_rate; } // create I/Q sink and close it iq_sink = gr::blocks::file_sink::make(sizeof(gr_complex), get_null_file().c_str(), true); iq_sink->set_unbuffered(true); iq_sink->close(); rx = make_nbrx(d_quad_rate, d_audio_rate); lo = gr::analog::sig_source_c::make(d_quad_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_quad_rate, 1.0); iq_fft = make_rx_fft_c(8192u, 0); audio_fft = make_rx_fft_f(8192u); audio_gain0 = gr::blocks::multiply_const_ff::make(0.1); audio_gain1 = gr::blocks::multiply_const_ff::make(0.1); wav_sink = gr::blocks::wavfile_sink::make(get_null_file().c_str(), 2, (unsigned int) d_audio_rate, 16); audio_udp_sink = make_udp_sink_f(); #ifdef WITH_PULSEAUDIO audio_snk = make_pa_sink(audio_device, d_audio_rate, "GQRX", "Audio output"); #elif WITH_PORTAUDIO audio_snk = make_portaudio_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 } receiver::~receiver() { tb->stop(); } /** Start the receiver. */ void receiver::start() { if (!d_running) { tb->start(); d_running = true; } } /** 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() can hang occasionally if (d_running) { tb->stop(); tb->wait(); } if (d_decim >= 2) { tb->disconnect(src, 0, input_decim, 0); tb->disconnect(input_decim, 0, iq_swap, 0); } else { tb->disconnect(src, 0, iq_swap, 0); } src.reset(); src = osmosdr::source::make(device); if(src->get_sample_rate() != 0) set_input_rate(src->get_sample_rate()); if (d_decim >= 2) { tb->connect(src, 0, input_decim, 0); tb->connect(input_decim, 0, iq_swap, 0); } else { tb->connect(src, 0, iq_swap, 0); } if (d_running) tb->start(); } /** 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 return; } #ifndef QT_NO_DEBUG_OUTPUT std::cout << "New audio output device:" << std::endl << " old: " << output_devstr << std::endl << " new: " << device << std::endl; #endif output_devstr = device; tb->lock(); if (d_demod != RX_DEMOD_OFF) { 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, "GQRX", "Audio output"); #elif WITH_PORTAUDIO audio_snk = make_portaudio_sink(device, d_audio_rate, "GQRX", "Audio output"); #else audio_snk = gr::audio::sink::make(d_audio_rate, device, true); #endif if (d_demod != RX_DEMOD_OFF) { tb->connect(audio_gain0, 0, audio_snk, 0); tb->connect(audio_gain1, 0, audio_snk, 1); } tb->unlock(); } /** Get a list of available antenna connectors. */ std::vector receiver::get_antennas(void) const { return src->get_antennas(); } /** Select antenna conenctor. */ void receiver::set_antenna(const std::string &antenna) { if (!antenna.empty()) { src->set_antenna(antenna); } } /** * @brief Set new input sample rate. * @param rate The desired input rate * @return The actual sample rate set or 0 if there was an error with the * device. */ double receiver::set_input_rate(double rate) { double current_rate; bool rate_has_changed; current_rate = src->get_sample_rate(); rate_has_changed = !(rate == current_rate || std::abs(rate - current_rate) < std::abs(std::min(rate, current_rate)) * std::numeric_limits::epsilon()); tb->lock(); d_input_rate = src->set_sample_rate(rate); if (d_input_rate == 0) { // This can be the case when no device is attached and gr-osmosdr // puts in a null_source with rate 100 ksps or if the rate has not // changed if (rate_has_changed) { std::cerr << std::endl; std::cerr << "Failed to set RX input rate to " << rate << std::endl; std::cerr << "Your device may not be working properly." << std::endl; std::cerr << std::endl; } d_input_rate = rate; } d_quad_rate = d_input_rate / (double)d_decim; dc_corr->set_sample_rate(d_quad_rate); rx->set_quad_rate(d_quad_rate); lo->set_sampling_freq(d_quad_rate); tb->unlock(); return d_input_rate; } /** Set input decimation */ unsigned int receiver::set_input_decim(unsigned int decim) { if (decim == d_decim) return d_decim; if (d_running) { tb->stop(); tb->wait(); } if (d_decim >= 2) { tb->disconnect(src, 0, input_decim, 0); tb->disconnect(input_decim, 0, iq_swap, 0); } else { tb->disconnect(src, 0, iq_swap, 0); } input_decim.reset(); d_decim = decim; if (d_decim >= 2) { try { input_decim = make_fir_decim_cc(d_decim); } catch (std::range_error &e) { std::cout << "Error opening creating input decimator " << d_decim << ": " << e.what() << std::endl << "Using decimation 1." << std::endl; d_decim = 1; } d_quad_rate = d_input_rate / (double)d_decim; } else { d_quad_rate = d_input_rate; } // update quadrature rate dc_corr->set_sample_rate(d_quad_rate); rx->set_quad_rate(d_quad_rate); lo->set_sampling_freq(d_quad_rate); if (d_decim >= 2) { tb->connect(src, 0, input_decim, 0); tb->connect(input_decim, 0, iq_swap, 0); } else { tb->connect(src, 0, iq_swap, 0); } #ifdef CUSTOM_AIRSPY_KERNELS if (input_devstr.find("airspy") != std::string::npos) src->set_bandwidth(d_quad_rate); #endif if (d_running) tb->start(); return d_decim; } /** * @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); } /** Get current analog bandwidth. */ double receiver::get_analog_bandwidth(void) const { return src->get_bandwidth(); } /** 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) const { 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) const { 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) const { 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(void) { 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; } /** 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) const { 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) const { 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 + d_cw_offset); return STATUS_OK; } /** * @brief Get filter offset. * @return The current filter offset. * @sa set_filter_offset() */ double receiver::get_filter_offset(void) const { return d_filter_offset; } /* CW offset can serve as a "BFO" if the GUI needs it */ receiver::status receiver::set_cw_offset(double offset_hz) { d_cw_offset = offset_hz; lo->set_frequency(-d_filter_offset + d_cw_offset); rx->set_cw_offset(d_cw_offset); return STATUS_OK; } double receiver::get_cw_offset(void) const { return d_cw_offset; } receiver::status receiver::set_filter(double low, double high, filter_shape shape) { double trans_width; if ((low >= high) || (std::abs(high-low) < RX_FILTER_MIN_WIDTH)) return STATUS_ERROR; switch (shape) { case FILTER_SHAPE_SOFT: trans_width = std::abs(high - low) * 0.5; break; case FILTER_SHAPE_SHARP: trans_width = std::abs(high - low) * 0.1; break; case FILTER_SHAPE_NORMAL: default: trans_width = std::abs(high - low) * 0.2; break; } rx->set_filter(low, high, trans_width); return STATUS_OK; } receiver::status receiver::set_freq_corr(double 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) const { return rx->get_signal_level(dbfs); } /** Set new FFT size. */ void receiver::set_iq_fft_size(int newsize) { iq_fft->set_fft_size(newsize); } /** Get latest baseband FFT data. */ void receiver::get_iq_fft_data(std::complex* fftPoints, unsigned int &fftsize) { iq_fft->get_fft_data(fftPoints, fftsize); } /** 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 } /** 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 } /** 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 } /** Set AGC threshold. */ receiver::status receiver::set_agc_threshold(int threshold) { if (rx->has_agc()) rx->set_agc_threshold(threshold); return STATUS_OK; // FIXME } /** Set AGC slope. */ receiver::status receiver::set_agc_slope(int slope) { if (rx->has_agc()) rx->set_agc_slope(slope); return STATUS_OK; // FIXME } /** 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 } /** 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) { 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; // tb->lock() seems to hang occasioanlly if (d_running) { tb->stop(); tb->wait(); } tb->disconnect_all(); switch (demod) { case RX_DEMOD_OFF: connect_all(RX_CHAIN_NONE); break; case RX_DEMOD_NONE: connect_all(RX_CHAIN_NBRX); rx->set_demod(nbrx::NBRX_DEMOD_NONE); break; case RX_DEMOD_AM: connect_all(RX_CHAIN_NBRX); rx->set_demod(nbrx::NBRX_DEMOD_AM); break; case RX_DEMOD_NFM: connect_all(RX_CHAIN_NBRX); rx->set_demod(nbrx::NBRX_DEMOD_FM); break; case RX_DEMOD_WFM_M: connect_all(RX_CHAIN_WFMRX); rx->set_demod(wfmrx::WFMRX_DEMOD_MONO); break; case RX_DEMOD_WFM_S: connect_all(RX_CHAIN_WFMRX); rx->set_demod(wfmrx::WFMRX_DEMOD_STEREO); break; case RX_DEMOD_WFM_S_OIRT: connect_all(RX_CHAIN_WFMRX); rx->set_demod(wfmrx::WFMRX_DEMOD_STEREO_UKW); break; case RX_DEMOD_SSB: connect_all(RX_CHAIN_NBRX); rx->set_demod(nbrx::NBRX_DEMOD_SSB); break; default: ret = STATUS_ERROR; break; } d_demod = demod; if (d_running) tb->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(); // if this fails, we don't want to go and crash now, do we try { wav_sink->open(filename.c_str()); wav_sink->set_sample_rate((unsigned int) d_audio_rate); } catch (std::runtime_error &e) { std::cout << "Error opening " << filename << ": " << e.what() << std::endl; return STATUS_ERROR; } 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; } /** 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 stop 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); tb->unlock(); d_recording_wav = false; std::cout << "Audio recorder stopped" << std::endl; return STATUS_OK; } /** 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; } /** 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; } /** 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; } /** 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) { receiver::status status = STATUS_OK; if (d_recording_iq) { std::cout << __func__ << ": already recording" << std::endl; return STATUS_ERROR; } // iq_sink was created in the constructor if (iq_sink) { tb->lock(); if (!iq_sink->open(filename.c_str())) { status = STATUS_ERROR; } else { if (d_decim >= 2) tb->connect(input_decim, 0, iq_sink, 0); else tb->connect(src, 0, iq_sink, 0); d_recording_iq = true; } tb->unlock(); } else { std::cout << __func__ << ": I/Q file sink does not exist" << std::endl; return STATUS_ERROR; } return status; } /** Stop I/Q data recorder. */ receiver::status receiver::stop_iq_recording() { if (!d_recording_iq) { /* error: we are not recording */ return STATUS_ERROR; } tb->lock(); iq_sink->close(); if (d_decim >= 2) tb->disconnect(input_decim, 0, iq_sink, 0); else tb->disconnect(src, 0, iq_sink, 0); tb->unlock(); d_recording_iq = false; return STATUS_OK; } /** * @brief Seek to position in IQ file source. * @param pos Byte offset from the beginning of the file. */ receiver::status receiver::seek_iq_file(long pos) { receiver::status status = STATUS_OK; tb->lock(); if (src->seek(pos, SEEK_SET)) { status = STATUS_OK; } else { status = STATUS_ERROR; } tb->unlock(); return status; } /** * @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; } /** Get sniffer data. */ void receiver::get_sniffer_data(float * outbuff, unsigned int &num) { sniffer->get_samples(outbuff, num); } /** Convenience function to connect all blocks. */ void receiver::connect_all(rx_chain type) { switch (type) { case RX_CHAIN_NONE: if (d_decim >= 2) { tb->connect(src, 0, input_decim, 0); tb->connect(input_decim, 0, iq_swap, 0); } else { 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_quad_rate, d_audio_rate); } if (d_decim >= 2) { tb->connect(src, 0, input_decim, 0); tb->connect(input_decim, 0, iq_swap, 0); } else { 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_quad_rate, d_audio_rate); } if (d_decim >= 2) { tb->connect(src, 0, input_decim, 0); tb->connect(input_decim, 0, iq_swap, 0); } else { 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; } // reconnect recorders and sniffers if (d_recording_iq) { if (d_decim >= 2) tb->connect(input_decim, 0, iq_sink, 0); else tb->connect(src, 0, iq_sink, 0); } if (d_recording_wav) { tb->connect(rx, 0, wav_sink, 0); tb->connect(rx, 1, wav_sink, 1); } if (d_sniffer_active) { tb->connect(rx, 0, sniffer_rr, 0); tb->connect(sniffer_rr, 0, sniffer, 0); } } void receiver::get_rds_data(std::string &outbuff, int &num) { rx->get_rds_data(outbuff, num); } void receiver::start_rds_decoder(void) { stop(); rx->start_rds_decoder(); start(); } void receiver::stop_rds_decoder(void) { stop(); rx->stop_rds_decoder(); start(); } bool receiver::is_rds_decoder_active(void) const { return rx->is_rds_decoder_active(); } void receiver::reset_rds_parser(void) { rx->reset_rds_parser(); } gqrx-2.9/src/applications/gqrx/receiver.h000066400000000000000000000255271320142145500205700ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2014 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 #include "dsp/correct_iq_cc.h" #include "dsp/filter/fir_decim.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 "pulseaudio/pa_sink.h" #elif WITH_PORTAUDIO #include "portaudio/portaudio_sink.h" #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: /** Flag used to indicate success or failure of an operation */ enum status { STATUS_OK = 0, /*!< Operation was successful. */ STATUS_ERROR = 1 /*!< There was an error. */ }; /** 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_WFM_S_OIRT = 6, /*!< Frequency modulation (wide, stereo oirt). */ RX_DEMOD_SSB = 7 /*!< Single Side Band. */ }; /** 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). */ }; /** 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="", unsigned int decimation=1); ~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) const; void set_antenna(const std::string &antenna); double set_input_rate(double rate); double get_input_rate(void) const { return d_input_rate; } unsigned int set_input_decim(unsigned int decim); unsigned int get_input_decim(void) const { return d_decim; } double get_quad_rate(void) const { return d_input_rate / (double)d_decim; } double set_analog_bandwidth(double bw); double get_analog_bandwidth(void) const; void set_iq_swap(bool reversed); bool get_iq_swap(void) const; void set_dc_cancel(bool enable); bool get_dc_cancel(void) const; void set_iq_balance(bool enable); bool get_iq_balance(void) const; status set_rf_freq(double freq_hz); double get_rf_freq(void); 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) const; status set_auto_gain(bool automatic); status set_gain(std::string name, double value); double get_gain(std::string name) const; status set_filter_offset(double offset_hz); double get_filter_offset(void) const; status set_cw_offset(double offset_hz); double get_cw_offset(void) const; status set_filter(double low, double high, filter_shape shape); status set_freq_corr(double ppm); float get_signal_pwr(bool dbfs) const; 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 seek_iq_file(long pos); /* sample sniffer */ status start_sniffer(unsigned int samplrate, int buffsize); status stop_sniffer(); void get_sniffer_data(float * outbuff, unsigned int &num); bool is_recording_audio(void) const { return d_recording_wav; } bool is_snifffer_active(void) const { return d_sniffer_active; } /* rds functions */ void get_rds_data(std::string &outbuff, int &num); void start_rds_decoder(void); void stop_rds_decoder(); bool is_rds_decoder_active(void) const; void reset_rds_parser(void); 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_quad_rate; /*!< Quadrature rate (input_rate / decim) */ double d_audio_rate; /*!< Audio output rate. */ unsigned int d_decim; /*!< input decimation. */ double d_rf_freq; /*!< Current RF frequency. */ double d_filter_offset; /*!< Current filter offset */ double d_cw_offset; /*!< CW offset */ bool d_recording_iq; /*!< Whether we are recording I/Q file. */ 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. */ fir_decim_cc_sptr input_decim; /*!< Input decimator. */ 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::file_sink::sptr iq_sink; /*!< I/Q file sink. */ 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. */ #elif WITH_PORTAUDIO portaudio_sink_sptr audio_snk; /*!< portaudio sink */ #else gr::audio::sink::sptr audio_snk; /*!< gr audio sink */ #endif //! Get a path to a file containing random bytes static std::string get_random_file(void); //! Get a path to a file containing all-zero bytes static std::string get_null_file(void); }; #endif // RECEIVER_H gqrx-2.9/src/applications/gqrx/remote_control.cpp000066400000000000000000000477601320142145500223550ustar00rootroot00000000000000/* -*- 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 #include #include "remote_control.h" #define DEFAULT_RC_PORT 7356 #define DEFAULT_RC_ALLOWED_HOSTS "::ffff:127.0.0.1" RemoteControl::RemoteControl(QObject *parent) : QObject(parent) { rc_freq = 0; rc_filter_offset = 0; bw_half = 740e3; rc_lnb_lo_mhz = 0.0; rc_mode = 0; rc_passband_lo = 0; rc_passband_hi = 0; signal_level = -200.0; squelch_level = -150.0; audio_recorder_status = false; receiver_running = false; hamlib_compatible = false; rc_port = DEFAULT_RC_PORT; rc_allowed_hosts.append(DEFAULT_RC_ALLOWED_HOSTS); rc_socket = 0; #if QT_VERSION < 0x050900 // Disable proxy setting detected by Qt // Workaround for https://bugreports.qt.io/browse/QTBUG-58374 // Fix: https://codereview.qt-project.org/#/c/186124/ rc_server.setProxy(QNetworkProxy::NoProxy); #endif connect(&rc_server, SIGNAL(newConnection()), this, SLOT(acceptConnection())); } RemoteControl::~RemoteControl() { stop_server(); } /*! \brief Start the server. */ void RemoteControl::start_server() { if (!rc_server.isListening()) 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) { if (!settings) return; settings->beginGroup("remote_control"); // Get port number; restart server if running if (settings->contains("port")) setPort(settings->value("port").toInt()); // Get list of allowed hosts if (settings->contains("allowed_hosts")) setHosts(settings->value("allowed_hosts").toStringList()); settings->endGroup(); } void RemoteControl::saveSettings(QSettings *settings) const { if (!settings) return; settings->beginGroup("remote_control"); if (rc_server.isListening()) settings->setValue("enabled", true); else settings->remove("enabled"); if (rc_port != DEFAULT_RC_PORT) settings->setValue("port", rc_port); else settings->remove("port"); if (rc_allowed_hosts.count() > 0) settings->setValue("allowed_hosts", rc_allowed_hosts); else settings->remove("allowed_hosts"); settings->endGroup(); } /*! \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 = hosts; } /*! \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) { std::cout << "*** Remote connection attempt from " << address.toStdString() << " (not in allowed list)" << std::endl; 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; QString answer = ""; bytes_read = rc_socket->readLine(buffer, 1024); if (bytes_read < 2) // command + '\n' return; QStringList cmdlist = QString(buffer).trimmed().split(" ", QString::SkipEmptyParts); if (cmdlist.size() == 0) return; QString cmd = cmdlist[0]; if (cmd == "f") answer = cmd_get_freq(); else if (cmd == "F") answer = cmd_set_freq(cmdlist); else if (cmd == "m") answer = cmd_get_mode(); else if (cmd == "M") answer = cmd_set_mode(cmdlist); else if (cmd == "l") answer = cmd_get_level(cmdlist); else if (cmd == "L") answer = cmd_set_level(cmdlist); else if (cmd == "u") answer = cmd_get_func(cmdlist); else if (cmd == "U") answer = cmd_set_func(cmdlist); else if (cmd == "v") answer = cmd_get_vfo(); else if (cmd == "V") answer = cmd_set_vfo(cmdlist); else if (cmd == "s") answer = cmd_get_split_vfo(); else if (cmd == "S") answer = cmd_set_split_vfo(); else if (cmd == "_") answer = cmd_get_info(); else if (cmd == "AOS") answer = cmd_AOS(); else if (cmd == "LOS") answer = cmd_LOS(); else if (cmd == "LNB_LO") answer = cmd_lnb_lo(cmdlist); else if (cmd == "\\dump_state") answer = cmd_dump_state(); else if (cmd == "q" || cmd == "Q") { // FIXME: for now we assume 'close' command rc_socket->close(); return; } else { // print unknown command and respond with an error qWarning() << "Unknown remote command:" << cmdlist; answer = QString("RPRT 1\n"); } rc_socket->write(answer.toLatin1()); } /*! \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; } /*! \brief Slot called when the LNB LO frequency has changed * \param freq_mhz new LNB LO frequency in MHz */ void RemoteControl::setLnbLo(double freq_mhz) { rc_lnb_lo_mhz = freq_mhz; } void RemoteControl::setBandwidth(qint64 bw) { // we want to leave some margin bw_half = (qint64)(0.9f * (bw / 2.f)); } /*! \brief Set signal level in dBFS. */ void RemoteControl::setSignalLevel(float level) { signal_level = level; } /*! \brief Set demodulator (from mainwindow). */ void RemoteControl::setMode(int mode) { rc_mode = mode; if (rc_mode == 0) audio_recorder_status = false; } /*! \brief Set passband (from mainwindow). */ void RemoteControl::setPassband(int passband_lo, int passband_hi) { rc_passband_lo = passband_lo; rc_passband_hi = passband_hi; } /*! \brief New remote frequency received. */ void RemoteControl::setNewRemoteFreq(qint64 freq) { qint64 delta = freq - rc_freq; qint64 bwh_eff = 0.8f * (float)bw_half; rc_filter_offset += delta; if ((rc_filter_offset > 0 && rc_filter_offset + rc_passband_hi < bwh_eff) || (rc_filter_offset < 0 && rc_filter_offset + rc_passband_lo > -bwh_eff)) { // move filter offset emit newFilterOffset(rc_filter_offset); } else { // moving filter offset would push it too close to or beyond the edge // move it close to the center and adjust hardware freq if (rc_filter_offset < 0) rc_filter_offset = -0.2f * bwh_eff; else rc_filter_offset = 0.2f * bwh_eff; emit newFilterOffset(rc_filter_offset); emit newFrequency(freq); } rc_freq = freq; } /*! \brief Set squelch level (from mainwindow). */ void RemoteControl::setSquelchLevel(double level) { squelch_level = level; } /*! \brief Start audio recorder (from mainwindow). */ void RemoteControl::startAudioRecorder(QString unused) { if (rc_mode > 0) audio_recorder_status = true; } /*! \brief Stop audio recorder (from mainwindow). */ void RemoteControl::stopAudioRecorder() { audio_recorder_status = false; } /*! \brief Set receiver status (from mainwindow). */ void RemoteControl::setReceiverStatus(bool enabled) { receiver_running = enabled; } /*! \brief Convert mode string to enum (DockRxOpt::rxopt_mode_idx) * \param mode The Hamlib rigctld compatible mode string * \return An integer corresponding to the mode. * * Following mode strings are recognized: OFF, RAW, AM, FM, WFM, * WFM_ST, WFM_ST_OIRT, LSB, USB, CW, CWL, CWU. */ int RemoteControl::modeStrToInt(QString mode_str) { int mode_int = -1; if (mode_str.compare("OFF", Qt::CaseInsensitive) == 0) { mode_int = 0; } else if (mode_str.compare("RAW", Qt::CaseInsensitive) == 0) { mode_int = 1; } else if (mode_str.compare("AM", Qt::CaseInsensitive) == 0) { mode_int = 2; } else if (mode_str.compare("FM", Qt::CaseInsensitive) == 0) { mode_int = 3; } else if (mode_str.compare("WFM", Qt::CaseInsensitive) == 0) { mode_int = 4; } else if (mode_str.compare("WFM_ST", Qt::CaseInsensitive) == 0) { mode_int = 5; } else if (mode_str.compare("LSB", Qt::CaseInsensitive) == 0) { mode_int = 6; } else if (mode_str.compare("USB", Qt::CaseInsensitive) == 0) { mode_int = 7; } else if (mode_str.compare("CW", Qt::CaseInsensitive) == 0) { mode_int = 9; hamlib_compatible = true; } else if (mode_str.compare("CWL", Qt::CaseInsensitive) == 0) { mode_int = 8; hamlib_compatible = false; } else if (mode_str.compare("CWR", Qt::CaseInsensitive) == 0) { mode_int = 8; hamlib_compatible = true; } else if (mode_str.compare("CWU", Qt::CaseInsensitive) == 0) { mode_int = 9; hamlib_compatible = false; } else if (mode_str.compare("WFM_ST_OIRT", Qt::CaseInsensitive) == 0) { mode_int = 10; } return mode_int; } /*! \brief Convert mode enum to string. * \param mode The mode ID c.f. DockRxOpt::rxopt_mode_idx * \returns The mode string. */ QString RemoteControl::intToModeStr(int mode) { QString mode_str; switch (mode) { case 0: mode_str = "OFF"; break; case 1: mode_str = "RAW"; break; case 2: mode_str = "AM"; break; case 3: mode_str = "FM"; break; case 4: mode_str = "WFM"; break; case 5: mode_str = "WFM_ST"; break; case 6: mode_str = "LSB"; break; case 7: mode_str = "USB"; break; case 8: mode_str = (hamlib_compatible) ? "CWR" : "CWL"; break; case 9: mode_str = (hamlib_compatible) ? "CW" : "CWU"; break; case 10: mode_str = "WFM_ST_OIRT"; break; default: mode_str = "ERR"; break; } return mode_str; } /* Get frequency */ QString RemoteControl::cmd_get_freq() const { return QString("%1\n").arg(rc_freq); } /* Set new frequency */ QString RemoteControl::cmd_set_freq(QStringList cmdlist) { bool ok; double freq = cmdlist.value(1, "ERR").toDouble(&ok); if (ok) { setNewRemoteFreq((qint64)freq); return QString("RPRT 0\n"); } return QString("RPRT 1\n"); } /* Get mode and passband */ QString RemoteControl::cmd_get_mode() { return QString("%1\n%2\n") .arg(intToModeStr(rc_mode)) .arg(rc_passband_hi - rc_passband_lo); } /* Set mode and passband */ QString RemoteControl::cmd_set_mode(QStringList cmdlist) { QString answer; QString cmd_arg = cmdlist.value(1, ""); if (cmd_arg == "?") answer = QString("OFF RAW AM FM WFM WFM_ST WFM_ST_OIRT LSB USB CW CWU CWR CWL\n"); else { int mode = modeStrToInt(cmd_arg); if (mode == -1) { // invalid mode string answer = QString("RPRT 1\n"); } else { rc_mode = mode; emit newMode(rc_mode); int passband = cmdlist.value(2, "0").toInt(); if ( passband != 0 ) emit newPassband(passband); if (rc_mode == 0) audio_recorder_status = false; answer = QString("RPRT 0\n"); } } return answer; } /* Get level */ QString RemoteControl::cmd_get_level(QStringList cmdlist) { QString answer; QString lvl = cmdlist.value(1, ""); if (lvl == "?") answer = QString("SQL STRENGTH\n"); else if (lvl.compare("STRENGTH", Qt::CaseInsensitive) == 0 || lvl.isEmpty()) answer = QString("%1\n").arg(signal_level, 0, 'f', 1); else if (lvl.compare("SQL", Qt::CaseInsensitive) == 0) answer = QString("%1\n").arg(squelch_level, 0, 'f', 1); else answer = QString("RPRT 1\n"); return answer; } /* Set level */ QString RemoteControl::cmd_set_level(QStringList cmdlist) { QString answer; QString lvl = cmdlist.value(1, ""); if (lvl == "?") answer = QString("SQL\n"); else if (lvl.compare("SQL", Qt::CaseInsensitive) == 0) { bool ok; double squelch = cmdlist.value(2, "ERR").toDouble(&ok); if (ok) { answer = QString("RPRT 0\n"); squelch_level = std::max(-150, std::min(0, squelch)); emit newSquelchLevel(squelch_level); } else { answer = QString("RPRT 1\n"); } } else { answer = QString("RPRT 1\n"); } return answer; } /* Get function */ QString RemoteControl::cmd_get_func(QStringList cmdlist) { QString answer; QString func = cmdlist.value(1, ""); if (func == "?") answer = QString("RECORD\n"); else if (func.compare("RECORD", Qt::CaseInsensitive) == 0) answer = QString("%1\n").arg(audio_recorder_status); else answer = QString("RPRT 1\n"); return answer; } /* Set function */ QString RemoteControl::cmd_set_func(QStringList cmdlist) { bool ok; QString answer; QString func = cmdlist.value(1, ""); int status = cmdlist.value(2, "ERR").toInt(&ok); if (func == "?") { answer = QString("RECORD\n"); } else if ((func.compare("RECORD", Qt::CaseInsensitive) == 0) && ok) { if (rc_mode == 0 || !receiver_running) { answer = QString("RPRT 1\n"); } else { answer = QString("RPRT 0\n"); audio_recorder_status = status; if (status) emit startAudioRecorderEvent(); else emit stopAudioRecorderEvent(); } } else { answer = QString("RPRT 1\n"); } return answer; } /* Get current 'VFO' (fake, only for hamlib) */ QString RemoteControl::cmd_get_vfo() const { return QString("VFOA\n"); }; /* Set 'VFO' (fake, only for hamlib) */ QString RemoteControl::cmd_set_vfo(QStringList cmdlist) { QString cmd_arg = cmdlist.value(1, ""); QString answer; if (cmd_arg == "?") answer = QString("VFOA\n"); else if (cmd_arg == "VFOA") answer = QString("RPRT 0\n"); else answer = QString("RPRT 1\n"); return answer; }; /* Get 'Split' mode (fake, only for hamlib) */ QString RemoteControl::cmd_get_split_vfo() const { return QString("0\nVFOA\n"); }; /* Set 'Split' mode (fake, only for hamlib) */ QString RemoteControl::cmd_set_split_vfo() { return QString("RPRT 1\n"); } /* Get info */ QString RemoteControl::cmd_get_info() const { return QString("Gqrx %1\n").arg(VERSION); }; /* Gpredict / Gqrx specific command: AOS - satellite AOS event */ QString RemoteControl::cmd_AOS() { if (rc_mode > 0 && receiver_running) { emit startAudioRecorderEvent(); audio_recorder_status = true; } return QString("RPRT 0\n"); } /* Gpredict / Gqrx specific command: LOS - satellite LOS event */ QString RemoteControl::cmd_LOS() { emit stopAudioRecorderEvent(); audio_recorder_status = false; return QString("RPRT 0\n"); } /* Set the LNB LO value */ QString RemoteControl::cmd_lnb_lo(QStringList cmdlist) { if(cmdlist.size() == 2) { bool ok; qint64 freq = cmdlist[1].toLongLong(&ok); if (ok) { rc_lnb_lo_mhz = freq / 1e6; emit newLnbLo(rc_lnb_lo_mhz); return QString("RPRT 0\n"); } return QString("RPRT 1\n"); } else { return QString("%1\n").arg((qint64)(rc_lnb_lo_mhz * 1e6)); } } /* * '\dump_state' used by hamlib clients, e.g. xdx, fldigi, rigctl and etc * More info: * https://github.com/N0NB/hamlib/blob/master/include/hamlib/rig.h (bit fields) * https://github.com/N0NB/hamlib/blob/master/dummy/netrigctl.c */ QString RemoteControl::cmd_dump_state() const { return QString( /* rigctl protocol version */ "0\n" /* rigctl model */ "2\n" /* ITU region */ "1\n" /* RX/TX frequency ranges * start, end, modes, low_power, high_power, vfo, ant * start/end - Start/End frequency [Hz] * modes - Bit field of RIG_MODE's (AM|CW|CWR|USB|LSB|FM|WFM) * low_power/high_power - Lower/Higher RF power in mW, * -1 for no power (ie. rx list) * vfo - VFO list equipped with this range (RIG_VFO_A) * ant - Antenna list equipped with this range, 0 means all * FIXME: limits can be gets from receiver::get_rf_range() */ "0.000000 10000000000.000000 0xef -1 -1 0x1 0x0\n" /* End of RX frequency ranges. */ "0 0 0 0 0 0 0\n" /* End of TX frequency ranges. The Gqrx is reciver only. */ "0 0 0 0 0 0 0\n" /* Tuning steps: modes, tuning_step */ "0xef 1\n" "0xef 0\n" /* End of tuning steps */ "0 0\n" /* Filter sizes: modes, width * FIXME: filter can be gets from filter_preset_table */ "0x82 500\n" /* CW | CWR normal */ "0x82 200\n" /* CW | CWR narrow */ "0x82 2000\n" /* CW | CWR wide */ "0x21 10000\n" /* AM | FM normal */ "0x21 5000\n" /* AM | FM narrow */ "0x21 20000\n" /* AM | FM wide */ "0x0c 2700\n" /* SSB normal */ "0x0c 1400\n" /* SSB narrow */ "0x0c 3900\n" /* SSB wide */ "0x40 160000\n" /* WFM normal */ "0x40 120000\n" /* WFM narrow */ "0x40 200000\n" /* WFM wide */ /* End of filter sizes */ "0 0\n" /* max_rit */ "0\n" /* max_xit */ "0\n" /* max_ifshift */ "0\n" /* Announces (bit field list) */ "0\n" /* RIG_ANN_NONE */ /* Preamp list in dB, 0 terminated */ "0\n" /* Attenuator list in dB, 0 terminated */ "0\n" /* Bit field list of get functions */ "0\n" /* RIG_FUNC_NONE */ /* Bit field list of set functions */ "0\n" /* RIG_FUNC_NONE */ /* Bit field list of get level */ "0x40000020\n" /* RIG_LEVEL_SQL | RIG_LEVEL_STRENGTH */ /* Bit field list of set level */ "0x20\n" /* RIG_LEVEL_SQL */ /* Bit field list of get parm */ "0\n" /* RIG_PARM_NONE */ /* Bit field list of set parm */ "0\n" /* RIG_PARM_NONE */); } gqrx-2.9/src/applications/gqrx/remote_control.h000066400000000000000000000116371320142145500220140ustar00rootroot00000000000000/* -*- 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 #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; } void setReceiverStatus(bool enabled); public slots: void setNewFrequency(qint64 freq); void setFilterOffset(qint64 freq); void setLnbLo(double freq_mhz); void setBandwidth(qint64 bw); void setSignalLevel(float level); void setMode(int mode); void setPassband(int passband_lo, int passband_hi); void setSquelchLevel(double level); void startAudioRecorder(QString unused); void stopAudioRecorder(); signals: void newFrequency(qint64 freq); void newFilterOffset(qint64 offset); void newLnbLo(double freq_mhz); void newMode(int mode); void newPassband(int passband); void newSquelchLevel(double level); void startAudioRecorderEvent(); void stopAudioRecorderEvent(); 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; double rc_lnb_lo_mhz; /*!< Current LNB LO freq in MHz */ int rc_mode; /*!< Current mode. */ int rc_passband_lo; /*!< Current low cutoff. */ int rc_passband_hi; /*!< Current high cutoff. */ float signal_level; /*!< Signal level in dBFS */ double squelch_level; /*!< Squelch level in dBFS */ bool audio_recorder_status; /*!< Recording enabled */ bool receiver_running; /*!< Wether the receiver is running or not */ bool hamlib_compatible; void setNewRemoteFreq(qint64 freq); int modeStrToInt(QString mode_str); QString intToModeStr(int mode); /* RC commands */ QString cmd_get_freq() const; QString cmd_set_freq(QStringList cmdlist); QString cmd_get_mode(); QString cmd_set_mode(QStringList cmdlist); QString cmd_get_level(QStringList cmdlist); QString cmd_set_level(QStringList cmdlist); QString cmd_get_func(QStringList cmdlist); QString cmd_set_func(QStringList cmdlist); QString cmd_get_vfo() const; QString cmd_set_vfo(QStringList cmdlist); QString cmd_get_split_vfo() const; QString cmd_set_split_vfo(); QString cmd_get_info() const; QString cmd_AOS(); QString cmd_LOS(); QString cmd_lnb_lo(QStringList cmdlist); QString cmd_dump_state() const; }; #endif // REMOTE_CONTROL_H gqrx-2.9/src/applications/gqrx/remote_control_settings.cpp000066400000000000000000000057111320142145500242630ustar00rootroot00000000000000/* -*- 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-2.9/src/applications/gqrx/remote_control_settings.h000066400000000000000000000030651320142145500237300ustar00rootroot00000000000000/* -*- 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-2.9/src/applications/gqrx/remote_control_settings.ui000066400000000000000000000131021320142145500241070ustar00rootroot00000000000000 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 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-2.9/src/dsp/000077500000000000000000000000001320142145500137175ustar00rootroot00000000000000gqrx-2.9/src/dsp/CMakeLists.txt000066400000000000000000000014561320142145500164650ustar00rootroot00000000000000 # Add the source files to SRCS_LIST add_source_files(SRCS_LIST afsk1200/cafsk12.cpp afsk1200/cafsk12.h afsk1200/costabf.c afsk1200/filter-i386.h afsk1200/filter.h filter/fir_decim.cpp filter/fir_decim.h filter/fir_decim_coef.h rds/api.h rds/constants.h rds/decoder_impl.cc rds/decoder_impl.h rds/decoder.h rds/parser_impl.cc rds/parser_impl.h rds/parser.h rds/tmc_events.h agc_impl.cpp agc_impl.h correct_iq_cc.cpp correct_iq_cc.h lpf.cpp lpf.h resampler_xx.cpp resampler_xx.h rx_agc_xx.cpp rx_agc_xx.h rx_demod_am.cpp rx_demod_am.h rx_demod_fm.cpp rx_demod_fm.h rx_fft.cpp rx_fft.h rx_filter.cpp rx_filter.h rx_meter.cpp rx_meter.h rx_noise_blanker_cc.cpp rx_noise_blanker_cc.h rx_rds.cpp rx_rds.h sniffer_f.cpp sniffer_f.h stereo_demod.cpp stereo_demod.h ) gqrx-2.9/src/dsp/afsk1200/000077500000000000000000000000001320142145500151465ustar00rootroot00000000000000gqrx-2.9/src/dsp/afsk1200/cafsk12.cpp000066400000000000000000000341031320142145500171050ustar00rootroot00000000000000/* * 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-2.9/src/dsp/afsk1200/cafsk12.h000066400000000000000000000111201320142145500165440ustar00rootroot00000000000000/* * 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-2.9/src/dsp/afsk1200/costabf.c000066400000000000000000000351211320142145500167350ustar00rootroot00000000000000/* * 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-2.9/src/dsp/afsk1200/filter-i386.h000066400000000000000000000163211320142145500172760ustar00rootroot00000000000000/* * 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-2.9/src/dsp/afsk1200/filter.h000066400000000000000000000067121320142145500166120ustar00rootroot00000000000000/* * 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 */ #ifndef _MSC_VER #define DECL_UNUSED __attribute__ ((unused)) #endif #ifndef DECL_UNUSED #define DECL_UNUSED #endif /* ---------------------------------------------------------------------- */ extern inline unsigned int hweight32(unsigned int w) DECL_UNUSED; extern inline unsigned int hweight16(unsigned short w) DECL_UNUSED; extern inline unsigned int hweight8(unsigned char w) DECL_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) DECL_UNUSED; extern inline unsigned int lcm(unsigned int x, unsigned int y) DECL_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-2.9/src/dsp/agc_impl.cpp000066400000000000000000000443261320142145500162070ustar00rootroot00000000000000////////////////////////////////////////////////////////////////////// // 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 LOG_MAX_AMPL log10f(MAX_AMPLITUDE) #define MIN_CONSTANT 1e-8 // const for calc log() so that a value of 0 magnitude == -8 // corresponding to -160dB. // K = 10^(-8 + log(MAX_AMP)) ////////////////////////////////////////////////////////////////////// // 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; m_SigDelayBuf_r = (float*)(&m_SigDelayBuf); m_ManualAgcGain = 0.f; m_DecayAve = 0.f; m_AttackAve = 0.f; m_AttackRiseAlpha = 0.f; m_AttackFallAlpha = 0.f; m_DecayRiseAlpha = 0.f; m_DecayFallAlpha = 0.f; m_FixedGain = 0.f; m_Knee = 0.f; m_GainSlope = 0.f; m_Peak = 0.f; m_SigDelayPtr = 0; m_MagBufPos = 0; m_DelaySamples = 0; m_WindowSamples = 0; m_HangTime = 0; m_HangTimer = 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_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 < MAX_DELAY_BUF; i++) { m_SigDelayBuf[i] = 0.0; m_MagBuf[i] = -16.0; } m_SigDelayPtr = 0; m_HangTimer = 0; m_Peak = -16.0; m_DecayAve = -5.0; m_AttackAve = -5.0; m_MagBufPos = 0; } // convert m_ThreshGain to linear manual gain value m_ManualAgcGain = MAX_MANUAL_AMPLITUDE * powf(10.0, (float)m_ManualGain / 20.0); // calculate parameters for AGC gain as a function of input magnitude m_Knee = (float)m_Threshold / 20.0; m_GainSlope = m_SlopeFactor / 100.0; // fixed gain value used below knee threshold m_FixedGain = AGC_OUTSCALE * powf(10.0, m_Knee * (m_GainSlope - 1.0) ); // calculate fast and slow filter values. m_AttackRiseAlpha = (1.0 - expf(-1.0 / (m_SampleRate * ATTACK_RISE_TIMECONST))); m_AttackFallAlpha = (1.0 - expf(-1.0 / (m_SampleRate * ATTACK_FALL_TIMECONST))); // make rise time DECAY_RISEFALL_RATIO of fall m_DecayRiseAlpha = (1.0 - expf(-1.0 / (m_SampleRate * (float)m_Decay * 0.001 * DECAY_RISEFALL_RATIO))); m_HangTime = (int)(m_SampleRate * (float)m_Decay * .001); if (m_UseHang) m_DecayFallAlpha = (1.0 - expf(-1.0 / (m_SampleRate * RELEASE_TIMECONST))); else m_DecayFallAlpha = (1.0 - expf(-1.0 / (m_SampleRate * (float)m_Decay * 0.001))); m_DelaySamples = (int)(m_SampleRate * DELAY_TIMECONST); m_WindowSamples = (int)(m_SampleRate * WINDOW_TIMECONST); // clamp Delay samples within buffer limit if (m_DelaySamples >= MAX_DELAY_BUF - 1) m_DelaySamples = MAX_DELAY_BUF - 1; } ////////////////////////////////////////////////////////////////////// // Automatic Gain Control calculator for COMPLEX data ////////////////////////////////////////////////////////////////////// void CAgc::ProcessData(int Length, const TYPECPX * pInData, TYPECPX * pOutData) { float gain; float mag; TYPECPX delayedin; if (m_AgcOn) { for (int i = 0; i < Length; i++) { // get latest input sample TYPECPX in = pInData[i]; // Get delayed sample of input signal delayedin = m_SigDelayBuf[m_SigDelayPtr]; // put new input sample into signal delay buffer m_SigDelayBuf[m_SigDelayPtr++] = in; // deal with delay buffer wrap around if (m_SigDelayPtr >= m_DelaySamples) m_SigDelayPtr = 0; mag = fabs(in.real()); float mim = fabs(in.imag()); if (mim > mag) mag = mim; mag = log10f(mag + MIN_CONSTANT) - LOG_MAX_AMPL; // create a sliding window of 'm_WindowSamples' magnitudes and output the peak value within the sliding window float tmp = m_MagBuf[m_MagBufPos]; // get oldest mag sample from buffer into tmp m_MagBuf[m_MagBufPos++] = mag; // put latest mag sample in buffer; if (m_MagBufPos >= m_WindowSamples) // deal with magnitude buffer wrap around m_MagBufPos = 0; if (mag > m_Peak) { m_Peak = mag; //if new sample is larger than current peak then use it, no need to look at buffer values } else { if (tmp == m_Peak) //tmp is oldest sample pulled out of buffer { //if oldest sample pulled out was last peak then need to find next highest peak in buffer m_Peak = -8.0; //set to lowest value to find next max peak //search all buffer for maximum value and set as new peak for (int i = 0; i < m_WindowSamples; i++) { tmp = m_MagBuf[i]; if (tmp > m_Peak) m_Peak = tmp; } } } if (m_UseHang) { // using hang timer mode if (m_Peak > m_AttackAve) // if power is rising (use m_AttackRiseAlpha time constant) m_AttackAve = (1.0 - m_AttackRiseAlpha) * m_AttackAve + m_AttackRiseAlpha * m_Peak; else // else magnitude is falling (use m_AttackFallAlpha time constant) m_AttackAve = (1.0 - m_AttackFallAlpha) * m_AttackAve + m_AttackFallAlpha * m_Peak; if (m_Peak > m_DecayAve) { // if magnitude is rising (use m_DecayRiseAlpha time constant) m_DecayAve = (1.0 - m_DecayRiseAlpha) * m_DecayAve + m_DecayRiseAlpha * m_Peak; // reset hang timer m_HangTimer = 0; } else { // here if decreasing signal if (m_HangTimer < m_HangTime) m_HangTimer++; // just inc and hold current m_DecayAve else // else decay with m_DecayFallAlpha which is RELEASE_TIMECONST m_DecayAve = (1.0 - m_DecayFallAlpha) * m_DecayAve + m_DecayFallAlpha * m_Peak; } } else { // using exponential decay mode // perform average of magnitude using 2 averagers each with separate rise and fall time constants if (m_Peak > m_AttackAve) //if magnitude is rising (use m_AttackRiseAlpha time constant) m_AttackAve = (1.0 - m_AttackRiseAlpha) * m_AttackAve + m_AttackRiseAlpha * m_Peak; else // else magnitude is falling (use m_AttackFallAlpha time constant) m_AttackAve = (1.0 - m_AttackFallAlpha) * m_AttackAve + m_AttackFallAlpha * m_Peak; if (m_Peak > m_DecayAve) // if magnitude is rising (use m_DecayRiseAlpha time constant) m_DecayAve = (1.0 - m_DecayRiseAlpha) * m_DecayAve + m_DecayRiseAlpha * m_Peak; else // else magnitude is falling (use m_DecayFallAlpha time constant) m_DecayAve = (1.0 - m_DecayFallAlpha) * m_DecayAve + m_DecayFallAlpha * m_Peak; } // use greater magnitude of attack or Decay Averager if (m_AttackAve > m_DecayAve) mag = m_AttackAve; else mag = m_DecayAve; // calc gain depending on which side of knee the magnitude is on if (mag <= m_Knee) // use fixed gain if below knee gain = m_FixedGain; else // use variable gain if above knee gain = AGC_OUTSCALE * powf(10.0, mag * (m_GainSlope - 1.0)); pOutData[i] = delayedin * gain; } } else { // manual gain just multiply by m_ManualGain for (int i = 0; i < Length; i++) { pOutData[i] = m_ManualAgcGain * pInData[i]; } } } ////////////////////////////////////////////////////////////////////// // Automatic Gain Control calculator for REAL data ////////////////////////////////////////////////////////////////////// void CAgc::ProcessData(int Length, const float *pInData, float * pOutData) { float gain; float mag; float delayedin; if (m_AgcOn) { for (int i = 0; i < Length; i++) { // get latest input sample float in = pInData[i]; // Get delayed sample of input signal delayedin = m_SigDelayBuf_r[m_SigDelayPtr]; // put new input sample into signal delay buffer m_SigDelayBuf_r[m_SigDelayPtr++] = in; if (m_SigDelayPtr >= m_DelaySamples) //deal with delay buffer wrap around m_SigDelayPtr = 0; // convert |mag| to log |mag| mag = log10f(fabs(in) + MIN_CONSTANT) - LOG_MAX_AMPL; // create a sliding window of 'm_WindowSamples' magnitudes and output the peak value within the sliding window float tmp = m_MagBuf[m_MagBufPos]; // get oldest mag sample from buffer into tmp m_MagBuf[m_MagBufPos++] = mag; // put latest mag sample in buffer; if (m_MagBufPos >= m_WindowSamples) // deal with magnitude buffer wrap around m_MagBufPos = 0; if (mag > m_Peak) { m_Peak = mag; // if new sample is larger than current peak then use it, no need to look at buffer values } else { if (tmp == m_Peak) // tmp is oldest sample pulled out of buffer { // if oldest sample pulled out was last peak then need to find next highest peak in buffer m_Peak = -8.0; // set to lowest value to find next max peak // search all buffer for maximum value and set as new peak for (int i = 0; i < m_WindowSamples; i++) { tmp = m_MagBuf[i]; if (tmp > m_Peak) m_Peak = tmp; } } } if (m_UseHang) { // using hang timer mode if (m_Peak > m_AttackAve) // if magnitude is rising (use m_AttackRiseAlpha time constant) m_AttackAve = (1.0 - m_AttackRiseAlpha) * m_AttackAve + m_AttackRiseAlpha * m_Peak; else // else magnitude is falling (use m_AttackFallAlpha time constant) m_AttackAve = (1.0 - m_AttackFallAlpha) * m_AttackAve + m_AttackFallAlpha * m_Peak; if (m_Peak > m_DecayAve) { // if magnitude is rising (use m_DecayRiseAlpha time constant) m_DecayAve = (1.0 - m_DecayRiseAlpha) * m_DecayAve + m_DecayRiseAlpha*m_Peak; m_HangTimer = 0; // reset hang timer } else { // here if decreasing signal if (m_HangTimer < m_HangTime) m_HangTimer++; // just inc and hold current m_DecayAve else // else decay with m_DecayFallAlpha which is RELEASE_TIMECONST m_DecayAve = (1.0 - m_DecayFallAlpha) * m_DecayAve + m_DecayFallAlpha * m_Peak; } } else { // using exponential decay mode // perform average of magnitude using 2 averagers each with separate rise and fall time constants if (m_Peak > m_AttackAve) // if magnitude is rising (use m_AttackRiseAlpha time constant) m_AttackAve = (1.0 - m_AttackRiseAlpha) * m_AttackAve + m_AttackRiseAlpha * m_Peak; else // else magnitude is falling (use m_AttackFallAlpha time constant) m_AttackAve = (1.0 - m_AttackFallAlpha) * m_AttackAve + m_AttackFallAlpha * m_Peak; if (m_Peak > m_DecayAve) // if magnitude is rising (use m_DecayRiseAlpha time constant) m_DecayAve = (1.0 - m_DecayRiseAlpha) * m_DecayAve + m_DecayRiseAlpha * m_Peak; else // else magnitude is falling (use m_DecayFallAlpha time constant) m_DecayAve = (1.0 - m_DecayFallAlpha) * m_DecayAve + m_DecayFallAlpha * m_Peak; } // use greater magnitude of attack or Decay Averager if (m_AttackAve > m_DecayAve) mag = m_AttackAve; else mag = m_DecayAve; // calc gain depending on which side of knee the magnitude is on if (mag <= m_Knee) // use fixed gain if below knee gain = m_FixedGain; else // use variable gain if above knee gain = AGC_OUTSCALE * powf(10.0, mag * (m_GainSlope - 1.0)); pOutData[i] = delayedin * gain; } } else { // manual gain just multiply by m_ManualGain for (int i = 0; i < Length; i++) pOutData[i] = m_ManualAgcGain * pInData[i]; } } gqrx-2.9/src/dsp/agc_impl.h000066400000000000000000000035201320142145500156430ustar00rootroot00000000000000////////////////////////////////////////////////////////////////////// // agc_impl.h: interface for 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 ////////////////////////////////////////////////////////////////////// #ifndef AGC_IMPL_H #define AGC_IMPL_H #include #define MAX_DELAY_BUF 2048 /* typedef struct _dCplx { double re; double im; } tDComplex; #define TYPECPX tDComplex */ #define TYPECPX std::complex 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, const TYPECPX * pInData, TYPECPX * pOutData); void ProcessData(int Length, const float * pInData, float * pOutData); private: bool m_AgcOn; bool m_UseHang; int m_Threshold; int m_ManualGain; int m_Decay; float m_SampleRate; float m_SlopeFactor; float m_ManualAgcGain; float m_DecayAve; float m_AttackAve; float m_AttackRiseAlpha; float m_AttackFallAlpha; float m_DecayRiseAlpha; float m_DecayFallAlpha; float m_FixedGain; float m_Knee; float m_GainSlope; float m_Peak; int m_SigDelayPtr; int m_MagBufPos; int m_DelaySamples; int m_WindowSamples; int m_HangTime; int m_HangTimer; TYPECPX m_SigDelayBuf[MAX_DELAY_BUF]; float* m_SigDelayBuf_r; float m_MagBuf[MAX_DELAY_BUF]; }; #endif // AGC_IMPL_H gqrx-2.9/src/dsp/correct_iq_cc.cpp000066400000000000000000000102551320142145500172250ustar00rootroot00000000000000/* -*- 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-2.9/src/dsp/correct_iq_cc.h000066400000000000000000000053631320142145500166760ustar00rootroot00000000000000/* -*- 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-2.9/src/dsp/filter/000077500000000000000000000000001320142145500152045ustar00rootroot00000000000000gqrx-2.9/src/dsp/filter/decimator.cpp000066400000000000000000000217121320142145500176620ustar00rootroot00000000000000/* * Decimate by power-of-2 using half band filters. * * Originally from CuteSdr and modified for nanosdr * * Copyright 2010 Moe Wheatley. * Copyright 2015 Alexandru Csete. * All rights reserved. * * This Software is released under the "Simplified BSD License" * */ #include #include #include "decimator.h" #include "filtercoef_hbf_70.h" #include "filtercoef_hbf_100.h" #include "filtercoef_hbf_140.h" #define MAX_HALF_BAND_BUFSIZE 32768 #define DECIM_IS_POWER_OF_2(x) ((x != 0) && ((x & (~x + 1)) == x)) Decimator::Decimator() { int i; decim = 0; atten = 0; for (i = 0; i < MAX_STAGES; i++) filter_table[i] = 0; } Decimator::~Decimator() { delete_filters(); } unsigned int Decimator::init(unsigned int _decim, unsigned int _att) { if (_decim == decim && _att == atten) return decim; if (_decim < 2 || _decim > MAX_DECIMATION || !DECIM_IS_POWER_OF_2(_decim)) return 0; delete_filters(); atten = _att; if (atten <= 70) { decim = init_filters_70(_decim); } else if (atten <= 100) { decim = init_filters_100(_decim); } else { decim = init_filters_140(_decim); } return decim; } int Decimator::process(int samples, gr_complex * pin, gr_complex * pout) { int i = 0; int n = samples; while (filter_table[i]) n = filter_table[i++]->DecBy2(n, pin, pin); for(i = 0; i < n; i++) pout[i] = pin[i]; return n; } void Decimator::delete_filters() { int i; for(i = 0; i < MAX_STAGES; i++) { if (filter_table[i]) { delete filter_table[i]; filter_table[i] = 0; } } } int Decimator::init_filters_70(unsigned int decimation) { int n = 0; while (decimation >= 2) { if (decimation >= 4) { filter_table[n++] = new CHalfBand11TapDecimateBy2(HBF_70_11); fprintf(stderr, " DEC %d: HBF_70_11 (unrolled)\n", n); } else if (decimation == 2) { filter_table[n++] = new CHalfBandDecimateBy2(HBF_70_39_LENGTH, HBF_70_39); fprintf(stderr, " DEC %d: HBF_70_39\n", n); } decimation /= 2; } return (1 << n); } int Decimator::init_filters_100(unsigned int decimation) { int n = 0; while (decimation >= 2) { if (decimation >= 8) { filter_table[n++] = new CHalfBand11TapDecimateBy2(HBF_100_11); fprintf(stderr, " DEC %d: HBF_100_11 (unrolled)\n", n); } else if (decimation == 4) { filter_table[n++] = new CHalfBandDecimateBy2(HBF_100_19_LENGTH, HBF_100_19); fprintf(stderr, " DEC %d: HBF_100_19\n", n); } else if (decimation == 2) { filter_table[n++] = new CHalfBandDecimateBy2(HBF_100_59_LENGTH, HBF_100_59); fprintf(stderr, " DEC %d: HBF_100_59\n", n); } decimation /= 2; } return (1 << n); } int Decimator::init_filters_140(unsigned int decimation) { int n = 0; while (decimation >= 2) { if (decimation >= 16) { filter_table[n++] = new CHalfBand11TapDecimateBy2(HBF_140_11); fprintf(stderr, " DEC %d: HBF_140_11 (unrolled)\n", n); } else if (decimation == 8) { filter_table[n++] = new CHalfBandDecimateBy2(HBF_140_15_LENGTH, HBF_140_15); fprintf(stderr, " DEC %d: HBF_140_15\n", n); } else if (decimation == 4) { filter_table[n++] = new CHalfBandDecimateBy2(HBF_140_27_LENGTH, HBF_140_27); fprintf(stderr, " DEC %d: HBF_140_27\n", n); } else if (decimation == 2) { filter_table[n++] = new CHalfBandDecimateBy2(HBF_140_87_LENGTH, HBF_140_87); fprintf(stderr, " DEC %d: HBF_140_87\n", n); } decimation /= 2; } return (1 << n); } Decimator::CHalfBandDecimateBy2::CHalfBandDecimateBy2(int len, const float * pCoef) : m_FirLength(len), m_pCoef(pCoef) { gr_complex CPXZERO(0.0,0.0); // create buffer for FIR implementation m_pHBFirBuf = new gr_complex[MAX_HALF_BAND_BUFSIZE]; for (int i = 0; i < MAX_HALF_BAND_BUFSIZE; i++) m_pHBFirBuf[i] = CPXZERO; } /* * Half band filter and decimate by 2 function. * Two restrictions on this routine: * InLength must be larger or equal to the Number of Halfband Taps * InLength must be an even number */ int Decimator::CHalfBandDecimateBy2::DecBy2(int InLength, gr_complex * pInData, gr_complex * pOutData) { gr_complex acc; int i; int j; int numoutsamples = 0; // copy input samples into buffer starting at position m_FirLength-1 for (i = 0, j = m_FirLength - 1; i < InLength; i++) m_pHBFirBuf[j++] = pInData[i]; // perform decimation FIR filter on even samples for (i = 0; i < InLength; i += 2) { acc = m_pHBFirBuf[i] * m_pCoef[0]; for (j = 0; j < m_FirLength; j += 2) acc += m_pHBFirBuf[i+j] * m_pCoef[j]; // center coefficient acc += m_pHBFirBuf[i+(m_FirLength-1)/2] * m_pCoef[(m_FirLength-1)/2]; pOutData[numoutsamples++] = acc; } // need to copy last m_FirLength - 1 input samples in buffer to beginning of buffer // for FIR wrap around management for (i = 0, j = InLength - m_FirLength + 1; i < m_FirLength - 1; i++) m_pHBFirBuf[i] = pInData[j++]; return numoutsamples; } /* * Decimate by 2 Fixed 11 Tap Halfband filter class implementation */ Decimator::CHalfBand11TapDecimateBy2::CHalfBand11TapDecimateBy2(const float * coef) { gr_complex CPXZERO(0.0, 0.0); // preload only the taps that are used since evey other one is zero // except center tap 5 H0 = coef[0]; H2 = coef[2]; H4 = coef[4]; H5 = coef[5]; H6 = coef[6]; H8 = coef[8]; H10 = coef[10]; d0 = CPXZERO; d1 = CPXZERO; d2 = CPXZERO; d3 = CPXZERO; d4 = CPXZERO; d5 = CPXZERO; d6 = CPXZERO; d7 = CPXZERO; d8 = CPXZERO; d9 = CPXZERO; } /* * Decimate by 2 Fixed 11 Tap Halfband filter class implementation * Two restrictions on this routine: * InLength must be larger or equal to the Number of Halfband Taps(11) * InLength must be an even number * Loop unrolled for speed */ int Decimator::CHalfBand11TapDecimateBy2::DecBy2(int InLength, gr_complex * pInData, gr_complex * pOutData) { // calculate first 10 samples using previous samples in delay buffer gr_complex tmpout[9]; tmpout[0] = H0 * d0 + H2 * d2 + H4 * d4 + H5 * d5 + H6 * d6 + H8 * d8 + H10 * pInData[0]; tmpout[1] = H0 * d2 + H2 * d4 + H4 * d6 + H5 * d7 + H6 * d8 + H8 * pInData[0] + H10 * pInData[2]; tmpout[2] = H0 * d4 + H2 * d6 + H4 * d8 + H5 * d9 + H6 * pInData[0] + H8 * pInData[2] + H10 * pInData[4]; tmpout[3] = H0 * d6 + H2 * d8 + H4 * pInData[0] + H5 * pInData[1] + H6 * pInData[2] + H8 * pInData[4] + H10 * pInData[6]; tmpout[4] = H0 * d8 + H2 * pInData[0] + H4 * pInData[2] + H5 * pInData[3] + H6 * pInData[4] + H8 * pInData[6] + H10 * pInData[8]; tmpout[5] = H0 * pInData[0] + H2 * pInData[2] + H4 * pInData[4] + H5 * pInData[5] + H6 * pInData[6] + H8 * pInData[8] + H10 * pInData[10]; tmpout[6] = H0 * pInData[2] + H2 * pInData[4] + H4 * pInData[6] + H5 * pInData[7] + H6 * pInData[8] + H8 * pInData[10] + H10 * pInData[12]; tmpout[7] = H0 * pInData[4] + H2 * pInData[6] + H4 * pInData[8] + H5 * pInData[9] + H6 * pInData[10] + H8 * pInData[12] + H10 * pInData[14]; tmpout[8] = H0 * pInData[6] + H2 * pInData[8] + H4 * pInData[10] + H5 * pInData[11] + H6 * pInData[12] + H8 * pInData[14] + H10 * pInData[16]; // now loop through remaining input samples gr_complex *pIn = &pInData[8]; gr_complex *pOut = &pOutData[9]; int i; for(i = 0; i < (InLength - 11 - 6) / 2; i++) { *pOut++ = (H0 * pIn[0]) + (H2 * pIn[2]) + (H4 * pIn[4]) + (H5 * pIn[5]) + (H6 * pIn[6]) + (H8 * pIn[8]) + (H10 * pIn[10]); pIn += 2; } // copy first outputs back into output array so outbuf can be same as inbuf pOutData[0] = tmpout[0]; pOutData[1] = tmpout[1]; pOutData[2] = tmpout[2]; pOutData[3] = tmpout[3]; pOutData[4] = tmpout[4]; pOutData[5] = tmpout[5]; pOutData[6] = tmpout[6]; pOutData[7] = tmpout[7]; pOutData[8] = tmpout[8]; // copy last 10 input samples into delay buffer for next time pIn = &pInData[InLength-1]; d9 = *pIn--; d8 = *pIn--; d7 = *pIn--; d6 = *pIn--; d5 = *pIn--; d4 = *pIn--; d3 = *pIn--; d2 = *pIn--; d1 = *pIn--; d0 = *pIn; return InLength / 2; } gqrx-2.9/src/dsp/filter/decimator.h000066400000000000000000000044441320142145500173320ustar00rootroot00000000000000/* * Decimate by power-of-2 classes using half band filters. * * Originally from CuteSdr and modified for nanosdr * * Copyright 2010 Moe Wheatley. * Copyright 2015 Alexandru Csete. * All rights reserved. * * This Software is released under the "Simplified BSD License" * */ #ifndef DECIMATOR_H #define DECIMATOR_H 1 #include #define MAX_DECIMATION 512 #define MAX_STAGES 9 class Decimator { public: Decimator(); virtual ~Decimator(); unsigned int init(unsigned int _decim, unsigned int _att); int process(int samples, gr_complex * pin, gr_complex * pout); private: /** * Abstract base class for all the different types of decimate by 2 stages */ class CDec2 { public: CDec2() {} virtual ~CDec2(){} virtual int DecBy2(int InLength, gr_complex * pInData, gr_complex * pOutData) = 0; }; /** * Generic dcimate-by-2 class */ class CHalfBandDecimateBy2 : public CDec2 { public: CHalfBandDecimateBy2(int len, const float * pCoef); ~CHalfBandDecimateBy2() { if(m_pHBFirBuf) delete [] m_pHBFirBuf; } int DecBy2(int InLength, gr_complex * pInData, gr_complex * pOutData); gr_complex *m_pHBFirBuf; int m_FirLength; const float *m_pCoef; }; /** * 11-tap half-band filter with unrolled loop. */ class CHalfBand11TapDecimateBy2 : public CDec2 { public: CHalfBand11TapDecimateBy2(const float * coef); ~CHalfBand11TapDecimateBy2() {} int DecBy2(int InLength, gr_complex * pInData, gr_complex * pOutData); // coefficients float H0, H2, H4, H5, H6, H8, H10; // delay buffer std::complex d0, d1, d2, d3, d4, d5, d6, d7, d8, d9; }; private: int init_filters_70(unsigned int decimation); int init_filters_100(unsigned int decimation); int init_filters_140(unsigned int decimation); void delete_filters(); CDec2 *filter_table[MAX_STAGES]; unsigned int atten; unsigned int decim; }; #endif gqrx-2.9/src/dsp/filter/filtercoef_hbf_100.h000066400000000000000000000056711320142145500207070ustar00rootroot00000000000000/* * Half-band filter coefficients with 100 dB stop band attenuation. * * Copyright 2015 Alexandru Csete * All rights reserved. * * This Software is released under the Simplified BSD License. * */ #ifndef FILTERCOEF_HBF_100_H #define FILTERCOEF_HBF_100_H // Normalized 100 dB alias free bandwidths #define HBF_100_11_BW 0.10f #define HBF_100_19_BW 0.20f #define HBF_100_59_BW 0.40f /* * Discrete-Time FIR Filter (real) * ------------------------------- * Filter Structure : Direct-Form FIR * Filter Length : 11 * Stable : Yes * Linear Phase : Yes (Type 1) * Alias free BW : 0.1 * Passband ripple : +/- 4e-5 dB * Stop band : -106 dB */ #define HBF_100_11_LENGTH 11 const float HBF_100_11[HBF_100_11_LENGTH] = { 0.006633419280515, 0.0, -0.05103572995383, 0.0, 0.2944047777616, 0.5, 0.2944047777616, 0.0, -0.05103572995383, 0.0, 0.006633419280515 }; /* * Discrete-Time FIR Filter (real) * ------------------------------- * Filter Structure : Direct-Form FIR * Filter Length : 19 * Stable : Yes * Linear Phase : Yes (Type 1) * Alias free BW : 0.2 * Passband ripple : +/- 3e-5 dB * Stop band : -110 dB */ #define HBF_100_19_LENGTH 19 const float HBF_100_19[HBF_100_19_LENGTH] = { 0.0006617102188732, 0.0, -0.005279598620593, 0.0, 0.02264160210385, 0.0, -0.07405775919395, 0.0, 0.3060356661387, 0.5, 0.3060356661387, 0.0, -0.07405775919395, 0.0, 0.02264160210385, 0.0, -0.005279598620593, 0.0, 0.0006617102188732 }; /* * Discrete-Time FIR Filter (real) * ------------------------------- * Filter Structure : Direct-Form FIR * Filter Length : 59 * Stable : Yes * Linear Phase : Yes (Type 1) * Alias free BW : 0.4 * Passband ripple : +/- 1e-4 dB * Stop band : -99 dB */ #define HBF_100_59_LENGTH 59 const float HBF_100_59[HBF_100_59_LENGTH] = { 3.501888525991e-005, 0.0, -0.0001214332078977, 0.0, 0.000317104911172, 0.0, -0.0006982363724448, 0.0, 0.001372073862195, 0.0, -0.002480664366365, 0.0, 0.004207318621822, 0.0, -0.006789354746327, 0.0, 0.01054794795195, 0.0, -0.01596102646845, 0.0, 0.02384893442861, 0.0, -0.03589869172827, 0.0, 0.05646939759171, 0.0, -0.1016387706686, 0.0, 0.3167960996293, 0.5, 0.3167960996293, 0.0, -0.1016387706686, 0.0, 0.05646939759171, 0.0, -0.03589869172827, 0.0, 0.02384893442861, 0.0, -0.01596102646845, 0.0, 0.01054794795195, 0.0, -0.006789354746327, 0.0, 0.004207318621822, 0.0, -0.002480664366365, 0.0, 0.001372073862195, 0.0, -0.0006982363724448, 0.0, 0.000317104911172, 0.0, -0.0001214332078977, 0.0, 3.501888525991e-005 }; #endif gqrx-2.9/src/dsp/filter/filtercoef_hbf_140.h000066400000000000000000000321561320142145500207110ustar00rootroot00000000000000/* * Half-band filter coefficients with 140 dB stop band attenuation. * * Copyright 2010-2011 Moe Wheatley * Copyright 2015 Alexandru Csete * All rights reserved. * * This Software is released under the Simplified BSD License. * */ #ifndef FILTERCOEF_HBF_140_H #define FILTERCOEF_HBF_140_H // normalized 140 dB alias free bandwidths for the filters #define HBF_140_7_BW 0.013 #define HBF_140_11_BW 0.050 #define HBF_140_15_BW 0.100 #define HBF_140_19_BW 0.143 #define HBF_140_23_BW 0.182 #define HBF_140_27_BW 0.216 #define HBF_140_31_BW 0.244 #define HBF_140_35_BW 0.268 #define HBF_140_39_BW 0.288 #define HBF_140_43_BW 0.306 #define HBF_140_47_BW 0.320 #define HBF_140_51_BW 0.333 #define HBF_140_87_BW 0.400 /* * Discrete-Time FIR Filter (real) * ------------------------------- * Filter Structure : Direct-Form FIR * Filter Length : 7 * Stable : Yes * Linear Phase : Yes (Type 1) * Alias free BW : 0.013 * Passband ripple : +/- 5e-7 dB * Stop band : -140 dB */ #define HBF_140_7_LENGTH 7 const float HBF_140_7[HBF_140_7_LENGTH] = { -0.0312891214414, 0.0, 0.2812890888126, 0.5, 0.2812890888126, 0.0, -0.0312891214414 }; /* * Discrete-Time FIR Filter (real) * ------------------------------- * Filter Structure : Direct-Form FIR * Filter Length : 11 * Stable : Yes * Linear Phase : Yes (Type 1) * Alias free BW : 0.05 * Passband ripple : +/- 1e-6 dB * Stop band : -140 dB */ #define HBF_140_11_LENGTH 11 const float HBF_140_11[HBF_140_11_LENGTH] = { 0.0060431029837374152, 0.0, -0.049372515458761493, 0.0, 0.29332944952052842, 0.5, 0.29332944952052842, 0.0, -0.049372515458761493, 0.0, 0.0060431029837374152, }; /* * Discrete-Time FIR Filter (real) * ------------------------------- * Filter Structure : Direct-Form FIR * Filter Length : 15 * Stable : Yes * Linear Phase : Yes (Type 1) * Alias free BW : 0.1 * Passband ripple : +/- 1e-6 dB * Stop band : -140 dB */ #define HBF_140_15_LENGTH 15 const float HBF_140_15[HBF_140_15_LENGTH] = { -0.001442203300285281, 0.0, 0.013017512802724852, 0.0, -0.061653278604903369, 0.0, 0.30007792316024057, 0.5, 0.30007792316024057, 0.0, -0.061653278604903369, 0.0, 0.013017512802724852, 0.0, -0.001442203300285281 }; /* * Discrete-Time FIR Filter (real) * ------------------------------- * Filter Structure : Direct-Form FIR * Filter Length : 19 * Stable : Yes * Linear Phase : Yes (Type 1) * Alias free BW : 0.143 * Passband ripple : +/- 1e-6 dB * Stop band : -140 dB */ #define HBF_140_19_LENGTH 19 const float HBF_140_19[HBF_140_19_LENGTH] = { 0.00042366527106480427, 0.0, -0.0040717333369021894, 0.0, 0.019895653881950692, 0.0, -0.070740034412329067, 0.0, 0.30449249772844139, 0.5, 0.30449249772844139, 0.0, -0.070740034412329067, 0.0, 0.019895653881950692, 0.0, -0.0040717333369021894, 0.0, 0.00042366527106480427 }; /* * Discrete-Time FIR Filter (real) * ------------------------------- * Filter Structure : Direct-Form FIR * Filter Length : 23 * Stable : Yes * Linear Phase : Yes (Type 1) * Alias free BW : 0.182 * Passband ripple : +/- 1e-6 dB * Stop band : -140 dB */ #define HBF_140_23_LENGTH 23 const float HBF_140_23[HBF_140_23_LENGTH] = { -0.00014987651418332164, 0.0, 0.0014748633283609852, 0.0, -0.0074416944990005314, 0.0, 0.026163522731980929, 0.0, -0.077593699116544707, 0.0, 0.30754683719791986, 0.5, 0.30754683719791986, 0.0, -0.077593699116544707, 0.0, 0.026163522731980929, 0.0, -0.0074416944990005314, 0.0, 0.0014748633283609852, 0.0, -0.00014987651418332164 }; /* * Discrete-Time FIR Filter (real) * ------------------------------- * Filter Structure : Direct-Form FIR * Filter Length : 27 * Stable : Yes * Linear Phase : Yes (Type 1) * Alias free BW : 0.216 * Passband ripple : +/- 1e-6 dB * Stop band : -140 dB */ #define HBF_140_27_LENGTH 27 const float HBF_140_27[HBF_140_27_LENGTH] = { 0.000063730426952664685, 0.0, -0.00061985193978569082, 0.0, 0.0031512504783365756, 0.0, -0.011173151342856621, 0.0, 0.03171888754393197, 0.0, -0.082917863582770729, 0.0, 0.3097770473566307, 0.5, 0.3097770473566307, 0.0, -0.082917863582770729, 0.0, 0.03171888754393197, 0.0, -0.011173151342856621, 0.0, 0.0031512504783365756, 0.0, -0.00061985193978569082, 0.0, 0.000063730426952664685 }; /* * Discrete-Time FIR Filter (real) * ------------------------------- * Filter Structure : Direct-Form FIR * Filter Length : 31 * Stable : Yes * Linear Phase : Yes (Type 1) * Alias free BW : 0.244 * Passband ripple : +/- 1e-6 dB * Stop band : -140 dB */ #define HBF_140_31_LENGTH 31 const float HBF_140_31[HBF_140_31_LENGTH] = { -0.000030957335326552226, 0.0, 0.00029271992847303054, 0.0, -0.0014770381124258423, 0.0, 0.0052539088990950535, 0.0, -0.014856378748476874, 0.0, 0.036406651919555999, 0.0, -0.08699862567952929, 0.0, 0.31140967076042625, 0.5, 0.31140967076042625, 0.0, -0.08699862567952929, 0.0, 0.036406651919555999, 0.0, -0.014856378748476874, 0.0, 0.0052539088990950535, 0.0, -0.0014770381124258423, 0.0, 0.00029271992847303054, 0.0, -0.000030957335326552226 }; /* * Discrete-Time FIR Filter (real) * ------------------------------- * Filter Structure : Direct-Form FIR * Filter Length : 35 * Stable : Yes * Linear Phase : Yes (Type 1) * Alias free BW : 0.268 * Passband ripple : +/- 1e-6 dB * Stop band : -140 dB */ #define HBF_140_35_LENGTH 35 const float HBF_140_35[HBF_140_35_LENGTH] = { 0.000017017718072971716, 0.0, -0.00015425042851962818, 0.0, 0.00076219685751140838, 0.0, -0.002691614694785393, 0.0, 0.0075927497927344764, 0.0, -0.018325727896057686, 0.0, 0.040351004914363969, 0.0, -0.090198224668969554, 0.0, 0.31264689763504327, 0.5, 0.31264689763504327, 0.0, -0.090198224668969554, 0.0, 0.040351004914363969, 0.0, -0.018325727896057686, 0.0, 0.0075927497927344764, 0.0, -0.002691614694785393, 0.0, 0.00076219685751140838, 0.0, -0.00015425042851962818, 0.0, 0.000017017718072971716 }; /* * Discrete-Time FIR Filter (real) * ------------------------------- * Filter Structure : Direct-Form FIR * Filter Length : 39 * Stable : Yes * Linear Phase : Yes (Type 1) * Alias free BW : 0.288 * Passband ripple : +/- 1e-6 dB * Stop band : -140 dB */ #define HBF_140_39_LENGTH 39 const float HBF_140_39[HBF_140_39_LENGTH] = { -0.000010175082832074367, 0.0, 0.000088036416015024345, 0.0, -0.00042370835558387595, 0.0, 0.0014772557414459019, 0.0, -0.0041468438954260153, 0.0, 0.0099579126901608011, 0.0, -0.021433527104289002, 0.0, 0.043598963493432855, 0.0, -0.092695953625928404, 0.0, 0.31358799113382152, 0.5, 0.31358799113382152, 0, -0.092695953625928404, 0.0, 0.043598963493432855, 0.0, -0.021433527104289002, 0.0, 0.0099579126901608011, 0.0, -0.0041468438954260153, 0.0, 0.0014772557414459019, 0.0, -0.00042370835558387595, 0.0, 0.000088036416015024345, 0.0, -0.000010175082832074367 }; /* * Discrete-Time FIR Filter (real) * ------------------------------- * Filter Structure : Direct-Form FIR * Filter Length : 43 * Stable : Yes * Linear Phase : Yes (Type 1) * Alias free BW : 0.306 * Passband ripple : +/- 1e-6 dB * Stop band : -139 dB */ #define HBF_140_43_LENGTH 43 const float HBF_140_43[HBF_140_43_LENGTH] = { 0.0000067666739082756387, 0.0, -0.000055275221547958285, 0.0, 0.00025654074579418561, 0.0, -0.0008748125689163153, 0.0, 0.0024249876017061502, 0.0, -0.0057775190656021748, 0.0, 0.012299834239523121, 0.0, -0.024244050662087069, 0.0, 0.046354303503099069, 0.0, -0.094729903598633314, 0.0, 0.31433918020123208, 0.5, 0.31433918020123208, 0.0, -0.094729903598633314, 0.0, 0.046354303503099069, 0.0, -0.024244050662087069, 0.0, 0.012299834239523121, 0.0, -0.0057775190656021748, 0.0, 0.0024249876017061502, 0.0, -0.0008748125689163153, 0.0, 0.00025654074579418561, 0.0, -0.000055275221547958285, 0.0, 0.0000067666739082756387 }; /* * Discrete-Time FIR Filter (real) * ------------------------------- * Filter Structure : Direct-Form FIR * Filter Length : 47 * Stable : Yes * Linear Phase : Yes (Type 1) * Alias free BW : 0.32 * Passband ripple : +/- 1e-6 dB * Stop band : -140 dB */ #define HBF_140_47_LENGTH 47 const float HBF_140_47[HBF_140_47_LENGTH] = { -0.0000045298314172004251, 0.0, 0.000035333704512843228, 0.0, -0.00015934776420643447, 0.0, 0.0005340788063118928, 0.0, -0.0014667949695500761, 0.0, 0.0034792089350833247, 0.0, -0.0073794356720317733, 0.0, 0.014393786384683398, 0.0, -0.026586603160193314, 0.0, 0.048538673667907428, 0.0, -0.09629115286535718, 0.0, 0.31490673428547367, 0.5, 0.31490673428547367, 0.0, -0.09629115286535718, 0.0, 0.048538673667907428, 0.0, -0.026586603160193314, 0.0, 0.014393786384683398, 0.0, -0.0073794356720317733, 0.0, 0.0034792089350833247, 0.0, -0.0014667949695500761, 0.0, 0.0005340788063118928, 0.0, -0.00015934776420643447, 0.0, 0.000035333704512843228, 0.0, -0.0000045298314172004251 }; /* * Discrete-Time FIR Filter (real) * ------------------------------- * Filter Structure : Direct-Form FIR * Filter Length : 51 * Stable : Yes * Linear Phase : Yes (Type 1) * Alias free BW : 0.333 * Passband ripple : +/- 1e-6 dB * Stop band : -139 dB */ #define HBF_140_51_LENGTH 51 const float HBF_140_51[HBF_140_51_LENGTH] = { 0.0000033359253688981639, 0.0, -0.000024584155158361803, 0.0, 0.00010677777483317733, 0.0, -0.00034890723143173914, 0.0, 0.00094239127078189603, 0.0, -0.0022118302078923137, 0.0, 0.0046575030752162277, 0.0, -0.0090130973415220566, 0.0, 0.016383673864361164, 0.0, -0.028697281101743237, 0.0, 0.05043292242400841, 0.0, -0.097611898315791965, 0.0, 0.31538104435015801, 0.5, 0.31538104435015801, 0.0, -0.097611898315791965, 0.0, 0.05043292242400841, 0.0, -0.028697281101743237, 0.0, 0.016383673864361164, 0.0, -0.0090130973415220566, 0.0, 0.0046575030752162277, 0.0, -0.0022118302078923137, 0.0, 0.00094239127078189603, 0.0, -0.00034890723143173914, 0.0, 0.00010677777483317733, 0.0, -0.000024584155158361803, 0.0, 0.0000033359253688981639 }; /* * Discrete-Time FIR Filter (real) * ------------------------------- * Filter Structure : Direct-Form FIR * Filter Length : 87 * Stable : Yes * Linear Phase : Yes (Type 1) * Alias free BW : 0.4 * Passband ripple : +/- 1e-6 dB * Stop band : -139 dB */ #define HBF_140_87_LENGTH 87 const float HBF_140_87[HBF_140_87_LENGTH] = { -6.662792202e-007, 0.0, 3.182105957e-006, 0.0, -1.036207959e-005, 0.0, 2.745109305e-005, 0.0, -6.34655371e-005, 0.0, 0.0001327739883, 0.0, -0.0002569012286, 0.0, 0.0004664516309, 0.0, -0.0008030554163, 0.0, 0.001321306569, 0.0, -0.002090826631, 0.0, 0.003198919585, 0.0, -0.004754905123, 0.0, 0.006898404565, 0.0, -0.009816322476, 0.0, 0.01377890632, 0.0, -0.01921991631, 0.0, 0.02692959271, 0.0, -0.03858491778, 0.0, 0.05855843052, 0.0, -0.1029648632, 0.0, 0.3172507286, 0.5, 0.3172507286, 0.0, -0.1029648632, 0.0, 0.05855843052, 0.0, -0.03858491778, 0.0, 0.02692959271, 0.0, -0.01921991631, 0.0, 0.01377890632, 0.0, -0.009816322476, 0.0, 0.006898404565, 0.0, -0.004754905123, 0.0, 0.003198919585, 0.0, -0.002090826631, 0.0, 0.001321306569, 0.0, -0.0008030554163, 0.0, 0.0004664516309, 0.0, -0.0002569012286, 0.0, 0.0001327739883, 0.0, -6.34655371e-005, 0.0, 2.745109305e-005, 0.0, -1.036207959e-005, 0.0, 3.182105957e-006, 0.0, -6.662792202e-007 }; #endif gqrx-2.9/src/dsp/filter/filtercoef_hbf_70.h000066400000000000000000000035551320142145500206340ustar00rootroot00000000000000/* * Half-band filter coefficients with 70 dB stop band attenuation. * * Copyright 2015 Alexandru Csete * All rights reserved. * * This Software is released under the Simplified BSD License. * */ #ifndef FILTERCOEF_HBF_70_H #define FILTERCOEF_HBF_70_H // Normalized 70 dB alias free bandwidths #define HBF_70_11_BW 0.2f #define HBF_70_39_BW 0.4f /* * Discrete-Time FIR Filter (real) * ------------------------------- * Filter Structure : Direct-Form FIR * Filter Length : 11 * Stable : Yes * Linear Phase : Yes (Type 1) * Alias free BW : 0.2 * Passband ripple : +/- 3.2e-3 dB * Stop band : -70 dB */ #define HBF_70_11_LENGTH 11 const float HBF_70_11[HBF_70_11_LENGTH] = { 0.009707733567516, 0.0, -0.05811715559409, 0.0, 0.2985919803575, 0.5, 0.2985919803575, 0.0, -0.05811715559409, 0.0, 0.009707733567516 }; /* * Discrete-Time FIR Filter (real) * ------------------------------- * Filter Structure : Direct-Form FIR * Filter Length : 39 * Stable : Yes * Linear Phase : Yes (Type 1) * Alias free BW : 0.4 * Passband ripple : +/- 3e-3 dB * Stop band : -70 dB */ #define HBF_70_39_LENGTH 39 const float HBF_70_39[HBF_70_39_LENGTH] = { -0.0006388614035059, 0.0, 0.001631195589637, 0.0, -0.003550156604839, 0.0, 0.006773869396241, 0.0, -0.01188293607946, 0.0, 0.01978182909123, 0.0, -0.03220528568021, 0.0, 0.05351179043142, 0.0, -0.09972534459278, 0.0, 0.3161340967929, 0.5, 0.3161340967929, 0.0, -0.09972534459278, 0.0, 0.05351179043142, 0.0, -0.03220528568021, 0.0, 0.01978182909123, 0.0, -0.01188293607946, 0.0, 0.006773869396241, 0.0, -0.003550156604839, 0.0, 0.001631195589637, 0.0, -0.0006388614035059 }; #endif gqrx-2.9/src/dsp/filter/fir_decim.cpp000066400000000000000000000207741320142145500176430ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2016 Alexandru Csete OZ9AEC. * Copyright 2017 Youssef Touil. * * 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 "fir_decim.h" #include "fir_decim_coef.h" #ifdef USE_NEW_FIR_DECIM struct decimation_stage { int decimation; int ratio; int length; const float *kernel; }; static const int decimation_stage_count = 8; static const decimation_stage decimation_stages[] = { { 2, 2, d_2_r_2_len, d_2_r_2_kernel }, { 4, 4, d_4_r_4_len, d_4_r_4_kernel }, { 8, 8, d_8_r_8_len, d_8_r_8_kernel }, { 16, 8, d_16_r_8_len, d_16_r_8_kernel }, { 32, 16, d_32_r_16_len, d_32_r_16_kernel }, { 64, 32, d_64_r_32_len, d_64_r_32_kernel }, { 128, 32, d_128_r_32_len, d_128_r_32_kernel }, { 256, 64, d_256_r_64_len, d_256_r_64_kernel } }; #endif fir_decim_cc_sptr make_fir_decim_cc(unsigned int decim) { return gnuradio::get_initial_sptr(new fir_decim_cc(decim)); } fir_decim_cc::fir_decim_cc(unsigned int decim) : gr::hier_block2("fir_decim_cc", gr::io_signature::make(1, 1, sizeof(gr_complex)), gr::io_signature::make(1, 1, sizeof(gr_complex))) { std::vector taps; #ifdef USE_NEW_FIR_DECIM int this_stage = 0; int index = decimation_stage_count - 1; std::cout << "Decimation: " << decim << std::endl; while (decim > 1 && index >= 0) { const decimation_stage *stage = &decimation_stages[index]; if (decim % stage->decimation == 0) { this_stage++; taps.assign(stage->kernel, stage->kernel + stage->length); if (this_stage == 1) fir1 = gr::filter::fir_filter_ccf::make(stage->ratio, taps); else if (this_stage == 2) fir2 = gr::filter::fir_filter_ccf::make(stage->ratio, taps); else if (this_stage == 3) // NB: currently max 2 stages fir3 = gr::filter::fir_filter_ccf::make(stage->ratio, taps); else std::cout << " Too many decimation stages: " << this_stage << std::endl; std::cout << " stage: " << this_stage << " ratio: " << stage->ratio << std::endl; decim /= stage->ratio; } else { index--; } } if (this_stage == 1) { connect(self(), 0, fir1, 0); connect(fir1, 0, self(), 0); } else if (this_stage == 2) { connect(self(), 0, fir1, 0); connect(fir1, 0, fir2, 0); connect(fir2, 0, self(), 0); } else { connect(self(), 0, fir1, 0); connect(fir1, 0, fir2, 0); connect(fir2, 0, fir3, 0); connect(fir3, 0, self(), 0); } #else switch (decim) { default: std::cout << "Invalid decimation: " << decim << " (falling back to 2)." << std::endl; // fallthrough case 2: // Stage Passband Stopband Decimation // FIR1 0.4500000000 0.5500000000 2 taps.assign(FIR_2_1_TAPS, FIR_2_1_TAPS + FIR_2_1_LEN); fir1 = gr::filter::fir_filter_ccf::make(2, taps); connect(self(), 0, fir1, 0); connect(fir1, 0, self(), 0); break; case 4: // Stage Passband Stopband Decimation // FIR1 0.2250000000 0.7750000000 2 // FIR2 0.4500000000 0.5500000000 2 taps.assign(FIR_4_1_TAPS, FIR_4_1_TAPS + FIR_4_1_LEN); fir1 = gr::filter::fir_filter_ccf::make(2, taps); taps.assign(FIR_4_2_TAPS, FIR_4_2_TAPS + FIR_4_2_LEN); fir2 = gr::filter::fir_filter_ccf::make(2, taps); connect(self(), 0, fir1, 0); connect(fir1, 0, fir2, 0); connect(fir2, 0, self(), 0); break; case 8: // Stage Passband Stopband Decimation // FIR1 0.1125000000 0.3875000000 4 // FIR2 0.4500000000 0.5500000000 2 taps.assign(FIR_8_1_TAPS, FIR_8_1_TAPS + FIR_8_1_LEN); fir1 = gr::filter::fir_filter_ccf::make(4, taps); taps.assign(FIR_8_2_TAPS, FIR_8_2_TAPS + FIR_8_2_LEN); fir2 = gr::filter::fir_filter_ccf::make(2, taps); connect(self(), 0, fir1, 0); connect(fir1, 0, fir2, 0); connect(fir2, 0, self(), 0); break; case 16: // Stage Passband Stopband Decimation // FIR1 0.0562500000 0.4437500000 4 // FIR2 0.2250000000 0.7750000000 2 // FIR3 0.4500000000 0.5500000000 2 taps.assign(FIR_16_1_TAPS, FIR_16_1_TAPS + FIR_16_1_LEN); fir1 = gr::filter::fir_filter_ccf::make(4, taps); taps.assign(FIR_16_2_TAPS, FIR_16_2_TAPS + FIR_16_2_LEN); fir2 = gr::filter::fir_filter_ccf::make(2, taps); taps.assign(FIR_16_3_TAPS, FIR_16_3_TAPS + FIR_16_3_LEN); fir3 = gr::filter::fir_filter_ccf::make(2, taps); connect(self(), 0, fir1, 0); connect(fir1, 0, fir2, 0); connect(fir2, 0, fir3, 0); connect(fir3, 0, self(), 0); break; case 32: // Stage Passband Stopband Decimation // FIR1 0.0281250000 0.2218750000 8 // FIR2 0.2250000000 0.7750000000 2 // FIR3 0.4500000000 0.5500000000 2 taps.assign(FIR_32_1_TAPS, FIR_32_1_TAPS + FIR_32_1_LEN); fir1 = gr::filter::fir_filter_ccf::make(8, taps); taps.assign(FIR_32_2_TAPS, FIR_32_2_TAPS + FIR_32_2_LEN); fir2 = gr::filter::fir_filter_ccf::make(2, taps); taps.assign(FIR_32_3_TAPS, FIR_32_3_TAPS + FIR_32_3_LEN); fir3 = gr::filter::fir_filter_ccf::make(2, taps); connect(self(), 0, fir1, 0); connect(fir1, 0, fir2, 0); connect(fir2, 0, fir3, 0); connect(fir3, 0, self(), 0); break; case 64: // Stage Passband Stopband Decimation // FIR1 0.0140625000 0.2359375000 8 // FIR2 0.1125000000 0.3875000000 4 // FIR3 0.4500000000 0.5500000000 2 taps.assign(FIR_64_1_TAPS, FIR_64_1_TAPS + FIR_64_1_LEN); fir1 = gr::filter::fir_filter_ccf::make(8, taps); taps.assign(FIR_64_2_TAPS, FIR_64_2_TAPS + FIR_64_2_LEN); fir2 = gr::filter::fir_filter_ccf::make(4, taps); taps.assign(FIR_64_3_TAPS, FIR_64_3_TAPS + FIR_64_3_LEN); fir3 = gr::filter::fir_filter_ccf::make(2, taps); connect(self(), 0, fir1, 0); connect(fir1, 0, fir2, 0); connect(fir2, 0, fir3, 0); connect(fir3, 0, self(), 0); break; case 128: // Stage Passband Stopband Decimation // FIR1 0.0070312500 0.1179688000 16 // FIR2 0.1125000000 0.3875000000 4 // FIR3 0.4500000000 0.5500000000 2 taps.assign(FIR_128_1_TAPS, FIR_128_1_TAPS + FIR_128_1_LEN); fir1 = gr::filter::fir_filter_ccf::make(16, taps); taps.assign(FIR_128_2_TAPS, FIR_128_2_TAPS + FIR_128_2_LEN); fir2 = gr::filter::fir_filter_ccf::make(4, taps); taps.assign(FIR_128_3_TAPS, FIR_128_3_TAPS + FIR_128_3_LEN); fir3 = gr::filter::fir_filter_ccf::make(2, taps); connect(self(), 0, fir1, 0); connect(fir1, 0, fir2, 0); connect(fir2, 0, fir3, 0); connect(fir3, 0, self(), 0); break; } #endif } fir_decim_cc::~fir_decim_cc() { } gqrx-2.9/src/dsp/filter/fir_decim.h000066400000000000000000000026711320142145500173040ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2016 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. */ #pragma once #include #include class fir_decim_cc; typedef boost::shared_ptr fir_decim_cc_sptr; fir_decim_cc_sptr make_fir_decim_cc(unsigned int decim); class fir_decim_cc : public gr::hier_block2 { friend fir_decim_cc_sptr make_fir_decim_cc(unsigned int decim); //protected: public: fir_decim_cc(unsigned int decim); public: ~fir_decim_cc(); private: gr::filter::fir_filter_ccf::sptr fir1; gr::filter::fir_filter_ccf::sptr fir2; gr::filter::fir_filter_ccf::sptr fir3; }; gqrx-2.9/src/dsp/filter/fir_decim_coef.h000066400000000000000000001350121320142145500202740ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2016 Alexandru Csete OZ9AEC. * Copyright 2017 Youssef Touil. * * 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. */ #pragma once #define USE_NEW_FIR_DECIM #ifdef USE_NEW_FIR_DECIM // New two-stage FIR decimator provided by Youssef static const int d_2_r_2_len = 69; static const float d_2_r_2_kernel[] = { 0.000399985734121014f, 0.002073370222476234f, 0.004853019240304940f, 0.005977464755456102f, 0.002625480716699286f, -0.002713628293157090f, -0.003379460736391527f, 0.001637205667907783f, 0.004080689203761314f, -0.000982433398470081f, -0.005119016867477646f, 0.000177424749467386f, 0.006424890506348696f, 0.001008090651156955f, -0.007915926496343137f, -0.002717062331286239f, 0.009511042111613645f, 0.005077150379032227f, -0.011151300589456603f, -0.008251061374053964f, 0.012781117215688672f, 0.012490374909303374f, -0.014344287749952384f, -0.018209086168089920f, 0.015778580923975386f, 0.026178028229057050f, -0.017029165534577999f, -0.038051941291319488f, 0.018060406839966188f, 0.058210457193828925f, -0.018831122390764166f, -0.102778567522516130f, 0.019291268777910851f, 0.317182812089170980f, 0.480536048712957740f, 0.317182812089170980f, 0.019291268777910851f, -0.102778567522516130f, -0.018831122390764166f, 0.058210457193828925f, 0.018060406839966188f, -0.038051941291319488f, -0.017029165534577999f, 0.026178028229057050f, 0.015778580923975386f, -0.018209086168089920f, -0.014344287749952384f, 0.012490374909303374f, 0.012781117215688672f, -0.008251061374053964f, -0.011151300589456603f, 0.005077150379032227f, 0.009511042111613645f, -0.002717062331286239f, -0.007915926496343137f, 0.001008090651156955f, 0.006424890506348696f, 0.000177424749467386f, -0.005119016867477646f, -0.000982433398470081f, 0.004080689203761314f, 0.001637205667907783f, -0.003379460736391527f, -0.002713628293157090f, 0.002625480716699286f, 0.005977464755456102f, 0.004853019240304940f, 0.002073370222476234f, 0.000399985734121014f }; static const int d_4_r_4_len = 139; static const float d_4_r_4_kernel[] = { 0.000042167047949931f, 0.000162480875041441f, 0.000413474829003805f, 0.000815488719349860f, 0.001323484844111109f, 0.001803932401530970f, 0.002050805379425780f, 0.001851476423100084f, 0.001088358238163842f, -0.000165599354540670f, -0.001612840277093291f, -0.002799173360323900f, -0.003280737659419560f, -0.002830930204582274f, -0.001589296551711622f, -0.000060142418430366f, 0.001072157673911288f, 0.001252059507347206f, 0.000346781210023729f, -0.001233491421169468f, -0.002682865592015316f, -0.003178340139233828f, -0.002320119390193667f, -0.000402092792444196f, 0.001661437823975322f, 0.002761727219457103f, 0.002186981534424376f, 0.000071307331744879f, -0.002570893712874658f, -0.004301512250227543f, -0.004015094439084645f, -0.001606879839533175f, 0.001838674337622738f, 0.004510048531846937f, 0.004804547613530567f, 0.002266863620581679f, -0.002004704037111716f, -0.005785548895574678f, -0.006849655924079080f, -0.004239720077392586f, 0.001024318787772757f, 0.006275193248599259f, 0.008511512338709764f, 0.006070603106211769f, -0.000247611530141919f, -0.007317511868656072f, -0.011170336815604164f, -0.009152698013614463f, -0.001621157345452147f, 0.007851435931989321f, 0.014073238852672171f, 0.012972692080589394f, 0.004087473189926854f, -0.008634954375856124f, -0.018413964067748419f, -0.019050011539439745f, -0.008545626179408438f, 0.009039966712164142f, 0.024793283315122296f, 0.029034181171149441f, 0.016560363014866013f, -0.009522346448795137f, -0.037569562545468192f, -0.051411140638474247f, -0.037221789978391415f, 0.009679493935209005f, 0.081286213859684806f, 0.158552299980228740f, 0.217917169253869340f, 0.240164834839879550f, 0.217917169253869340f, 0.158552299980228740f, 0.081286213859684806f, 0.009679493935209005f, -0.037221789978391415f, -0.051411140638474247f, -0.037569562545468192f, -0.009522346448795137f, 0.016560363014866013f, 0.029034181171149441f, 0.024793283315122296f, 0.009039966712164142f, -0.008545626179408438f, -0.019050011539439745f, -0.018413964067748419f, -0.008634954375856124f, 0.004087473189926854f, 0.012972692080589394f, 0.014073238852672171f, 0.007851435931989321f, -0.001621157345452147f, -0.009152698013614463f, -0.011170336815604164f, -0.007317511868656072f, -0.000247611530141919f, 0.006070603106211769f, 0.008511512338709764f, 0.006275193248599259f, 0.001024318787772757f, -0.004239720077392586f, -0.006849655924079080f, -0.005785548895574678f, -0.002004704037111716f, 0.002266863620581679f, 0.004804547613530567f, 0.004510048531846937f, 0.001838674337622738f, -0.001606879839533175f, -0.004015094439084645f, -0.004301512250227543f, -0.002570893712874658f, 0.000071307331744879f, 0.002186981534424376f, 0.002761727219457103f, 0.001661437823975322f, -0.000402092792444196f, -0.002320119390193667f, -0.003178340139233828f, -0.002682865592015316f, -0.001233491421169468f, 0.000346781210023729f, 0.001252059507347206f, 0.001072157673911288f, -0.000060142418430366f, -0.001589296551711622f, -0.002830930204582274f, -0.003280737659419560f, -0.002799173360323900f, -0.001612840277093291f, -0.000165599354540670f, 0.001088358238163842f, 0.001851476423100084f, 0.002050805379425780f, 0.001803932401530970f, 0.001323484844111109f, 0.000815488719349860f, 0.000413474829003805f, 0.000162480875041441f, 0.000042167047949931f }; static const int d_8_r_8_len = 280; static const float d_8_r_8_kernel[] = { 0.000005299484859782f, 0.000023653198869943f, 0.000045443155025411f, 0.000086660616597102f, 0.000145468147843215f, 0.000227646032445337f, 0.000334591194217999f, 0.000466890457500183f, 0.000622018386430552f, 0.000794466626663634f, 0.000975304575250323f, 0.001152454785657775f, 0.001311296864337127f, 0.001435783630074849f, 0.001509953148215840f, 0.001519741581366084f, 0.001454903824191261f, 0.001310813201213376f, 0.001089882810057675f, 0.000802361759738747f, 0.000466304093914126f, 0.000106589241438445f, -0.000247017317721992f, -0.000562646941659305f, -0.000809708828030606f, -0.000962510996013040f, -0.001003685261621032f, -0.000926952775265634f, -0.000738799897838057f, -0.000458730744403599f, -0.000117916487516219f, 0.000243742648939376f, 0.000581911777806535f, 0.000853035291692318f, 0.001019866746633974f, 0.001056611000192218f, 0.000952962453331239f, 0.000716408838645576f, 0.000372351073038558f, -0.000038150274869667f, -0.000462881782404900f, -0.000844797294892937f, -0.001129425716907750f, -0.001272432677940103f, -0.001246307890160262f, -0.001045192378203970f, -0.000687044203214210f, -0.000212653183490116f, 0.000318579724580746f, 0.000835734644546107f, 0.001265542439063603f, 0.001542487733459282f, 0.001618487960413105f, 0.001470755262270050f, 0.001106608046232222f, 0.000564346197878288f, -0.000090197750452171f, -0.000771230463597417f, -0.001383890554706424f, -0.001837132239362339f, -0.002056896211703681f, -0.001997709076123834f, -0.001650953765714851f, -0.001048413504576680f, -0.000260276733547870f, 0.000612469118131840f, 0.001450527381084600f, 0.002132190093380017f, 0.002550584440683641f, 0.002629881881769309f, 0.002338080196576415f, 0.001694308341293216f, 0.000769260408641065f, -0.000321736170028782f, -0.001432485915523021f, -0.002404976322971185f, -0.003091310085438950f, -0.003375549372556256f, -0.003192330168178722f, -0.002539372888669491f, -0.001481713556478757f, -0.000146533823587781f, 0.001291251516558131f, 0.002631154368260663f, 0.003674321213032700f, 0.004252198884660451f, 0.004252637433548458f, 0.003639528435255283f, 0.002462762274858131f, 0.000856475239499534f, -0.000974892854265319f, -0.002781496880057425f, -0.004301065594668099f, -0.005295755541230454f, -0.005587859925192526f, -0.005089203765495300f, -0.003819632950038145f, -0.001911267954229858f, 0.000402995000090862f, 0.002816115056235722f, 0.004985868963592199f, 0.006581423530514738f, 0.007331423828496663f, 0.007066900231758860f, 0.005752595831756849f, 0.003501526672561130f, 0.000569595914029656f, -0.002670342281476470f, -0.005772755486290897f, -0.008277141861910673f, -0.009772095428011296f, -0.009957304065452081f, -0.008694779007964944f, -0.006041501906687299f, -0.002257673645446692f, 0.002212348869418207f, 0.006785874581560984f, 0.010810304734210376f, 0.013646746123386143f, 0.014758915386036504f, 0.013796112346034117f, 0.010659097540521932f, 0.005539210274923477f, -0.001076115841043161f, -0.008434444147245189f, -0.015584625308516726f, -0.021472430244782261f, -0.025055744331654087f, -0.025426655616721288f, -0.021926322954481325f, -0.014238733805135361f, -0.002451384366749276f, 0.012925647002682830f, 0.030986186274100380f, 0.050498778538203032f, 0.070015421670629074f, 0.088007266252761618f, 0.103012981470773550f, 0.113783757663955240f, 0.119409036455313980f, 0.119409036455313980f, 0.113783757663955240f, 0.103012981470773550f, 0.088007266252761618f, 0.070015421670629074f, 0.050498778538203032f, 0.030986186274100380f, 0.012925647002682830f, -0.002451384366749276f, -0.014238733805135361f, -0.021926322954481325f, -0.025426655616721288f, -0.025055744331654087f, -0.021472430244782261f, -0.015584625308516726f, -0.008434444147245189f, -0.001076115841043161f, 0.005539210274923477f, 0.010659097540521932f, 0.013796112346034117f, 0.014758915386036504f, 0.013646746123386143f, 0.010810304734210376f, 0.006785874581560984f, 0.002212348869418207f, -0.002257673645446692f, -0.006041501906687299f, -0.008694779007964944f, -0.009957304065452081f, -0.009772095428011296f, -0.008277141861910673f, -0.005772755486290897f, -0.002670342281476470f, 0.000569595914029656f, 0.003501526672561130f, 0.005752595831756849f, 0.007066900231758860f, 0.007331423828496663f, 0.006581423530514738f, 0.004985868963592199f, 0.002816115056235722f, 0.000402995000090862f, -0.001911267954229858f, -0.003819632950038145f, -0.005089203765495300f, -0.005587859925192526f, -0.005295755541230454f, -0.004301065594668099f, -0.002781496880057425f, -0.000974892854265319f, 0.000856475239499534f, 0.002462762274858131f, 0.003639528435255283f, 0.004252637433548458f, 0.004252198884660451f, 0.003674321213032700f, 0.002631154368260663f, 0.001291251516558131f, -0.000146533823587781f, -0.001481713556478757f, -0.002539372888669491f, -0.003192330168178722f, -0.003375549372556256f, -0.003091310085438950f, -0.002404976322971185f, -0.001432485915523021f, -0.000321736170028782f, 0.000769260408641065f, 0.001694308341293216f, 0.002338080196576415f, 0.002629881881769309f, 0.002550584440683641f, 0.002132190093380017f, 0.001450527381084600f, 0.000612469118131840f, -0.000260276733547870f, -0.001048413504576680f, -0.001650953765714851f, -0.001997709076123834f, -0.002056896211703681f, -0.001837132239362339f, -0.001383890554706424f, -0.000771230463597417f, -0.000090197750452171f, 0.000564346197878288f, 0.001106608046232222f, 0.001470755262270050f, 0.001618487960413105f, 0.001542487733459282f, 0.001265542439063603f, 0.000835734644546107f, 0.000318579724580746f, -0.000212653183490116f, -0.000687044203214210f, -0.001045192378203970f, -0.001246307890160262f, -0.001272432677940103f, -0.001129425716907750f, -0.000844797294892937f, -0.000462881782404900f, -0.000038150274869667f, 0.000372351073038558f, 0.000716408838645576f, 0.000952962453331239f, 0.001056611000192218f, 0.001019866746633974f, 0.000853035291692318f, 0.000581911777806535f, 0.000243742648939376f, -0.000117916487516219f, -0.000458730744403599f, -0.000738799897838057f, -0.000926952775265634f, -0.001003685261621032f, -0.000962510996013040f, -0.000809708828030606f, -0.000562646941659305f, -0.000247017317721992f, 0.000106589241438445f, 0.000466304093914126f, 0.000802361759738747f, 0.001089882810057675f, 0.001310813201213376f, 0.001454903824191261f, 0.001519741581366084f, 0.001509953148215840f, 0.001435783630074849f, 0.001311296864337127f, 0.001152454785657775f, 0.000975304575250323f, 0.000794466626663634f, 0.000622018386430552f, 0.000466890457500183f, 0.000334591194217999f, 0.000227646032445337f, 0.000145468147843215f, 0.000086660616597102f, 0.000045443155025411f, 0.000023653198869943f, 0.000005299484859782f }; static const int d_16_r_8_len = 54; static const float d_16_r_8_kernel[] = { -0.000010553664672862f, -0.000061498701563991f, -0.000169601122616288f, -0.000389180581238296f, -0.000769984128346191f, -0.001370862255977838f, -0.002239657737237394f, -0.003399895346870093f, -0.004830710741492023f, -0.006448450828431843f, -0.008091858901382079f, -0.009515843884697457f, -0.010397443295914180f, -0.010356613550454467f, -0.008992200646607676f, -0.005930785604319544f, -0.000883275448312690f, 0.006298249742162992f, 0.015572313369334116f, 0.026677316967261836f, 0.039125602626276818f, 0.052226529544645239f, 0.065138652777540620f, 0.076946528099683636f, 0.086753462256061120f, 0.093778457566739207f, 0.097444274566674538f, 0.097444274566674538f, 0.093778457566739207f, 0.086753462256061120f, 0.076946528099683636f, 0.065138652777540620f, 0.052226529544645239f, 0.039125602626276818f, 0.026677316967261836f, 0.015572313369334116f, 0.006298249742162992f, -0.000883275448312690f, -0.005930785604319544f, -0.008992200646607676f, -0.010356613550454467f, -0.010397443295914180f, -0.009515843884697457f, -0.008091858901382079f, -0.006448450828431843f, -0.004830710741492023f, -0.003399895346870093f, -0.002239657737237394f, -0.001370862255977838f, -0.000769984128346191f, -0.000389180581238296f, -0.000169601122616288f, -0.000061498701563991f, -0.000010553664672862f }; static const int d_32_r_16_len = 107; static const float d_32_r_16_kernel[] = { -0.000004024163639795f, -0.000017672975608646f, -0.000028369983125930f, -0.000051841257358260f, -0.000084114931088978f, -0.000130374705334955f, -0.000193165706690690f, -0.000276282392544972f, -0.000383294466797586f, -0.000517836385761646f, -0.000683246849920059f, -0.000882378788952720f, -0.001117308161626894f, -0.001389043294976996f, -0.001697213461428019f, -0.002039760314912465f, -0.002412643435722481f, -0.002809577646098091f, -0.003221818992095683f, -0.003638016956253216f, -0.004044149516577933f, -0.004423556043039536f, -0.004757080371798020f, -0.005023332869090757f, -0.005199075945795754f, -0.005259732456805450f, -0.005180010907146506f, -0.004934635615957972f, -0.004499164222082719f, -0.003850869437258024f, -0.002969657050308204f, -0.001838988139263688f, -0.000446770521598867f, 0.001213817123243464f, 0.003143605116121320f, 0.005336851770962665f, 0.007780833517050342f, 0.010455633416035585f, 0.013334149150643793f, 0.016382334116677784f, 0.019559676833852839f, 0.022819914826782396f, 0.026111969807273771f, 0.029381081811115123f, 0.032570111325119062f, 0.035620970792186964f, 0.038476140562399175f, 0.041080219701286683f, 0.043381459308086866f, 0.045333225316341701f, 0.046895339212960678f, 0.048035248704517766f, 0.048728985952954711f, 0.048961878385199747f, 0.048728985952954711f, 0.048035248704517766f, 0.046895339212960678f, 0.045333225316341701f, 0.043381459308086866f, 0.041080219701286683f, 0.038476140562399175f, 0.035620970792186964f, 0.032570111325119062f, 0.029381081811115123f, 0.026111969807273771f, 0.022819914826782396f, 0.019559676833852839f, 0.016382334116677784f, 0.013334149150643793f, 0.010455633416035585f, 0.007780833517050342f, 0.005336851770962665f, 0.003143605116121320f, 0.001213817123243464f, -0.000446770521598867f, -0.001838988139263688f, -0.002969657050308204f, -0.003850869437258024f, -0.004499164222082719f, -0.004934635615957972f, -0.005180010907146506f, -0.005259732456805450f, -0.005199075945795754f, -0.005023332869090757f, -0.004757080371798020f, -0.004423556043039536f, -0.004044149516577933f, -0.003638016956253216f, -0.003221818992095683f, -0.002809577646098091f, -0.002412643435722481f, -0.002039760314912465f, -0.001697213461428019f, -0.001389043294976996f, -0.001117308161626894f, -0.000882378788952720f, -0.000683246849920059f, -0.000517836385761646f, -0.000383294466797586f, -0.000276282392544972f, -0.000193165706690690f, -0.000130374705334955f, -0.000084114931088978f, -0.000051841257358260f, -0.000028369983125930f, -0.000017672975608646f, -0.000004024163639795f }; static const int d_64_r_32_len = 212; static const float d_64_r_32_kernel[] = { -0.000004403823942508f, -0.000008963059919890f, -0.000007276052892980f, -0.000013448546764569f, -0.000016407338246392f, -0.000022821877811828f, -0.000029059859493782f, -0.000037502053326083f, -0.000047050128769168f, -0.000058647705667398f, -0.000072066821233235f, -0.000087775432302674f, -0.000105848488644829f, -0.000126598475702349f, -0.000150205141042045f, -0.000176926965601505f, -0.000206973701715915f, -0.000240575602493366f, -0.000277937749971926f, -0.000319260777052827f, -0.000364722541989086f, -0.000414482134953896f, -0.000468670554403767f, -0.000527388854339363f, -0.000590701722925615f, -0.000658633566464172f, -0.000731163061150287f, -0.000808218604975926f, -0.000889673345622962f, -0.000975340650633742f, -0.001064969616730599f, -0.001158240957389821f, -0.001254763178484453f, -0.001354069203096897f, -0.001455613461052586f, -0.001558769541846508f, -0.001662828458596891f, -0.001766997597152117f, -0.001870400403116935f, -0.001972076865069115f, -0.002070984841172726f, -0.002166002272448999f, -0.002255930317598858f, -0.002339497436442704f, -0.002415364439683740f, -0.002482130514177367f, -0.002538340220525754f, -0.002582491452580508f, -0.002613044332960035f, -0.002628431011812668f, -0.002627066321370591f, -0.002607359228695053f, -0.002567725018271331f, -0.002506598123060911f, -0.002422445514576562f, -0.002313780550524778f, -0.002179177170034060f, -0.002017284317828416f, -0.001826840471286680f, -0.001606688137875605f, -0.001355788185007890f, -0.001073233860638388f, -0.000758264360902667f, -0.000410277798843436f, -0.000028843430628706f, 0.000386287004249912f, 0.000835168966487215f, 0.001317655958647924f, 0.001833391650175865f, 0.002381803404668191f, 0.002962097316406667f, 0.003573254853063226f, 0.004214031187543062f, 0.004882955288187807f, 0.005578331822331469f, 0.006298244909815173f, 0.007040563748502659f, 0.007802950114525442f, 0.008582867722817980f, 0.009377593413961051f, 0.010184230117287451f, 0.010999721518727896f, 0.011820868346749476f, 0.012644346170734195f, 0.013466724590201118f, 0.014284487677241258f, 0.015094055520281103f, 0.015891806704001060f, 0.016674101548252919f, 0.017437305919088521f, 0.018177815416425963f, 0.018892079736813970f, 0.019576627004361302f, 0.020228087861972253f, 0.020843219113185100f, 0.021418926707647539f, 0.021952287867040517f, 0.022440572154110033f, 0.022881261295670835f, 0.023272067580484536f, 0.023610950665041715f, 0.023896132633698622f, 0.024126111175283287f, 0.024299670755100852f, 0.024415891678976044f, 0.024474156966027200f, 0.024474156966027200f, 0.024415891678976044f, 0.024299670755100852f, 0.024126111175283287f, 0.023896132633698622f, 0.023610950665041715f, 0.023272067580484536f, 0.022881261295670835f, 0.022440572154110033f, 0.021952287867040517f, 0.021418926707647539f, 0.020843219113185100f, 0.020228087861972253f, 0.019576627004361302f, 0.018892079736813970f, 0.018177815416425963f, 0.017437305919088521f, 0.016674101548252919f, 0.015891806704001060f, 0.015094055520281103f, 0.014284487677241258f, 0.013466724590201118f, 0.012644346170734195f, 0.011820868346749476f, 0.010999721518727896f, 0.010184230117287451f, 0.009377593413961051f, 0.008582867722817980f, 0.007802950114525442f, 0.007040563748502659f, 0.006298244909815173f, 0.005578331822331469f, 0.004882955288187807f, 0.004214031187543062f, 0.003573254853063226f, 0.002962097316406667f, 0.002381803404668191f, 0.001833391650175865f, 0.001317655958647924f, 0.000835168966487215f, 0.000386287004249912f, -0.000028843430628706f, -0.000410277798843436f, -0.000758264360902667f, -0.001073233860638388f, -0.001355788185007890f, -0.001606688137875605f, -0.001826840471286680f, -0.002017284317828416f, -0.002179177170034060f, -0.002313780550524778f, -0.002422445514576562f, -0.002506598123060911f, -0.002567725018271331f, -0.002607359228695053f, -0.002627066321370591f, -0.002628431011812668f, -0.002613044332960035f, -0.002582491452580508f, -0.002538340220525754f, -0.002482130514177367f, -0.002415364439683740f, -0.002339497436442704f, -0.002255930317598858f, -0.002166002272448999f, -0.002070984841172726f, -0.001972076865069115f, -0.001870400403116935f, -0.001766997597152117f, -0.001662828458596891f, -0.001558769541846508f, -0.001455613461052586f, -0.001354069203096897f, -0.001254763178484453f, -0.001158240957389821f, -0.001064969616730599f, -0.000975340650633742f, -0.000889673345622962f, -0.000808218604975926f, -0.000731163061150287f, -0.000658633566464172f, -0.000590701722925615f, -0.000527388854339363f, -0.000468670554403767f, -0.000414482134953896f, -0.000364722541989086f, -0.000319260777052827f, -0.000277937749971926f, -0.000240575602493366f, -0.000206973701715915f, -0.000176926965601505f, -0.000150205141042045f, -0.000126598475702349f, -0.000105848488644829f, -0.000087775432302674f, -0.000072066821233235f, -0.000058647705667398f, -0.000047050128769168f, -0.000037502053326083f, -0.000029059859493782f, -0.000022821877811828f, -0.000016407338246392f, -0.000013448546764569f, -0.000007276052892980f, -0.000008963059919890f, -0.000004403823942508f }; static const int d_128_r_32_len = 174; static const float d_128_r_32_kernel[] = { -0.000007703161797332f, -0.000007067232655723f, -0.000010206071983051f, -0.000014172048980291f, -0.000019092331824095f, -0.000025093561850217f, -0.000032309725460887f, -0.000040869410108629f, -0.000050902771287247f, -0.000062527941809152f, -0.000075857080877625f, -0.000090981984892608f, -0.000107979276021387f, -0.000126895337525829f, -0.000147750778600044f, -0.000170524898241214f, -0.000195159646270053f, -0.000221543901544016f, -0.000249517228886741f, -0.000278854334896464f, -0.000309268991706370f, -0.000340399096744052f, -0.000371811190567229f, -0.000402986592324930f, -0.000433326971103446f, -0.000462142048725246f, -0.000488656689255886f, -0.000512000649038707f, -0.000531217622524194f, -0.000545257474301118f, -0.000552987616398289f, -0.000553188077307909f, -0.000544565488882758f, -0.000525751232639430f, -0.000495318184744208f, -0.000451782034515639f, -0.000393620779029084f, -0.000319279140356296f, -0.000227190622760720f, -0.000115784771504684f, 0.000016488581452787f, 0.000171149664304299f, 0.000349662890884785f, 0.000553425442457719f, 0.000783740497411292f, 0.001041804838025221f, 0.001328682064289453f, 0.001645290145967291f, 0.001992375608565783f, 0.002370502036550272f, 0.002780026279675712f, 0.003221088947286086f, 0.003693593686280741f, 0.004197200680900796f, 0.004731309996663634f, 0.005295059011515552f, 0.005887310701543637f, 0.006506655780921748f, 0.007151406625936390f, 0.007819604696828528f, 0.008509020566706571f, 0.009217166946123657f, 0.009941305011649831f, 0.010678463067957514f, 0.011425449071796635f, 0.012178874660574504f, 0.012935173457761552f, 0.013690629889370804f, 0.014441402554104810f, 0.015183556958199267f, 0.015913092956531424f, 0.016625980278750660f, 0.017318188813430120f, 0.017985725593062503f, 0.018624666516886546f, 0.019231193320663705f, 0.019801625228558870f, 0.020332454367773867f, 0.020820375812973399f, 0.021262319920613983f, 0.021655479285054610f, 0.021997336565315176f, 0.022285687008084795f, 0.022518660515178174f, 0.022694738600454345f, 0.022812769691468757f, 0.022871979661449618f, 0.022871979661449618f, 0.022812769691468757f, 0.022694738600454345f, 0.022518660515178174f, 0.022285687008084795f, 0.021997336565315176f, 0.021655479285054610f, 0.021262319920613983f, 0.020820375812973399f, 0.020332454367773867f, 0.019801625228558870f, 0.019231193320663705f, 0.018624666516886546f, 0.017985725593062503f, 0.017318188813430120f, 0.016625980278750660f, 0.015913092956531424f, 0.015183556958199267f, 0.014441402554104810f, 0.013690629889370804f, 0.012935173457761552f, 0.012178874660574504f, 0.011425449071796635f, 0.010678463067957514f, 0.009941305011649831f, 0.009217166946123657f, 0.008509020566706571f, 0.007819604696828528f, 0.007151406625936390f, 0.006506655780921748f, 0.005887310701543637f, 0.005295059011515552f, 0.004731309996663634f, 0.004197200680900796f, 0.003693593686280741f, 0.003221088947286086f, 0.002780026279675712f, 0.002370502036550272f, 0.001992375608565783f, 0.001645290145967291f, 0.001328682064289453f, 0.001041804838025221f, 0.000783740497411292f, 0.000553425442457719f, 0.000349662890884785f, 0.000171149664304299f, 0.000016488581452787f, -0.000115784771504684f, -0.000227190622760720f, -0.000319279140356296f, -0.000393620779029084f, -0.000451782034515639f, -0.000495318184744208f, -0.000525751232639430f, -0.000544565488882758f, -0.000553188077307909f, -0.000552987616398289f, -0.000545257474301118f, -0.000531217622524194f, -0.000512000649038707f, -0.000488656689255886f, -0.000462142048725246f, -0.000433326971103446f, -0.000402986592324930f, -0.000371811190567229f, -0.000340399096744052f, -0.000309268991706370f, -0.000278854334896464f, -0.000249517228886741f, -0.000221543901544016f, -0.000195159646270053f, -0.000170524898241214f, -0.000147750778600044f, -0.000126895337525829f, -0.000107979276021387f, -0.000090981984892608f, -0.000075857080877625f, -0.000062527941809152f, -0.000050902771287247f, -0.000040869410108629f, -0.000032309725460887f, -0.000025093561850217f, -0.000019092331824095f, -0.000014172048980291f, -0.000010206071983051f, -0.000007067232655723f, -0.000007703161797332f }; static const int d_256_r_64_len = 348; static const float d_256_r_64_kernel[] = { -0.000006032200297229f, -0.000002790586794678f, -0.000003423524464373f, -0.000004141892670381f, -0.000004959329780957f, -0.000005880953015414f, -0.000006916263845337f, -0.000008064028056128f, -0.000009336504822940f, -0.000010742797132820f, -0.000012302324525658f, -0.000014003128525449f, -0.000015868003792231f, -0.000017906061357310f, -0.000020111369902626f, -0.000022508204619172f, -0.000025093613992638f, -0.000027881953296601f, -0.000030875236885189f, -0.000034086060833210f, -0.000037517497377010f, -0.000041176665997414f, -0.000045067469478981f, -0.000049199353227621f, -0.000053568215176887f, -0.000058185242147041f, -0.000063047547807853f, -0.000068158618146586f, -0.000073517650784389f, -0.000079124725926570f, -0.000084976788235246f, -0.000091070103041806f, -0.000097399649098627f, -0.000103959504668590f, -0.000110738977492950f, -0.000117729935644196f, -0.000124919754739577f, -0.000132293212128623f, -0.000139836270835869f, -0.000147529334756596f, -0.000155353239032747f, -0.000163284378055491f, -0.000171298895948762f, -0.000179368841745378f, -0.000187464129074148f, -0.000195552549576070f, -0.000203599379753965f, -0.000211565351775375f, -0.000219411598860001f, -0.000227093646507866f, -0.000234565855614429f, -0.000241779257421431f, -0.000248682177854843f, -0.000255220180724547f, -0.000261335292980978f, -0.000266967862740791f, -0.000272054419087176f, -0.000276528686849870f, -0.000280322370041046f, -0.000283363923695878f, -0.000285578633373008f, -0.000286890694598539f, -0.000287220112764771f, -0.000286486063219693f, -0.000284603980740463f, -0.000281488659987540f, -0.000277051522643753f, -0.000271202733437524f, -0.000263850662609383f, -0.000254902132867105f, -0.000244261829079865f, -0.000231834561467387f, -0.000217522684614226f, -0.000201228717970838f, -0.000182854056621652f, -0.000162300101972573f, -0.000139467943568298f, -0.000114258627064630f, -0.000086574168670878f, -0.000056316612617103f, -0.000023389083735015f, 0.000012303734435184f, 0.000050856088211618f, 0.000092360432854175f, 0.000136906695012963f, 0.000184583050199368f, 0.000235474395169584f, 0.000289663252276950f, 0.000347228091667187f, 0.000408244468331575f, 0.000472783700962668f, 0.000540912739938505f, 0.000612694289527581f, 0.000688186201030564f, 0.000767440808596336f, 0.000850505496981770f, 0.000937421598757782f, 0.001028224584686942f, 0.001122943571650730f, 0.001221601170177604f, 0.001324213422447449f, 0.001430788817328081f, 0.001541329271676986f, 0.001655828820474164f, 0.001774273932442011f, 0.001896643321221287f, 0.002022907760579356f, 0.002153029565104709f, 0.002286963138555204f, 0.002424654105591249f, 0.002566039990527927f, 0.002711049190015392f, 0.002859602052878106f, 0.003011609882021581f, 0.003166975340851216f, 0.003325592606007947f, 0.003487347156138999f, 0.003652115769225347f, 0.003819766988190932f, 0.003990160791661657f, 0.004163149004759460f, 0.004338575232032592f, 0.004516275281704776f, 0.004696077226065694f, 0.004877801369702575f, 0.005061261175365414f, 0.005246262760259225f, 0.005432605634097002f, 0.005620082968897619f, 0.005808481837004595f, 0.005997583515661679f, 0.006187164045980849f, 0.006376994441064236f, 0.006566841257680600f, 0.006756466702660143f, 0.006945629644096785f, 0.007134085474240178f, 0.007321586857189369f, 0.007507884388699728f, 0.007692726710717013f, 0.007875861286146892f, 0.008057034853723447f, 0.008235993962285608f, 0.008412485423992495f, 0.008586256905270668f, 0.008757057550423931f, 0.008924638381879985f, 0.009088752763977616f, 0.009249157391806035f, 0.009405612190690901f, 0.009557881349450560f, 0.009705733651032743f, 0.009848943025250930f, 0.009987289003226731f, 0.010120557317645952f, 0.010248540384643352f, 0.010371037696321509f, 0.010487856281889540f, 0.010598811426250850f, 0.010703726661456589f, 0.010802434494593470f, 0.010894776824678817f, 0.010980605060844476f, 0.011059780725514858f, 0.011132175645830765f, 0.011197672374601442f, 0.011256164283833567f, 0.011307555970121824f, 0.011351763540287503f, 0.011388714558166329f, 0.011418348423280301f, 0.011440616600089638f, 0.011455482402780947f, 0.011462921415360990f, 0.011462921415360990f, 0.011455482402780947f, 0.011440616600089638f, 0.011418348423280301f, 0.011388714558166329f, 0.011351763540287503f, 0.011307555970121824f, 0.011256164283833567f, 0.011197672374601442f, 0.011132175645830765f, 0.011059780725514858f, 0.010980605060844476f, 0.010894776824678817f, 0.010802434494593470f, 0.010703726661456589f, 0.010598811426250850f, 0.010487856281889540f, 0.010371037696321509f, 0.010248540384643352f, 0.010120557317645952f, 0.009987289003226731f, 0.009848943025250930f, 0.009705733651032743f, 0.009557881349450560f, 0.009405612190690901f, 0.009249157391806035f, 0.009088752763977616f, 0.008924638381879985f, 0.008757057550423931f, 0.008586256905270668f, 0.008412485423992495f, 0.008235993962285608f, 0.008057034853723447f, 0.007875861286146892f, 0.007692726710717013f, 0.007507884388699728f, 0.007321586857189369f, 0.007134085474240178f, 0.006945629644096785f, 0.006756466702660143f, 0.006566841257680600f, 0.006376994441064236f, 0.006187164045980849f, 0.005997583515661679f, 0.005808481837004595f, 0.005620082968897619f, 0.005432605634097002f, 0.005246262760259225f, 0.005061261175365414f, 0.004877801369702575f, 0.004696077226065694f, 0.004516275281704776f, 0.004338575232032592f, 0.004163149004759460f, 0.003990160791661657f, 0.003819766988190932f, 0.003652115769225347f, 0.003487347156138999f, 0.003325592606007947f, 0.003166975340851216f, 0.003011609882021581f, 0.002859602052878106f, 0.002711049190015392f, 0.002566039990527927f, 0.002424654105591249f, 0.002286963138555204f, 0.002153029565104709f, 0.002022907760579356f, 0.001896643321221287f, 0.001774273932442011f, 0.001655828820474164f, 0.001541329271676986f, 0.001430788817328081f, 0.001324213422447449f, 0.001221601170177604f, 0.001122943571650730f, 0.001028224584686942f, 0.000937421598757782f, 0.000850505496981770f, 0.000767440808596336f, 0.000688186201030564f, 0.000612694289527581f, 0.000540912739938505f, 0.000472783700962668f, 0.000408244468331575f, 0.000347228091667187f, 0.000289663252276950f, 0.000235474395169584f, 0.000184583050199368f, 0.000136906695012963f, 0.000092360432854175f, 0.000050856088211618f, 0.000012303734435184f, -0.000023389083735015f, -0.000056316612617103f, -0.000086574168670878f, -0.000114258627064630f, -0.000139467943568298f, -0.000162300101972573f, -0.000182854056621652f, -0.000201228717970838f, -0.000217522684614226f, -0.000231834561467387f, -0.000244261829079865f, -0.000254902132867105f, -0.000263850662609383f, -0.000271202733437524f, -0.000277051522643753f, -0.000281488659987540f, -0.000284603980740463f, -0.000286486063219693f, -0.000287220112764771f, -0.000286890694598539f, -0.000285578633373008f, -0.000283363923695878f, -0.000280322370041046f, -0.000276528686849870f, -0.000272054419087176f, -0.000266967862740791f, -0.000261335292980978f, -0.000255220180724547f, -0.000248682177854843f, -0.000241779257421431f, -0.000234565855614429f, -0.000227093646507866f, -0.000219411598860001f, -0.000211565351775375f, -0.000203599379753965f, -0.000195552549576070f, -0.000187464129074148f, -0.000179368841745378f, -0.000171298895948762f, -0.000163284378055491f, -0.000155353239032747f, -0.000147529334756596f, -0.000139836270835869f, -0.000132293212128623f, -0.000124919754739577f, -0.000117729935644196f, -0.000110738977492950f, -0.000103959504668590f, -0.000097399649098627f, -0.000091070103041806f, -0.000084976788235246f, -0.000079124725926570f, -0.000073517650784389f, -0.000068158618146586f, -0.000063047547807853f, -0.000058185242147041f, -0.000053568215176887f, -0.000049199353227621f, -0.000045067469478981f, -0.000041176665997414f, -0.000037517497377010f, -0.000034086060833210f, -0.000030875236885189f, -0.000027881953296601f, -0.000025093613992638f, -0.000022508204619172f, -0.000020111369902626f, -0.000017906061357310f, -0.000015868003792231f, -0.000014003128525449f, -0.000012302324525658f, -0.000010742797132820f, -0.000009336504822940f, -0.000008064028056128f, -0.000006916263845337f, -0.000005880953015414f, -0.000004959329780957f, -0.000004141892670381f, -0.000003423524464373f, -0.000002790586794678f, -0.000006032200297229f }; #else // Previous FIR decimator /* Filter taps generated by matlab. * All filters are designed for 0.5 dB passband ripple and 90-100 dB stop-band * attenuation. * Optimal FIR sequence generated using FIRCalc 1.0 by Youssef Touil */ // 0.4500000000 0.5500000000 2 #define FIR_2_1_LEN 65 const float FIR_2_1_TAPS[FIR_2_1_LEN] = { -0.0003278252261, -0.002040079795, -0.005349315703, -0.007344492245, -0.004085056018, 0.002583198482, 0.004540402908, -0.001126032323, -0.005173824262, 0.0001933958993, 0.006274622399, 0.0009823472938, -0.007659214549, -0.002643365879, 0.009217977524, 0.004966503475, -0.01085040253, -0.008121663705, 0.01248027012, 0.01234706212, -0.01405533589, -0.01806676947, 0.01550395414, 0.02604486234, -0.01677149534, -0.03794026747, 0.0178176146, 0.05812988058, -0.01859222725, -0.1027237624, 0.01906614564, 0.3171727061, 0.4807731807, 0.3171727061, 0.01906614564, -0.1027237624, -0.01859222725, 0.05812988058, 0.0178176146, -0.03794026747, -0.01677149534, 0.02604486234, 0.01550395414, -0.01806676947, -0.01405533589, 0.01234706212, 0.01248027012, -0.008121663705, -0.01085040253, 0.004966503475, 0.009217977524, -0.002643365879, -0.007659214549, 0.0009823472938, 0.006274622399, 0.0001933958993, -0.005173824262, -0.001126032323, 0.004540402908, 0.002583198482, -0.004085056018, -0.007344492245, -0.005349315703, -0.002040079795, -0.0003278252261 }; // 0.2250000000 0.7750000000 2 #define FIR_4_1_LEN 11 const float FIR_4_1_TAPS[FIR_4_1_LEN] = { -0.005957326386, -0.02477577329, -0.01910482161, 0.09140145779, 0.2892911136, 0.3952179253, 0.2892911136, 0.09140145779, -0.01910482161, -0.02477577329, -0.005957326386 }; // 0.4500000000 0.5500000000 2 #define FIR_4_2_LEN FIR_2_1_LEN const float *FIR_4_2_TAPS = FIR_2_1_TAPS; // 0.1125000000 0.3875000000 4 #define FIR_8_1_LEN 24 const float FIR_8_1_TAPS[FIR_8_1_LEN] = { -0.0003039507719, -0.00151092303, -0.004308381584, -0.008604046889, -0.0124123916, -0.01131789293, 0.0005229047383, 0.02762223035, 0.06973481923, 0.1198379695, 0.1653192043, 0.1925006062, 0.1925006062, 0.1653192043, 0.1198379695, 0.06973481923, 0.02762223035, 0.0005229047383, -0.01131789293, -0.0124123916, -0.008604046889, -0.004308381584, -0.00151092303, -0.0003039507719 }; // 0.4500000000 0.5500000000 2 #define FIR_8_2_LEN FIR_2_1_LEN const float *FIR_8_2_TAPS = FIR_2_1_TAPS; // 0.0562500000 0.4437500000 4 #define FIR_16_1_LEN 16 const float FIR_16_1_TAPS[FIR_16_1_LEN] = { 0.0005477772793, 0.003594921902, 0.01279040519, 0.03234443441, 0.06392806023, 0.103605859, 0.141295597, 0.1644843221, 0.1644843221, 0.141295597, 0.103605859, 0.06392806023, 0.03234443441, 0.01279040519, 0.003594921902, 0.0005477772793 }; // 0.2250000000 0.7750000000 2 #define FIR_16_2_LEN FIR_4_1_LEN const float *FIR_16_2_TAPS = FIR_4_1_TAPS; // 0.4500000000 0.5500000000 2 #define FIR_16_3_LEN FIR_2_1_LEN const float *FIR_16_3_TAPS = FIR_2_1_TAPS; // 0.0281250000 0.2218750000 8 #define FIR_32_1_LEN 42 const float FIR_32_1_TAPS[FIR_32_1_LEN] = { -4.750685548e-5, -0.0001519439538, -0.0003587162937, -0.0006931721, -0.001151426113, -0.001673014951, -0.002116090851, -0.002241667826, -0.001715262071, -0.0001324449986, 0.002929489361, 0.007837772369, 0.01481874287, 0.02387691103, 0.03473669663, 0.0468236953, 0.05929613858, 0.07112794369, 0.08123386651, 0.08861737698, 0.09251579642, 0.09251579642, 0.08861737698, 0.08123386651, 0.07112794369, 0.05929613858, 0.0468236953, 0.03473669663, 0.02387691103, 0.01481874287, 0.007837772369, 0.002929489361, -0.0001324449986, -0.001715262071, -0.002241667826, -0.002116090851, -0.001673014951, -0.001151426113, -0.0006931721, -0.0003587162937, -0.0001519439538, -4.750685548e-5 }; // 0.2250000000 0.7750000000 2 #define FIR_32_2_LEN FIR_4_1_LEN const float *FIR_32_2_TAPS = FIR_4_1_TAPS; // 0.4500000000 0.5500000000 2 #define FIR_32_3_LEN FIR_2_1_LEN const float *FIR_32_3_TAPS = FIR_2_1_TAPS; // 0.0140625000 0.2359375000 8 #define FIR_64_1_LEN 30 const float FIR_64_1_TAPS[FIR_64_1_LEN] = { 0.0001188354872, 0.0004522371455, 0.001223598025, 0.002714014845, 0.005253443029, 0.009166177362, 0.0146980416, 0.02193524688, 0.03073395602, 0.04068056494, 0.05109928548, 0.06111412868, 0.06976035982, 0.07612732053, 0.07950519025, 0.07950519025, 0.07612732053, 0.06976035982, 0.06111412868, 0.05109928548, 0.04068056494, 0.03073395602, 0.02193524688, 0.0146980416, 0.009166177362, 0.005253443029, 0.002714014845, 0.001223598025, 0.0004522371455, 0.0001188354872 }; // 0.1125000000 0.3875000000 4 #define FIR_64_2_LEN FIR_8_1_LEN const float *FIR_64_2_TAPS = FIR_8_1_TAPS; // 0.4500000000 0.5500000000 2 #define FIR_64_3_LEN FIR_2_1_LEN const float *FIR_64_3_TAPS = FIR_2_1_TAPS; // 0.0070312500 0.1179688000 16 #define FIR_128_1_LEN 61 const float FIR_128_1_TAPS[FIR_128_1_LEN] = { 3.695723717e-5, 7.529251889e-5, 0.0001481503132, 0.000263013033, 0.0004344411718, 0.0006792644854, 0.001016279799, 0.001465716981, 0.002048497088, 0.002785283606, 0.003695455613, 0.004795861896, 0.006099532824, 0.007614453789, 0.00934238825, 0.01127781812, 0.01340724807, 0.01570868306, 0.01815159246, 0.02069726959, 0.02329952456, 0.02590598352, 0.02845956571, 0.03090048954, 0.03316837549, 0.03520460054, 0.03695458919, 0.03837010637, 0.03941125423, 0.04004824534, 0.0402626507, 0.04004824534, 0.03941125423, 0.03837010637, 0.03695458919, 0.03520460054, 0.03316837549, 0.03090048954, 0.02845956571, 0.02590598352, 0.02329952456, 0.02069726959, 0.01815159246, 0.01570868306, 0.01340724807, 0.01127781812, 0.00934238825, 0.007614453789, 0.006099532824, 0.004795861896, 0.003695455613, 0.002785283606, 0.002048497088, 0.001465716981, 0.001016279799, 0.0006792644854, 0.0004344411718, 0.000263013033, 0.0001481503132, 7.529251889e-5, 3.695723717e-5 }; // 0.1125000000 0.3875000000 4 #define FIR_128_2_LEN FIR_8_1_LEN const float *FIR_128_2_TAPS = FIR_8_1_TAPS; // 0.4500000000 0.5500000000 2 #define FIR_128_3_LEN FIR_2_1_LEN const float *FIR_128_3_TAPS = FIR_2_1_TAPS; #endif // USE_NEW_FIR_DECIM gqrx-2.9/src/dsp/hbf_decim.cpp000066400000000000000000000040521320142145500163240ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Copyright 2015 Alexandru Csete. * * This is free software; you can redistribute 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. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR 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 software; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include "filter/decimator.h" #include "hbf_decim.h" hbf_decim_sptr make_hbf_decim(unsigned int decim) { return gnuradio::get_initial_sptr (new hbf_decim(decim)); } hbf_decim::hbf_decim(unsigned int decim) : gr::sync_decimator("hbf_decim", gr::io_signature::make(1, 1, sizeof(gr_complex)), gr::io_signature::make(1, 1, sizeof(gr_complex)), decim) { decimation = decim; dec = new Decimator(); if (dec->init(decim, 100) != decim) throw std::range_error("Decimation not supported"); std::cout << "New decimator: " << decimation << std::endl; } hbf_decim::~hbf_decim() { delete dec; } int hbf_decim::work(int noutput_items, gr_vector_const_void_star &input_items, gr_vector_void_star &output_items) { gr_complex *in = (gr_complex *) input_items[0]; gr_complex *out = (gr_complex *) output_items[0]; /* HBF input length must be even and >= number of taps. * Max taps is 87 => we need at least 44 * decim input samples */ if (noutput_items < 44) return 0; return dec->process(noutput_items * decimation, in, out); } gqrx-2.9/src/dsp/hbf_decim.h000066400000000000000000000030301320142145500157640ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Copyright 2015 Alexandru Csete. * * This is free software; you can redistribute 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. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR 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 software; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef INCLUDED_MYMOD_HBF_DECIM_H #define INCLUDED_MYMOD_HBF_DECIM_H #include #include #include "filter/decimator.h" class hbf_decim; typedef boost::shared_ptr hbf_decim_sptr; hbf_decim_sptr make_hbf_decim(unsigned int decim); /** * Decimator block using half-band filters. */ class hbf_decim : virtual public gr::sync_decimator { friend hbf_decim_sptr make_hbf_decim(unsigned int decim); protected: hbf_decim(unsigned int decim); public: ~hbf_decim(); int work(int noutput_items, gr_vector_const_void_star &input_items, gr_vector_void_star &output_items); private: Decimator *dec; unsigned int decimation; }; #endif /* INCLUDED_MYMOD_HBF_DECIM_H */ gqrx-2.9/src/dsp/lpf.cpp000066400000000000000000000052771320142145500152170ustar00rootroot00000000000000/* -*- 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-2.9/src/dsp/lpf.h000066400000000000000000000054601320142145500146560ustar00rootroot00000000000000/* -*- 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-2.9/src/dsp/rds/000077500000000000000000000000001320142145500145075ustar00rootroot00000000000000gqrx-2.9/src/dsp/rds/api.h000066400000000000000000000016661320142145500154420ustar00rootroot00000000000000/* * Copyright (C) 2013 Bastian Bloessl * * 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 . */ #ifndef INCLUDED_RDS_API_H #define INCLUDED_RDS_API_H #include #ifdef gnuradio_RDS_EXPORTS # define RDS_API __GR_ATTR_EXPORT #else # define RDS_API __GR_ATTR_IMPORT #endif #endif /* INCLUDED_RDS_API_H */ gqrx-2.9/src/dsp/rds/constants.h000066400000000000000000000105671320142145500167050ustar00rootroot00000000000000/* * Copyright 2004 Free Software Foundation, Inc. * * This file is part of GNU Radio * * GNU Radio is free software; you can 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, or (at your option) * any later version. * * GNU Radio is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Radio; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ /* see page 59, Annex C, table C.1 in the standard * offset word C' has been put at the end */ static const unsigned int offset_pos[5]={0,1,2,3,2}; static const unsigned int offset_word[5]={252,408,360,436,848}; static const unsigned int syndrome[5]={383,14,303,663,748}; static const char * const offset_name[]={"A","B","C","D","C'"}; /* page 77, Annex F in the standard */ const std::string pty_table[32]={ "None", "News", "Current Affairs", "Information", "Sport", "Education", "Drama", "Cultures", "Science", "Varied Speech", "Pop Music", "Rock Music", "Easy Listening", "Light Classics M", "Serious Classics", "Other Music", "Weather & Metr", "Finance", "Children’s Progs", "Social Affairs", "Religion", "Phone In", "Travel & Touring", "Leisure & Hobby", "Jazz Music", "Country Music", "National Music", "Oldies Music", "Folk Music", "Documentary", "Alarm Test", "Alarm-Alarm!"}; /* page 71, Annex D, table D.1 in the standard */ const std::string pi_country_codes[15][5]={ {"DE","GR","MA","__","MD"}, {"DZ","CY","CZ","IE","EE"}, {"AD","SM","PL","TR","__"}, {"IL","CH","VA","MK","__"}, {"IT","JO","SK","__","__"}, {"BE","FI","SY","__","UA"}, {"RU","LU","TN","__","__"}, {"PS","BG","__","NL","PT"}, {"AL","DK","LI","LV","SI"}, {"AT","GI","IS","LB","__"}, {"HU","IQ","MC","__","__"}, {"MT","GB","LT","HR","__"}, {"DE","LY","YU","__","__"}, {"__","RO","ES","SE","__"}, {"EG","FR","NO","BY","BA"}}; /* page 72, Annex D, table D.2 in the standard */ const std::string coverage_area_codes[16]={ "Local", "International", "National", "Supra-regional", "Regional 1", "Regional 2", "Regional 3", "Regional 4", "Regional 5", "Regional 6", "Regional 7", "Regional 8", "Regional 9", "Regional 10", "Regional 11", "Regional 12"}; const std::string rds_group_acronyms[16]={ "BASIC", "PIN/SL", "RT", "AID", "CT", "TDC", "IH", "RP", "TMC", "EWS", "___", "___", "___", "___", "EON", "___"}; /* page 74, Annex E, table E.1 in the standard: that's the ASCII table!!! */ /* see page 84, Annex J in the standard */ const std::string language_codes[44]={ "Unkown/not applicable", "Albanian", "Breton", "Catalan", "Croatian", "Welsh", "Czech", "Danish", "German", "English", "Spanish", "Esperanto", "Estonian", "Basque", "Faroese", "French", "Frisian", "Irish", "Gaelic", "Galician", "Icelandic", "Italian", "Lappish", "Latin", "Latvian", "Luxembourgian", "Lithuanian", "Hungarian", "Maltese", "Dutch", "Norwegian", "Occitan", "Polish", "Portuguese", "Romanian", "Romansh", "Serbian", "Slovak", "Slovene", "Finnish", "Swedish", "Turkish", "Flemish", "Walloon"}; /* see page 12 in ISO 14819-1 */ const std::string tmc_duration[8][2]={ {"no duration given", "no duration given"}, {"15 minutes", "next few hours"}, {"30 minutes", "rest of the day"}, {"1 hour", "until tomorrow evening"}, {"2 hours", "rest of the week"}, {"3 hours", "end of next week"}, {"4 hours", "end of the month"}, {"rest of the day", "long period"}}; /* optional message content, data field lengths and labels * see page 15 in ISO 14819-1 */ const int optional_content_lengths[16]={3,3,5,5,5,8,8,8,8,11,16,16,16,16,0,0}; const std::string label_descriptions[16]={ "Duration", "Control code", "Length of route affected", "Speed limit advice", "Quantifier", "Quantifier", "Supplementary information code", "Explicit start time", "Explicit stop time", "Additional event", "Detailed diversion instructions", "Destination", "RFU (Reserved for future use)", "Cross linkage to source of problem, or another route", "Separator", "RFU (Reserved for future use)"}; gqrx-2.9/src/dsp/rds/decoder.h000066400000000000000000000021101320142145500162570ustar00rootroot00000000000000/* * Copyright (C) 2014 Bastian Bloessl * * 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 . */ #ifndef INCLUDED_RDS_DECODER_H #define INCLUDED_RDS_DECODER_H #include "dsp/rds/api.h" #include namespace gr { namespace rds { class RDS_API decoder : virtual public gr::sync_block { public: typedef boost::shared_ptr sptr; static sptr make(bool log, bool debug); }; } // namespace rds } // namespace gr #endif /* INCLUDED_RDS_DECODER_H */ gqrx-2.9/src/dsp/rds/decoder_impl.cc000066400000000000000000000142221320142145500174450ustar00rootroot00000000000000/* * Copyright (C) 2014 Bastian Bloessl * * 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 . */ #define dout debug && std::cout #define lout log && std::cout #include "decoder_impl.h" #include "constants.h" #include using namespace gr::rds; decoder::sptr decoder::make(bool log, bool debug) { return gnuradio::get_initial_sptr(new decoder_impl(log, debug)); } decoder_impl::decoder_impl(bool log, bool debug) : gr::sync_block ("gr_rds_decoder", gr::io_signature::make (1, 1, sizeof(char)), gr::io_signature::make (0, 0, 0)), log(log), debug(debug) { bit_counter = 0; lastseen_offset_counter = 0; reg = 0; block_bit_counter = 0; wrong_blocks_counter = 0; blocks_counter = 0; group_good_blocks_counter = 0; good_block = false; group_assembly_started = false; lastseen_offset = 0; block_number = 0; set_output_multiple(104); // 1 RDS datagroup = 104 bits message_port_register_out(pmt::mp("out")); enter_no_sync(); } decoder_impl::~decoder_impl() { } ////////////////////////// HELPER FUNTIONS ///////////////////////// void decoder_impl::enter_no_sync() { presync = false; d_state = NO_SYNC; } void decoder_impl::enter_sync(unsigned int sync_block_number) { wrong_blocks_counter = 0; blocks_counter = 0; block_bit_counter = 0; block_number = (sync_block_number + 1) % 4; group_assembly_started = false; d_state = SYNC; } /* see Annex B, page 64 of the standard */ unsigned int decoder_impl::calc_syndrome(unsigned long message, unsigned char mlen) { unsigned long reg = 0; unsigned int i; const unsigned long poly = 0x5B9; const unsigned char plen = 10; for (i = mlen; i > 0; i--) { reg = (reg << 1) | ((message >> (i-1)) & 0x01); if (reg & (1 << plen)) reg = reg ^ poly; } for (i = plen; i > 0; i--) { reg = reg << 1; if (reg & (1<>10) & 0xffff; block_calculated_crc=calc_syndrome(dataword,16); checkword=reg & 0x3ff; /* manage special case of C or C' offset word */ if (block_number==2) { block_received_crc=checkword^offset_word[block_number]; if (block_received_crc==block_calculated_crc) good_block=true; else { block_received_crc=checkword^offset_word[4]; if (block_received_crc==block_calculated_crc) good_block=true; else { wrong_blocks_counter++; good_block=false; } } } else { block_received_crc=checkword^offset_word[block_number]; if (block_received_crc==block_calculated_crc) good_block=true; else { wrong_blocks_counter++; good_block=false; } } /* done checking CRC */ if (block_number==0 && good_block) { group_assembly_started=true; group_good_blocks_counter=1; } if (group_assembly_started) { if (!good_block) group_assembly_started=false; else { group[block_number]=dataword; group_good_blocks_counter++; } if (group_good_blocks_counter==5) decode_group(group); } block_bit_counter=0; block_number=(block_number+1) % 4; blocks_counter++; /* 1187.5 bps / 104 bits = 11.4 groups/sec, or 45.7 blocks/sec */ if (blocks_counter==50) { if (wrong_blocks_counter>35) { lout << "@@@@@ Lost Sync (Got " << wrong_blocks_counter << " bad blocks on " << blocks_counter << " total)" << std::endl; enter_no_sync(); } else { lout << "@@@@@ Still Sync-ed (Got " << wrong_blocks_counter << " bad blocks on " << blocks_counter << " total)" << std::endl; } blocks_counter=0; wrong_blocks_counter=0; } } break; default: d_state=NO_SYNC; break; } i++; bit_counter++; } return noutput_items; } gqrx-2.9/src/dsp/rds/decoder_impl.h000066400000000000000000000034071320142145500173120ustar00rootroot00000000000000/* * Copyright (C) 2014 Bastian Bloessl * * 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 . */ #ifndef INCLUDED_RDS_DECODER_IMPL_H #define INCLUDED_RDS_DECODER_IMPL_H #include "dsp/rds/decoder.h" namespace gr { namespace rds { class decoder_impl : public decoder { public: decoder_impl(bool log, bool debug); private: ~decoder_impl(); int work(int noutput_items, gr_vector_const_void_star &input_items, gr_vector_void_star &output_items); void enter_no_sync(); void enter_sync(unsigned int); unsigned int calc_syndrome(unsigned long, unsigned char); void decode_group(unsigned int*); unsigned long bit_counter; unsigned long lastseen_offset_counter, reg; unsigned int block_bit_counter; unsigned int wrong_blocks_counter; unsigned int blocks_counter; unsigned int group_good_blocks_counter; unsigned int group[4]; bool log; bool debug; bool presync; bool good_block; bool group_assembly_started; unsigned char lastseen_offset; unsigned char block_number; enum { NO_SYNC, SYNC } d_state; }; } /* namespace rds */ } /* namespace gr */ #endif /* INCLUDED_RDS_DECODER_IMPL_H */ gqrx-2.9/src/dsp/rds/parser.h000066400000000000000000000021261320142145500161550ustar00rootroot00000000000000/* * Copyright (C) 2014 Bastian Bloessl * * 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 . */ #ifndef INCLUDED_RDS_PARSER_H #define INCLUDED_RDS_PARSER_H #include "dsp/rds/api.h" #include namespace gr { namespace rds { class RDS_API parser : virtual public gr::block { public: typedef boost::shared_ptr sptr; static sptr make(bool log, bool debug); virtual void reset() = 0; }; } // namespace rds } // namespace gr #endif /* INCLUDED_RDS_PARSER_H */ gqrx-2.9/src/dsp/rds/parser_impl.cc000066400000000000000000000510521320142145500173360ustar00rootroot00000000000000/* * Copyright (C) 2014 Bastian Bloessl * * 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 . */ #define dout debug && std::cout #define lout log && std::cout #include "parser_impl.h" #include "constants.h" #include "tmc_events.h" #include #include using namespace gr::rds; parser::sptr parser::make(bool log, bool debug) { return gnuradio::get_initial_sptr(new parser_impl(log, debug)); } parser_impl::parser_impl(bool log, bool debug) : gr::block ("gr_rds_parser", gr::io_signature::make (0, 0, 0), gr::io_signature::make (0, 0, 0)), log(log), debug(debug) { message_port_register_in(pmt::mp("in")); set_msg_handler(pmt::mp("in"), boost::bind(&parser_impl::parse, this, _1)); message_port_register_out(pmt::mp("out")); reset(); } parser_impl::~parser_impl() { } void parser_impl::reset() { gr::thread::scoped_lock lock(d_mutex); memset(radiotext, ' ', sizeof(radiotext)); memset(program_service_name, '.', sizeof(program_service_name)); program_identification = 0; radiotext_AB_flag = 0; traffic_program = false; traffic_announcement = false; music_speech = false; program_type = 0; pi_country_identification = 0; pi_area_coverage = 0; pi_program_reference_number = 0; mono_stereo = false; artificial_head = false; compressed = false; static_pty = false; } /* type 0 = PI * type 1 = PS * type 2 = PTY * type 3 = flagstring: TP, TA, MuSp, MoSt, AH, CMP, stPTY * type 4 = RadioText * type 5 = ClockTime * type 6 = Alternative Frequencies */ void parser_impl::send_message(long msgtype, std::string msgtext) { pmt::pmt_t msg = pmt::mp(msgtext); pmt::pmt_t type = pmt::from_long(msgtype); message_port_pub(pmt::mp("out"), pmt::make_tuple(type, msg)); } /* BASIC TUNING: see page 21 of the standard */ void parser_impl::decode_type0(unsigned int *group, bool B) { unsigned int af_code_1 = 0; unsigned int af_code_2 = 0; unsigned int no_af = 0; double af_1 = 0; double af_2 = 0; char flagstring[8] = "0000000"; traffic_program = (group[1] >> 10) & 0x01; // "TP" traffic_announcement = (group[1] >> 4) & 0x01; // "TA" music_speech = (group[1] >> 3) & 0x01; // "MuSp" bool decoder_control_bit = (group[1] >> 2) & 0x01; // "DI" unsigned char segment_address = group[1] & 0x03; // "DI segment" program_service_name[segment_address * 2] = (group[3] >> 8) & 0xff; program_service_name[segment_address * 2 + 1] = group[3] & 0xff; /* see page 41, table 9 of the standard */ switch (segment_address) { case 0: mono_stereo=decoder_control_bit; break; case 1: artificial_head=decoder_control_bit; break; case 2: compressed=decoder_control_bit; break; case 3: static_pty=decoder_control_bit; break; default: break; } flagstring[0] = traffic_program ? '1' : '0'; flagstring[1] = traffic_announcement ? '1' : '0'; flagstring[2] = music_speech ? '1' : '0'; flagstring[3] = mono_stereo ? '1' : '0'; flagstring[4] = artificial_head ? '1' : '0'; flagstring[5] = compressed ? '1' : '0'; flagstring[6] = static_pty ? '1' : '0'; static std::string af_string; if(!B) { // type 0A af_code_1 = int(group[2] >> 8) & 0xff; af_code_2 = int(group[2]) & 0xff; af_1 = decode_af(af_code_1); af_2 = decode_af(af_code_2); if(af_1) { no_af += 1; } if(af_2) { no_af += 2; } std::string af1_string; std::string af2_string; /* only AF1 => no_af==1, only AF2 => no_af==2, both AF1 and AF2 => no_af==3 */ if(no_af) { if(af_1 > 80e3) { af1_string = str(boost::format("%2.2fMHz") % (af_1/1e3)); } else if((af_1<2e3)&&(af_1>100)) { af1_string = str(boost::format("%ikHz") % int(af_1)); } if(af_2 > 80e3) { af2_string = str(boost::format("%2.2fMHz") % (af_2/1e3)); } else if ((af_2 < 2e3) && (af_2 > 100)) { af2_string = str(boost::format("%ikHz") % int(af_2)); } } if(no_af == 1) { af_string = af1_string; } else if(no_af == 2) { af_string = af2_string; } else if(no_af == 3) { af_string = str(boost::format("%s, %s") % af1_string %af2_string); } } lout << "==>" << std::string(program_service_name, 8) << "<== -" << (traffic_program ? "TP" : " ") << '-' << (traffic_announcement ? "TA" : " ") << '-' << (music_speech ? "Music" : "Speech") << '-' << (mono_stereo ? "MONO" : "STEREO") << " - AF:" << af_string << std::endl; send_message(1, std::string(program_service_name, 8)); send_message(3, flagstring); send_message(6, af_string); } double parser_impl::decode_af(unsigned int af_code) { static unsigned int number_of_freqs = 0; static bool vhf_or_lfmf = 0; // 0 = vhf, 1 = lf/mf double alt_frequency = 0; // in kHz if((af_code == 0) || // not to be used ( af_code == 205) || // filler code ((af_code >= 206) && (af_code <= 223)) || // not assigned ( af_code == 224) || // No AF exists ( af_code >= 251)) { // not assigned number_of_freqs = 0; alt_frequency = 0; } if((af_code >= 225) && (af_code <= 249)) { // VHF frequencies follow number_of_freqs = af_code - 224; alt_frequency = 0; vhf_or_lfmf = 1; } if(af_code == 250) { // an LF/MF frequency follows number_of_freqs = 1; alt_frequency = 0; vhf_or_lfmf = 0; } if((af_code > 0) && (af_code < 205) && vhf_or_lfmf) alt_frequency = 100.0 * (af_code + 875); // VHF (87.6-107.9MHz) else if((af_code > 0) && (af_code < 16) && !vhf_or_lfmf) alt_frequency = 153.0 + (af_code - 1) * 9; // LF (153-279kHz) else if((af_code > 15) && (af_code < 136) && !vhf_or_lfmf) alt_frequency = 531.0 + (af_code - 16) * 9 + 531; // MF (531-1602kHz) (void) number_of_freqs; return alt_frequency; } void parser_impl::decode_type1(unsigned int *group, bool B){ int ecc = 0; int paging = 0; char country_code = (group[0] >> 12) & 0x0f; char radio_paging_codes = group[1] & 0x1f; int variant_code = (group[2] >> 12) & 0x7; unsigned int slow_labelling = group[2] & 0xfff; int day = (int)((group[3] >> 11) & 0x1f); int hour = (int)((group[3] >> 6) & 0x1f); int minute = (int) (group[3] & 0x3f); if(radio_paging_codes) { lout << "paging codes: " << int(radio_paging_codes) << " "; } if(day || hour || minute) { lout << boost::format("program item: %id, %i, %i ") % day % hour % minute; } if(!B){ switch(variant_code){ case 0: // paging + ecc paging = (slow_labelling >> 8) & 0x0f; ecc = slow_labelling & 0xff; if(paging) { lout << "paging: " << paging << " "; } if((ecc > 223) && (ecc < 229)) { lout << "extended country code: " << pi_country_codes[country_code-1][ecc-224] << std::endl; } else { lout << "invalid extended country code: " << ecc << std::endl; } break; case 1: // TMC identification lout << "TMC identification code received" << std::endl; break; case 2: // Paging identification lout << "Paging identification code received" << std::endl; break; case 3: // language codes if(slow_labelling < 44) { lout << "language: " << language_codes[slow_labelling] << std::endl; } else { lout << "language: invalid language code " << slow_labelling << std::endl; } break; default: break; } } } void parser_impl::decode_type2(unsigned int *group, bool B){ unsigned char text_segment_address_code = group[1] & 0x0f; // when the A/B flag is toggled, flush your current radiotext if(radiotext_AB_flag != ((group[1] >> 4) & 0x01)) { std::memset(radiotext, ' ', sizeof(radiotext)); } radiotext_AB_flag = (group[1] >> 4) & 0x01; if(!B) { radiotext[text_segment_address_code *4 ] = (group[2] >> 8) & 0xff; radiotext[text_segment_address_code * 4 + 1] = group[2] & 0xff; radiotext[text_segment_address_code * 4 + 2] = (group[3] >> 8) & 0xff; radiotext[text_segment_address_code * 4 + 3] = group[3] & 0xff; } else { radiotext[text_segment_address_code * 2 ] = (group[3] >> 8) & 0xff; radiotext[text_segment_address_code * 2 + 1] = group[3] & 0xff; } lout << "Radio Text " << (radiotext_AB_flag ? 'B' : 'A') << ": " << std::string(radiotext, sizeof(radiotext)) << std::endl; send_message(4,std::string(radiotext, sizeof(radiotext))); } void parser_impl::decode_type3(unsigned int *group, bool B){ if(B) { dout << "type 3B not implemented yet" << std::endl; return; } int application_group = (group[1] >> 1) & 0xf; int group_type = group[1] & 0x1; int message = group[2]; int aid = group[3]; lout << "aid group: " << application_group << " " << (group_type ? 'B' : 'A'); if((application_group == 8) && (group_type == false)) { // 8A int variant_code = (message >> 14) & 0x3; if(variant_code == 0) { int ltn = (message >> 6) & 0x3f; // location table number bool afi = (message >> 5) & 0x1; // alternative freq. indicator bool M = (message >> 4) & 0x1; // mode of transmission bool I = (message >> 3) & 0x1; // international bool N = (message >> 2) & 0x1; // national bool R = (message >> 1) & 0x1; // regional bool U = message & 0x1; // urban lout << "location table: " << ltn << " - " << (afi ? "AFI-ON" : "AFI-OFF") << " - " << (M ? "enhanced mode" : "basic mode") << " - " << (I ? "international " : "") << (N ? "national " : "") << (R ? "regional " : "") << (U ? "urban" : "") << " aid: " << aid << std::endl; } else if(variant_code==1) { int G = (message >> 12) & 0x3; // gap int sid = (message >> 6) & 0x3f; // service identifier int gap_no[4] = {3, 5, 8, 11}; lout << "gap: " << gap_no[G] << " groups, SID: " << sid << " "; } } lout << "message: " << message << " - aid: " << aid << std::endl;; } void parser_impl::decode_type4(unsigned int *group, bool B){ if(B) { dout << "type 4B not implemented yet" << std::endl; return; } unsigned int hours = ((group[2] & 0x1) << 4) | ((group[3] >> 12) & 0x0f); unsigned int minutes = (group[3] >> 6) & 0x3f; double local_time_offset = .5 * (group[3] & 0x1f); if((group[3] >> 5) & 0x1) { local_time_offset *= -1; } double modified_julian_date = ((group[1] & 0x03) << 15) | ((group[2] >> 1) & 0x7fff); unsigned int year = int((modified_julian_date - 15078.2) / 365.25); unsigned int month = int((modified_julian_date - 14956.1 - int(year * 365.25)) / 30.6001); unsigned int day = modified_julian_date - 14956 - int(year * 365.25) - int(month * 30.6001); bool K = ((month == 14) || (month == 15)) ? 1 : 0; year += K; month -= 1 + K * 12; std::string time = str(boost::format("%02i.%02i.%4i, %02i:%02i (%+.1fh)")\ % day % month % (1900 + year) % hours % minutes % local_time_offset); lout << "Clocktime: " << time << std::endl; send_message(5,time); } void parser_impl::decode_type5(unsigned int *group, bool B){ (void) group; (void) B; dout << "type 5 not implemented yet" << std::endl; } void parser_impl::decode_type6(unsigned int *group, bool B){ (void) group; (void) B; dout << "type 6 not implemented yet" << std::endl; } void parser_impl::decode_type7(unsigned int *group, bool B){ (void) group; (void) B; dout << "type 7 not implemented yet" << std::endl; } void parser_impl::decode_type8(unsigned int *group, bool B){ if(B) { dout << "type 8B not implemented yet" << std::endl; return; } bool T = (group[1] >> 4) & 0x1; // 0 = user message, 1 = tuning info bool F = (group[1] >> 3) & 0x1; // 0 = multi-group, 1 = single-group bool D = (group[2] > 15) & 0x1; // 1 = diversion recommended static unsigned long int free_format[4]; static int no_groups = 0; if(T) { // tuning info lout << "#tuning info# "; int variant = group[1] & 0xf; if((variant > 3) && (variant < 10)) { lout << "variant: " << variant << " - " << group[2] << " " << group[3] << std::endl; } else { lout << "invalid variant: " << variant << std::endl; } } else if(F || D) { // single-group or 1st of multi-group unsigned int dp_ci = group[1] & 0x7; // duration & persistence or continuity index bool sign = (group[2] >> 14) & 0x1; // event direction, 0 = +, 1 = - unsigned int extent = (group[2] >> 11) & 0x7; // number of segments affected unsigned int event = group[2] & 0x7ff; // event code, defined in ISO 14819-2 unsigned int location = group[3]; // location code, defined in ISO 14819-3 lout << "#user msg# " << (D ? "diversion recommended, " : ""); if(F) { lout << "single-grp, duration:" << tmc_duration[dp_ci][0]; } else { lout << "multi-grp, continuity index:" << dp_ci; } int event_line = tmc_event_code_index[event][1]; lout << ", extent:" << (sign ? "-" : "") << extent + 1 << " segments" << ", event" << event << ":" << tmc_events[event_line][1] << ", location:" << location << std::endl; } else { // 2nd or more of multi-group unsigned int ci = group[1] & 0x7; // countinuity index bool sg = (group[2] >> 14) & 0x1; // second group unsigned int gsi = (group[2] >> 12) & 0x3; // group sequence lout << "#user msg# multi-grp, continuity index:" << ci << (sg ? ", second group" : "") << ", gsi:" << gsi; lout << ", free format: " << (group[2] & 0xfff) << " " << group[3] << std::endl; // it's not clear if gsi=N-2 when gs=true if(sg) { no_groups = gsi; } free_format[gsi] = ((group[2] & 0xfff) << 12) | group[3]; if(gsi == 0) { decode_optional_content(no_groups, free_format); } } } void parser_impl::decode_optional_content(int no_groups, unsigned long int *free_format){ int label = 0; int content = 0; int content_length = 0; int ff_pointer = 0; for (int i = no_groups; i == 0; i--){ ff_pointer = 12 + 16; while(ff_pointer > 0){ ff_pointer -= 4; label = (free_format[i] && (0xf << ff_pointer)); content_length = optional_content_lengths[label]; ff_pointer -= content_length; content = (free_format[i] && (int(pow(2, content_length) - 1) << ff_pointer)); lout << "TMC optional content (" << label_descriptions[label] << "):" << content << std::endl; } } } void parser_impl::decode_type9(unsigned int *group, bool B){ (void) group; (void) B; dout << "type 9 not implemented yet" << std::endl; } void parser_impl::decode_type10(unsigned int *group, bool B){ (void) group; (void) B; dout << "type 10 not implemented yet" << std::endl; } void parser_impl::decode_type11(unsigned int *group, bool B){ (void) group; (void) B; dout << "type 11 not implemented yet" << std::endl; } void parser_impl::decode_type12(unsigned int *group, bool B){ (void) group; (void) B; dout << "type 12 not implemented yet" << std::endl; } void parser_impl::decode_type13(unsigned int *group, bool B){ (void) group; (void) B; dout << "type 13 not implemented yet" << std::endl; } void parser_impl::decode_type14(unsigned int *group, bool B){ bool tp_on = (group[1] >> 4) & 0x01; char variant_code = group[1] & 0x0f; unsigned int information = group[2]; unsigned int pi_on = group[3]; char pty_on = 0; bool ta_on = 0; static char ps_on[8] = {' ',' ',' ',' ',' ',' ',' ',' '}; double af_1 = 0; double af_2 = 0; if (!B){ switch (variant_code){ case 0: // PS(ON) case 1: // PS(ON) case 2: // PS(ON) case 3: // PS(ON) ps_on[variant_code * 2 ] = (information >> 8) & 0xff; ps_on[variant_code * 2 + 1] = information & 0xff; lout << "PS(ON): ==>" << std::string(ps_on, 8) << "<=="; break; case 4: // AF af_1 = 100.0 * (((information >> 8) & 0xff) + 875); af_2 = 100.0 * ((information & 0xff) + 875); lout << boost::format("AF:%3.2fMHz %3.2fMHz") % (af_1/1000) % (af_2/1000); break; case 5: // mapped frequencies case 6: // mapped frequencies case 7: // mapped frequencies case 8: // mapped frequencies af_1 = 100.0 * (((information >> 8) & 0xff) + 875); af_2 = 100.0 * ((information & 0xff) + 875); lout << boost::format("TN:%3.2fMHz - ON:%3.2fMHz") % (af_1/1000) % (af_2/1000); break; case 9: // mapped frequencies (AM) af_1 = 100.0 * (((information >> 8) & 0xff) + 875); af_2 = 9.0 * ((information & 0xff) - 16) + 531; lout << boost::format("TN:%3.2fMHz - ON:%ikHz") % (af_1/1000) % int(af_2); break; case 10: // unallocated break; case 11: // unallocated break; case 12: // linkage information lout << boost::format("Linkage information: %x%x") % ((information >> 8) & 0xff) % (information & 0xff); break; case 13: // PTY(ON), TA(ON) ta_on = information & 0x01; pty_on = (information >> 11) & 0x1f; lout << "PTY(ON):" << pty_table[int(pty_on)]; if(ta_on) { lout << " - TA"; } break; case 14: // PIN(ON) lout << boost::format("PIN(ON):%x%x") % ((information >> 8) & 0xff) % (information & 0xff); break; case 15: // Reserved for broadcasters use break; default: dout << "invalid variant code:" << variant_code; break; } } if (pi_on){ lout << " PI(ON):" << pi_on; if (tp_on) { lout << "-TP-"; } } lout << std::endl; } void parser_impl::decode_type15(unsigned int *group, bool B){ (void) group; (void) B; dout << "type 15 not implemented yet" << std::endl; } void parser_impl::parse(pmt::pmt_t msg) { if(!pmt::is_blob(msg)) { dout << "wrong input message (no blob)" << std::endl; } if(pmt::blob_length(msg) != 4 * sizeof(unsigned long)) { dout << "input message has wrong size (" << pmt::blob_length(msg) << ")" << std::endl; } unsigned int *group = (unsigned int*)pmt::blob_data(msg); unsigned int group_type = (unsigned int)((group[1] >> 12) & 0xf); bool ab = (group[1] >> 11 ) & 0x1; lout << boost::format("%02i%c ") % group_type % (ab ? 'B' :'A'); lout << "(" << rds_group_acronyms[group_type] << ")"; program_identification = group[0]; // "PI" program_type = (group[1] >> 5) & 0x1f; // "PTY" int pi_country_identification = (program_identification >> 12) & 0xf; int pi_area_coverage = (program_identification >> 8) & 0xf; unsigned char pi_program_reference_number = program_identification & 0xff; std::string pistring = str(boost::format("%04X") % program_identification); send_message(0, pistring); send_message(2, pty_table[program_type]); lout << " - PI:" << pistring << " - " << "PTY:" << pty_table[program_type]; lout << " (country:" << pi_country_codes[pi_country_identification - 1][0]; lout << "/" << pi_country_codes[pi_country_identification - 1][1]; lout << "/" << pi_country_codes[pi_country_identification - 1][2]; lout << "/" << pi_country_codes[pi_country_identification - 1][3]; lout << "/" << pi_country_codes[pi_country_identification - 1][4]; lout << ", area:" << coverage_area_codes[pi_area_coverage]; lout << ", program:" << int(pi_program_reference_number) << ")" << std::endl; switch (group_type) { case 0: decode_type0(group, ab); break; case 1: decode_type1(group, ab); break; case 2: decode_type2(group, ab); break; case 3: decode_type3(group, ab); break; case 4: decode_type4(group, ab); break; case 5: decode_type5(group, ab); break; case 6: decode_type6(group, ab); break; case 7: decode_type7(group, ab); break; case 8: decode_type8(group, ab); break; case 9: decode_type9(group, ab); break; case 10: decode_type10(group, ab); break; case 11: decode_type11(group, ab); break; case 12: decode_type12(group, ab); break; case 13: decode_type13(group, ab); break; case 14: decode_type14(group, ab); break; case 15: decode_type15(group, ab); break; } #define HEX(a) std::hex << std::setfill('0') << std::setw(4) << long(a) << std::dec for(int i = 0; i < 4; i++) { dout << " " << HEX(group[i]); } dout << std::endl; } gqrx-2.9/src/dsp/rds/parser_impl.h000066400000000000000000000051271320142145500172020ustar00rootroot00000000000000/* * Copyright (C) 2014 Bastian Bloessl * * 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 . */ #ifndef INCLUDED_RDS_PARSER_IMPL_H #define INCLUDED_RDS_PARSER_IMPL_H #include "dsp/rds/parser.h" #include namespace gr { namespace rds { class parser_impl : public parser { public: parser_impl(bool log, bool debug); private: ~parser_impl(); void reset(); void send_message(long, std::string); void parse(pmt::pmt_t msg); double decode_af(unsigned int); void decode_optional_content(int, unsigned long int *); void decode_type0( unsigned int* group, bool B); void decode_type1( unsigned int* group, bool B); void decode_type2( unsigned int* group, bool B); void decode_type3( unsigned int* group, bool B); void decode_type4( unsigned int* group, bool B); void decode_type5( unsigned int* group, bool B); void decode_type6( unsigned int* group, bool B); void decode_type7( unsigned int* group, bool B); void decode_type8( unsigned int* group, bool B); void decode_type9( unsigned int* group, bool B); void decode_type10(unsigned int* group, bool B); void decode_type11(unsigned int* group, bool B); void decode_type12(unsigned int* group, bool B); void decode_type13(unsigned int* group, bool B); void decode_type14(unsigned int* group, bool B); void decode_type15(unsigned int* group, bool B); unsigned int program_identification; unsigned char program_type; unsigned char pi_country_identification; unsigned char pi_area_coverage; unsigned char pi_program_reference_number; char radiotext[65]; char program_service_name[9]; bool radiotext_AB_flag; bool traffic_program; bool traffic_announcement; bool music_speech; bool mono_stereo; bool artificial_head; bool compressed; bool static_pty; bool log; bool debug; gr::thread::mutex d_mutex; }; } /* namespace rds */ } /* namespace gr */ #endif /* INCLUDED_RDS_PARSER_IMPL_H */ gqrx-2.9/src/dsp/rds/tmc_events.h000066400000000000000000003343411320142145500170370ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Copyright 2004 Free Software Foundation, Inc. * * This file is part of GNU Radio * * GNU Radio is free software; you can 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, or (at your option) * any later version. * * GNU Radio is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Radio; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ /* ISO 14819-2, Table 2 (paragraph 3.1.3, pages 5-57) */ #define TMC_EVENTS 2047+1 #define TMC_EVENT_LIST_LINES 1590+1 /* table 2, paragraph 3.1.3, pages 5-57 of ISO 14819-2 * 1st column: row number * 2nd column: text (CEN-English) * 3rd column: event code (to be transmitted/received) * 4th column: quantifier type */ static const std::string tmc_events[TMC_EVENTS][4]={ {"0"," "," "," "}, {"1"," "," "," "}, {"2"," "," "," "}, {"3"," "," "," "}, {"4"," "," "," "}, {"5","traffic problem","1"," "}, {"6","stationary traffic","101"," "}, {"7","stationary traffic for 1 km","102"," "}, {"8","stationary traffic for 2 km","103"," "}, {"9","stationary traffic for 3 km","129"," "}, {"10","stationary traffic for 4 km","104"," "}, {"11","stationary traffic for 6 km","105"," "}, {"12","stationary traffic for 10 km","106"," "}, {"13","danger of stationary traffic","130"," "}, {"14","queuing traffic (with average speeds Q)","108","4"}, {"15","queuing traffic for 1 km (with average speeds Q)","109","4"}, {"16","queuing traffic for 2 km (with average speeds Q)","110","4"}, {"17","queuing traffic for 3 km (with average speeds Q)","131","4"}, {"18","queuing traffic for 4 km (with average speeds Q)","111","4"}, {"19","queuing traffic for 6 km (with average speeds Q)","112","4"}, {"20","queuing traffic for 10 km (with average speeds Q)","113","4"}, {"21","danger of queuing traffic (with average speeds Q)","132","4"}, {"22","long queues (with average speeds Q)","133","4"}, {"23","slow traffic (with average speeds Q)","115","4"}, {"24","slow traffic for 1 km (with average speeds Q)","116","4"}, {"25","slow traffic for 2 km (with average speeds Q)","117","4"}, {"26","slow traffic for 3 km (with average speeds Q)","134","4"}, {"27","slow traffic for 4 km (with average speeds Q)","118","4"}, {"28","slow traffic for 6 km (with average speeds Q)","119","4"}, {"29","slow traffic for 10 km (with average speeds Q)","120","4"}, {"30","heavy traffic (with average speeds Q)","122","4"}, {"31","traffic heavier than normal (with average speeds Q)","142","4"}, {"32","traffic very much heavier than normal (with average speeds Q)","143","4"}, {"33","traffic flowing freely (with average speeds Q)","124","4"}, {"34","traffic building up (with average speeds Q)","125","4"}, {"35","traffic easing","135","4"}, {"36","traffic congestion (with average speeds Q)","136","4"}, {"37","traffic congestion, average speed of 10 km/h","70"," "}, {"38","traffic congestion, average speed of 20 km/h","71"," "}, {"39","traffic congestion, average speed of 30 km/h","72"," "}, {"40","traffic congestion, average speed of 40 km/h","73"," "}, {"41","traffic congestion, average speed of 50 km/h","74"," "}, {"42","traffic congestion, average speed of 60 km/h","75"," "}, {"43","traffic congestion, average speed of 70 km/h","76"," "}, {"44","traffic lighter than normal (with average speeds Q)","137","4"}, {"45","queuing traffic (with average speeds Q). Approach with care","138","4"}, {"46","queuing traffic around a bend in the road","139"," "}, {"47","queuing traffic over the crest of a hill","140"," "}, {"48","queuing traffic (with average speeds Q). Danger of stationary traffic","2","4"}, {"49","(Q) accident(s). Stationary traffic","215","0"}, {"50","(Q) accident(s). Stationary traffic for 1 km","216","0"}, {"51","(Q) accident(s). Stationary traffic for 2 km","217","0"}, {"52","(Q) accident(s). Stationary traffic for 3 km","348","0"}, {"53","(Q) accident(s). Stationary traffic for 4 km","218","0"}, {"54","(Q) accident(s). Stationary traffic for 6 km","219","0"}, {"55","(Q) accident(s). Stationary traffic for 10 km","220","0"}, {"56","(Q) accident(s). Danger of stationary traffic","221","0"}, {"57","(Q) accident(s). Queuing traffic","222","0"}, {"58","(Q) accident(s). Queuing traffic for 1 km","223","0"}, {"59","(Q) accident(s). Queuing traffic for 2 km","224","0"}, {"60","(Q) accident(s). Queuing traffic for 3 km","349","0"}, {"61","(Q) accident(s). Queuing traffic for 4 km","225","0"}, {"62","(Q) accident(s). Queuing traffic for 6 km","226","0"}, {"63","(Q) accident(s). Queuing traffic for 10 km","227","0"}, {"64","(Q) accident(s). Danger of queuing traffic","228","0"}, {"65","(Q) accident(s). Slow traffic","229","0"}, {"66","(Q) accident(s). Slow traffic for 1 km","230","0"}, {"67","(Q) accident(s). Slow traffic for 2 km","231","0"}, {"68","(Q) accident(s). Slow traffic for 3 km","350","0"}, {"69","(Q) accident(s). Slow traffic for 4 km","232","0"}, {"70","(Q) accident(s). Slow traffic for 6 km","233","0"}, {"71","(Q) accident(s). Slow traffic for 10 km","234","0"}, {"72","(Q) accident(s). Heavy traffic","236","0"}, {"73","(Q) accident(s). Traffic flowing freely","238","0"}, {"74","(Q) accident(s). Traffic building up","239","0"}, {"75","vehicles slowing to look at (Q) accident(s). Stationary traffic","250","0"}, {"76","vehicles slowing to look at (Q) accident(s). Stationary traffic for 1 km","251","0"}, {"77","vehicles slowing to look at (Q) accident(s). Stationary traffic for 2 km","252","0"}, {"78","vehicles slowing to look at (Q) accident(s). Stationary traffic for 3 km","352","0"}, {"79","vehicles slowing to look at (Q) accident(s). Stationary traffic for 4 km","253","0"}, {"80","vehicles slowing to look at (Q) accident(s). Stationary traffic for 6 km","254","0"}, {"81","vehicles slowing to look at (Q) accident(s). Stationary traffic for 10 km","255","0"}, {"82","vehicles slowing to look at (Q) accident(s). Danger of stationary traffic","256","0"}, {"83","vehicles slowing to look at (Q) accident(s). Queuing traffic","257","0"}, {"84","vehicles slowing to look at (Q) accident(s). Queuing traffic for 1 km","258","0"}, {"85","vehicles slowing to look at (Q) accident(s). Queuing traffic for 2 km","259","0"}, {"86","vehicles slowing to look at (Q) accident(s). Queuing traffic for 3 km","353","0"}, {"87","vehicles slowing to look at (Q) accident(s). Queuing traffic for 4 km","260","0"}, {"88","vehicles slowing to look at (Q) accident(s). Queuing traffic for 6 km","261","0"}, {"89","vehicles slowing to look at (Q) accident(s). Queuing traffic for 10 km","262","0"}, {"90","vehicles slowing to look at (Q) accident(s). Danger of queuing traffic","263","0"}, {"91","vehicles slowing to look at (Q) accident(s)","208","0"}, {"92","vehicles slowing to look at (Q) accident(s). Slow traffic","264","0"}, {"93","vehicles slowing to look at (Q) accident(s). Slow traffic for 1 km","265","0"}, {"94","vehicles slowing to look at (Q) accident(s). Slow traffic for 2 km","266","0"}, {"95","vehicles slowing to look at (Q) accident(s). Slow traffic for 3 km","354","0"}, {"96","vehicles slowing to look at (Q) accident(s). Slow traffic for 4 km","267","0"}, {"97","vehicles slowing to look at (Q) accident(s). Slow traffic for 6 km","268","0"}, {"98","vehicles slowing to look at (Q) accident(s). Slow traffic for 10 km","269","0"}, {"99","vehicles slowing to look at (Q) accident(s). Heavy traffic","271","0"}, {"100","vehicles slowing to look at (Q) accident(s). Traffic building up","274","0"}, {"101","vehicles slowing to look at (Q) accident(s). Danger","355","0"}, {"102","(Q) shed load(s). Stationary traffic","278","0"}, {"103","(Q) shed load(s). Stationary traffic for 1 km","279","0"}, {"104","(Q) shed load(s). Stationary traffic for 2 km","280","0"}, {"105","(Q) shed load(s). Stationary traffic for 3 km","356","0"}, {"106","(Q) shed load(s). Stationary traffic for 4 km","281","0"}, {"107","(Q) shed load(s). Stationary traffic for 6 km","282","0"}, {"108","(Q) shed load(s). Stationary traffic for 10 km","283","0"}, {"109","(Q) shed load(s). Danger of stationary traffic","284","0"}, {"110","(Q) shed load(s). Queuing traffic","285","0"}, {"111","(Q) shed load(s). Queuing traffic for 1 km","286","0"}, {"112","(Q) shed load(s). Queuing traffic for 2 km","287","0"}, {"113","(Q) shed load(s). Queuing traffic for 3 km","357","0"}, {"114","(Q) shed load(s). Queuing traffic for 4 km","288","0"}, {"115","(Q) shed load(s). Queuing traffic for 6 km","289","0"}, {"116","(Q) shed load(s). Queuing traffic for 10 km","290","0"}, {"117","(Q) shed load(s). Danger of queuing traffic","291","0"}, {"118","(Q) shed load(s). Slow traffic","292","0"}, {"119","(Q) shed load(s). Slow traffic for 1 km","293","0"}, {"120","(Q) shed load(s). Slow traffic for 2 km","294","0"}, {"121","(Q) shed load(s). Slow traffic for 3 km","358","0"}, {"122","(Q) shed load(s). Slow traffic for 4 km","295","0"}, {"123","(Q) shed load(s). Slow traffic for 6 km","296","0"}, {"124","(Q) shed load(s). Slow traffic for 10 km","297","0"}, {"125","(Q) shed load(s). Heavy traffic","299","0"}, {"126","(Q) shed load(s). Traffic flowing freely","301","0"}, {"127","(Q) shed load(s). Traffic building up","302","0"}, {"128","(Q) overturned vehicle(s). Stationary traffic","360","0"}, {"129","(Q) overturned vehicle(s). Danger of stationary traffic","361","0"}, {"130","(Q) overturned vehicle(s). Queuing traffic","362","0"}, {"131","(Q) overturned vehicle(s). Danger of queuing traffic","363","0"}, {"132","(Q) overturned vehicle(s). Slow traffic","364","0"}, {"133","(Q) overturned vehicle(s). Heavy traffic","366","0"}, {"134","(Q) overturned vehicle(s). Traffic building up","368","0"}, {"135","Stationary traffic due to (Q) earlier accident(s)","379","0"}, {"136","Danger of stationary traffic due to (Q) earlier accident(s)","380","0"}, {"137","Queuing traffic due to (Q) earlier accident(s)","381","0"}, {"138","Danger of queuing traffic due to (Q) earlier accident(s)","382","0"}, {"139","Slow traffic due to (Q) earlier accident(s)","383","0"}, {"140","Heavy traffic due to (Q) earlier accident(s)","385","0"}, {"141","Traffic building up due to (Q) earlier accident(s)","387","0"}, {"142","(Q) broken down vehicle(s). Stationary traffic","313","0"}, {"143","(Q) broken down vehicle(s). Danger of stationary traffic","314","0"}, {"144","(Q) broken down vehicle(s). Queuing traffic","315","0"}, {"145","(Q) broken down vehicle(s). Danger of queuing traffic","316","0"}, {"146","(Q) broken down vehicle(s). Slow traffic","317","0"}, {"147","(Q) broken down vehicle(s). Heavy traffic","319","0"}, {"148","(Q) broken down vehicle(s). Traffic flowing freely","321","0"}, {"149","(Q) broken down vehicle(s). Traffic building up","322","0"}, {"150","closed ahead. Stationary traffic","410"," "}, {"151","closed ahead. Stationary traffic for 1 km","411"," "}, {"152","closed ahead. Stationary traffic for 2 km","412"," "}, {"153","closed ahead. Stationary traffic for 3 km","495"," "}, {"154","closed ahead. Stationary traffic for 4 km","413"," "}, {"155","closed ahead. Stationary traffic for 6 km","414"," "}, {"156","closed ahead. Stationary traffic for 10 km","415"," "}, {"157","closed ahead. Danger of stationary traffic","416"," "}, {"158","closed ahead. Queuing traffic","417"," "}, {"159","closed ahead. Queuing traffic for 1 km","418"," "}, {"160","closed ahead. Queuing traffic for 2 km","419"," "}, {"161","closed ahead. Queuing traffic for 3 km","496"," "}, {"162","closed ahead. Queuing traffic for 4 km","420"," "}, {"163","closed ahead. Queuing traffic for 6 km","421"," "}, {"164","closed ahead. Queuing traffic for 10 km","422"," "}, {"165","closed ahead. Danger of queuing traffic","423"," "}, {"166","closed ahead. Slow traffic","424"," "}, {"167","closed ahead. Slow traffic for 1 km","425"," "}, {"168","closed ahead. Slow traffic for 2 km","426"," "}, {"169","closed ahead. Slow traffic for 3 km","497"," "}, {"170","closed ahead. Slow traffic for 4 km","427"," "}, {"171","closed ahead. Slow traffic for 6 km","428"," "}, {"172","closed ahead. Slow traffic for 10 km","429"," "}, {"173","closed ahead. Heavy traffic","431"," "}, {"174","closed ahead. Traffic flowing freely","433"," "}, {"175","closed ahead. Traffic building up","434"," "}, {"176","blocked ahead. Stationary traffic","438"," "}, {"177","blocked ahead. Stationary traffic for 1 km","439"," "}, {"178","blocked ahead. Stationary traffic for 2 km","440"," "}, {"179","blocked ahead. Stationary traffic for 3 km","498"," "}, {"180","blocked ahead. Stationary traffic for 4 km","441"," "}, {"181","blocked ahead. Stationary traffic for 6 km","442"," "}, {"182","blocked ahead. Stationary traffic for 10 km","443"," "}, {"183","blocked ahead. Danger of stationary traffic","444"," "}, {"184","blocked ahead. Queuing traffic","445"," "}, {"185","blocked ahead. Queuing traffic for 1 km","446"," "}, {"186","blocked ahead. Queuing traffic for 2 km","447"," "}, {"187","blocked ahead. Queuing traffic for 3 km","499"," "}, {"188","blocked ahead. Queuing traffic for 4 km","448"," "}, {"189","blocked ahead. Queuing traffic for 6 km","449"," "}, {"190","blocked ahead. Queuing traffic for 10 km","450"," "}, {"191","blocked ahead. Danger of queuing traffic","451"," "}, {"192","blocked ahead. Slow traffic","452"," "}, {"193","blocked ahead. Slow traffic for 1 km","453"," "}, {"194","blocked ahead. Slow traffic for 2 km","454"," "}, {"195","blocked ahead. Slow traffic for 3 km","626"," "}, {"196","blocked ahead. Slow traffic for 4 km","455"," "}, {"197","blocked ahead. Slow traffic for 6 km","456"," "}, {"198","blocked ahead. Slow traffic for 10 km","457"," "}, {"199","blocked ahead. Heavy traffic","459"," "}, {"200","blocked ahead. Traffic flowing freely","461"," "}, {"201","blocked ahead. Traffic building up","462"," "}, {"202","(Q) lanes closed. Stationary traffic","521","0"}, {"203","(Q) lanes closed. Stationary traffic for 1 km","522","0"}, {"204","(Q) lanes closed. Stationary traffic for 2 km","523","0"}, {"205","(Q) lanes closed. Stationary traffic for 3 km","651","0"}, {"206","(Q) lanes closed. Stationary traffic for 4 km","524","0"}, {"207","(Q) lanes closed. Stationary traffic for 6 km","525","0"}, {"208","(Q) lanes closed. Stationary traffic for 10 km","526","0"}, {"209","(Q) lanes closed. Danger of stationary traffic","527","0"}, {"210","(Q) lanes closed. Queuing traffic","528","0"}, {"211","(Q) lanes closed. Queuing traffic for 1 km","529","0"}, {"212","(Q) lanes closed. Queuing traffic for 2 km","530","0"}, {"213","(Q) lanes closed. Queuing traffic for 3 km","652","0"}, {"214","(Q) lanes closed. Queuing traffic for 4 km","531","0"}, {"215","(Q) lanes closed. Queuing traffic for 6 km","532","0"}, {"216","(Q) lanes closed. Queuing traffic for 10 km","533","0"}, {"217","(Q) lanes closed. Danger of queuing traffic","534","0"}, {"218","(Q) lanes closed. Slow traffic","535","0"}, {"219","(Q) lanes closed. Slow traffic for 1 km","536","0"}, {"220","(Q) lanes closed. Slow traffic for 2 km","537","0"}, {"221","(Q) lanes closed. Slow traffic for 3 km","653","0"}, {"222","(Q) lanes closed. Slow traffic for 4 km","538","0"}, {"223","(Q) lanes closed. Slow traffic for 6 km","539","0"}, {"224","(Q) lanes closed. Slow traffic for 10 km","540","0"}, {"225","(Q) lanes closed. Heavy traffic","542","0"}, {"226","(Q)lanes closed. Traffic flowing freely","544","0"}, {"227","(Q)lanes closed. Traffic building up","545","0"}, {"228","carriageway reduced (from Q lanes) to one lane. Stationary traffic","546","0"}, {"229","carriageway reduced (from Q lanes) to one lane. Danger of stationary traffic","547","0"}, {"230","carriageway reduced (from Q lanes) to one lane. Queuing traffic","548","0"}, {"231","carriageway reduced (from Q lanes) to one lane. Danger of queuing traffic","549","0"}, {"232","carriageway reduced (from Q lanes) to one lane. Slow traffic","550","0"}, {"233","carriageway reduced (from Q lanes) to one lane. Heavy traffic","552","0"}, {"234","carriageway reduced (from Q lanes) to one lane. Traffic flowing freely","554","0"}, {"235","carriageway reduced (from Q lanes) to one lane. Traffic building up","555","0"}, {"236","carriageway reduced (from Q lanes) to two lanes. Stationary traffic","556","0"}, {"237","carriageway reduced (from Q lanes) to two lanes. Danger of stationary traffic","557","0"}, {"238","carriageway reduced (from Q lanes) to two lanes. Queuing traffic","558","0"}, {"239","carriageway reduced (from Q lanes) to two lanes. Danger of queuing traffic","559","0"}, {"240","carriageway reduced (from Q lanes) to two lanes. Slow traffic","560","0"}, {"241","carriageway reduced (from Q lanes) to two lanes. Heavy traffic","562","0"}, {"242","carriageway reduced (from Q lanes) to two lanes. Traffic flowing freely","564","0"}, {"243","carriageway reduced (from Q lanes) to two lanes. Traffic building up","565","0"}, {"244","carriageway reduced (from Q lanes) three lanes. Stationary traffic","566","0"}, {"245","carriageway reduced (from Q lanes) three lanes. Danger of stationary traffic","567","0"}, {"246","carriageway reduced (from Q lanes) three lanes. Queuing traffic","568","0"}, {"247","carriageway reduced (from Q lanes) three lanes. Danger of queuing traffic","569","0"}, {"248","carriageway reduced (from Q lanes) to three lanes. Slow traffic","570","0"}, {"249","carriageway reduced (from Q lanes) to three lanes. Heavy traffic","572","0"}, {"250","carriageway reduced (from Q lanes) to three lanes. Traffic flowing freely","574","0"}, {"251","carriageway reduced (from Q lanes) to three lanes. Traffic building up","575","0"}, {"252","contraflow. Stationary traffic","576"," "}, {"253","contraflow. Stationary traffic for 1 km","577"," "}, {"254","contraflow. Stationary traffic for 2 km","578"," "}, {"255","contraflow. Stationary traffic for 3 km","654"," "}, {"256","contraflow. Stationary traffic for 4 km","579"," "}, {"257","contraflow. Stationary traffic for 6 km","580"," "}, {"258","contraflow. Stationary traffic for 10 km","581"," "}, {"259","contraflow. Danger of stationary traffic","582"," "}, {"260","contraflow. Queuing traffic","583"," "}, {"261","contraflow. Queuing traffic for1km","584"," "}, {"262","contraflow. Queuing traffic for2km","585"," "}, {"263","contraflow. Queuing traffic for3km","655"," "}, {"264","contraflow. Queuing traffic for4km","586"," "}, {"265","contraflow. Queuing traffic for6km","587"," "}, {"266","contraflow. Queuing traffic for 10 km","588"," "}, {"267","contraflow. Danger of queuing traffic","589"," "}, {"268","contraflow. Slow traffic","590"," "}, {"269","contraflow. Slow traffic for 1 km","591"," "}, {"270","contraflow. Slow traffic for 2 km","592"," "}, {"271","contraflow. Slow traffic for 3 km","656"," "}, {"272","contraflow. Slow traffic for 4 km","593"," "}, {"273","contraflow. Slow traffic for 6 km","594"," "}, {"274","contraflow. Slow traffic for 10 km","595"," "}, {"275","contraflow. Heavy traffic","597"," "}, {"276","contraflow. Traffic flowing freely","599"," "}, {"277","contraflow. Traffic building up","600"," "}, {"278","narrow lanes. Stationary traffic","604"," "}, {"279","narrow lanes. Danger of stationary traffic","605"," "}, {"280","narrow lanes. Queuing traffic","606"," "}, {"281","narrow lanes. Danger of queuing traffic","607"," "}, {"282","narrow lanes. Slow traffic","608"," "}, {"283","narrow lanes. Heavy traffic","610"," "}, {"284","narrow lanes. Traffic flowing freely","612"," "}, {"285","narrow lanes. Traffic building up","613"," "}, {"286","contraflow with narrow lanes. Stationary traffic","614"," "}, {"287","contraflow with narrow lanes. Danger of stationary traffic","615"," "}, {"288","contraflow with narrow lanes. Queuing traffic","616"," "}, {"289","contraflow with narrow lanes. Danger of queuing traffic","617"," "}, {"290","contraflow with narrow lanes. Slow traffic","618"," "}, {"291","contraflow with narrow lanes. Heavy traffic","620"," "}, {"292","contraflow with narrow lanes. Traffic flowing freely","622"," "}, {"293","contraflow with narrow lanes. Traffic building up","623"," "}, {"294","(Q sets of) roadworks. Stationary traffic","710","0"}, {"295","(Q sets of) roadworks. Stationary traffic for 1 km","711","0"}, {"296","(Q sets of) roadworks. Stationary traffic for 2 km","712","0"}, {"297","(Q sets of) roadworks. Stationary traffic for 3 km","812","0"}, {"298","(Q sets of) roadworks. Stationary traffic for 4 km","713","0"}, {"299","(Q sets of) roadworks. Stationary traffic for 6 km","714","0"}, {"300","(Q sets of) roadworks. Stationary traffic for 10 km","715","0"}, {"301","(Q sets of) roadworks. Danger of stationary traffic","716","0"}, {"302","(Q sets of) roadworks. Queuing traffic","717","0"}, {"303","(Q sets of) roadworks. Queuing traffic for 1 km","718","0"}, {"304","(Q sets of) roadworks. Queuing traffic for 2 km","719","0"}, {"305","(Q sets of) roadworks. Queuing traffic for 3 km","813","0"}, {"306","(Q sets of) roadworks. Queuing traffic for 4 km","720","0"}, {"307","(Q sets of) roadworks. Queuing traffic for 6 km","721","0"}, {"308","(Q sets of) roadworks. Queuing traffic for 10 km","722","0"}, {"309","(Q sets of) roadworks. Danger of queuing traffic","723","0"}, {"310","(Q sets of) roadworks. Slow traffic","724","0"}, {"311","(Q sets of) roadworks. Slow traffic for 1 km","725","0"}, {"312","(Q sets of) roadworks. Slow traffic for 2 km","726","0"}, {"313","(Q sets of) roadworks. Slow traffic for 3 km","814","0"}, {"314","(Q sets of) roadworks. Slow traffic for 4 km","727","0"}, {"315","(Q sets of) roadworks. Slow traffic for 6 km","728","0"}, {"316","(Q sets of) roadworks. Slow traffic for 10 km","729","0"}, {"317","(Q sets of) roadworks. Heavy traffic","731","0"}, {"318","(Q sets of) roadworks. Traffic flowing freely","733","0"}, {"319","(Q sets of) roadworks. Traffic building up","734","0"}, {"320","(Q sections of) resurfacing work. Stationary traffic","750","0"}, {"321","(Q sections of) resurfacing work. Stationary traffic for 1 km","751","0"}, {"322","(Q sections of) resurfacing work. Stationary traffic for 2 km","752","0"}, {"323","(Q sections of) resurfacing work. Stationary traffic for 3 km","818","0"}, {"324","(Q sections of) resurfacing work. Stationary traffic for 4 km","753","0"}, {"325","(Q sections of) resurfacing work. Stationary traffic for 6 km","754","0"}, {"326","(Q sections of) resurfacing work. Stationary traffic for 10 km","755","0"}, {"327","(Q sections of) resurfacing work. Danger of stationary traffic","756","0"}, {"328","(Q sections of) resurfacing work. Queuing traffic","757","0"}, {"329","(Q sections of) resurfacing work. Queuing traffic for 1 km","758","0"}, {"330","(Q sections of) resurfacing work. Queuing traffic for 2 km","759","0"}, {"331","(Q sections of) resurfacing work. Queuing traffic for 3 km","819","0"}, {"332","(Q sections of) resurfacing work. Queuing traffic for 4 km","760","0"}, {"333","(Q sections of) resurfacing work. Queuing traffic for 6 km","761","0"}, {"334","(Q sections of) resurfacing work. Queuing traffic for 10 km","762","0"}, {"335","(Q sections of) resurfacing work. Danger of queuing traffic","763","0"}, {"336","(Q sections of) resurfacing work. traffic","764","0"}, {"337","(Q sections of) resurfacing work. traffic for 1 km","765","0"}, {"338","(Q sections of) resurfacing work. traffic for 2 km","766","0"}, {"339","(Q sections of) resurfacing work. traffic for 3 km","820","0"}, {"340","(Q sections of) resurfacing work. traffic for 4 km","767","0"}, {"341","(Q sections of) resurfacing work. traffic for 6 km","768","0"}, {"342","(Q sections of) resurfacing work. traffic for 10 km","769","0"}, {"343","(Q sections of) resurfacing work. Heavy traffic","771","0"}, {"344","(Q sections of) resurfacing work. Traffic flowing freely","773","0"}, {"345","(Q sections of) resurfacing work. Traffic building up","774","0"}, {"346","(Q sets of) road marking work. Stationary traffic","783","0"}, {"347","(Q sets of) road marking work. Danger of stationary traffic","784","0"}, {"348","(Q sets of) road marking work. Queuing traffic","785","0"}, {"349","(Q sets of) road marking work. Danger of queuing traffic","786","0"}, {"350","(Q sets of) road marking work. Slow traffic","787","0"}, {"351","(Q sets of) road marking work. Heavy traffic","789","0"}, {"352","(Q sets of) road marking work. Traffic flowing freely","791","0"}, {"353","(Q sets of) road marking work. Traffic building up","792","0"}, {"354","(Q sets of) slow moving maintenance vehicles. Stationary traffic","825","0"}, {"355","(Q sets of) slow moving maintenance vehicles. Danger of stationary traffic","826","0"}, {"356","(Q sets of) slow moving maintenance vehicles. Queuing traffic","827","0"}, {"357","(Q sets of) slow moving maintenance vehicles. Danger of queuing traffic","828","0"}, {"358","(Q sets of) slow moving maintenance vehicles. Slow traffic","829","0"}, {"359","(Q sets of) slow moving maintenance vehicles. Heavy traffic","831","0"}, {"360","(Q sets of) slow moving maintenance vehicles. Traffic flowing freely","833","0"}, {"361","(Q sets of) slow moving maintenance vehicles. Traffic building up","834","0"}, {"362","flooding. Stationary traffic","928"," "}, {"363","flooding. Danger of stationary traffic","929"," "}, {"364","flooding. Queuing traffic","930"," "}, {"365","flooding. Danger of queuing traffic","931"," "}, {"366","flooding. Slow traffic","932"," "}, {"367","flooding. Heavy traffic","934"," "}, {"368","flooding. Traffic flowing freely","936"," "}, {"369","flooding. Traffic building up","937"," "}, {"370","major event. Stationary traffic","1517"," "}, {"371","major event. Danger of stationary traffic","1518"," "}, {"372","major event. Queuing traffic","1519"," "}, {"373","major event. Danger of queuing traffic","1520"," "}, {"374","major event. Slow traffic","1521"," "}, {"375","major event. Heavy traffic","1523"," "}, {"376","major event. Traffic flowing freely","1525"," "}, {"377","major event. Traffic building up","1526"," "}, {"378","sports meeting. Stationary traffic","1531"," "}, {"379","sports meeting. Danger of stationary traffic","1532"," "}, {"380","sports meeting. Queuing traffic","1533"," "}, {"381","sports meeting. Danger of queuing traffic","1534"," "}, {"382","sports meeting. Slow traffic","1535"," "}, {"383","sports meeting. Heavy traffic","1537"," "}, {"384","sports meeting. Traffic flowing freely","1539"," "}, {"385","sports meeting. Traffic building up","1540"," "}, {"386","fair. Stationary traffic","1545"," "}, {"387","fair. Danger of stationary traffic","1546"," "}, {"388","fair. Queuing traffic","1547"," "}, {"389","fair. Danger of queuing traffic","1548"," "}, {"390","fair. Slow traffic","1549"," "}, {"391","fair. Heavy traffic","1551"," "}, {"392","fair. Traffic flowing freely","1553"," "}, {"393","fair. Traffic building up","1554"," "}, {"394","security alert. Stationary traffic","1571"," "}, {"395","security alert. Danger of stationary traffic","1572"," "}, {"396","security alert. Queuing traffic","1573"," "}, {"397","security alert. Danger of queuing traffic","1574"," "}, {"398","security alert. Slow traffic","1575"," "}, {"399","security alert. Heavy traffic","1577"," "}, {"400","evacuation. Heavy traffic","1495"," "}, {"401","security alert. Traffic flowing freely","1586"," "}, {"402","security alert. Traffic building up","1579"," "}, {"403","(Q sets of) traffic lights not working. Stationary traffic","1807","0"}, {"404","(Q sets of) traffic lights not working. Danger of stationary traffic","1808","0"}, {"405","(Q sets of) traffic lights not working. Queuing traffic","1809","0"}, {"406","(Q sets of) traffic lights not working. Danger of queuing traffic","1810","0"}, {"407","(Q sets of) traffic lights not working. Slow traffic","1811","0"}, {"408","(Q sets of) traffic lights not working. Heavy traffic","1813","0"}, {"409","(Q sets of) traffic lights not working. Traffic flowing freely","1815","0"}, {"410","(Q sets of) traffic lights not working. Traffic building up","1816","0"}, {"411","level crossing failure. Stationary traffic","1820"," "}, {"412","level crossing failure. Danger of stationary traffic","1821"," "}, {"413","level crossing failure. Queuing traffic","1822"," "}, {"414","level crossing failure. Danger of queuing traffic","1823"," "}, {"415","level crossing failure. Slow traffic","1824"," "}, {"416","level crossing failure. Heavy traffic","1826"," "}, {"417","level crossing failure. Traffic flowing freely","1828"," "}, {"418","level crossing failure. Traffic building up","1829"," "}, {"419"," "," "," "}, {"420","no problems to report","126"," "}, {"421","traffic congestion cleared","127"," "}, {"422","traffic has returned to normal","1584"," "}, {"423"," "," "," "}, {"424","message cancelled","128"," "}, {"425"," "," "," "}, {"426"," "," "," "}, {"427"," "," "," "}, {"428"," "," "," "}, {"429"," "," "," "}, {"430","traffic problem expected","55"," "}, {"431","stationary traffic expected","107"," "}, {"432","queuing traffic expected","114"," "}, {"433","slow traffic expected","121"," "}, {"434","heavy traffic expected","123"," "}, {"435","traffic congestion expected","56"," "}, {"436","(Q) accident(s). Slow traffic expected","235","0"}, {"437","(Q) accident(s). Heavy traffic expected","237","0"}, {"438","vehicles slowing to look at (Q) accident(s). Slow traffic expected","270","0"}, {"439","vehicles slowing to look at (Q) accident(s). Heavy traffic expected","272","0"}, {"440","(Q) shed load(s). Slow traffic expected","298","0"}, {"441","(Q) shed load(s). Heavy traffic expected","300","0"}, {"442","(Q) overturned vehicle(s). Slow traffic expected","365","0"}, {"443","(Q) overturned vehicle(s). Heavy traffic expected","367","0"}, {"444","(Q) broken down vehicle(s). Slow traffic expected","318","0"}, {"445","(Q) broken down vehicle(s). Heavy traffic expected","320","0"}, {"446","closed ahead. Slow traffic expected","430"," "}, {"447","closed ahead. Heavy traffic expected","432"," "}, {"448","blocked ahead. Slow traffic expected","458"," "}, {"449","blocked ahead. Heavy traffic expected","460"," "}, {"450","(Q) lanes closed. Slow traffic expected","541","0"}, {"451","(Q) lanes closed. Heavy traffic expected","543","0"}, {"452","carriageway reduced (from Q lanes) to one lane. Slow traffic expected","551","0"}, {"453","carriageway reduced (from Q lanes) to one lane. Heavy traffic expected","553","0"}, {"454","carriageway reduced (from Q lanes) to two lanes. Slow traffic expected","561","0"}, {"455","carriageway reduced (from Q lanes) to two lanes. Heavy traffic expected","563","0"}, {"456","carriageway reduced (from Q lanes) to three lanes. Slow traffic expected","571","0"}, {"457","carriageway reduced (from Q lanes) to three lanes. Heavy traffic expected","573","0"}, {"458","contraflow. Slow traffic expected","596"," "}, {"459","contraflow. Heavy traffic expected","598"," "}, {"460","narrow lanes. Slow traffic expected","609"," "}, {"461","narrow lanes. Heavy traffic expected","611"," "}, {"462","contraflow with narrow lanes. Slow traffic expected","619"," "}, {"463","contraflow with narrow lanes. Heavy traffic expected","621"," "}, {"464","(Q sets of) roadworks. Slow traffic expected","730","0"}, {"465","(Q sets of) roadworks. Heavy traffic expected","732","0"}, {"466","(Q sections of) resurfacing work. Slow traffic expected","770","0"}, {"467","(Q sections of) resurfacing work. Heavy traffic expected","772","0"}, {"468","(Q sets of) road marking work. Slow traffic expected","788","0"}, {"469","(Q sets of) road marking work. Heavy traffic expected","790","0"}, {"470","(Q sets of) slow moving maintenance vehicles. Slow traffic expected","830","0"}, {"471","(Q sets of) slow moving maintenance vehicles. Heavy traffic expected","832","0"}, {"472","flooding. Slow traffic expected","933"," "}, {"473","flooding. Heavy traffic expected","935"," "}, {"474","major event. Slow traffic expected","1522"," "}, {"475","major event. Heavy traffic expected","1524"," "}, {"476","sports meeting. Slow traffic expected","1536"," "}, {"477","sports meeting. Heavy traffic expected","1538"," "}, {"478","fair. Slow traffic expected","1550"," "}, {"479","fair. Heavy traffic expected","1552"," "}, {"480","security alert. Slow traffic expected","1576"," "}, {"481","security alert. Heavy traffic expected","1578"," "}, {"482","(Q sets of) traffic lights not working. Slow traffic expected","1812","0"}, {"483","(Q sets of) traffic lights not working. Heavy traffic expected","1814","0"}, {"484","level crossing failure. Slow traffic expected","1825"," "}, {"485","level crossing failure. Heavy traffic expected","1827"," "}, {"486"," "," "," "}, {"487","normal traffic expected","57"," "}, {"488"," "," "," "}, {"489","message cancelled","1589"," "}, {"490"," "," "," "}, {"491"," "," "," "}, {"492"," "," "," "}, {"493"," "," "," "}, {"494"," "," "," "}, {"495","(Q) accident(s)","201","0"}, {"496","(Q) serious accident(s)","202","0"}, {"497","multi-vehicle accident (involving Q vehicles)","203","0"}, {"498","accident involving (a/Q) heavy lorr(y/ies)","204","0"}, {"499","accident involving (a/Q) bus(es)","335","0"}, {"500","(Q) accident(s) involving hazardous materials","205","0"}, {"501","(Q) fuel spillage accident(s)","206","0"}, {"502","(Q) chemical spillage accident(s)","207","0"}, {"503","(Q) oil spillage accident(s)","336","0"}, {"504","(Q) overturned vehicle(s)","337","0"}, {"505","(Q) overturned heavy lorr(y/ies)","338","0"}, {"506","(Q) jack-knifed trailer(s)","339","0"}, {"507","(Q) jack-knifed caravan(s)","340","0"}, {"508","(Q) jack-knifed articulated lorr(y/ies)","341","0"}, {"509","(Q) vehicle fire(s)","213","0"}, {"510","(Q) vehicle(s) spun around","342","0"}, {"511","(Q) overturned vehicle(s). Danger","378","0"}, {"512","(Q) earlier accident(s)","343","0"}, {"513","accident investigation work","344"," "}, {"514","accident investigation work. Danger","391"," "}, {"515","(Q) accident(s) in roadworks area","351","0"}, {"516","(Q) accident(s), traffic being directed around accident area","12","0"}, {"517","(Q) secondary accident(s)","345","0"}, {"518","(Q) secondary accident(s). Danger","392","0"}, {"519","(Q) accident(s) in the opposing lanes","209","0"}, {"520"," "," "," "}, {"521","all accidents cleared, no problems to report","141"," "}, {"522","accident cleared","333"," "}, {"523"," "," "," "}, {"524","message cancelled","334"," "}, {"525"," "," "," "}, {"526"," "," "," "}, {"527"," "," "," "}, {"528"," "," "," "}, {"529"," "," "," "}, {"530","(Q) incident(s)","214","0"}, {"531","(Q) broken down vehicle(s)","211","0"}, {"532","(Q) broken down vehicle(s). Danger","393","0"}, {"533","(Q) broken down heavy lorr(y/ies)","212","0"}, {"534","(Q) broken down heavy lorr(y/ies). Danger","394","0"}, {"535","(Q) broken down bus(es)","346","0"}, {"536","over-height warning system triggered","11"," "}, {"537","clearance work","924"," "}, {"538","clearance work. Danger","1034"," "}, {"539","rescue and recovery work in progress","397"," "}, {"540","rescue and recovery work in progress. Danger","1066"," "}, {"541"," "," "," "}, {"542","incident cleared","396"," "}, {"543","road cleared","395"," "}, {"544"," "," "," "}, {"545","message cancelled","2028"," "}, {"546"," "," "," "}, {"547"," "," "," "}, {"548"," "," "," "}, {"549"," "," "," "}, {"550","intermittent short term closures","666"," "}, {"551","Closed","401"," "}, {"552","road closed due to (Q) accident(s)","240","0"}, {"553","closed, rescue and recovery work in progress","16"," "}, {"554","(Q) lane(s) closed","500","0"}, {"555","(Q) right lane(s) closed","501","0"}, {"556","(Q) centre lane(s) closed","502","0"}, {"557","(Q) left lane(s) closed","503","0"}, {"558","hard shoulder closed","504"," "}, {"559","emergency lane closed","637"," "}, {"560","(Q) overtaking lane(s) closed","41","0"}, {"561","one lane closed","641"," "}, {"562","two lanes closed","505"," "}, {"563","three lanes closed","506"," "}, {"564","carriageway reduced (from Q lanes) one lane","514","0"}, {"565","carriageway reduced (from Q lanes) two lanes","515","0"}, {"566","carriageway reduced (from Q lanes) three lanes","516","0"}, {"567","contraflow. Carriageway reduced (from Q lanes) to one lane","601","0"}, {"568","contraflow. Carriageway reduced (from Q lanes) to two lanes","602","0"}, {"569","contraflow. Carriageway reduced (from Q lanes) to three lanes","603","0"}, {"570","closed due to (Q sets of) roadworks","735","0"}, {"571","(Q sets of) roadworks. Right lane closed","736","0"}, {"572","(Q sets of) roadworks. Centre lane closed","737","0"}, {"573","(Q sets of) roadworks. Left lane closed","738","0"}, {"574","(Q sets of) roadworks. Hard shoulder closed","739","0"}, {"575","(Q sets of) roadworks. Two lanes closed","740","0"}, {"576","(Q sets of) roadworks. Three lanes closed","741","0"}, {"577","roadworks, (Q) overtaking lane(s) closed","51","0"}, {"578","roadworks. Carriageway reduced (from Q lanes) to one lane","743","0"}, {"579","roadworks. Carriageway reduced (from Q lanes) to two lanes","744","0"}, {"580","roadworks. Carriageway reduced (from Q lanes) to three lanes","745","0"}, {"581","resurfacing work. Carriageway reduced (from Q lanes) to one lane","776","0"}, {"582","resurfacing work. Carriageway reduced (from Q lanes) to two lanes","777","0"}, {"583","resurfacing work. Carriageway reduced (from Q lanes) to three lanes","778","0"}, {"584","(Q sets of) road marking work. Right lane closed","793","0"}, {"585","(Q sets of) road marking work. Centre lane closed","794","0"}, {"586","(Q sets of) road marking work. Left lane closed","795","0"}, {"587","(Q sets of) road marking work. Hard shoulder closed","796","0"}, {"588","(Q sets of) road marking work. Two lanes closed","797","0"}, {"589","(Q sets of) road marking work. Three lanes closed","798","0"}, {"590","(Q sets of) slow moving maintenance vehicles. Right lane closed","835","0"}, {"591","(Q sets of) slow moving maintenance vehicles. Centre lane closed","836","0"}, {"592","(Q sets of) slow moving maintenance vehicles. Left lane closed","837","0"}, {"593","(Q sets of) slow moving maintenance vehicles. Two lanes closed","838","0"}, {"594","(Q sets of) slow moving maintenance vehicles. Three lanes closed","839","0"}, {"595","closed for bridge demolition work (at Q bridges)","799","0"}, {"596","road closed due to landslips","947"," "}, {"597","road closed due to burst water main","957"," "}, {"598","closed due to serious fire","965"," "}, {"599","subsidence. Carriageway reduced (from Q lanes) to one lane","951","0"}, {"600","subsidence. Carriageway reduced (from Q lanes) to two lanes","952","0"}, {"601","subsidence. Carriageway reduced (from Q lanes) to three lanes","953","0"}, {"602","closed due to sewer collapse","956"," "}, {"603","closed due to gas leak","961"," "}, {"604","closed for clearance work","969"," "}, {"605","snow on the road. Carriageway reduced (from Q lanes) to one lane","1021","0"}, {"606","snow on the road. Carriageway reduced (from Q lanes) to two lanes","1022","0"}, {"607","snow on the road. Carriageway reduced (from Q lanes) to three lanes","1023","0"}, {"608","(Q) accident(s). Right lane blocked","241","0"}, {"609","(Q) accident(s). Centre lane blocked","242","0"}, {"610","(Q) accident(s). Left lane blocked","243","0"}, {"611","(Q) accident(s). Hard shoulder blocked","244","0"}, {"612","(Q) accident(s). Two lanes blocked","245","0"}, {"613","(Q) accident(s). Three lanes blocked","246","0"}, {"614","blocked by (Q) shed load(s)","303","0"}, {"615","(Q) shed load(s). Right lane blocked","304","0"}, {"616","(Q) shed load(s). Centre lane blocked","305","0"}, {"617","(Q) shed load(s). Left lane blocked","306","0"}, {"618","(Q) shed load(s). Hard shoulder blocked","307","0"}, {"619","(Q) shed load(s). Two lanes blocked","308","0"}, {"620","(Q) shed load(s). Three lanes blocked","309","0"}, {"621","blocked by (Q) overturned vehicle(s)","369","0"}, {"622","(Q) overturned vehicle(s). Right lane blocked","370","0"}, {"623","(Q) overturned vehicle(s). Centre lane blocked","371","0"}, {"624","(Q) overturned vehicle(s). Left lane blocked","372","0"}, {"625","(Q) overturned vehicle(s). Two lanes blocked","373","0"}, {"626","(Q) overturned vehicle(s). Three lanes blocked","374","0"}, {"627","blocked by (Q) broken down vehicle(s).","323","0"}, {"628","(Q) broken down vehicle(s). Right lane blocked","324","0"}, {"629","(Q) broken down lane vehicle(s). Centre lane blocked","325","0"}, {"630","(Q) broken down vehicle(s). Left lane blocked","326","0"}, {"631","(Q) broken down vehicle(s). Hard shoulder blocked","327","0"}, {"632","(Q) broken down vehicle(s). Two lanes blocked","328","0"}, {"633","(Q) broken down vehicle(s). Three lanes blocked","329","0"}, {"634","blocked","402"," "}, {"635","(Q) lane(s) blocked","520","0"}, {"636","(Q) right lane(s) blocked","507","0"}, {"637","(Q) centre lane(s) blocked","508","0"}, {"638","(Q) left lane(s) blocked","509","0"}, {"639","hard shoulder blocked","510"," "}, {"640","emergency lane blocked","642"," "}, {"641","(Q) overtaking lane(s) blocked","42","0"}, {"642","One lane blocked","646"," "}, {"643","Two lanes blocked","511"," "}, {"644","three lanes blocked","512"," "}, {"645","blocked by (Q) obstruction(s) on the road","980","0"}, {"646","blocked due to spillage on roadway","982"," "}, {"647","blocked by storm damage","925"," "}, {"648","blocked by (Q) fallen trees","926","0"}, {"649","blocked by fallen power cables","987"," "}, {"650","contraflow","517"," "}, {"651","narrow lanes","518"," "}, {"652","contraflow with narrow lanes","519"," "}, {"653","single alternate line traffic","513"," "}, {"654","(Q sets of) roadworks. Contraflow","746","0"}, {"655","(Q sets of) roadworks. Single alternate line traffic","742","0"}, {"656","(Q sections of) resurfacing work. Contraflow","779","0"}, {"657","(Q sections of) resurfacing work. Single alternate line traffic","775","0"}, {"658","subsidence. Contraflow in operation","954"," "}, {"659","turning lane closed","638"," "}, {"660","turning lane blocked","643"," "}, {"661","crawler lane closed","639"," "}, {"662","slow vehicle lane closed","640"," "}, {"663","heavy vehicle lane closed","678"," "}, {"664","crawler lane blocked","644"," "}, {"665","slow vehicle lane blocked","645"," "}, {"666","heavy vehicle lane blocked","679"," "}, {"667","lane blockages cleared","657"," "}, {"668","road cleared","631"," "}, {"669","contraflow removed","658"," "}, {"670","reopened","467"," "}, {"671","open","630"," "}, {"672","lane closures removed","624"," "}, {"673","carriageway closed","664"," "}, {"674","both directions closed","665"," "}, {"675","message cancelled","625"," "}, {"676"," "," "," "}, {"677"," "," "," "}, {"678"," "," "," "}, {"679"," "," "," "}, {"680"," "," "," "}, {"681","parallel carriageway closed","479"," "}, {"682","right-hand parallel carriageway closed","480"," "}, {"683","left-hand parallel carriageway closed","481"," "}, {"684","express lanes closed","482"," "}, {"685","through traffic lanes closed","483"," "}, {"686","local lanes closed","484"," "}, {"687","parallel carriageway blocked","486"," "}, {"688","right-hand parallel carriageway blocked","487"," "}, {"689","left-hand parallel carriageway blocked","488"," "}, {"690","express lanes blocked","489"," "}, {"691","through traffic lanes blocked","490"," "}, {"692","local lanes blocked","491"," "}, {"693","bus lane closed","1982"," "}, {"694","bus lane blocked","676"," "}, {"695","bridge blocked","26"," "}, {"696","tunnel blocked","27"," "}, {"697"," "," "," "}, {"698","all carriageways cleared","663"," "}, {"699","all carriageways reopened","634"," "}, {"700"," "," "," "}, {"701","message cancelled","672"," "}, {"702"," "," "," "}, {"703"," "," "," "}, {"704"," "," "," "}, {"705"," "," "," "}, {"706"," "," "," "}, {"707","(Q th) exit slip road closed","407","0"}, {"708","(Q) exit slip road(s) closed","474","0"}, {"709","(Q th) exit slip road blocked","475","0"}, {"710","exit blocked","476"," "}, {"711","slip roads closed","408"," "}, {"712","slip roads blocked","477"," "}, {"713","slip road restrictions","409"," "}, {"714","connecting carriageway closed","478"," "}, {"715","connecting carriageway blocked","485"," "}, {"716"," "," "," "}, {"717","exit reopened","633"," "}, {"718","slip roads reopened","466"," "}, {"719"," "," "," "}, {"720","message cancelled","673"," "}, {"721"," "," "," "}, {"722"," "," "," "}, {"723"," "," "," "}, {"724"," "," "," "}, {"725"," "," "," "}, {"726","(Q th) entry slip road closed","406","0"}, {"727","(Q) entry slip road(s) closed","471","0"}, {"728","(Q th) entry slip road blocked","472","0"}, {"729","entry blocked","473"," "}, {"730"," "," "," "}, {"731","entry reopened","632"," "}, {"732"," "," "," "}, {"733","message cancelled","399"," "}, {"734"," "," "," "}, {"735"," "," "," "}, {"736"," "," "," "}, {"737"," "," "," "}, {"738"," "," "," "}, {"739","no motor vehicles","492"," "}, {"740","restrictions","493"," "}, {"741","no motor vehicles without catalytic converters","627"," "}, {"742","no motor vehicles with even-numbered registration plates","628"," "}, {"743","no motor vehicles with odd-numbered registration plates","629"," "}, {"744","no through traffic","405"," "}, {"745","no through traffic for heavy lorries (over Q)","404","8"}, {"746","smog alert","1332"," "}, {"747","no motor vehicles due to smog alert","1338"," "}, {"748","closed ahead","469"," "}, {"749","blocked ahead","470"," "}, {"750","closed due to smog alert (until Q)","2000","7"}, {"751","closed due to major event","1527"," "}, {"752","closed due to sports meeting","1541"," "}, {"753","closed due to fair","1555"," "}, {"754","closed due to parade","1559"," "}, {"755","closed due to strike","1563"," "}, {"756","closed due to demonstration","1567"," "}, {"757","closed due to security alert","1580"," "}, {"758","closed due to security incident","1485"," "}, {"759","closed due to flooding","938"," "}, {"760","closed due to avalanches","943"," "}, {"761","closed due to avalanche risk","993"," "}, {"762","closed due to ice build-up","995"," "}, {"763","closed due to rockfalls","945"," "}, {"764","closed due to subsidence","949"," "}, {"765","service area busy","2013"," "}, {"766","service area overcrowded, drive to another service area","20"," "}, {"767","service area, fuel station closed","22"," "}, {"768","service area, restaurant closed","23"," "}, {"769","bridge closed","24"," "}, {"770","tunnel closed","25"," "}, {"771","closed for heavy vehicles (over Q)","403","8"}, {"772","closed for heavy lorries (over Q)","494","8"}, {"773","use of hard shoulder allowed","661"," "}, {"774","traffic regulations have been changed","1854"," "}, {"775","road closed intermittently","28"," "}, {"776","police directing traffic","1971"," "}, {"777","bus lane available for all vehicles","1972"," "}, {"778","police directing traffic via the bus-lane","1973"," "}, {"779","heavy vehicle lane available for all vehicles","1978"," "}, {"780","police directing traffic via the heavy vehicle lane","1979"," "}, {"781","normal lane regulations restored","662"," "}, {"782","allow emergency vehicles to pass","1974"," "}, {"783","allow emergency vehicles to pass in the heavy vehicle lane","1977"," "}, {"784","allow emergency vehicles to pass in the carpool lane","1961"," "}, {"785","closed for high-sided vehicles due to strong winds (Q)","1212","4"}, {"786"," "," "," "}, {"787","lane restrictions lifted","660"," "}, {"788","restrictions for high-sided vehicles lifted","1215"," "}, {"789","reopened for through traffic","680"," "}, {"790","fuel station reopened","36"," "}, {"791","restaurant reopened","37"," "}, {"792","motor vehicle restrictions lifted","635"," "}, {"793","smog alert ended","40"," "}, {"794","traffic restrictions lifted {reopened for all traffic}","636"," "}, {"795"," "," "," "}, {"796","message cancelled","468"," "}, {"797"," "," "," "}, {"798"," "," "," "}, {"799"," "," "," "}, {"800"," "," "," "}, {"801"," "," "," "}, {"802","(Q person) carpool lane in operation","647","0"}, {"803","(Q person) carpool lane closed","648","0"}, {"804","(Q person) carpool lane blocked","649","0"}, {"805","bus lane available for carpools (with at least Q occupants)","671","0"}, {"806","carpool restrictions changed (to Q persons per vehicle)","650","0"}, {"807","carpool lane available for all vehicles","1962"," "}, {"808","police directing traffic via the carpool lane","1963"," "}, {"809","closed for vehicles with less than three occupants {not valid for lorries}","2006"," "}, {"810","closed for vehicles with only one occupant {not valid for lorries}","2007"," "}, {"811"," "," "," "}, {"812","(Q person) carpool restrictions lifted","659","0"}, {"813"," "," "," "}, {"814","message cancelled","2029"," "}, {"815"," "," "," "}, {"816"," "," "," "}, {"817"," "," "," "}, {"818"," "," "," "}, {"819"," "," "," "}, {"820","(Q sets of) roadworks","701","0"}, {"821","(Q sets of) major roadworks","702","0"}, {"822","(Q sets of) long-term roadworks","802","0"}, {"823","(Q sets of) construction work","803","0"}, {"824","(Q sections of) resurfacing work","704","0"}, {"825","(Q sets of) central reservation work","705","0"}, {"826","(Q sets of) maintenance work","703","0"}, {"827","roadwork clearance in progress","853"," "}, {"828","bridge maintenance work (at Q bridges)","707","0"}, {"829","bridge demolition work (at Q bridges)","805","0"}, {"830","(Q sections of) blasting work","709","0"}, {"831","(Q sets of) roadworks on the hard shoulder","52","0"}, {"832","(Q sets of) roadworks in the emergency lane","53","0"}, {"833","(Q sets of) roadworks during the day time","815","0"}, {"834","(Q sets of) roadworks during off-peak periods","816","0"}, {"835","(Q sets of) roadworks during the night","817","0"}, {"836","(Q sets of) resurfacing work during the day time","821","0"}, {"837","(Q sets of) resurfacing work during off- peak periods","822","0"}, {"838","(Q sets of) resurfacing work during the night","823","0"}, {"839","(Q sets of) temporary traffic lights","708","0"}, {"840","(Q sets of) water main work","806","0"}, {"841","(Q sets of) gas main work","807","0"}, {"842","(Q sets of) work on buried cables","808","0"}, {"843","(Q sets of) work on buried services","809","0"}, {"844","new roadworks layout","810"," "}, {"845","road layout unchanged","855"," "}, {"846","new road layout","811"," "}, {"847"," "," "," "}, {"848","maintenance work cleared","854"," "}, {"849","roadworks cleared","800"," "}, {"850"," "," "," "}, {"851","message cancelled","801"," "}, {"852"," "," "," "}, {"853"," "," "," "}, {"854"," "," "," "}, {"855"," "," "," "}, {"856"," "," "," "}, {"857","(Q) obstruction(s) on roadway {something that does block the road or part of it}","901","0"}, {"858","(Q) obstructions on the road. Danger","902","0"}, {"859","(Q) obstructions on the road. Passable with care","981","0"}, {"860","(Q) object(s) on roadway {something that does not neccessarily block the road or part of it} ","61","0"}, {"861","(Q) object(s) on the road. Danger","63","0"}, {"862","(Q) shed load(s)","210","0"}, {"863","(Q) shed load(s). Danger","359","0"}, {"864","spillage on the road","903"," "}, {"865","spillage on the road. Danger","984"," "}, {"866","storm damage","904"," "}, {"867","storm damage. Danger","986"," "}, {"868","storm damage expected","972"," "}, {"869","(Q) fallen trees","905","0"}, {"870","(Q) fallen trees. Danger","906","0"}, {"871","fallen power cables","973"," "}, {"872","fallen power cables. Danger","989"," "}, {"873","flooding","907"," "}, {"874","flooding expected","900"," "}, {"875","flooding. Danger","908"," "}, {"876","sewer overflow","974"," "}, {"877","sewer overflow. Danger","990"," "}, {"878","flash floods","909"," "}, {"879","danger of flash floods","910"," "}, {"880","flash floods. Danger","991"," "}, {"881","avalanches","911"," "}, {"882","avalanches. Danger","992"," "}, {"883","avalanche risk","912"," "}, {"884","avalanche risk. Danger","994"," "}, {"885","ice build-up","975"," "}, {"886","rockfalls","913"," "}, {"887","rockfalls. Danger","998"," "}, {"888","landslips","914"," "}, {"889","landslips. Danger","999"," "}, {"890","mud slide","976"," "}, {"891","earthquake damage","915"," "}, {"892","earthquake damage. Danger","1000"," "}, {"893","subsidence","917"," "}, {"894","subsidence. Danger","1026"," "}, {"895","(Q) collapsed sewer(s)","918","0"}, {"896","sewer collapse. Danger","1030"," "}, {"897","burst water main","919"," "}, {"898","burst water main. Danger","1031"," "}, {"899","(Q) burst pipe(s)","62","0"}, {"900","burst pipe. Danger","64"," "}, {"901","gas leak","920"," "}, {"902","gas leak. Danger","1032"," "}, {"903","grass fire","977"," "}, {"904","serious fire","921"," "}, {"905","serious fire. Danger","1033"," "}, {"906","air crash","978"," "}, {"907","rail crash","979"," "}, {"908","spillage on the road. Passable with care","983"," "}, {"909","storm damage. Passable with care","985"," "}, {"910","(Q) fallen tree(s). Passable with care","927","0"}, {"911","fallen power cables. Passable with care","988"," "}, {"912","flooding. Passable with care","942"," "}, {"913","avalanches. Passable with care (above Q hundred metres)","944","0"}, {"914","ice build-up. Passable with care (above Q hundred metres)","996","0"}, {"915","rockfalls. Passable with care","946"," "}, {"916","landslips. Passable with care","948"," "}, {"917","subsidence. Passable with care","955"," "}, {"918","subsidence. Single alternate line traffic","950"," "}, {"919","ice build-up. Single alternate traffic","997"," "}, {"920","spillage occurring from moving vehicle","1709"," "}, {"921","(Q) slow moving maintenance vehicle(s)","1700","0"}, {"922"," "," "," "}, {"923","obstruction warning withdrawn","898"," "}, {"924","clearance work in progress, road free again","899"," "}, {"925","road free again","970"," "}, {"926","road cleared","1712"," "}, {"927","house fire","1084"," "}, {"928"," "," "," "}, {"929","vehicle stuck under bridge","1086"," "}, {"930"," "," "," "}, {"931","message cancelled","971"," "}, {"932"," "," "," "}, {"933"," "," "," "}, {"934","animals on roadway","922"," "}, {"935","sightseers obstructing access","1471"," "}, {"936","people on roadway","1472"," "}, {"937","children on roadway","1473"," "}, {"938","cyclists on roadway","1474"," "}, {"939","animals on the road. Danger","923"," "}, {"940","people on roadway. Danger","1482"," "}, {"941","children on roadway. Danger","1483"," "}, {"942","cyclists on roadway. Danger","1484"," "}, {"943","large animals on roadway","1067"," "}, {"944","herds of animals on roadway","1068"," "}, {"945","construction traffic merging","852"," "}, {"946","construction traffic merging. Danger","856"," "}, {"947","(Q sets of) road marking work","706","0"}, {"948","(Q sets of) road marking work. Danger","824","0"}, {"949","warning cleared","1771"," "}, {"950","(Q) unprotected accident area(s)","857","0"}, {"951","danger of (Q) unprotected accident area(s)","858","0"}, {"952","(Q) unlit vehicle(s) on the road","859","0"}, {"953","danger of (Q) unlit vehicle(s) on the road","860","0"}, {"954","snow and ice debris","861"," "}, {"955","danger of snow and ice debris","862"," "}, {"956","message cancelled","2030"," "}, {"957"," "," "," "}, {"958","impassable (above Q hundred metres)","1035","0"}, {"959","almost impassable (above Q hundred metres)","1036","0"}, {"960","extremely hazardous driving conditions (above Q hundred metres)","1037","0"}, {"961","extremely hazardous driving conditions expected (above Q hundred meters)","1073","0"}, {"962","hazardous driving conditions (above Q hundred metres)","1001","0"}, {"963","difficult driving conditions (above Q hundred metres)","1038","0"}, {"964","passable with care (up to Q hundred metres)","1039","0"}, {"965","passable (up to Q hundred metres)","1040","0"}, {"966","impassable for heavy vehicles (over Q)","1063","8"}, {"967","impassable (above Q hundred metres) for vehicles with trailers","1064","0"}, {"968","surface water hazard","1041"," "}, {"969","danger of aquaplaning","1002"," "}, {"970","slippery road (above Q hundred metres)","1003","0"}, {"971","mud on road","1004"," "}, {"972","mud on road. Danger","1055"," "}, {"973","leaves on road","1005"," "}, {"974","loose sand on road","1042"," "}, {"975","loose chippings","1043"," "}, {"976","loose chippings. Danger","1056"," "}, {"977","oil on road","1044"," "}, {"978","oil on road. Danger","1057"," "}, {"979","petrol on road","1045"," "}, {"980","petrol on road. Danger","1058"," "}, {"981","road surface in poor condition","916"," "}, {"982","road surface in poor condition. Danger","1059"," "}, {"983","ice (above Q hundred metres)","1006","0"}, {"984","danger of ice (above Q hundred metres)","1007","0"}, {"985","icy patches (above Q hundred metres)","1047","0"}, {"986","danger of icy patches (above Q hundred metres)","1048","0"}, {"987","black ice (above Q hundred metres)","1008","0"}, {"988","danger of black ice (above Q hundred metres)","1050","0"}, {"989","freezing rain (above Q hundred metres)","1009","0"}, {"990","freezing rain expected (above Q hundred metres)","1074","0"}, {"991","wet and icy roads (above Q hundred metres)","1010","0"}, {"992","slush (above Q hundred metres)","1011","0"}, {"993","snow on the road (above Q hundred metres)","1012","0"}, {"994","packed snow (above Q hundred metres)","1013","0"}, {"995","fresh snow (above Q hundred metres)","1014","0"}, {"996","deep snow (above Q hundred metres)","1015","0"}, {"997","snow drifts (above Q hundred metres)","1016","0"}, {"998","slippery road (above Q hundred metres) due to snow","1018","0"}, {"999","slippery road (above Q hundred metres) due to frost","1019","0"}, {"1000","slippery due to spillage on roadway","1017"," "}, {"1001","slippery due to loose sand on roadway","1054"," "}, {"1002","icy patches (above Q hundred metres) on bridges","1060","0"}, {"1003","danger of icy patches (above Q hundred metres) on bridges","1061","0"}, {"1004","icy patches (above Q hundred metres) on bridges, in shaded areas and on slip roads","1062","0"}, {"1005","road blocked by snow (above Q hundred metres)","1020","0"}, {"1006","danger of road being blocked by snow (above Q hundred metres)","1075","0"}, {"1007","conditions of road surface improved","1024"," "}, {"1008","snow cleared","1070"," "}, {"1009","driving conditions improved","1065"," "}, {"1010","skid hazard reduced","1069"," "}, {"1011","rain changing to snow","1165"," "}, {"1012","snow changing to rain","1166"," "}, {"1013","message cancelled","1025"," "}, {"1014"," "," "," "}, {"1015"," "," "," "}, {"1016"," "," "," "}, {"1017"," "," "," "}, {"1018"," "," "," "}, {"1019","current temperature (Q)","1083","6"}, {"1020","heavy frost","1115"," "}, {"1021","frost","1116"," "}, {"1022","temperature falling rapidly (to Q)","1079","6"}, {"1023","extreme heat (up to Q)","1080","6"}, {"1024","extreme cold (of Q)","1081","6"}, {"1025"," "," "," "}, {"1026","less extreme temperatures","1082"," "}, {"1027"," "," "," "}, {"1028","message cancelled","1127"," "}, {"1029"," "," "," "}, {"1030"," "," "," "}, {"1031"," "," "," "}, {"1032"," "," "," "}, {"1033"," "," "," "}, {"1034","damaging hail (visibility reduced to Q)","1132","2"}, {"1035","damaging hail (with visibility reduced to Q) expected","1174","2"}, {"1036","hail (visibility reduced to Q)","1106","2"}, {"1037","sleet (visibility reduced to Q)","1107","2"}, {"1038","thunderstorms (visibility reduced to Q)","1108","2"}, {"1039","winter storm (visibility reduced to Q)","1128","2"}, {"1040","blizzard (visibility reduced to Q)","1130","2"}, {"1041","blizzard (with visibility reduced to Q) expected","1173","2"}, {"1042","heavy snowfall (Q)","1101","10"}, {"1043","heavy snowfall (Q) expected","1170","10"}, {"1044","heavy snowfall. Visibility reduced (to Q)","1134","2"}, {"1045","heavy snowfall (Q). Visibility reduced to <30 m","1102","10"}, {"1046","heavy snowfall (Q). Visibility reduced to <50 m","1103","10"}, {"1047","snowfall (Q)","1104","10"}, {"1048","snowfall. Visibility reduced (to Q)","1135","2"}, {"1049","snowfall (Q). Visibility reduced to <100 m","1105","10"}, {"1050","heavy rain (Q)","1109","10"}, {"1051","heavy rain (Q) expected","1171","10"}, {"1052","heavy rain. Visibility reduced (to Q)","1136","2"}, {"1053","heavy rain (Q). Visibility reduced to <30 m","1110","10"}, {"1054","heavy rain (Q). Visibility reduced to <50 m","1111","10"}, {"1055","rain (Q)","1112","10"}, {"1056","rain. Visibility reduced (to Q)","1137","2"}, {"1057","rain (Q). Visibility reduced to <100 m","1113","10"}, {"1058","showers (visibility reduced to Q)","1114","2"}, {"1059","visibility reduced (to Q)","1318","2"}, {"1060","visibility reduced to <30 m","1319"," "}, {"1061","visibility reduced to <50 m","1320"," "}, {"1062","visibility reduced to <100 m","1321"," "}, {"1063","reduced visibility (to Q) expected","1175","2"}, {"1064","white out (visibility reduced to Q)","1322","2"}, {"1065","blowing snow (visibility reduced to Q)","1323","2"}, {"1066","smoke hazard (visibility reduced to Q)","1309","2"}, {"1067","spray hazard (visibility reduced to Q)","1324","2"}, {"1068","low sun glare","1325"," "}, {"1069","blowing dust (visibility reduced to Q)","1310","2"}, {"1070","sandstorms (visibility reduced to Q)","1326","2"}, {"1071","swarms of insects (visibility reduced to Q)","1340","2"}, {"1072","dense fog (visibility reduced to Q)","1301","2"}, {"1073","dense fog (with visibility reduced to Q) expected","1177","2"}, {"1074","fog (visibility reduced to Q)","1304","2"}, {"1075","patchy fog (visibility reduced to Q)","1307","2"}, {"1076","patchy fog (with visibility reduced to Q) expected","1178","2"}, {"1077","freezing fog (visibility reduced to Q)","1308","2"}, {"1078","freezing fog (visibility reduced to Q). Slippery roads","1337","2"}, {"1079","freezing fog expected (with visibility reduced to Q). Danger of slippery roads","1176","2"}, {"1080","dense fog. Visibility reduced to <30 m","1302"," "}, {"1081","dense fog. Visibility reduced to <50 m","1303"," "}, {"1082","fog. Visibility reduced to <100 m","1305"," "}, {"1083","snowfall and fog (visibility reduced to Q)","1312","2"}, {"1084"," "," "," "}, {"1085","fog forecast withdrawn","1346"," "}, {"1086","fog clearing","1345"," "}, {"1087","visibility improved","1313"," "}, {"1088","visibility expected to improve","1179"," "}, {"1089","weather expected to improve","1172"," "}, {"1090","weather situation improved","1126"," "}, {"1091","adverse weather warning withdrawn","1180"," "}, {"1092"," "," "," "}, {"1093","message cancelled","1314"," "}, {"1094"," "," "," "}, {"1095"," "," "," "}, {"1096"," "," "," "}, {"1097"," "," "," "}, {"1098"," "," "," "}, {"1099","tornadoes","1201"," "}, {"1100","hurricane force winds (Q)","1202","4"}, {"1101","gales (Q)","1203","4"}, {"1102","storm force winds (Q)","1204","4"}, {"1103","strong winds (Q)","1205","4"}, {"1104","gusty winds (Q)","1209","4"}, {"1105","crosswinds (Q)","1210","4"}, {"1106","strong winds (Q) affecting high-sided vehicles","1211","4"}, {"1107","severe smog","1190"," "}, {"1108","severe exhaust pollution","1191"," "}, {"1109"," "," "," "}, {"1110","tornado warning ended","1217"," "}, {"1111","strong winds easing","1213"," "}, {"1112"," "," "," "}, {"1113","message cancelled","1214"," "}, {"1114"," "," "," "}, {"1115"," "," "," "}, {"1116"," "," "," "}, {"1117"," "," "," "}, {"1118"," "," "," "}, {"1119","major event","1501"," "}, {"1120","several major events","1590"," "}, {"1121","sports event meeting","1502"," "}, {"1122","international sports meeting","1450"," "}, {"1123","match","1451"," "}, {"1124","tournament","1452"," "}, {"1125","athletics meeting","1453"," "}, {"1126","ball game","1454"," "}, {"1127","boxing tournament","1455"," "}, {"1128","bull fight","1456"," "}, {"1129","cricket match","1457"," "}, {"1130","cycle race","1458"," "}, {"1131","football match","1459"," "}, {"1132","golf tournament","1460"," "}, {"1133","marathon","1461"," "}, {"1134","race meeting","1462"," "}, {"1135","rugby match","1463"," "}, {"1136","show jumping","1464"," "}, {"1137","tennis tournament","1465"," "}, {"1138","water sports meeting","1466"," "}, {"1139","winter sports meeting","1467"," "}, {"1140","show","1503"," "}, {"1141","festival","1504"," "}, {"1142","exhibition","1505"," "}, {"1143","market","1507"," "}, {"1144","fair","1506"," "}, {"1145","funfair","1468"," "}, {"1146","trade fair","1469"," "}, {"1147","ceremonial event","1508"," "}, {"1148","state occasion","1509"," "}, {"1149","parade","1510"," "}, {"1150","procession","1470"," "}, {"1151","crowd","1511"," "}, {"1152","strike","1475"," "}, {"1153","march","1512"," "}, {"1154","demonstration","1513"," "}, {"1155","public disturbance","1514"," "}, {"1156","information about major event no longer valid","1591"," "}, {"1157","sports traffic cleared","1493"," "}, {"1158","traffic disruption cleared","1496"," "}, {"1159","automobile race","1592"," "}, {"1160","baseball game","1593"," "}, {"1161","basketball game","1594"," "}, {"1162","boat race","1595"," "}, {"1163","concert","1596"," "}, {"1164","hockey game","1597"," "}, {"1165","message cancelled","1585"," "}, {"1166"," "," "," "}, {"1167"," "," "," "}, {"1168","security alert","1515"," "}, {"1169","security incident","1476"," "}, {"1170","police checkpoint","1477"," "}, {"1171","bomb alert","1516"," "}, {"1172","terrorist incident","1478"," "}, {"1173","gunfire on roadway, danger","1479"," "}, {"1174","civil emergency","1480"," "}, {"1175","air raid, danger","1481"," "}, {"1176","evacuation","1494"," "}, {"1177"," "," "," "}, {"1178","air raid warning cancelled","1587"," "}, {"1179","security alert withdrawn","1492"," "}, {"1180","civil emergency cancelled","1588"," "}, {"1181"," "," "," "}, {"1182","message cancelled","2033"," "}, {"1183"," "," "," "}, {"1184"," "," "," "}, {"1185"," "," "," "}, {"1186"," "," "," "}, {"1187"," "," "," "}, {"1188","delays (Q)","1601","5"}, {"1189","delays (Q) expected","1607","5"}, {"1190","delays (Q) possible","1650","5"}, {"1191","delays up to 5 minutes","1621"," "}, {"1192","delays up to 10 minutes","1622"," "}, {"1193","delays up to 15 minutes","1602"," "}, {"1194","delays up to 20 minutes","1623"," "}, {"1195","delays up to 25 minutes","1624"," "}, {"1196","delays up to 30 minutes","1603"," "}, {"1197","delays up to 40 minutes","1625"," "}, {"1198","delays up to 50 minutes","1626"," "}, {"1199","delays up to one hour","1604"," "}, {"1200","delays up to 90 minutes","1627"," "}, {"1201","delays up to two hours","1605"," "}, {"1202","delays up to three hours","1628"," "}, {"1203","delays up to four hours","1629"," "}, {"1204","delays up to five hours","1630"," "}, {"1205","delays of several hours","1606"," "}, {"1206","long delays (Q)","1608","5"}, {"1207","very long delays (Q)","1631","5"}, {"1208","delays of uncertain duration","1632"," "}, {"1209","accident. Delays (Q)","247","5"}, {"1210","accident. Delays (Q) expected","248","5"}, {"1211","accident. Long delays (Q)","249","5"}, {"1212","multi vehicle pile up. Delays (Q)","200","5"}, {"1213","vehicles slowing to look at accident. Delays (Q)","275","5"}, {"1214","vehicles slowing to look at accident. Delays (Q) expected","276","5"}, {"1215","vehicles slowing to look at accident. Long delays (Q)","277","5"}, {"1216","shed load. Delays (Q)","310","5"}, {"1217","shed load. Delays (Q) expected","311","5"}, {"1218","shed load. Long delays (Q)","312","5"}, {"1219","overturned vehicle. Delays (Q)","375","5"}, {"1220","overturned vehicle. Delays (Q) expected","376","5"}, {"1221","overturned vehicle. Long delays (Q)","377","5"}, {"1222","Delays (Q) due to earlier accident","388","5"}, {"1223","Long delays (Q) due to earlier accident","390","5"}, {"1224","broken down vehicle. Delays (Q)","330","5"}, {"1225","broken down vehicle. Delays (Q) expected","331","5"}, {"1226","broken down vehicle. Long delays (Q)","332","5"}, {"1227","closed ahead. Delays (Q)","435","5"}, {"1228","closed ahead. Delays (Q) expected","436","5"}, {"1229","closed ahead. Long delays (Q)","437","5"}, {"1230","blocked ahead. Delays (Q)","463","5"}, {"1231","blocked ahead. Delays (Q) expected","464","5"}, {"1232","blocked ahead. Long delays (Q)","465","5"}, {"1233","roadworks. Delays (Q)","747","5"}, {"1234","roadworks. Delays (Q) expected","748","5"}, {"1235","roadworks. Long delays (Q)","749","5"}, {"1236","resurfacing work. Delays (Q)","780","5"}, {"1237","resurfacing work. Delays (Q) expected","781","5"}, {"1238","resurfacing work. Long delays (Q)","782","5"}, {"1239","water main work. Delays (Q)","840","5"}, {"1240","water main work. Delays (Q) expected","841","5"}, {"1241","water main work. Long delays (Q)","842","5"}, {"1242","gas main work. Delays (Q)","843","5"}, {"1243","gas main work. Delays (Q) expected","844","5"}, {"1244","gas main work. Long delays (Q)","845","5"}, {"1245","work on buried cables. Delays (Q)","846","5"}, {"1246","work on buried cables. Delays (Q) expected","847","5"}, {"1247","work on buried cables. Long delays (Q)","848","5"}, {"1248","work on buried services. Delays (Q)","849","5"}, {"1249","work on buried services. Delays (Q) expected","850","5"}, {"1250","work on buried services. Long delays (Q)","851","5"}, {"1251","flooding. Delays (Q)","939","5"}, {"1252","flooding. Delays (Q) expected","940","5"}, {"1253","flooding. Long delays (Q)","941","5"}, {"1254","sewer collapse. Delays (Q)","1027","5"}, {"1255","sewer collapse. Delays (Q) expected","1028","5"}, {"1256","sewer collapse. Long delays (Q)","1029","5"}, {"1257","burst water main. Delays (Q)","958","5"}, {"1258","burst water main. Delays (Q) expected","959","5"}, {"1259","burst water main. Long delays (Q)","960","5"}, {"1260","gas leak. Delays (Q)","962","5"}, {"1261","gas leak. Delays (Q) expected","963","5"}, {"1262","gas leak. Long delays (Q)","964","5"}, {"1263","serious fire. Delays (Q)","966","5"}, {"1264","serious fire. Delays (Q) expected","967","5"}, {"1265","serious fire. Long delays (Q)","968","5"}, {"1266","major event. Delays (Q)","1528","5"}, {"1267","major event. Delays (Q) expected","1529","5"}, {"1268","major event. Long delays (Q)","1530","5"}, {"1269","sports meeting. Delays (Q)","1542","5"}, {"1270","sports meeting. Delays (Q) expected","1543","5"}, {"1271","sports meeting. Long delays (Q)","1544","5"}, {"1272","fair. Delays (Q)","1556","5"}, {"1273","fair. Delays (Q) expected","1557","5"}, {"1274","fair. Long delays (Q)","1558","5"}, {"1275","parade. Delays (Q)","1560","5"}, {"1276","parade. Delays (Q) expected","1561","5"}, {"1277","parade. Long delays (Q)","1562","5"}, {"1278","strike. Delays (Q)","1564","5"}, {"1279","strike. Delays (Q) expected","1565","5"}, {"1280","strike. Long delays (Q)","1566","5"}, {"1281","demonstration. Delays (Q)","1568","5"}, {"1282","demonstration. Delays (Q) expected","1569","5"}, {"1283","demonstration. Long delays (Q)","1570","5"}, {"1284","security alert. Delays (Q)","1581","5"}, {"1285","security alert. Delays (Q) expected","1582","5"}, {"1286","security alert. Long delays (Q)","1583","5"}, {"1287","security incident. Delays (Q)","1486","5"}, {"1288","security incident. Delays (Q) expected","1487","5"}, {"1289","security incident. Long delays (Q)","1488","5"}, {"1290","police checkpoint. Delays (Q)","1489","5"}, {"1291","police checkpoint. Delays (Q) expected","1490","5"}, {"1292","police checkpoint. Long delays (Q)","1491","5"}, {"1293","delays (Q) for heavy vehicles","1609","5"}, {"1294","delays (Q) for cars","91","5"}, {"1295","delays (Q) for heavy lorr(y/ies)","1642","5"}, {"1296","delays up to 15 minutes for heavy lorr(y/ies)","1610"," "}, {"1297","delays up to 30 minutes for heavy lorr(y/ies)","1611"," "}, {"1298","delays up to one hour for heavy lorr(y/ies)","1612"," "}, {"1299","delays up to two hours for heavy lorr(y/ies)","1613"," "}, {"1300","delays of several hours for heavy lorr(y/ies)","1614"," "}, {"1301","delays (Q) for buses","1643","5"}, {"1302","long delays expected","1653"," "}, {"1303","very long delays expected","1654"," "}, {"1304","abnormal load. Delays (Q) expected","1757","5"}, {"1305","abnormal load. Long delays (Q)","1758","5"}, {"1306","abnormal load. Delays (Q)","1756","5"}, {"1307","abnormal load causing slow traffic. Delays (Q)","1740","5"}, {"1308","convoy causing delays (Q)","1759","5"}, {"1309","convoy. Delays (Q) expected","1760","5"}, {"1310","convoy causing long delays (Q)","1761","5"}, {"1311","convoy causing slow traffic. Delays (Q)","1741","5"}, {"1312","traffic lights not working. Delays (Q)","1817","5"}, {"1313","traffic lights not working. Delays (Q) expected","1818","5"}, {"1314","traffic lights not working. Long delays (Q)","1819","5"}, {"1315","traffic lights working incorrectly. Delays (Q)","1868","5"}, {"1316","traffic lights working incorrectly. Delays (Q) expected","1869","5"}, {"1317","traffic lights working incorrectly. Long delays (Q)","1870","5"}, {"1318","temporary traffic lights not working. Delays (Q)","1876","5"}, {"1319","temporary traffic lights not working. Delays (Q) expected","1877","5"}, {"1320","temporary traffic lights not working. Long delays (Q)","1878","5"}, {"1321","traffic signal control computer not working. Delays (Q)","1880","5"}, {"1322","traffic signal control computer not working. Delays (Q) expected","1884","5"}, {"1323","traffic signal control computer not working. Long delays (Q)","1885","5"}, {"1324","level crossing failure. Delays (Q)","1830","5"}, {"1325","level crossing failure. Delays (Q) expected","1831","5"}, {"1326","level crossing failure. Long delays (Q)","1832","5"}, {"1327","Snowplough. Delays (Q)","1858","5"}, {"1328","delays cleared","1648"," "}, {"1329","delay expected to be cleared","1663"," "}, {"1330"," "," "," "}, {"1331","message cancelled","1620"," "}, {"1332"," "," "," "}, {"1333"," "," "," "}, {"1334"," "," "," "}, {"1335"," "," "," "}, {"1336"," "," "," "}, {"1337","cancellations","1634"," "}, {"1338","cancellations expected","1652"," "}, {"1339","service suspended (until Q)","1615","7"}, {"1340","delayed until further notice","1633"," "}, {"1341","(Q) service withdrawn","1616","7"}, {"1342","(Q) service(s) fully booked","1617","7"}, {"1343","all services fully booked (until Q)","1655","7"}, {"1344","park and ride service operating (until Q)","1906","7"}, {"1345","park and ride service not operating (until Q)","1635","7"}, {"1346","special public transport services operating (until Q)","1636","7"}, {"1347","normal services not operating (until Q)","1637","7"}, {"1348","rail services not operating (until Q)","1638","7"}, {"1349","rail services irregular","1720"," "}, {"1350","rail services irregular. Delays (Q)","1657","5"}, {"1351","bus services irregular. Delays (Q)","1658","5"}, {"1352","bus services not operating (until Q)","1639","7"}, {"1353","rapid transit service not operating (until Q)","1649","7"}, {"1354","shuttle service operating (until Q)","1640","7"}, {"1355","free shuttle service operating (until Q)","1641","7"}, {"1356","underground services irregular","1659"," "}, {"1357","underground service not operating (until Q)","1651","7"}, {"1358","service not operating, substitute service available","2021"," "}, {"1359","public transport strike","2022"," "}, {"1360","public transport services not operating","1721"," "}, {"1361","ferry service not operating (until Q)","1661","7"}, {"1362","(Q) service(s) fully booked for heavy vehicles","1618","7"}, {"1363","(Q) service(s) fully booked for heavy lorr(y/ies)","1644","7"}, {"1364","(Q) service(s) fully booked for buses","1645","7"}, {"1365"," "," "," "}, {"1366","normal public transport services resumed","1660"," "}, {"1367","normal services resumed","1619"," "}, {"1368"," "," "," "}, {"1369","message cancelled","2034"," "}, {"1370"," "," "," "}, {"1371"," "," "," "}, {"1372"," "," "," "}, {"1373"," "," "," "}, {"1374"," "," "," "}, {"1375","park and ride trip time (Q)","1662","5"}, {"1376","current trip time (Q)","1695","5"}, {"1377","expected trip time (Q)","1696","5"}, {"1378","next arrival (Q)","1656","7"}, {"1379","next departure (Q)","1901","7"}, {"1380","next departure (Q) for heavy vehicles","1902","7"}, {"1381","next departure (Q) for heavy lorr(y/ies)","1646","7"}, {"1382","next departure (Q) for buses","1647","7"}, {"1383"," "," "," "}, {"1384","message cancelled","2035"," "}, {"1385"," "," "," "}, {"1386"," "," "," "}, {"1387"," "," "," "}, {"1388"," "," "," "}, {"1389"," "," "," "}, {"1390","(Q) vehicle(s) on wrong carriageway","1701","0"}, {"1391","(Q) reckless driver(s)","1704","0"}, {"1392","(Q) prohibited vehicle(s) on the roadway","1705","0"}, {"1393","(Q) emergency vehicles","1706","0"}, {"1394","(Q) high-speed emergency vehicles","1707","0"}, {"1395","high-speed chase (involving Q vehicles)","1708","0"}, {"1396","objects falling from moving vehicle","1710"," "}, {"1397","(Q sets of) slow moving maintenance vehicles","804","0"}, {"1398","(Q) vehicle(s) carrying hazardous materials","1765","0"}, {"1399","(Q) vehicle(s) carrying hazardous materials. Danger","1736","0"}, {"1400","Vehicles carrying hazardous materials have to stop at next safe place!","1768"," "}, {"1401","(Q) snowploughs","681","0"}, {"1402","emergency vehicle warning cleared","1711"," "}, {"1403","dangerous vehicle warning cleared","1702"," "}, {"1404","hazardous load warning cleared","1769"," "}, {"1405"," "," "," "}, {"1406","message cancelled","1703"," "}, {"1407"," "," "," "}, {"1408"," "," "," "}, {"1409"," "," "," "}, {"1410"," "," "," "}, {"1411"," "," "," "}, {"1412","(Q) over-height vehicle(s)","347","0"}, {"1413","(Q) over-height load(s), danger","1739","0"}, {"1414","(Q) abnormal load(s)","1751","0"}, {"1415","(Q) abnormal load(s), danger","1731","0"}, {"1416","(Q) wide load(s)","1752","0"}, {"1417","(Q) wide load(s), danger","1732","0"}, {"1418","(Q) long load(s)","1753","0"}, {"1419","(Q) long load(s), danger","1733","0"}, {"1420","(Q) slow vehicle(s)","1754","0"}, {"1421","(Q) slow vehicle(s), danger","1734","0"}, {"1422","(Q) track-laying vehicle(s)","1764","0"}, {"1423","(Q) track-laying vehicle(s), danger","1735","0"}, {"1424","(Q) abnormal load(s). No overtaking","1767","0"}, {"1425","(Q) convoy(s)","1755","0"}, {"1426","(Q) convoy(s), danger","1737","0"}, {"1427","(Q) military convoy(s)","1766","0"}, {"1428","(Q) military convoy(s), danger","1738","0"}, {"1429"," "," "," "}, {"1430","convoy cleared","1770"," "}, {"1431","exceptional load warning cleared","1762"," "}, {"1432"," "," "," "}, {"1433","message cancelled","1763"," "}, {"1434"," "," "," "}, {"1435"," "," "," "}, {"1436"," "," "," "}, {"1437"," "," "," "}, {"1438"," "," "," "}, {"1439","lane control signs not working","1801"," "}, {"1440","lane control signs working incorrectly","1838"," "}, {"1441","lane control signs operating","1839"," "}, {"1442","variable message signs not working","1840"," "}, {"1443","variable message signs working incorrectly","1841"," "}, {"1444","variable message signs operating","1842"," "}, {"1445","emergency telephones not working","1802"," "}, {"1446","emergency telephone number not working","1803"," "}, {"1447","emergency telephones out of order. Extra police patrols in operation","1865"," "}, {"1448","emergency telephones out of order. In emergency, wait for police patrol","1866"," "}, {"1449","(Q sets of) traffic lights not working","1804","0"}, {"1450","(Q sets of) traffic lights working incorrectly","1805","0"}, {"1451","(Q sets of) ramp control signals not working","1843","0"}, {"1452","(Q sets of) ramp control signals working incorrectly","1844","0"}, {"1453","(Q sets of) temporary traffic lights not working","1845","0"}, {"1454","(Q sets of) temporary traffic lights working incorrectly","1846","0"}, {"1455","lane control signs not working. Danger","1850"," "}, {"1456","lane control signs working incorrectly. Danger","1864"," "}, {"1457","(Q sets of) traffic lights not working. Danger","1867","0"}, {"1458","(Q sets of) traffic lights working incorrectly. Danger","1875","0"}, {"1459","(Q sets of) temporary traffic lights not working. Danger","1879","0"}, {"1460","traffic signal control computer not working","1847"," "}, {"1461","traffic signal timings changed","1848"," "}, {"1462","level crossing failure","1806"," "}, {"1463","tunnel ventilation not working","1849"," "}, {"1464"," "," "," "}, {"1465","electronic signs repaired","1833"," "}, {"1466","emergency call facilities restored","1834"," "}, {"1467","traffic signals repaired","1835"," "}, {"1468","level crossing now working normally","1836"," "}, {"1469","power failure","1983"," "}, {"1470","message cancelled","1837"," "}, {"1471"," "," "," "}, {"1472"," "," "," "}, {"1473"," "," "," "}, {"1474"," "," "," "}, {"1475"," "," "," "}, {"1476","temporary width limit (Q)","1851","9"}, {"1477","temporary height limit (Q)","1861","9"}, {"1478","temporary length limit (Q)","1881","9"}, {"1479","temporary axle load limit (Q)","1871","8"}, {"1480","temporary gross weight limit (Q)","1872","8"}, {"1481"," "," "," "}, {"1482","temporary width limit lifted","1852"," "}, {"1483","temporary height limit lifted","1862"," "}, {"1484","temporary length limit lifted","1882"," "}, {"1485","temporary axle weight limit lifted","1874"," "}, {"1486","temporary gross weight limit lifted","1873"," "}, {"1487"," "," "," "}, {"1488","message cancelled","1857"," "}, {"1489"," "," "," "}, {"1490"," "," "," "}, {"1491"," "," "," "}, {"1492"," "," "," "}, {"1493"," "," "," "}, {"1494","normal parking restrictions lifted","1886"," "}, {"1495","special parking restrictions in force","1887"," "}, {"1496","no parking (until Q)","1927","7"}, {"1497"," "," "," "}, {"1498","special parking restrictions lifted","1928"," "}, {"1499"," "," "," "}, {"1500","message cancelled","1883"," "}, {"1501"," "," "," "}, {"1502"," "," "," "}, {"1503"," "," "," "}, {"1504"," "," "," "}, {"1505"," "," "," "}, {"1506","car park (Q) full","1903","3"}, {"1507","expect car park to be full","1922"," "}, {"1508","10% full","1888"," "}, {"1509","20% full","1889"," "}, {"1510","30% full","1890"," "}, {"1511","40% full","1891"," "}, {"1512","50% full","1892"," "}, {"1513","60% full","1893"," "}, {"1514","70% full","1894"," "}, {"1515","80% full","1895"," "}, {"1516","90% full","1896"," "}, {"1517","full","1918"," "}, {"1518","all car parks (Q) full","1904","3"}, {"1519","multi story car parks full","1924"," "}, {"1520","less than (Q) car parking spaces available","1905","1"}, {"1521","no parking spaces available","1926"," "}, {"1522","expect no parking spaces available","1923"," "}, {"1523","only a few parking spaces available","1920"," "}, {"1524","(Q) parking spaces available","1921","1"}, {"1525","less than 10 parking spaces available","1897"," "}, {"1526","less than 20 parking spaces available","1898"," "}, {"1527","less than 30 parking spaces available","1899"," "}, {"1528","less than 40 parking spaces available","1900"," "}, {"1529","less than 50 parking spaces available","1855"," "}, {"1530","no problems to report with park and ride services","1925"," "}, {"1531","no park and ride information available (until Q)","1934","7"}, {"1532"," "," "," "}, {"1533","park and ride information service resumed","1938"," "}, {"1534","no parking information available (until Q)","1856","7"}, {"1535"," "," "," "}, {"1536","message cancelled","2038"," "}, {"1537"," "," "," "}, {"1538"," "," "," "}, {"1539"," "," "," "}, {"1540"," "," "," "}, {"1541"," "," "," "}, {"1542","switch your car radio (to Q)","1913","12"}, {"1543","switch your car radio (to Q)","1908","11"}, {"1544","urgent information will be given (at Q) on normal programme broadcasts","1929","7"}, {"1545","detailed information will be given (at Q) on normal programme broadcasts","1931","7"}, {"1546","alarm call: important new information on this frequency follows now in normal programme","1909"," "}, {"1547","alarm set: new information will be broadcast between these times in normal programme","1910"," "}, {"1548"," "," "," "}, {"1549","reference to audio programmes no longer valid","1955"," "}, {"1550"," "," "," "}, {"1551","message cancelled","1911"," "}, {"1552"," "," "," "}, {"1553"," "," "," "}, {"1554"," "," "," "}, {"1555"," "," "," "}, {"1556"," "," "," "}, {"1557","additional regional information is provided by another TMC service","1940"," "}, {"1558","additional local information is provided by another TMC service","1941"," "}, {"1559","additional public transport information is provided by another TMC service","1942"," "}, {"1560","national traffic information is provided by another TMC service","1943"," "}, {"1561","this service provides major road information","1944"," "}, {"1562","this service provides regional travel information","1945"," "}, {"1563","this service provides local travel information","1946"," "}, {"1564","no detailed regional information provided by this service","1947"," "}, {"1565","no detailed local information provided by this service","1948"," "}, {"1566","detailed information is provided by another TMC service","1932"," "}, {"1567","no cross-border information provided by this service","1949"," "}, {"1568","information restricted to this area","1950"," "}, {"1569","no new traffic information available (until Q)","1951","7"}, {"1570","no public transport information available","1952"," "}, {"1571","this TMC-service is being suspended (at Q)","1953","7"}, {"1572","active TMC-service will resume (at Q)","1954","7"}, {"1573","this TMC-service is not active (until Q)","1930","7"}, {"1574","no information available (until Q)","1914","7"}, {"1575","no information available (until Q) due to technical problems","1916","7"}, {"1576"," "," "," "}, {"1577","reference to other TMC services no longer valid","1956"," "}, {"1578","previous announcement about this or other TMC services no longer valid","1957"," "}, {"1579","rail Information service not available","1964"," "}, {"1580","rail information service resumed","1965"," "}, {"1581","rapid transit information service not available","1966"," "}, {"1582","rapid transit information service resumed","1967"," "}, {"1583","message cancelled","2039"," "}, {"1584"," "," "," "}, {"1585"," "," "," "}, {"1586","this message is for test purposes only (number Q), please ignore","1915","0"}, {"1587","(null event) {no event description, but location etc. given in message}","1907"," "}, {"1588","(null message) {completely silent message, see EN ISO 14819-1, 3.5.4}","2047"," "}, {"1589","nothing to report","2041"," "}, {"1590","message cancelled","2040"," "}}; /* this is like an index key: * 1st column: tmc event code * 2nd column: row in tmc_events */ static const int tmc_event_code_index[TMC_EVENTS][2]={ {0,}, {1,5}, {2,48}, {3,}, {4,}, {5,}, {6,}, {7,}, {8,}, {9,}, {10,}, {11,536}, {12,516}, {13,}, {14,}, {15,}, {16,553}, {17,}, {18,}, {19,}, {20,766}, {21,}, {22,767}, {23,768}, {24,769}, {25,770}, {26,695}, {27,696}, {28,775}, {29,}, {30,}, {31,}, {32,}, {33,}, {34,}, {35,}, {36,790}, {37,791}, {38,}, {39,}, {40,793}, {41,560}, {42,641}, {43,}, {44,}, {45,}, {46,}, {47,}, {48,}, {49,}, {50,}, {51,577}, {52,831}, {53,832}, {54,}, {55,430}, {56,435}, {57,487}, {58,}, {59,}, {60,}, {61,860}, {62,899}, {63,861}, {64,900}, {65,}, {66,}, {67,}, {68,}, {69,}, {70,37}, {71,38}, {72,39}, {73,40}, {74,41}, {75,42}, {76,43}, {77,}, {78,}, {79,}, {80,}, {81,}, {82,}, {83,}, {84,}, {85,}, {86,}, {87,}, {88,}, {89,}, {90,}, {91,1294}, {92,}, {93,}, {94,}, {95,}, {96,}, {97,}, {98,}, {99,}, {100,}, {101,6}, {102,7}, {103,8}, {104,10}, {105,11}, {106,12}, {107,431}, {108,14}, {109,15}, {110,16}, {111,18}, {112,19}, {113,20}, {114,432}, {115,23}, {116,24}, {117,25}, {118,27}, {119,28}, {120,29}, {121,433}, {122,30}, {123,434}, {124,33}, {125,34}, {126,420}, {127,421}, {128,424}, {129,9}, {130,13}, {131,17}, {132,21}, {133,22}, {134,26}, {135,35}, {136,36}, {137,44}, {138,45}, {139,46}, {140,47}, {141,521}, {142,31}, {143,32}, {144,}, {145,}, {146,}, {147,}, {148,}, {149,}, {150,}, {151,}, {152,}, {153,}, {154,}, {155,}, {156,}, {157,}, {158,}, {159,}, {160,}, {161,}, {162,}, {163,}, {164,}, {165,}, {166,}, {167,}, {168,}, {169,}, {170,}, {171,}, {172,}, {173,}, {174,}, {175,}, {176,}, {177,}, {178,}, {179,}, {180,}, {181,}, {182,}, {183,}, {184,}, {185,}, {186,}, {187,}, {188,}, {189,}, {190,}, {191,}, {192,}, {193,}, {194,}, {195,}, {196,}, {197,}, {198,}, {199,}, {200,1212}, {201,495}, {202,496}, {203,497}, {204,498}, {205,500}, {206,501}, {207,502}, {208,91}, {209,519}, {210,862}, {211,531}, {212,533}, {213,509}, {214,530}, {215,49}, {216,50}, {217,51}, {218,53}, {219,54}, {220,55}, {221,56}, {222,57}, {223,58}, {224,59}, {225,61}, {226,62}, {227,63}, {228,64}, {229,65}, {230,66}, {231,67}, {232,69}, {233,70}, {234,71}, {235,436}, {236,72}, {237,437}, {238,73}, {239,74}, {240,552}, {241,608}, {242,609}, {243,610}, {244,611}, {245,612}, {246,613}, {247,1209}, {248,1210}, {249,1211}, {250,75}, {251,76}, {252,77}, {253,79}, {254,80}, {255,81}, {256,82}, {257,83}, {258,84}, {259,85}, {260,87}, {261,88}, {262,89}, {263,90}, {264,92}, {265,93}, {266,94}, {267,96}, {268,97}, {269,98}, {270,438}, {271,99}, {272,439}, {273,}, {274,100}, {275,1213}, {276,1214}, {277,1215}, {278,102}, {279,103}, {280,104}, {281,106}, {282,107}, {283,108}, {284,109}, {285,110}, {286,111}, {287,112}, {288,114}, {289,115}, {290,116}, {291,117}, {292,118}, {293,119}, {294,120}, {295,122}, {296,123}, {297,124}, {298,440}, {299,125}, {300,441}, {301,126}, {302,127}, {303,614}, {304,615}, {305,616}, {306,617}, {307,618}, {308,619}, {309,620}, {310,1216}, {311,1217}, {312,1218}, {313,142}, {314,143}, {315,144}, {316,145}, {317,146}, {318,444}, {319,147}, {320,445}, {321,148}, {322,149}, {323,627}, {324,628}, {325,629}, {326,630}, {327,631}, {328,632}, {329,633}, {330,1224}, {331,1225}, {332,1226}, {333,522}, {334,524}, {335,499}, {336,503}, {337,504}, {338,505}, {339,506}, {340,507}, {341,508}, {342,510}, {343,512}, {344,513}, {345,517}, {346,535}, {347,1412}, {348,52}, {349,60}, {350,68}, {351,515}, {352,78}, {353,86}, {354,95}, {355,101}, {356,105}, {357,113}, {358,121}, {359,863}, {360,128}, {361,129}, {362,130}, {363,131}, {364,132}, {365,442}, {366,133}, {367,443}, {368,134}, {369,621}, {370,622}, {371,623}, {372,624}, {373,625}, {374,626}, {375,1219}, {376,1220}, {377,1221}, {378,511}, {379,135}, {380,136}, {381,137}, {382,138}, {383,139}, {384,}, {385,140}, {386,}, {387,141}, {388,1222}, {389,}, {390,1223}, {391,514}, {392,518}, {393,532}, {394,534}, {395,543}, {396,542}, {397,539}, {398,}, {399,733}, {400,}, {401,551}, {402,634}, {403,771}, {404,745}, {405,744}, {406,726}, {407,707}, {408,711}, {409,713}, {410,150}, {411,151}, {412,152}, {413,154}, {414,155}, {415,156}, {416,157}, {417,158}, {418,159}, {419,160}, {420,162}, {421,163}, {422,164}, {423,165}, {424,166}, {425,167}, {426,168}, {427,170}, {428,171}, {429,172}, {430,446}, {431,173}, {432,447}, {433,174}, {434,175}, {435,1227}, {436,1228}, {437,1229}, {438,176}, {439,177}, {440,178}, {441,180}, {442,181}, {443,182}, {444,183}, {445,184}, {446,185}, {447,186}, {448,188}, {449,189}, {450,190}, {451,191}, {452,192}, {453,193}, {454,194}, {455,196}, {456,197}, {457,198}, {458,448}, {459,199}, {460,449}, {461,200}, {462,201}, {463,1230}, {464,1231}, {465,1232}, {466,718}, {467,670}, {468,796}, {469,748}, {470,749}, {471,727}, {472,728}, {473,729}, {474,708}, {475,709}, {476,710}, {477,712}, {478,714}, {479,681}, {480,682}, {481,683}, {482,684}, {483,685}, {484,686}, {485,715}, {486,687}, {487,688}, {488,689}, {489,690}, {490,691}, {491,692}, {492,739}, {493,740}, {494,772}, {495,153}, {496,161}, {497,169}, {498,179}, {499,187}, {500,554}, {501,555}, {502,556}, {503,557}, {504,558}, {505,562}, {506,563}, {507,636}, {508,637}, {509,638}, {510,639}, {511,643}, {512,644}, {513,653}, {514,564}, {515,565}, {516,566}, {517,650}, {518,651}, {519,652}, {520,635}, {521,202}, {522,203}, {523,204}, {524,206}, {525,207}, {526,208}, {527,209}, {528,210}, {529,211}, {530,212}, {531,214}, {532,215}, {533,216}, {534,217}, {535,218}, {536,219}, {537,220}, {538,222}, {539,223}, {540,224}, {541,450}, {542,225}, {543,451}, {544,226}, {545,227}, {546,228}, {547,229}, {548,230}, {549,231}, {550,232}, {551,452}, {552,233}, {553,453}, {554,234}, {555,235}, {556,236}, {557,237}, {558,238}, {559,239}, {560,240}, {561,454}, {562,241}, {563,455}, {564,242}, {565,243}, {566,244}, {567,245}, {568,246}, {569,247}, {570,248}, {571,456}, {572,249}, {573,457}, {574,250}, {575,251}, {576,252}, {577,253}, {578,254}, {579,256}, {580,257}, {581,258}, {582,259}, {583,260}, {584,261}, {585,262}, {586,264}, {587,265}, {588,266}, {589,267}, {590,268}, {591,269}, {592,270}, {593,272}, {594,273}, {595,274}, {596,458}, {597,275}, {598,459}, {599,276}, {600,277}, {601,567}, {602,568}, {603,569}, {604,278}, {605,279}, {606,280}, {607,281}, {608,282}, {609,460}, {610,283}, {611,461}, {612,284}, {613,285}, {614,286}, {615,287}, {616,288}, {617,289}, {618,290}, {619,462}, {620,291}, {621,463}, {622,292}, {623,293}, {624,672}, {625,675}, {626,195}, {627,741}, {628,742}, {629,743}, {630,671}, {631,668}, {632,731}, {633,717}, {634,699}, {635,792}, {636,794}, {637,559}, {638,659}, {639,661}, {640,662}, {641,561}, {642,640}, {643,660}, {644,664}, {645,665}, {646,642}, {647,802}, {648,803}, {649,804}, {650,806}, {651,205}, {652,213}, {653,221}, {654,255}, {655,263}, {656,271}, {657,667}, {658,669}, {659,812}, {660,787}, {661,773}, {662,781}, {663,698}, {664,673}, {665,674}, {666,550}, {667,}, {668,}, {669,}, {670,}, {671,805}, {672,701}, {673,720}, {674,}, {675,}, {676,694}, {677,}, {678,663}, {679,666}, {680,789}, {681,1401}, {682,}, {683,}, {684,}, {685,}, {686,}, {687,}, {688,}, {689,}, {690,}, {691,}, {692,}, {693,}, {694,}, {695,}, {696,}, {697,}, {698,}, {699,}, {700,}, {701,820}, {702,821}, {703,826}, {704,824}, {705,825}, {706,947}, {707,828}, {708,839}, {709,830}, {710,294}, {711,295}, {712,296}, {713,298}, {714,299}, {715,300}, {716,301}, {717,302}, {718,303}, {719,304}, {720,306}, {721,307}, {722,308}, {723,309}, {724,310}, {725,311}, {726,312}, {727,314}, {728,315}, {729,316}, {730,464}, {731,317}, {732,465}, {733,318}, {734,319}, {735,570}, {736,571}, {737,572}, {738,573}, {739,574}, {740,575}, {741,576}, {742,655}, {743,578}, {744,579}, {745,580}, {746,654}, {747,1233}, {748,1234}, {749,1235}, {750,320}, {751,321}, {752,322}, {753,324}, {754,325}, {755,326}, {756,327}, {757,328}, {758,329}, {759,330}, {760,332}, {761,333}, {762,334}, {763,335}, {764,336}, {765,337}, {766,338}, {767,340}, {768,341}, {769,342}, {770,466}, {771,343}, {772,467}, {773,344}, {774,345}, {775,657}, {776,581}, {777,582}, {778,583}, {779,656}, {780,1236}, {781,1237}, {782,1238}, {783,346}, {784,347}, {785,348}, {786,349}, {787,350}, {788,468}, {789,351}, {790,469}, {791,352}, {792,353}, {793,584}, {794,585}, {795,586}, {796,587}, {797,588}, {798,589}, {799,595}, {800,849}, {801,851}, {802,822}, {803,823}, {804,1397}, {805,829}, {806,840}, {807,841}, {808,842}, {809,843}, {810,844}, {811,846}, {812,297}, {813,305}, {814,313}, {815,833}, {816,834}, {817,835}, {818,323}, {819,331}, {820,339}, {821,836}, {822,837}, {823,838}, {824,948}, {825,354}, {826,355}, {827,356}, {828,357}, {829,358}, {830,470}, {831,359}, {832,471}, {833,360}, {834,361}, {835,590}, {836,591}, {837,592}, {838,593}, {839,594}, {840,1239}, {841,1240}, {842,1241}, {843,1242}, {844,1243}, {845,1244}, {846,1245}, {847,1246}, {848,1247}, {849,1248}, {850,1249}, {851,1250}, {852,945}, {853,827}, {854,848}, {855,845}, {856,946}, {857,950}, {858,951}, {859,952}, {860,953}, {861,954}, {862,955}, {863,}, {864,}, {865,}, {866,}, {867,}, {868,}, {869,}, {870,}, {871,}, {872,}, {873,}, {874,}, {875,}, {876,}, {877,}, {878,}, {879,}, {880,}, {881,}, {882,}, {883,}, {884,}, {885,}, {886,}, {887,}, {888,}, {889,}, {890,}, {891,}, {892,}, {893,}, {894,}, {895,}, {896,}, {897,}, {898,923}, {899,924}, {900,874}, {901,857}, {902,858}, {903,864}, {904,866}, {905,869}, {906,870}, {907,873}, {908,875}, {909,878}, {910,879}, {911,881}, {912,883}, {913,886}, {914,888}, {915,891}, {916,981}, {917,893}, {918,895}, {919,897}, {920,901}, {921,904}, {922,934}, {923,939}, {924,537}, {925,647}, {926,648}, {927,910}, {928,362}, {929,363}, {930,364}, {931,365}, {932,366}, {933,472}, {934,367}, {935,473}, {936,368}, {937,369}, {938,759}, {939,1251}, {940,1252}, {941,1253}, {942,912}, {943,760}, {944,913}, {945,763}, {946,915}, {947,596}, {948,916}, {949,764}, {950,918}, {951,599}, {952,600}, {953,601}, {954,658}, {955,917}, {956,602}, {957,597}, {958,1257}, {959,1258}, {960,1259}, {961,603}, {962,1260}, {963,1261}, {964,1262}, {965,598}, {966,1263}, {967,1264}, {968,1265}, {969,604}, {970,925}, {971,931}, {972,868}, {973,871}, {974,876}, {975,885}, {976,890}, {977,903}, {978,906}, {979,907}, {980,645}, {981,859}, {982,646}, {983,908}, {984,865}, {985,909}, {986,867}, {987,649}, {988,911}, {989,872}, {990,877}, {991,880}, {992,882}, {993,761}, {994,884}, {995,762}, {996,914}, {997,919}, {998,887}, {999,889}, {1000,892}, {1001,962}, {1002,969}, {1003,970}, {1004,971}, {1005,973}, {1006,983}, {1007,984}, {1008,987}, {1009,989}, {1010,991}, {1011,992}, {1012,993}, {1013,994}, {1014,995}, {1015,996}, {1016,997}, {1017,1000}, {1018,998}, {1019,999}, {1020,1005}, {1021,605}, {1022,606}, {1023,607}, {1024,1007}, {1025,1013}, {1026,894}, {1027,1254}, {1028,1255}, {1029,1256}, {1030,896}, {1031,898}, {1032,902}, {1033,905}, {1034,538}, {1035,958}, {1036,959}, {1037,960}, {1038,963}, {1039,964}, {1040,965}, {1041,968}, {1042,974}, {1043,975}, {1044,977}, {1045,979}, {1046,}, {1047,985}, {1048,986}, {1049,}, {1050,988}, {1051,}, {1052,}, {1053,}, {1054,1001}, {1055,972}, {1056,976}, {1057,978}, {1058,980}, {1059,982}, {1060,1002}, {1061,1003}, {1062,1004}, {1063,966}, {1064,967}, {1065,1009}, {1066,540}, {1067,943}, {1068,944}, {1069,1010}, {1070,1008}, {1071,}, {1072,}, {1073,961}, {1074,990}, {1075,1006}, {1076,}, {1077,}, {1078,}, {1079,1022}, {1080,1023}, {1081,1024}, {1082,1026}, {1083,1019}, {1084,927}, {1085,}, {1086,929}, {1087,}, {1088,}, {1089,}, {1090,}, {1091,}, {1092,}, {1093,}, {1094,}, {1095,}, {1096,}, {1097,}, {1098,}, {1099,}, {1100,}, {1101,1042}, {1102,1045}, {1103,1046}, {1104,1047}, {1105,1049}, {1106,1036}, {1107,1037}, {1108,1038}, {1109,1050}, {1110,1053}, {1111,1054}, {1112,1055}, {1113,1057}, {1114,1058}, {1115,1020}, {1116,1021}, {1117,}, {1118,}, {1119,}, {1120,}, {1121,}, {1122,}, {1123,}, {1124,}, {1125,}, {1126,1090}, {1127,1028}, {1128,1039}, {1129,}, {1130,1040}, {1131,}, {1132,1034}, {1133,}, {1134,1044}, {1135,1048}, {1136,1052}, {1137,1056}, {1138,}, {1139,}, {1140,}, {1141,}, {1142,}, {1143,}, {1144,}, {1145,}, {1146,}, {1147,}, {1148,}, {1149,}, {1150,}, {1151,}, {1152,}, {1153,}, {1154,}, {1155,}, {1156,}, {1157,}, {1158,}, {1159,}, {1160,}, {1161,}, {1162,}, {1163,}, {1164,}, {1165,1011}, {1166,1012}, {1167,}, {1168,}, {1169,}, {1170,1043}, {1171,1051}, {1172,1089}, {1173,1041}, {1174,1035}, {1175,1063}, {1176,1079}, {1177,1073}, {1178,1076}, {1179,1088}, {1180,1091}, {1181,}, {1182,}, {1183,}, {1184,}, {1185,}, {1186,}, {1187,}, {1188,}, {1189,}, {1190,1107}, {1191,1108}, {1192,}, {1193,}, {1194,}, {1195,}, {1196,}, {1197,}, {1198,}, {1199,}, {1200,}, {1201,1099}, {1202,1100}, {1203,1101}, {1204,1102}, {1205,1103}, {1206,}, {1207,}, {1208,}, {1209,1104}, {1210,1105}, {1211,1106}, {1212,785}, {1213,1111}, {1214,1113}, {1215,788}, {1216,}, {1217,1110}, {1218,}, {1219,}, {1220,}, {1221,}, {1222,}, {1223,}, {1224,}, {1225,}, {1226,}, {1227,}, {1228,}, {1229,}, {1230,}, {1231,}, {1232,}, {1233,}, {1234,}, {1235,}, {1236,}, {1237,}, {1238,}, {1239,}, {1240,}, {1241,}, {1242,}, {1243,}, {1244,}, {1245,}, {1246,}, {1247,}, {1248,}, {1249,}, {1250,}, {1251,}, {1252,}, {1253,}, {1254,}, {1255,}, {1256,}, {1257,}, {1258,}, {1259,}, {1260,}, {1261,}, {1262,}, {1263,}, {1264,}, {1265,}, {1266,}, {1267,}, {1268,}, {1269,}, {1270,}, {1271,}, {1272,}, {1273,}, {1274,}, {1275,}, {1276,}, {1277,}, {1278,}, {1279,}, {1280,}, {1281,}, {1282,}, {1283,}, {1284,}, {1285,}, {1286,}, {1287,}, {1288,}, {1289,}, {1290,}, {1291,}, {1292,}, {1293,}, {1294,}, {1295,}, {1296,}, {1297,}, {1298,}, {1299,}, {1300,}, {1301,1072}, {1302,1080}, {1303,1081}, {1304,1074}, {1305,1082}, {1306,}, {1307,1075}, {1308,1077}, {1309,1066}, {1310,1069}, {1311,}, {1312,1083}, {1313,1087}, {1314,1093}, {1315,}, {1316,}, {1317,}, {1318,1059}, {1319,1060}, {1320,1061}, {1321,1062}, {1322,1064}, {1323,1065}, {1324,1067}, {1325,1068}, {1326,1070}, {1327,}, {1328,}, {1329,}, {1330,}, {1331,}, {1332,746}, {1333,}, {1334,}, {1335,}, {1336,}, {1337,1078}, {1338,747}, {1339,}, {1340,1071}, {1341,}, {1342,}, {1343,}, {1344,}, {1345,1086}, {1346,1085}, {1347,}, {1348,}, {1349,}, {1350,}, {1351,}, {1352,}, {1353,}, {1354,}, {1355,}, {1356,}, {1357,}, {1358,}, {1359,}, {1360,}, {1361,}, {1362,}, {1363,}, {1364,}, {1365,}, {1366,}, {1367,}, {1368,}, {1369,}, {1370,}, {1371,}, {1372,}, {1373,}, {1374,}, {1375,}, {1376,}, {1377,}, {1378,}, {1379,}, {1380,}, {1381,}, {1382,}, {1383,}, {1384,}, {1385,}, {1386,}, {1387,}, {1388,}, {1389,}, {1390,}, {1391,}, {1392,}, {1393,}, {1394,}, {1395,}, {1396,}, {1397,}, {1398,}, {1399,}, {1400,}, {1401,}, {1402,}, {1403,}, {1404,}, {1405,}, {1406,}, {1407,}, {1408,}, {1409,}, {1410,}, {1411,}, {1412,}, {1413,}, {1414,}, {1415,}, {1416,}, {1417,}, {1418,}, {1419,}, {1420,}, {1421,}, {1422,}, {1423,}, {1424,}, {1425,}, {1426,}, {1427,}, {1428,}, {1429,}, {1430,}, {1431,}, {1432,}, {1433,}, {1434,}, {1435,}, {1436,}, {1437,}, {1438,}, {1439,}, {1440,}, {1441,}, {1442,}, {1443,}, {1444,}, {1445,}, {1446,}, {1447,}, {1448,}, {1449,}, {1450,1122}, {1451,1123}, {1452,1124}, {1453,1125}, {1454,1126}, {1455,1127}, {1456,1128}, {1457,1129}, {1458,1130}, {1459,1131}, {1460,1132}, {1461,1133}, {1462,1134}, {1463,1135}, {1464,1136}, {1465,1137}, {1466,1138}, {1467,1139}, {1468,1145}, {1469,1146}, {1470,1150}, {1471,935}, {1472,936}, {1473,937}, {1474,938}, {1475,1152}, {1476,1169}, {1477,1170}, {1478,1172}, {1479,1173}, {1480,1174}, {1481,1175}, {1482,940}, {1483,941}, {1484,942}, {1485,758}, {1486,1287}, {1487,1288}, {1488,1289}, {1489,1290}, {1490,1291}, {1491,1292}, {1492,1179}, {1493,1157}, {1494,1176}, {1495,400}, {1496,1158}, {1497,}, {1498,}, {1499,}, {1500,}, {1501,1119}, {1502,1121}, {1503,1140}, {1504,1141}, {1505,1142}, {1506,1144}, {1507,1143}, {1508,1147}, {1509,1148}, {1510,1149}, {1511,1151}, {1512,1153}, {1513,1154}, {1514,1155}, {1515,1168}, {1516,1171}, {1517,370}, {1518,371}, {1519,372}, {1520,373}, {1521,374}, {1522,474}, {1523,375}, {1524,475}, {1525,376}, {1526,377}, {1527,751}, {1528,1266}, {1529,1267}, {1530,1268}, {1531,378}, {1532,379}, {1533,380}, {1534,381}, {1535,382}, {1536,476}, {1537,383}, {1538,477}, {1539,384}, {1540,385}, {1541,752}, {1542,1269}, {1543,1270}, {1544,1271}, {1545,386}, {1546,387}, {1547,388}, {1548,389}, {1549,390}, {1550,478}, {1551,391}, {1552,479}, {1553,392}, {1554,393}, {1555,753}, {1556,1272}, {1557,1273}, {1558,1274}, {1559,754}, {1560,1275}, {1561,1276}, {1562,1277}, {1563,755}, {1564,1278}, {1565,1279}, {1566,1280}, {1567,756}, {1568,1281}, {1569,1282}, {1570,1283}, {1571,394}, {1572,395}, {1573,396}, {1574,397}, {1575,398}, {1576,480}, {1577,399}, {1578,481}, {1579,402}, {1580,757}, {1581,1284}, {1582,1285}, {1583,1286}, {1584,422}, {1585,1165}, {1586,401}, {1587,1178}, {1588,1180}, {1589,489}, {1590,1120}, {1591,1156}, {1592,1159}, {1593,1160}, {1594,1161}, {1595,1162}, {1596,1163}, {1597,1164}, {1598,}, {1599,}, {1600,}, {1601,1188}, {1602,1193}, {1603,1196}, {1604,1199}, {1605,1201}, {1606,1205}, {1607,1189}, {1608,1206}, {1609,1293}, {1610,1296}, {1611,1297}, {1612,1298}, {1613,1299}, {1614,1300}, {1615,1339}, {1616,1341}, {1617,1342}, {1618,1362}, {1619,1367}, {1620,1331}, {1621,1191}, {1622,1192}, {1623,1194}, {1624,1195}, {1625,1197}, {1626,1198}, {1627,1200}, {1628,1202}, {1629,1203}, {1630,1204}, {1631,1207}, {1632,1208}, {1633,1340}, {1634,1337}, {1635,1345}, {1636,1346}, {1637,1347}, {1638,1348}, {1639,1352}, {1640,1354}, {1641,1355}, {1642,1295}, {1643,1301}, {1644,1363}, {1645,1364}, {1646,1381}, {1647,1382}, {1648,1328}, {1649,1353}, {1650,1190}, {1651,1357}, {1652,1338}, {1653,1302}, {1654,1303}, {1655,1343}, {1656,1378}, {1657,1350}, {1658,1351}, {1659,1356}, {1660,1366}, {1661,1361}, {1662,1375}, {1663,1329}, {1664,}, {1665,}, {1666,}, {1667,}, {1668,}, {1669,}, {1670,}, {1671,}, {1672,}, {1673,}, {1674,}, {1675,}, {1676,}, {1677,}, {1678,}, {1679,}, {1680,}, {1681,}, {1682,}, {1683,}, {1684,}, {1685,}, {1686,}, {1687,}, {1688,}, {1689,}, {1690,}, {1691,}, {1692,}, {1693,}, {1694,}, {1695,1376}, {1696,1377}, {1697,}, {1698,}, {1699,}, {1700,921}, {1701,1390}, {1702,1403}, {1703,1406}, {1704,1391}, {1705,1392}, {1706,1393}, {1707,1394}, {1708,1395}, {1709,920}, {1710,1396}, {1711,1402}, {1712,926}, {1713,}, {1714,}, {1715,}, {1716,}, {1717,}, {1718,}, {1719,}, {1720,1349}, {1721,1360}, {1722,}, {1723,}, {1724,}, {1725,}, {1726,}, {1727,}, {1728,}, {1729,}, {1730,}, {1731,1415}, {1732,1417}, {1733,1419}, {1734,1421}, {1735,1423}, {1736,1399}, {1737,1426}, {1738,1428}, {1739,1413}, {1740,1307}, {1741,1311}, {1742,}, {1743,}, {1744,}, {1745,}, {1746,}, {1747,}, {1748,}, {1749,}, {1750,}, {1751,1414}, {1752,1416}, {1753,1418}, {1754,1420}, {1755,1425}, {1756,1306}, {1757,1304}, {1758,1305}, {1759,1308}, {1760,1309}, {1761,1310}, {1762,1431}, {1763,1433}, {1764,1422}, {1765,1398}, {1766,1427}, {1767,1424}, {1768,1400}, {1769,1404}, {1770,1430}, {1771,949}, {1772,}, {1773,}, {1774,}, {1775,}, {1776,}, {1777,}, {1778,}, {1779,}, {1780,}, {1781,}, {1782,}, {1783,}, {1784,}, {1785,}, {1786,}, {1787,}, {1788,}, {1789,}, {1790,}, {1791,}, {1792,}, {1793,}, {1794,}, {1795,}, {1796,}, {1797,}, {1798,}, {1799,}, {1800,}, {1801,1439}, {1802,1445}, {1803,1446}, {1804,1449}, {1805,1450}, {1806,1462}, {1807,403}, {1808,404}, {1809,405}, {1810,406}, {1811,407}, {1812,482}, {1813,408}, {1814,483}, {1815,409}, {1816,410}, {1817,1312}, {1818,1313}, {1819,1314}, {1820,411}, {1821,412}, {1822,413}, {1823,414}, {1824,415}, {1825,484}, {1826,416}, {1827,485}, {1828,417}, {1829,418}, {1830,1324}, {1831,1325}, {1832,1326}, {1833,1465}, {1834,1466}, {1835,1467}, {1836,1468}, {1837,1470}, {1838,1440}, {1839,1441}, {1840,1442}, {1841,1443}, {1842,1444}, {1843,1451}, {1844,1452}, {1845,1453}, {1846,1454}, {1847,1460}, {1848,1461}, {1849,1463}, {1850,1455}, {1851,1476}, {1852,1482}, {1853,}, {1854,774}, {1855,1529}, {1856,1534}, {1857,1488}, {1858,1327}, {1859,}, {1860,}, {1861,1477}, {1862,1483}, {1863,}, {1864,1456}, {1865,1447}, {1866,1448}, {1867,1457}, {1868,1315}, {1869,1316}, {1870,1317}, {1871,1479}, {1872,1480}, {1873,1486}, {1874,1485}, {1875,1458}, {1876,1318}, {1877,1319}, {1878,1320}, {1879,1459}, {1880,1321}, {1881,1478}, {1882,1484}, {1883,1500}, {1884,1322}, {1885,1323}, {1886,1494}, {1887,1495}, {1888,1508}, {1889,1509}, {1890,1510}, {1891,1511}, {1892,1512}, {1893,1513}, {1894,1514}, {1895,1515}, {1896,1516}, {1897,1525}, {1898,1526}, {1899,1527}, {1900,1528}, {1901,1379}, {1902,1380}, {1903,1506}, {1904,1518}, {1905,1520}, {1906,1344}, {1907,1587}, {1908,1543}, {1909,1546}, {1910,1547}, {1911,1551}, {1912,}, {1913,1542}, {1914,1574}, {1915,1586}, {1916,1575}, {1917,}, {1918,1517}, {1919,}, {1920,1523}, {1921,1524}, {1922,1507}, {1923,1522}, {1924,1519}, {1925,1530}, {1926,1521}, {1927,1496}, {1928,1498}, {1929,1544}, {1930,1573}, {1931,1545}, {1932,1566}, {1933,}, {1934,1531}, {1935,}, {1936,}, {1937,}, {1938,1533}, {1939,}, {1940,1557}, {1941,1558}, {1942,1559}, {1943,1560}, {1944,1561}, {1945,1562}, {1946,1563}, {1947,1564}, {1948,1565}, {1949,1567}, {1950,1568}, {1951,1569}, {1952,1570}, {1953,1571}, {1954,1572}, {1955,1549}, {1956,1577}, {1957,1578}, {1958,}, {1959,}, {1960,}, {1961,784}, {1962,807}, {1963,808}, {1964,1579}, {1965,1580}, {1966,1581}, {1967,1582}, {1968,}, {1969,}, {1970,}, {1971,776}, {1972,777}, {1973,778}, {1974,782}, {1975,}, {1976,}, {1977,783}, {1978,779}, {1979,780}, {1980,}, {1981,}, {1982,693}, {1983,1469}, {1984,}, {1985,}, {1986,}, {1987,}, {1988,}, {1989,}, {1990,}, {1991,}, {1992,}, {1993,}, {1994,}, {1995,}, {1996,}, {1997,}, {1998,}, {1999,}, {2000,750}, {2001,}, {2002,}, {2003,}, {2004,}, {2005,}, {2006,809}, {2007,810}, {2008,}, {2009,}, {2010,}, {2011,}, {2012,}, {2013,765}, {2014,}, {2015,}, {2016,}, {2017,}, {2018,}, {2019,}, {2020,}, {2021,1358}, {2022,1359}, {2023,}, {2024,}, {2025,}, {2026,}, {2027,}, {2028,545}, {2029,814}, {2030,956}, {2031,}, {2032,}, {2033,1182}, {2034,1369}, {2035,1384}, {2036,}, {2037,}, {2038,1536}, {2039,1583}, {2040,1590}, {2041,1589}, {2042,}, {2043,}, {2044,}, {2045,}, {2046,}, {2047,1588}}; /* table 1, paragraph 3.1.2, page 4 of ISO 14819-2 * descriptions of the types of quantifiers */ const std::string quantifier_types[13][2]={ {"0", "n (small number)"}, {"1", "N (number)"}, {"2", "less than V metres"}, {"3", "P percent"}, {"4", "of up to S km/h"}, {"5", "of up to M minutes"}, {"6", "T degrees Celsius"}, {"7", "H time"}, {"8", "W tonnes"}, {"9", "L metres"}, {"10", "of up to D millimetres"}, {"11", "M MHz"}, {"12", "k kHz"}}; gqrx-2.9/src/dsp/resampler_ff_old.cpp000066400000000000000000000062451320142145500177350ustar00rootroot00000000000000/* -*- 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-2.9/src/dsp/resampler_ff_old.h000066400000000000000000000040761320142145500174020ustar00rootroot00000000000000/* -*- 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-2.9/src/dsp/resampler_xx.cpp000066400000000000000000000113441320142145500171370ustar00rootroot00000000000000/* -*- 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-2.9/src/dsp/resampler_xx.h000066400000000000000000000054271320142145500166110ustar00rootroot00000000000000/* -*- 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-2.9/src/dsp/rx_agc_xx.cpp000066400000000000000000000136311320142145500164110ustar00rootroot00000000000000/* -*- 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]; boost::mutex::scoped_lock lock(d_mutex); d_agc->ProcessData(noutput_items, in, out); 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-2.9/src/dsp/rx_agc_xx.h000066400000000000000000000075371320142145500160660ustar00rootroot00000000000000/* -*- 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 0 dB. * \param manual_gain Manual gain when AGC is OFF. Range 0 to 100 dB. * \param slope AGC slope factor. Specifies dB reduction in output at * knee from maximum output level. Range 0 to 10 dB * \param decay AGC decay time in milliseconds. Range 20 to 5000. This * parameter determines whether AGC is fast, slow or medium. * \param use_hang Whether AGC should "hang" before starting to decay. * * 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, bool agc_on, int threshold, int manual_gain, int slope, int decay, bool use_hang); /** * \brief Experimental AGC block for analog voice modes (AM, SSB, CW). * \ingroup DSP * * This block performs automatic gain control. * To be written... */ 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). */ }; #endif /* RX_AGC_XX_H */ gqrx-2.9/src/dsp/rx_demod_am.cpp000066400000000000000000000060751320142145500167110ustar00rootroot00000000000000/* -*- 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, bool dcr) { return gnuradio::get_initial_sptr(new rx_demod_am(quad_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, 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_dcr_enabled(dcr) { (void) quad_rate; /* 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-2.9/src/dsp/rx_demod_am.h000066400000000000000000000045141320142145500163520ustar00rootroot00000000000000/* -*- 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 dcr Enable DCR * * This is effectively the public constructor. */ rx_demod_am_sptr make_rx_demod_am(float quad_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, 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 */ 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-2.9/src/dsp/rx_demod_fm.cpp000066400000000000000000000120211320142145500167020ustar00rootroot00000000000000/* -*- 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_demod_fm.h" /* 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 max_dev, double tau) { return gnuradio::get_initial_sptr(new rx_demod_fm(quad_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 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_max_dev(max_dev), d_tau(tau) { float gain; /* demodulator gain */ gain = d_quad_rate / (2.0 * M_PI * d_max_dev); #ifndef QT_NO_DEBUG_OUTPUT std::cerr << "FM demod gain: " << gain << std::endl; #endif /* 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 { #ifndef QT_NO_DEBUG_OUTPUT std::cerr << "FM de-emphasis tau is 0: " << tau << std::endl; #endif /* diable de-emph if conencted */ if (d_tau > 1.0e-9) { #ifndef QT_NO_DEBUG_OUTPUT std::cout << " Disable de-emphasis" << std::endl; #endif 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 gr-analog double w_c; // Digital corner frequency double w_ca; // Prewarped analog corner frequency double k, z1, p1, b0; double fs = d_quad_rate; w_c = 1.0 / tau; w_ca = 2.0 * fs * tan(w_c / (2.0 * fs)); // Resulting digital pole, zero, and gain term from the bilinear // transformation of H(s) = w_ca / (s + w_ca) to // H(z) = b0 (1 - z1 z^-1)/(1 - p1 z^-1) k = -w_ca / (2.0 * fs); z1 = -1.0; p1 = (1.0 + k) / (1.0 - k); b0 = -k / (1.0 - k); d_fftaps[0] = b0; d_fftaps[1] = -z1 * b0; d_fbtaps[0] = 1.0; d_fbtaps[1] = -p1; } gqrx-2.9/src/dsp/rx_demod_fm.h000066400000000000000000000053641320142145500163630ustar00rootroot00000000000000/* -*- 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. */ #pragma once #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 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 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, float max_dev, double tau); // 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. */ std::vector d_taps; /*! Taps for the PFB resampler. */ /* other parameters */ float d_quad_rate; /*! Quadrature 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); }; gqrx-2.9/src/dsp/rx_fft.cpp000066400000000000000000000223601320142145500157160ustar00rootroot00000000000000/* -*- 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-2.9/src/dsp/rx_fft.h000066400000000000000000000123301320142145500153570ustar00rootroot00000000000000/* -*- 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 1048576 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-2.9/src/dsp/rx_filter.cpp000066400000000000000000000130341320142145500164220ustar00rootroot00000000000000/* -*- 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), d_cw_offset(0) { 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_cw_offset, d_high + d_cw_offset, 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 << " Taps: " << d_taps.size() << std::endl; #endif d_bpf->set_taps(d_taps); } void rx_filter::set_cw_offset(double offset) { if (offset != d_cw_offset) { d_cw_offset = offset; set_param(d_low, d_high, d_trans_width); } } /** 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-2.9/src/dsp/rx_filter.h000066400000000000000000000125541320142145500160750ustar00rootroot00000000000000/* -*- 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); void set_cw_offset(double offset); 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; double d_cw_offset; }; /*! \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-2.9/src/dsp/rx_meter.cpp000066400000000000000000000101201320142145500162420ustar00rootroot00000000000000/* -*- 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) { } 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. * log10f(d_level + 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-2.9/src/dsp/rx_meter.h000066400000000000000000000060621320142145500157210ustar00rootroot00000000000000/* -*- 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;} 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. */ void reset_stats(); }; #endif /* RX_METER_H */ gqrx-2.9/src/dsp/rx_noise_blanker_cc.cpp000066400000000000000000000110321320142145500204110ustar00rootroot00000000000000/* -*- 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-2.9/src/dsp/rx_noise_blanker_cc.h000066400000000000000000000064111320142145500200630ustar00rootroot00000000000000/* -*- 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-2.9/src/dsp/rx_rds.cpp000066400000000000000000000075751320142145500157420ustar00rootroot00000000000000/* -*- 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 #include #include #include #include "dsp/rx_rds.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_rds and return * a boost shared_ptr. This is effectively the public constructor. */ rx_rds_sptr make_rx_rds(double sample_rate) { return gnuradio::get_initial_sptr(new rx_rds(sample_rate)); } rx_rds_store_sptr make_rx_rds_store() { return gnuradio::get_initial_sptr(new rx_rds_store()); } rx_rds::rx_rds(double sample_rate) : gr::hier_block2 ("rx_rds", gr::io_signature::make (MIN_IN, MAX_IN, sizeof (float)), gr::io_signature::make (MIN_OUT, MAX_OUT, sizeof (char))), d_sample_rate(sample_rate) { const int decimation = d_sample_rate / 2375; d_taps2 = gr::filter::firdes::low_pass(2500.0, d_sample_rate, 2400, 2000); f_fxff = gr::filter::freq_xlating_fir_filter_fcf::make(decimation, d_taps2, 57000, d_sample_rate); f_rrcf = gr::filter::firdes::root_raised_cosine(1, sample_rate/decimation, 2375, 1, 100); d_bpf2 = gr::filter::fir_filter_ccf::make(1, f_rrcf); gr::digital::constellation_sptr p_c = gr::digital::constellation_bpsk::make()->base(); d_mpsk = gr::digital::constellation_receiver_cb::make(p_c, 1*M_PI/100.0, -0.06, 0.06); b_koin = gr::blocks::keep_one_in_n::make(sizeof(unsigned char), 2); d_ddbb = gr::digital::diff_decoder_bb::make(2); rds_decoder = gr::rds::decoder::make(0, 0); rds_parser = gr::rds::parser::make(1, 0); /* connect filter */ connect(self(), 0, f_fxff, 0); connect(f_fxff, 0, d_bpf2, 0); connect(d_bpf2, 0, d_mpsk, 0); connect(d_mpsk, 0, b_koin, 0); connect(b_koin, 0, d_ddbb, 0); connect(d_ddbb, 0, self(), 0); } rx_rds::~rx_rds () { } rx_rds_store::rx_rds_store() : gr::block ("rx_rds_store", gr::io_signature::make (0, 0, 0), gr::io_signature::make (0, 0, 0)) { message_port_register_in(pmt::mp("store")); set_msg_handler(pmt::mp("store"), boost::bind(&rx_rds_store::store, this, _1)); d_messages.set_capacity(100); } rx_rds_store::~rx_rds_store () { } void rx_rds_store::store(pmt::pmt_t msg) { boost::mutex::scoped_lock lock(d_mutex); d_messages.push_back(msg); } void rx_rds_store::get_message(std::string &out, int &type) { boost::mutex::scoped_lock lock(d_mutex); if (d_messages.size()>0) { pmt::pmt_t msg=d_messages.front(); type=pmt::to_long(pmt::tuple_ref(msg,0)); out=pmt::symbol_to_string(pmt::tuple_ref(msg,1)); d_messages.pop_front(); } else { type=-1; } } gqrx-2.9/src/dsp/rx_rds.h000066400000000000000000000056341320142145500154010ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2015 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_RDS_H #define RX_RDS_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dsp/rds/decoder.h" #include "dsp/rds/parser.h" class rx_rds; class rx_rds_store; typedef boost::shared_ptr rx_rds_sptr; typedef boost::shared_ptr rx_rds_store_sptr; rx_rds_sptr make_rx_rds(double sample_rate); rx_rds_store_sptr make_rx_rds_store(); class rx_rds_store : public gr::block { public: rx_rds_store(); ~rx_rds_store(); void get_message(std::string &out, int &type); private: void store(pmt::pmt_t msg); boost::mutex d_mutex; boost::circular_buffer d_messages; }; class rx_rds : public gr::hier_block2 { public: rx_rds(double sample_rate=96000.0); ~rx_rds(); void set_param(double low, double high, double trans_width); private: std::vector d_taps; std::vector d_taps2; gr::filter::fir_filter_ccc::sptr d_bpf; gr::filter::fir_filter_ccf::sptr d_bpf2; gr::filter::freq_xlating_fir_filter_fcf::sptr f_fxff; gr::filter::freq_xlating_fir_filter_ccf::sptr f_fxff_ccf; std::vector f_rrcf; gr::digital::constellation_receiver_cb::sptr d_mpsk; gr::blocks::keep_one_in_n::sptr b_koin; gr::digital::diff_decoder_bb::sptr d_ddbb; gr::rds::decoder::sptr rds_decoder; gr::rds::parser::sptr rds_parser; gr::blocks::udp_sink::sptr udp_sink1; gr::blocks::udp_sink::sptr udp_sink2; gr::blocks::udp_sink::sptr udp_sink3; gr::blocks::udp_sink::sptr udp_sink4; double d_sample_rate; }; #endif // RX_RDS_H gqrx-2.9/src/dsp/sniffer_f.cpp000066400000000000000000000072521320142145500163720ustar00rootroot00000000000000/* -*- 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-2.9/src/dsp/sniffer_f.h000066400000000000000000000054101320142145500160310ustar00rootroot00000000000000/* -*- 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-2.9/src/dsp/stereo_demod.cpp000066400000000000000000000136271320142145500171050ustar00rootroot00000000000000/* -*- 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, bool oirt) { return gnuradio::get_initial_sptr(new stereo_demod(quad_rate, audio_rate, stereo, oirt)); } 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, bool oirt) : 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), d_oirt(oirt) { double cutof_freq = d_oirt ? 15e3 : 17e3; lpf0 = make_lpf_ff(d_input_rate, cutof_freq, 2e3); // FIXME audio_rr0 = make_resampler_ff(d_audio_rate/d_input_rate); if (d_stereo) { lpf1 = make_lpf_ff(d_input_rate, cutof_freq, 2e3); // FIXME audio_rr1 = make_resampler_ff(d_audio_rate/d_input_rate); if (!d_oirt) { 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 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(); } else { d_tone_taps = gr::filter::firdes::complex_band_pass( 1.0, // gain, d_input_rate, // sampling_freq 31200., // low_cutoff_freq 31300., // high_cutoff_freq 100.); // transition_width pll = gr::analog::pll_refout_cc::make(0.001, // loop_bw FIXME 2*M_PI * 31200 / input_rate, // max_freq 2*M_PI * 31300 / input_rate); // min_freq } tone = gr::filter::fir_filter_fcc::make(1, d_tone_taps); 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( 5.5); // FIXME cdm = gr::blocks::multiply_const_ff::make(-5.5); // FIXME add0 = gr::blocks::add_ff::make(); add1 = gr::blocks::add_ff::make(); /* connect block */ if (!d_oirt) { 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 } else { connect(self(), 0, tone, 0); connect(tone, 0, pll, 0); connect(pll, 0, lo, 0); connect(lo, 0, mixer, 0); } 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-2.9/src/dsp/stereo_demod.h000066400000000000000000000077471320142145500165600ustar00rootroot00000000000000/* -*- 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, bool oirt=false); /*! \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, bool oirt); protected: stereo_demod(float input_rate, float audio_rate, bool stereo, bool oirt); 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. */ bool d_oirt; std::vector d_tone_taps; /*! Tone BPF taps. */ std::vector d_pll_taps; /*! Subtone BPF taps. */ }; #endif // STEREO_DEMOD_H gqrx-2.9/src/interfaces/000077500000000000000000000000001320142145500152545ustar00rootroot00000000000000gqrx-2.9/src/interfaces/CMakeLists.txt000066400000000000000000000003271320142145500200160ustar00rootroot00000000000000####################################################################################################################### # Add the source files to SRCS_LIST add_source_files(SRCS_LIST udp_sink_f.cpp udp_sink_f.h ) gqrx-2.9/src/interfaces/udp_sink_f.cpp000066400000000000000000000050751320142145500201100ustar00rootroot00000000000000/* -*- 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); #ifdef GQRX_OS_MACX // There seems to be excessive packet loss (even to localhost) on OS X // unless the buffer size is limited. d_sink = gr::blocks::udp_sink::make(sizeof(short), "localhost", 7355, 512); #else d_sink = gr::blocks::udp_sink::make(sizeof(short), "localhost", 7355); #endif 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-2.9/src/interfaces/udp_sink_f.h000066400000000000000000000027651320142145500175600ustar00rootroot00000000000000/* -*- 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-2.9/src/osxaudio/000077500000000000000000000000001320142145500147645ustar00rootroot00000000000000gqrx-2.9/src/osxaudio/CMakeLists.txt000066400000000000000000000003311320142145500175210ustar00rootroot00000000000000####################################################################################################################### # Add the source files to SRCS_LIST add_source_files(SRCS_LIST device_list.cpp device_list.h ) gqrx-2.9/src/osxaudio/device_list.cpp000066400000000000000000000063041320142145500177650ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2014 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 "device_list.h" osxaudio_device::osxaudio_device(unsigned int idx, string name, string desc) : d_index(idx), d_name(name), d_description(desc) { } osxaudio_device::~osxaudio_device() { } osxaudio_device_list::osxaudio_device_list() { populate_device_list(); } osxaudio_device_list::~osxaudio_device_list() { d_sources.clear(); d_sinks.clear(); } /** \brief Populate osx audio device list. * * This method iterates through all the input and output decives * and stores them for later retrieval using getInputDevices() and * getOutputDevices(). */ int osxaudio_device_list::populate_device_list() { std::vector device_ids; std::vector device_names; int i, num_dev, err; // Get input devices gr::audio::osx::find_audio_devices("", true, &device_ids, &device_names); num_dev = device_ids.size(); if (num_dev < 0) { std::cerr << "ERROR: gr::audio::find_audio_devices() returned " << num_dev << std::endl; err = num_dev; goto error; } std::cout << "Number of audio input devices: " << num_dev << std::endl; for (i = 0; i < num_dev; i++) { add_source(i, device_names.at(i), device_names.at(i)); } device_ids.clear(); device_names.clear(); // Get output devices gr::audio::osx::find_audio_devices("", false, &device_ids, &device_names); num_dev = device_ids.size(); if (num_dev < 0) { std::cerr << "ERROR: gr::audio::find_audio_devices() returned " << num_dev << std::endl; err = num_dev; goto error; } std::cout << "Number of audio output devices: " << num_dev << std::endl; for (i = 0; i < num_dev; i++) { add_sink(i, device_names.at(i), device_names.at(i)); } device_ids.clear(); device_names.clear(); return 0; error: std::cerr << "An error occured while using OSX audio" << std::endl; std::cerr << "Error number: " << err << std::endl; return err; } void osxaudio_device_list::add_sink(unsigned int idx, string name, string desc) { d_sinks.push_back(osxaudio_device(idx, name, desc)); } void osxaudio_device_list::add_source(unsigned int idx, string name, string desc) { d_sources.push_back(osxaudio_device(idx, name, desc)); } gqrx-2.9/src/osxaudio/device_list.h000066400000000000000000000050511320142145500174300ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2014 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 OSXAUDIO_DEVICE_LIST_H #define OSXAUDIO_DEVICE_LIST_H #include #include using namespace std; /*! \brief Simple class to represent OSX audio devices. * * This class represents an OSX audio device. The device can be either source * or sink, this class does not differentiate between them. */ class osxaudio_device { public: osxaudio_device(unsigned int idx=0, string name="", string desc=""); ~osxaudio_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 osx audio sinks and sources. */ class osxaudio_device_list { public: osxaudio_device_list(); ~osxaudio_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 // OSXAUDIO_DEVICE_LIST_H gqrx-2.9/src/portaudio/000077500000000000000000000000001320142145500151375ustar00rootroot00000000000000gqrx-2.9/src/portaudio/CMakeLists.txt000066400000000000000000000002231320142145500176740ustar00rootroot00000000000000# Add the source files to SRCS_LIST add_source_files(SRCS_LIST device_list.cpp device_list.h portaudio_sink.cpp portaudio_sink.h ) gqrx-2.9/src/portaudio/device_list.cpp000066400000000000000000000066571320142145500201530ustar00rootroot00000000000000/* -*- 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 "device_list.h" using namespace std; 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; 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); } } return 0; error: 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)); } PaDeviceIndex portaudio_device_list::get_output_device_index(const string name) const { vector::const_iterator it; if (name.empty()) return -1; for (it = d_sinks.begin(); it < d_sinks.end(); it++) { if (it->get_name() == name) return it->get_index(); } return -1; } gqrx-2.9/src/portaudio/device_list.h000066400000000000000000000052071320142145500176060ustar00rootroot00000000000000/* -*- 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. */ #pragma once #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() const { return d_index; } string get_name() const { return d_name; } string get_description() const { 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; } /** Get output device index. Returns -1 if not found */ PaDeviceIndex get_output_device_index(const string name) const; 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); }; gqrx-2.9/src/portaudio/portaudio_sink.cpp000066400000000000000000000143031320142145500206760ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2016 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" #include "portaudio_sink.h" /** * Create a new portaudio 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. */ portaudio_sink_sptr make_portaudio_sink(const string device_name, int audio_rate, const string app_name, const string stream_name) { return gnuradio::get_initial_sptr(new portaudio_sink(device_name, audio_rate, app_name, stream_name)); } portaudio_sink::portaudio_sink(const string device_name, int audio_rate, const string app_name, const string stream_name) : gr::sync_block ("portaudio_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_audio_rate(audio_rate) { // find device index PaDeviceIndex idx; portaudio_device_list devices; idx = devices.get_output_device_index(device_name); if (idx == -1) { fprintf(stderr, "Using default audio device\n"); idx = Pa_GetDefaultOutputDevice(); } else { fprintf(stderr, "Audio device '%s' has index %d\n", device_name.data(), idx); } // Initialize stream parmaeters d_out_params.device = idx; d_out_params.channelCount = 2; d_out_params.sampleFormat = paFloat32; d_out_params.suggestedLatency = Pa_GetDeviceInfo(d_out_params.device)->defaultHighOutputLatency; d_out_params.hostApiSpecificStreamInfo = NULL; if (Pa_IsFormatSupported(NULL, &d_out_params, d_audio_rate) != paFormatIsSupported) fprintf(stderr, "portaudio_sink(): Audio output device does not support requested format.\n"); } portaudio_sink::~portaudio_sink() { // nothing to do } /* open and start audio stream */ bool portaudio_sink::start() { PaError err; err = Pa_OpenStream(&d_stream, NULL, // inputParameters &d_out_params, d_audio_rate, paFramesPerBufferUnspecified, paClipOff, NULL, // no callback, use blocking API NULL); if (err != paNoError) { fprintf(stderr, "portaudio_sink::start(): Failed to open audio stream: %s\n", Pa_GetErrorText(err)); return false; } err = Pa_StartStream(d_stream); if (err != paNoError) { fprintf(stderr, "portaudio_sink::start(): Failed to start audio stream: %s\n", Pa_GetErrorText(err)); return false; } return true; } /* Stop and close audio stream */ bool portaudio_sink::stop() { PaError err; bool retval = true; err = Pa_StopStream(d_stream); if (err != paNoError) { retval = false; fprintf(stderr, "portaudio_sink::stop(): Error stopping audio stream: %s\n", Pa_GetErrorText(err)); } err = Pa_CloseStream(d_stream); if (err != paNoError) { retval = false; fprintf(stderr, "portaudio_sink::stop(): Error closing audio stream: %s\n", Pa_GetErrorText(err)); } return retval; } void portaudio_sink::select_device(string device_name) { } #define BUFFER_SIZE 100000 int portaudio_sink::work(int noutput_items, gr_vector_const_void_star &input_items, gr_vector_void_star &output_items) { PaError err; static float audio_buffer[BUFFER_SIZE]; float *ptr = &audio_buffer[0]; int i; (void) output_items; if (noutput_items > BUFFER_SIZE/2) noutput_items = BUFFER_SIZE/2; // two channels (stereo) const float *data_l = (const float*) input_items[0]; const float *data_r = (const float*) input_items[1]; for (i = noutput_items; i > 0; i--) { *ptr++ = *data_l++; *ptr++ = *data_r++; } err = Pa_WriteStream(d_stream, audio_buffer, noutput_items); if (err) fprintf(stderr, "portaudio_sink::work(): Error writing to audio device: %s\n", Pa_GetErrorText(err)); return noutput_items; // code below supports 1 or 2 channels #if 0 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; } } #endif } gqrx-2.9/src/portaudio/portaudio_sink.h000066400000000000000000000043731320142145500203510ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2016 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. */ #pragma once #include #include #include using namespace std; class portaudio_sink; typedef boost::shared_ptr portaudio_sink_sptr; portaudio_sink_sptr make_portaudio_sink(const string device_name, int audio_rate, const string app_name, const string stream_name); /** Two-channel portaudio sink */ class portaudio_sink : public gr::sync_block { friend portaudio_sink_sptr make_portaudio_sink(const string device_name, int audio_rate, const string app_name, const string stream_name); public: portaudio_sink(const string device_name, int audio_rate, const string app_name, const string stream_name); ~portaudio_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: PaStream *d_stream; PaStreamParameters d_out_params; string d_stream_name; // Descriptive name of the stream. string d_app_name; // Descriptive name of the applcation. int d_audio_rate; }; gqrx-2.9/src/pulseaudio/000077500000000000000000000000001320142145500153035ustar00rootroot00000000000000gqrx-2.9/src/pulseaudio/CMakeLists.txt000066400000000000000000000002521320142145500200420ustar00rootroot00000000000000# Add the source files to SRCS_LIST add_source_files(SRCS_LIST pa_device_list.cc pa_device_list.h pa_sink.cc pa_sink.h pa_source.cc pa_source.h ) gqrx-2.9/src/pulseaudio/pa_device_list.cc000066400000000000000000000176461320142145500206020ustar00rootroot00000000000000/* -*- 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-2.9/src/pulseaudio/pa_device_list.h000066400000000000000000000052331320142145500204310ustar00rootroot00000000000000/* -*- 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-2.9/src/pulseaudio/pa_sink.cc000066400000000000000000000113511320142145500172370ustar00rootroot00000000000000/* -*- 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 #include #include "pa_sink.h" /*! \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) { int error; /* The sample type to use */ d_ss.format = PA_SAMPLE_FLOAT32LE; d_ss.rate = audio_rate; d_ss.channels = 2; 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)); } } pa_sink::~pa_sink() { if (d_pasink) { pa_simple_free(d_pasink); } } bool pa_sink::start() { 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 (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-2.9/src/pulseaudio/pa_sink.h000066400000000000000000000043171320142145500171050ustar00rootroot00000000000000/* -*- 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 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, const string stream_name); /*! \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, const string stream_name); ~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. */ }; #endif /* PA_SINK_H */ gqrx-2.9/src/pulseaudio/pa_source.cc000066400000000000000000000111041320142145500175670ustar00rootroot00000000000000/* -*- 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; 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, NULL, &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, NULL, &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-2.9/src/pulseaudio/pa_source.h000066400000000000000000000043611320142145500174400ustar00rootroot00000000000000/* -*- 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, const string app_name, const string stream_name); /*! \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, int num_chan, const string app_name, const string stream_name); ~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. */ 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-2.9/src/qtgui/000077500000000000000000000000001320142145500142625ustar00rootroot00000000000000gqrx-2.9/src/qtgui/CMakeLists.txt000066400000000000000000000023521320142145500170240ustar00rootroot00000000000000####################################################################################################################### # Add the source files to SRCS_LIST add_source_files(SRCS_LIST afsk1200win.cpp afsk1200win.h agc_options.cpp agc_options.h audio_options.cpp audio_options.h bookmarks.cpp bookmarks.h bookmarkstablemodel.cpp bookmarkstablemodel.h bookmarkstaglist.cpp bookmarkstaglist.h ctk/ctkPimpl.h ctk/ctkRangeSlider.cpp ctk/ctkRangeSlider.h demod_options.cpp demod_options.h dockaudio.cpp dockaudio.h dockbookmarks.cpp dockbookmarks.h dockfft.cpp dockfft.h dockinputctl.cpp dockinputctl.h dockrds.cpp dockrds.h dockrxopt.cpp dockrxopt.h freqctrl.cpp freqctrl.h ioconfig.cpp ioconfig.h iq_tool.cpp iq_tool.h meter.cpp meter.h nb_options.cpp nb_options.h plotter.cpp plotter.h qtcolorpicker.cpp qtcolorpicker.h ) ####################################################################################################################### # Add the source files to UI_SRCS_LIST add_source_files(UI_SRCS_LIST afsk1200win.ui agc_options.ui audio_options.ui demod_options.ui dockaudio.ui dockbookmarks.ui dockfft.ui dockinputctl.ui dockrds.ui dockrxopt.ui ioconfig.ui iq_tool.ui nb_options.ui ) gqrx-2.9/src/qtgui/afsk1200win.cpp000066400000000000000000000102021320142145500167260ustar00rootroot00000000000000/* -*- 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-2.9/src/qtgui/afsk1200win.h000066400000000000000000000033371320142145500164060ustar00rootroot00000000000000/* -*- 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-2.9/src/qtgui/afsk1200win.ui000066400000000000000000000102461320142145500165710ustar00rootroot00000000000000 Afsk1200Win 0 0 800 400 AFSK1200 Decoder :/icons/icons/terminal.svg:/icons/icons/terminal.svg 5 0 0 0 0 Monospace Qt::ScrollBarAsNeeded QPlainTextEdit::NoWrap true 10000 0 0 800 27 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-2.9/src/qtgui/agc_options.cpp000066400000000000000000000135261320142145500173020ustar00rootroot00000000000000/* -*- 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(0); enableSlope(false); enableGain(false); break; case AGC_MEDIUM: setDecay(500); enableDecay(false); setSlope(0); enableSlope(false); enableGain(false); break; case AGC_SLOW: setDecay(2000); enableDecay(false); setSlope(0); 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-2.9/src/qtgui/agc_options.h000066400000000000000000000054451320142145500167500ustar00rootroot00000000000000/* -*- 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-2.9/src/qtgui/agc_options.ui000066400000000000000000000203171320142145500171310ustar00rootroot00000000000000 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 0 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 500 500 Qt::Horizontal false 500 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 0 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-2.9/src/qtgui/audio_options.cpp000066400000000000000000000110751320142145500176460ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2013-2016 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; } /** * 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"); } } /** Set initial location of WAV files. */ void CAudioOptions::setRecDir(const QString &dir) { ui->recDirEdit->setText(dir); } /** Set new UDP host name or IP. */ void CAudioOptions::setUdpHost(const QString &host) { ui->udpHost->setText(host); } /** Set new UDP port. */ void CAudioOptions::setUdpPort(int port) { ui->udpPort->setValue(port); } void CAudioOptions::setFftSplit(int pct_2d) { ui->fftSplitSlider->setValue(100 - pct_2d); } int CAudioOptions::getFftSplit(void) const { return 100 - ui->fftSplitSlider->value(); } void CAudioOptions::on_fftSplitSlider_valueChanged(int value) { emit newFftSplit(100 - value); } void CAudioOptions::setPandapterRange(int min, int max) { if (min < max && max <= 0) ui->pandRangeSlider->setValues(min, max); } void CAudioOptions::getPandapterRange(int * min, int * max) const { *min = ui->pandRangeSlider->minimumValue(); *max = ui->pandRangeSlider->maximumValue(); } void CAudioOptions::on_pandRangeSlider_valuesChanged(int min, int max) { emit newPandapterRange(min, max); } void CAudioOptions::setWaterfallRange(int min, int max) { if (min < max && max <= 0) ui->wfRangeSlider->setValues(min, max); } void CAudioOptions::getWaterfallRange(int * min, int * max) const { *min = ui->wfRangeSlider->minimumValue(); *max = ui->wfRangeSlider->maximumValue(); } void CAudioOptions::on_wfRangeSlider_valuesChanged(int min, int max) { emit newWaterfallRange(min, max); } /** * 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 } } /** 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); } /** UDP host name has changed. */ void CAudioOptions::on_udpHost_textChanged(const QString &text) { if (!text.isEmpty()) emit newUdpHost(text); } /** UDP port number has changed. */ void CAudioOptions::on_udpPort_valueChanged(int port) { emit newUdpPort(port); } gqrx-2.9/src/qtgui/audio_options.h000066400000000000000000000051441320142145500173130ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2013-2016 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); void setFftSplit(int pct_2d); int getFftSplit(void) const; void setPandapterRange(int min, int max); void getPandapterRange(int * min, int * max) const; void setWaterfallRange(int min, int max); void getWaterfallRange(int * min, int * max) const; signals: void newFftSplit(int pct_2d); void newPandapterRange(int min, int max); void newWaterfallRange(int min, int max); /*! \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_fftSplitSlider_valueChanged(int value); void on_pandRangeSlider_valuesChanged(int min, int max); void on_wfRangeSlider_valuesChanged(int min, int max); 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-2.9/src/qtgui/audio_options.ui000066400000000000000000000220411320142145500174740ustar00rootroot00000000000000 CAudioOptions 0 0 315 169 Audio options :/icons/icons/audio-card.svg:/icons/icons/audio-card.svg 0 FFT Audio FFT settings Adjust split between pandapter and waterfall Pandapter Adjust split between pandapter and waterfall 100 Qt::Horizontal Adjust split between pandapter and waterfall Waterfall Adjust dB range on the pandapter Pandapter dB range Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Adjust dB range on the pandapter -120 0 -80 Qt::Horizontal Adjust dB range on the waterfall Waterfall dB range Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Adjust dB range on the waterfall -120 0 -80 Qt::Horizontal 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 ctkRangeSlider QSlider
    qtgui/ctk/ctkRangeSlider.h
    gqrx-2.9/src/qtgui/bookmarks.cpp000066400000000000000000000215311320142145500167600ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2013 Christian Lindner DL2VCL, Stefano Leucci. * * 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 "bookmarks.h" #include #include const QColor TagInfo::DefaultColor(Qt::lightGray); const QString TagInfo::strUntagged("Untagged"); Bookmarks* Bookmarks::m_pThis = 0; Bookmarks::Bookmarks() { TagInfo tag(TagInfo::strUntagged); m_TagList.append(tag); } void Bookmarks::create() { m_pThis = new Bookmarks; } Bookmarks& Bookmarks::Get() { return *m_pThis; } void Bookmarks::setConfigDir(const QString& cfg_dir) { m_bookmarksFile = cfg_dir + "/bookmarks.csv"; printf("BookmarksFile is %s\n", m_bookmarksFile.toStdString().c_str()); } void Bookmarks::add(BookmarkInfo &info) { m_BookmarkList.append(info); std::stable_sort(m_BookmarkList.begin(),m_BookmarkList.end()); save(); emit( BookmarksChanged() ); } void Bookmarks::remove(int index) { m_BookmarkList.removeAt(index); save(); emit BookmarksChanged(); } bool Bookmarks::load() { QFile file(m_bookmarksFile); if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { m_BookmarkList.clear(); m_TagList.clear(); // always create the "Untagged" entry. findOrAddTag(TagInfo::strUntagged); // Read Tags, until first empty line. while (!file.atEnd()) { QString line = QString::fromUtf8(file.readLine().trimmed()); if(line.isEmpty()) break; if(line.startsWith("#")) continue; QStringList strings = line.split(";"); if(strings.count() == 2) { TagInfo &info = findOrAddTag(strings[0]); info.color = QColor(strings[1].trimmed()); } else { printf("\nBookmarks: Ignoring Line:\n %s\n", line.toLatin1().data()); } } std::sort(m_TagList.begin(),m_TagList.end()); // Read Bookmarks, after first empty line. while (!file.atEnd()) { QString line = QString::fromUtf8(file.readLine().trimmed()); if(line.isEmpty() || line.startsWith("#")) continue; QStringList strings = line.split(";"); if(strings.count() == 5) { BookmarkInfo info; info.frequency = strings[0].toLongLong(); info.name = strings[1].trimmed(); info.modulation = strings[2].trimmed(); info.bandwidth = strings[3].toInt(); // Multiple Tags may be separated by comma. QString strTags = strings[4]; QStringList TagList = strTags.split(","); for(int iTag=0; iTag usedTags; for (int iBookmark = 0; iBookmark < m_BookmarkList.size(); iBookmark++) { BookmarkInfo& info = m_BookmarkList[iBookmark]; for(int iTag = 0; iTag < info.tags.size(); ++iTag) { TagInfo& tag = *info.tags[iTag]; usedTags.insert(&tag); } } for (QSet::iterator i = usedTags.begin(); i != usedTags.end(); i++) { TagInfo& info = **i; stream << info.name.leftJustified(20) + "; " + info.color.name() << endl; } stream << endl; stream << QString("# Frequency").leftJustified(12) + "; " + QString("Name").leftJustified(25)+ "; " + QString("Modulation").leftJustified(20) + "; " + QString("Bandwidth").rightJustified(10) + "; " + QString("Tags") << endl; for (int i = 0; i < m_BookmarkList.size(); i++) { BookmarkInfo& info = m_BookmarkList[i]; QString line = QString::number(info.frequency).rightJustified(12) + "; " + info.name.leftJustified(25) + "; " + info.modulation.leftJustified(20)+ "; " + QString::number(info.bandwidth).rightJustified(10) + "; "; for(int iTag = 0; iTag Bookmarks::getBookmarksInRange(qint64 low, qint64 high) { BookmarkInfo info; info.frequency=low; QList::const_iterator lb = qLowerBound(m_BookmarkList, info); info.frequency=high; QList::const_iterator ub = qUpperBound(m_BookmarkList, info); QList found; while (lb != ub) { const BookmarkInfo& info = *lb; //if(info.IsActive()) { found.append(info); } lb++; } return found; } TagInfo &Bookmarks::findOrAddTag(QString tagName) { tagName = tagName.trimmed(); if (tagName.isEmpty()) tagName=TagInfo::strUntagged; int idx = getTagIndex(tagName); if (idx != -1) return m_TagList[idx]; TagInfo info; info.name=tagName; m_TagList.append(info); emit TagListChanged(); return m_TagList.last(); } bool Bookmarks::removeTag(QString tagName) { tagName = tagName.trimmed(); // Do not delete "Untagged" tag. if(tagName.compare(TagInfo::strUntagged, tagName)==0) return false; int idx = getTagIndex(tagName); if (idx == -1) return false; // Delete Tag from all Bookmarks that use it. TagInfo* pTagToDelete = &m_TagList[idx]; for(int i=0; i1) bmi.tags.removeAt(t); else bmi.tags[0] = &findOrAddTag(TagInfo::strUntagged); } } } // Delete Tag. m_TagList.removeAt(idx); emit BookmarksChanged(); emit TagListChanged(); return true; } bool Bookmarks::setTagChecked(QString tagName, bool bChecked) { int idx = getTagIndex(tagName); if (idx == -1) return false; m_TagList[idx].active = bChecked; emit BookmarksChanged(); emit TagListChanged(); return true; } int Bookmarks::getTagIndex(QString tagName) { tagName = tagName.trimmed(); for (int i = 0; i < m_TagList.size(); i++) { if (m_TagList[i].name == tagName) return i; } return -1; } const QColor BookmarkInfo::GetColor() const { for(int iTag=0; iTag #include #include #include #include #include #include struct TagInfo { QString name; QColor color; bool active; static const QColor DefaultColor; static const QString strUntagged; TagInfo() { active=true; this->color=DefaultColor; } TagInfo(QString name) { active=true; this->color=DefaultColor; this->name = name; } bool operator<(const TagInfo &other) const { return name < other.name; } }; struct BookmarkInfo { qint64 frequency; QString name; QString modulation; qint64 bandwidth; QList tags; BookmarkInfo() { this->frequency = 0; this->bandwidth = 0; } /* BookmarkInfo( qint64 frequency, QString name, qint64 bandwidth, QString modulation ) { this->frequency = frequency; this->name = name; this->modulation = modulation; this->bandwidth = bandwidth; } */ bool operator<(const BookmarkInfo &other) const { return frequency < other.frequency; } /* void setTags(QString tagString); QString getTagString(); bool hasTags(QString _tags); bool hasTags(QStringList _tags); */ const QColor GetColor() const; bool IsActive() const; }; class Bookmarks : public QObject { Q_OBJECT public: // This is a Singleton Class now because you can not send qt-signals from static functions. static void create(); static Bookmarks& Get(); void add(BookmarkInfo& info); void remove(int index); bool load(); bool save(); int size() { return m_BookmarkList.size(); } BookmarkInfo& getBookmark(int i) { return m_BookmarkList[i]; } QList getBookmarksInRange(qint64 low, qint64 high); //int lowerBound(qint64 low); //int upperBound(qint64 high); QList getTagList() { return QList(m_TagList); } TagInfo& findOrAddTag(QString tagName); int getTagIndex(QString tagName); bool removeTag(QString tagName); bool setTagChecked(QString tagName, bool bChecked); void setConfigDir(const QString&); private: Bookmarks(); // Singleton Constructor is private. QList m_BookmarkList; QList m_TagList; QString m_bookmarksFile; static Bookmarks* m_pThis; signals: void BookmarksChanged(void); void TagListChanged(void); }; #endif // BOOKMARKS_H gqrx-2.9/src/qtgui/bookmarkstablemodel.cpp000066400000000000000000000142721320142145500210150ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2013 Christian Lindner DL2VCL, Stefano Leucci. * * 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 "bookmarks.h" #include "bookmarkstablemodel.h" #include "dockrxopt.h" BookmarksTableModel::BookmarksTableModel(QObject *parent) : QAbstractTableModel(parent) { } int BookmarksTableModel::rowCount ( const QModelIndex & /*parent*/ ) const { return m_Bookmarks.size(); } int BookmarksTableModel::columnCount ( const QModelIndex & /*parent*/ ) const { return 5; } QVariant BookmarksTableModel::headerData ( int section, Qt::Orientation orientation, int role ) const { if(orientation == Qt::Horizontal && role == Qt::DisplayRole) { switch(section) { case COL_FREQUENCY: return QString("Frequency"); break; case COL_NAME: return QString("Name"); break; case COL_MODULATION: return QString("Modulation"); break; case COL_BANDWIDTH: return QString("Bandwidth"); break; case COL_TAGS: return QString("Tag"); break; } } if(orientation == Qt::Vertical && role == Qt::DisplayRole) { return section; } return QVariant(); } QVariant BookmarksTableModel::data ( const QModelIndex & index, int role ) const { BookmarkInfo& info = *m_Bookmarks[index.row()]; if(role==Qt::BackgroundColorRole) { QColor bg(info.GetColor()); bg.setAlpha(0x60); return bg; } else if(role == Qt::DisplayRole || role==Qt::EditRole) { switch(index.column()) { case COL_FREQUENCY: return info.frequency; case COL_NAME: return (role==Qt::EditRole)?QString(info.name):info.name; case COL_MODULATION: return info.modulation; case COL_BANDWIDTH: return (info.bandwidth==0)?QVariant(""):QVariant(info.bandwidth); case COL_TAGS: QString strTags; for(int iTag=0; iTag #include #include "bookmarks.h" class BookmarksTableModel : public QAbstractTableModel { Q_OBJECT public: enum EColumns { COL_FREQUENCY, COL_NAME, COL_MODULATION, COL_BANDWIDTH, COL_TAGS }; explicit BookmarksTableModel(QObject *parent = 0); int rowCount ( const QModelIndex & parent = QModelIndex() ) const; int columnCount ( const QModelIndex & parent = QModelIndex() ) const; QVariant headerData ( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const; QVariant data ( const QModelIndex & index, int role = Qt::DisplayRole ) const; bool setData ( const QModelIndex & index, const QVariant & value, int role = Qt::EditRole ); Qt::ItemFlags flags ( const QModelIndex & index ) const; BookmarkInfo* getBookmarkAtRow(int row); int GetBookmarksIndexForRow(int iRow); private: QList m_Bookmarks; QMap m_mapRowToBookmarksIndex; signals: public slots: void update(); }; #endif gqrx-2.9/src/qtgui/bookmarkstaglist.cpp000066400000000000000000000202541320142145500203510ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2014 Stefano Leucci, Christian Lindner DL2VCL. * * 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 "bookmarkstaglist.h" #include "bookmarks.h" #include #include #include #include BookmarksTagList::BookmarksTagList(QWidget *parent, bool bShowUntagged ) : QTableWidget(parent) , m_bUpdating(false) , m_bShowUntagged(bShowUntagged) { connect(this, SIGNAL(cellClicked(int,int)), this, SLOT(on_cellClicked(int,int))); // right click menu setContextMenuPolicy(Qt::CustomContextMenu); connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(ShowContextMenu(const QPoint&))); horizontalHeader()->setVisible(false); verticalHeader()->setVisible(false); setColumnCount(2); setColumnWidth(0, 20); horizontalHeader()->setStretchLastSection(true); setSelectionMode(QAbstractItemView::SingleSelection); setSelectionBehavior(QAbstractItemView::SelectRows); setSortingEnabled(true); } void BookmarksTagList::on_cellClicked(int row, int column) { if(column==0) { changeColor(row, column); } if(column==1) { toggleCheckedState(row, column); } } void BookmarksTagList::changeColor(int row, int /*column*/) { TagInfo &info = Bookmarks::Get().findOrAddTag(item(row, 1)->text()); QColor color = QColorDialog::getColor(info.color, this); if(!color.isValid()) return; info.color=color; updateTags(); Bookmarks::Get().save(); } void BookmarksTagList::toggleCheckedState(int row, int column) { QTableWidgetItem* p = item(row,column); if(p->checkState()==Qt::Unchecked) { p->setCheckState(Qt::Checked); } else { p->setCheckState(Qt::Unchecked); } } void BookmarksTagList::updateTags() { m_bUpdating = true; // Remember which items were unchecked. QStringList unchecked; for(int i=0; icheckState()==Qt::Unchecked) unchecked.append(item(i,1)->text()); } // Get current List of Tags. QList newTags = Bookmarks::Get().getTagList(); if(!m_bShowUntagged) { for(int i=0; itext(); bool bChecked = list.contains(name); pItem->setCheckState(bChecked ? Qt::Checked : Qt::Unchecked); } setSortingEnabled(true); } void BookmarksTagList::setSelectedTags(QList tags) { int iRows = rowCount(); for(int i=0; itext(); bool bChecked = false; for(QList::const_iterator it=tags.begin(), itend=tags.end(); it!=itend; ++it) { TagInfo* pTag = *it; if(pTag->name == name) bChecked = true; } pItem->setCheckState(bChecked ? Qt::Checked : Qt::Unchecked); } setSortingEnabled(true); } QString BookmarksTagList::getSelectedTagsAsString() { QString strResult; int iRows = rowCount(); bool bFirst = true; for(int i=0; icheckState() == Qt::Checked) { if(!bFirst) strResult += ", "; strResult += pItem->text(); bFirst = false; } } return strResult; } void BookmarksTagList::ShowContextMenu(const QPoint& pos) { QMenu* menu=new QMenu(this); // Rename currently does not work. // The problem is that after the tag name is changed in GUI // you can not find the right TagInfo because you dont know // the old tag name. #if 0 // MenuItem "Rename" { QAction* actionRename = new QAction("Rename", this); menu->addAction(actionRename); connect(actionRename, SIGNAL(triggered()), this, SLOT(RenameSelectedTag())); } #endif // MenuItem "Create new Tag" { QAction* actionNewTag = new QAction("Create new Tag", this); menu->addAction(actionNewTag); connect(actionNewTag, SIGNAL(triggered()), this, SLOT(AddNewTag())); } // Menu "Delete Tag" { QAction* actionDeleteTag = new QAction("Delete Tag", this); menu->addAction(actionDeleteTag); connect(actionDeleteTag, SIGNAL(triggered()), this, SLOT(DeleteSelectedTag())); } // Menu "Select All" { QAction* action = new QAction("Select All", this); menu->addAction(action); connect(action, SIGNAL(triggered()), this, SLOT(SelectAll())); } // Menu "Deselect All" { QAction* action = new QAction("Deselect All", this); menu->addAction(action); connect(action, SIGNAL(triggered()), this, SLOT(DeselectAll())); } menu->popup(viewport()->mapToGlobal(pos)); } #if 0 bool BookmarksTagList::RenameSelectedTag() { QModelIndexList selected = selectionModel()->selectedRows(); if(selected.empty()) { return true; } int iRow = selected.first().row(); QTableWidgetItem* pItem = item(iRow,1);bUpdating editItem(pItem); //Bookmarks::Get().save(); return true; } #endif void BookmarksTagList::AddNewTag() { AddTag("*new*"); scrollToBottom(); editItem(item(rowCount()-1, 1)); } void BookmarksTagList::AddTag(QString name, Qt::CheckState checkstate, QColor color) { int i = rowCount(); setRowCount(i+1); // Column 1 QTableWidgetItem *item = new QTableWidgetItem(name); item->setCheckState(checkstate); item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled); setItem(i, 1, item); // Column 0 item = new QTableWidgetItem(); item->setFlags(Qt::ItemIsEnabled); item->setBackgroundColor(color); setItem(i, 0, item); } void BookmarksTagList::DeleteSelectedTag() { QModelIndexList selected = selectionModel()->selectedRows(); if(selected.empty()) { return; } int iRow = selected.first().row(); QTableWidgetItem* pItem = item(iRow,1); QString strTagName = pItem->text(); DeleteTag(strTagName); return; } void BookmarksTagList::DeleteTag(const QString& name) { Bookmarks::Get().removeTag(name); updateTags(); } void BookmarksTagList::SelectAll() { int iRows = rowCount(); for(int i=0; itext(); pItem->setCheckState(Qt::Checked); } } void BookmarksTagList::DeselectAll() { int iRows = rowCount(); for(int i=0; itext(); pItem->setCheckState(Qt::Unchecked); } } gqrx-2.9/src/qtgui/bookmarkstaglist.h000066400000000000000000000036201320142145500200140ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2014 Stefano Leucci, Christian Lindner DL2VCL. * * 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 BOOKMARKSTAGLIST_H #define BOOKMARKSTAGLIST_H #include #include "bookmarks.h" /// A QWidget containing the List of Bookmark-Tags. class BookmarksTagList : public QTableWidget { Q_OBJECT public: explicit BookmarksTagList(QWidget *parent = 0, bool bShowUntagged = true); QString getSelectedTagsAsString(); void setSelectedTagsAsString(const QString& strTags); void setSelectedTags(QList tags); bool m_bUpdating; private: bool m_bShowUntagged; signals: public slots: void updateTags(); void on_cellClicked(int row, int column); void changeColor(int row, int column); void toggleCheckedState(int row, int column); void ShowContextMenu(const QPoint& pos); //bool RenameSelectedTag(); void AddNewTag(); void AddTag(QString name, Qt::CheckState checkstate = Qt::Checked, QColor color = TagInfo::DefaultColor); void DeleteSelectedTag(); void DeleteTag(const QString& name); void SelectAll(); void DeselectAll(); }; #endif // BOOKMARKSTAGLIST_H gqrx-2.9/src/qtgui/ctk/000077500000000000000000000000001320142145500150435ustar00rootroot00000000000000gqrx-2.9/src/qtgui/ctk/ctkPimpl.h000066400000000000000000000140551320142145500170040ustar00rootroot00000000000000/*========================================================================= Library: CTK Copyright (c) Kitware Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.txt Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. =========================================================================*/ /** \page CorePimpl CTK Pimpl Macros \brief Utility macros for handling private implementations. It is in addition to QtGlobal: Q_DECLARE_PRIVATE, Q_DECLARE_PUBLIC, Q_D and Q_Q. Application code generally doesn't have to be concerned about hiding its implementation details, but when writing library code it is important to maintain a constant interface, both source and binary. Maintaining a constant source interface is easy enough, but keeping the binary interface constant means moving implementation details into a private class. The PIMPL, or d-pointer, idiom is a common method of implementing this separation. ctkPimpl offers a convenient way to connect the public and private sides of your class. \section start Getting Started Before you declare the public class, you need to make a forward declaration of the private class. The private class must have the same name as the public class, followed by the word Private. \subsection pub The Public Class Generally, you shouldn't keep any data members in the public class without a good reason. Functions that are part of the public interface should be declared in the public class, and functions that need to be available to subclasses (for calling or overriding) should be in the protected section of the public class. To connect the private class to the public class, include the Q_DECLARE_PRIVATE macro in the private section of the public class. Additionally, you must construct the d_ptr pointer in the constructor of the public class. \subsection priv The Private Class As mentioned above, data members should usually be kept in the private class. This allows the memory layout of the private class to change without breaking binary compatibility for the public class. Functions that exist only as implementation details, or functions that need access to private data members, should be implemented here. To define the private class, nothing special needs to be done, except if you want the private class to have access to the public class. Then use Q_DECLARE_PUBLIC and create a public class pointer member. The constructor of the private class should take a public class reference as a parameter. \section cross Accessing Private Members Use the Q_D() macros from functions in the public class to access the private class. Similarly, functions in the private class can invoke functions in the public class by using the Q_Q() macro. \section example Example Header file (ctkFooObject.h): \code // Qt includes #include // CTK includes #include "ctkCoreExport.h" class ctkFooObjectPrivate; class CTK_CORE_EXPORT ctkFooObject: public QObject { public: ctkFooObject(QObject* parent = 0); virtual ~ctkFooObject(); void setProperty(double property); double property()const; protected: QScopedPointer d_ptr; private: Q_DECLARE_PRIVATE(ctkFooObject); Q_DISABLE_COPY(ctkFooObject); }; \endcode Implementation file (ctkFooObject.cpp): \code // CTK includes #include "ctkFooObject.h" class ctkFooObjectPrivate { public: void processSomething(); double MyProperty; }; ctkFooObject::ctkFooObject(QObject* parentObject) : d_ptr(new ctkFooObjectPrivate) { Q_D(ctkFooObject); d->MyProperty = 10.; } void ctkFooObject::setProperty(double newProperty) { Q_D(ctkFooObject); d->MyProperty = newProperty; } double ctkFooObject::property()const { Q_D(const ctkFooObject); return d->MyProperty; } \endcode */ #ifndef __ctkPimpl_h #define __ctkPimpl_h // Qt includes #include /*! * \ingroup Core * @{ */ /*! * Define a public class constructor with no argument * * Also make sure the Pimpl is initalized * \see \ref CorePimpl */ #define CTK_CONSTRUCTOR_NO_ARG_CPP(PUB) \ PUB::PUB(): d_ptr(new PUB##Private) \ { \ } /*! * Define a public class constructor with one argument * * Also make sure the Pimpl is initalized * \see \ref CorePimpl */ #define CTK_CONSTRUCTOR_1_ARG_CPP(PUB, _ARG1) \ PUB::PUB(_ARG1 _parent) \ : Superclass( _parent ) \ , d_ptr(new PUB##Private) \ { \ } /*! * Define the setter in the public class. * * This should be put in the .cxx file of the public class. The parameter are * the name of the public class (PUB), the type of the argument to return (_TYPE), * the name of the getter(_NAME) and the name of the variable in the Private class(_VARNAME). * \see \ref CorePimpl */ #define CTK_SET_CPP(PUB, _TYPE, _NAME, _VARNAME) \ void PUB::_NAME(_TYPE var) \ { \ Q_D(PUB); \ d->_VARNAME = var; \ } /*! * Define the setter in the public class. * * This should be put in the .cxx file of the public class. The parameter are * the name of the public class (PUB), the type of the argument to return (_TYPE), * the name of the setter(_NAME) and the name of the variable in the Private class(_VARNAME). * \see \ref CorePimpl */ #define CTK_GET_CPP(PUB, _TYPE, _NAME, _VARNAME) \ _TYPE PUB::_NAME()const \ { \ Q_D(const PUB); \ return d->_VARNAME; \ } /**@}*/ #endif gqrx-2.9/src/qtgui/ctk/ctkRangeSlider.cpp000066400000000000000000000661741320142145500204660ustar00rootroot00000000000000/*========================================================================= Library: CTK Copyright (c) Kitware Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.txt Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. =========================================================================*/ // Qt includes #include #include #include #include #include #include #include #include // CTK includes #include "ctkRangeSlider.h" class ctkRangeSliderPrivate { Q_DECLARE_PUBLIC(ctkRangeSlider); protected: ctkRangeSlider* const q_ptr; public: /// Boolean indicates the selected handle /// True for the minimum range handle, false for the maximum range handle enum Handle { NoHandle = 0x0, MinimumHandle = 0x1, MaximumHandle = 0x2 }; Q_DECLARE_FLAGS(Handles, Handle); ctkRangeSliderPrivate(ctkRangeSlider& object); void init(); /// Return the handle at the given pos, or none if no handle is at the pos. /// If a handle is selected, handleRect is set to the handle rect. /// otherwise return NoHandle and handleRect is set to the combined rect of /// the min and max handles Handle handleAtPos(const QPoint& pos, QRect& handleRect)const; /// Copied verbatim from QSliderPrivate class (see QSlider.cpp) int pixelPosToRangeValue(int pos) const; int pixelPosFromRangeValue(int val) const; /// Draw the bottom and top sliders. void drawMinimumSlider( QStylePainter* painter ) const; void drawMaximumSlider( QStylePainter* painter ) const; /// End points of the range on the Model int m_MaximumValue; int m_MinimumValue; /// End points of the range on the GUI. This is synced with the model. int m_MaximumPosition; int m_MinimumPosition; /// Controls selected ? QStyle::SubControl m_MinimumSliderSelected; QStyle::SubControl m_MaximumSliderSelected; /// See QSliderPrivate::clickOffset. /// Overrides this ivar int m_SubclassClickOffset; /// See QSliderPrivate::position /// Overrides this ivar. int m_SubclassPosition; /// Original width between the 2 bounds before any moves float m_SubclassWidth; ctkRangeSliderPrivate::Handles m_SelectedHandles; /// When symmetricMoves is true, moving a handle will move the other handle /// symmetrically, otherwise the handles are independent. bool m_SymmetricMoves; QString m_HandleToolTip; private: Q_DISABLE_COPY(ctkRangeSliderPrivate); }; // -------------------------------------------------------------------------- ctkRangeSliderPrivate::ctkRangeSliderPrivate(ctkRangeSlider& object) :q_ptr(&object) { this->m_MinimumValue = 0; this->m_MaximumValue = 100; this->m_MinimumPosition = 0; this->m_MaximumPosition = 100; this->m_MinimumSliderSelected = QStyle::SC_None; this->m_MaximumSliderSelected = QStyle::SC_None; this->m_SubclassClickOffset = 0; this->m_SubclassPosition = 0; this->m_SubclassWidth = 0.0; this->m_SelectedHandles = 0; this->m_SymmetricMoves = false; } // -------------------------------------------------------------------------- void ctkRangeSliderPrivate::init() { Q_Q(ctkRangeSlider); this->m_MinimumValue = q->minimum(); this->m_MaximumValue = q->maximum(); this->m_MinimumPosition = q->minimum(); this->m_MaximumPosition = q->maximum(); q->connect(q, SIGNAL(rangeChanged(int,int)), q, SLOT(onRangeChanged(int,int))); } // -------------------------------------------------------------------------- ctkRangeSliderPrivate::Handle ctkRangeSliderPrivate::handleAtPos(const QPoint& pos, QRect& handleRect)const { Q_Q(const ctkRangeSlider); QStyleOptionSlider option; q->initStyleOption( &option ); // The functinos hitTestComplexControl only know about 1 handle. As we have // 2, we change the position of the handle and test if the pos correspond to // any of the 2 positions. // Test the MinimumHandle option.sliderPosition = this->m_MinimumPosition; option.sliderValue = this->m_MinimumValue; QStyle::SubControl minimumControl = q->style()->hitTestComplexControl( QStyle::CC_Slider, &option, pos, q); QRect minimumHandleRect = q->style()->subControlRect( QStyle::CC_Slider, &option, QStyle::SC_SliderHandle, q); // Test if the pos is under the Maximum handle option.sliderPosition = this->m_MaximumPosition; option.sliderValue = this->m_MaximumValue; QStyle::SubControl maximumControl = q->style()->hitTestComplexControl( QStyle::CC_Slider, &option, pos, q); QRect maximumHandleRect = q->style()->subControlRect( QStyle::CC_Slider, &option, QStyle::SC_SliderHandle, q); // The pos is above both handles, select the closest handle if (minimumControl == QStyle::SC_SliderHandle && maximumControl == QStyle::SC_SliderHandle) { int minDist = 0; int maxDist = 0; if (q->orientation() == Qt::Horizontal) { minDist = pos.x() - minimumHandleRect.left(); maxDist = maximumHandleRect.right() - pos.x(); } else //if (q->orientation() == Qt::Vertical) { minDist = minimumHandleRect.bottom() - pos.y(); maxDist = pos.y() - maximumHandleRect.top(); } Q_ASSERT( minDist >= 0 && maxDist >= 0); minimumControl = minDist < maxDist ? minimumControl : QStyle::SC_None; } if (minimumControl == QStyle::SC_SliderHandle) { handleRect = minimumHandleRect; return MinimumHandle; } else if (maximumControl == QStyle::SC_SliderHandle) { handleRect = maximumHandleRect; return MaximumHandle; } handleRect = minimumHandleRect.united(maximumHandleRect); return NoHandle; } // -------------------------------------------------------------------------- // Copied verbatim from QSliderPrivate::pixelPosToRangeValue. See QSlider.cpp // int ctkRangeSliderPrivate::pixelPosToRangeValue( int pos ) const { Q_Q(const ctkRangeSlider); QStyleOptionSlider option; q->initStyleOption( &option ); QRect gr = q->style()->subControlRect( QStyle::CC_Slider, &option, QStyle::SC_SliderGroove, q ); QRect sr = q->style()->subControlRect( QStyle::CC_Slider, &option, QStyle::SC_SliderHandle, q ); int sliderMin, sliderMax, sliderLength; if (option.orientation == Qt::Horizontal) { sliderLength = sr.width(); sliderMin = gr.x(); sliderMax = gr.right() - sliderLength + 1; } else { sliderLength = sr.height(); sliderMin = gr.y(); sliderMax = gr.bottom() - sliderLength + 1; } return QStyle::sliderValueFromPosition( q->minimum(), q->maximum(), pos - sliderMin, sliderMax - sliderMin, option.upsideDown ); } //--------------------------------------------------------------------------- int ctkRangeSliderPrivate::pixelPosFromRangeValue( int val ) const { Q_Q(const ctkRangeSlider); QStyleOptionSlider option; q->initStyleOption( &option ); QRect gr = q->style()->subControlRect( QStyle::CC_Slider, &option, QStyle::SC_SliderGroove, q ); QRect sr = q->style()->subControlRect( QStyle::CC_Slider, &option, QStyle::SC_SliderHandle, q ); int sliderMin, sliderMax, sliderLength; if (option.orientation == Qt::Horizontal) { sliderLength = sr.width(); sliderMin = gr.x(); sliderMax = gr.right() - sliderLength + 1; } else { sliderLength = sr.height(); sliderMin = gr.y(); sliderMax = gr.bottom() - sliderLength + 1; } return QStyle::sliderPositionFromValue( q->minimum(), q->maximum(), val, sliderMax - sliderMin, option.upsideDown ) + sliderMin; } //--------------------------------------------------------------------------- // Draw slider at the bottom end of the range void ctkRangeSliderPrivate::drawMinimumSlider( QStylePainter* painter ) const { Q_Q(const ctkRangeSlider); QStyleOptionSlider option; q->initMinimumSliderStyleOption( &option ); option.subControls = QStyle::SC_SliderHandle; option.sliderValue = m_MinimumValue; option.sliderPosition = m_MinimumPosition; if (q->isMinimumSliderDown()) { option.activeSubControls = QStyle::SC_SliderHandle; option.state |= QStyle::State_Sunken; } #ifdef Q_OS_MAC // On mac style, drawing just the handle actually draws also the groove. QRect clip = q->style()->subControlRect(QStyle::CC_Slider, &option, QStyle::SC_SliderHandle, q); painter->setClipRect(clip); #endif painter->drawComplexControl(QStyle::CC_Slider, option); } //--------------------------------------------------------------------------- // Draw slider at the top end of the range void ctkRangeSliderPrivate::drawMaximumSlider( QStylePainter* painter ) const { Q_Q(const ctkRangeSlider); QStyleOptionSlider option; q->initMaximumSliderStyleOption( &option ); option.subControls = QStyle::SC_SliderHandle; option.sliderValue = m_MaximumValue; option.sliderPosition = m_MaximumPosition; if (q->isMaximumSliderDown()) { option.activeSubControls = QStyle::SC_SliderHandle; option.state |= QStyle::State_Sunken; } #ifdef Q_OS_MAC // On mac style, drawing just the handle actually draws also the groove. QRect clip = q->style()->subControlRect(QStyle::CC_Slider, &option, QStyle::SC_SliderHandle, q); painter->setClipRect(clip); #endif painter->drawComplexControl(QStyle::CC_Slider, option); } // -------------------------------------------------------------------------- ctkRangeSlider::ctkRangeSlider(QWidget* _parent) : QSlider(_parent) , d_ptr(new ctkRangeSliderPrivate(*this)) { Q_D(ctkRangeSlider); d->init(); } // -------------------------------------------------------------------------- ctkRangeSlider::ctkRangeSlider( Qt::Orientation o, QWidget* parentObject ) :QSlider(o, parentObject) , d_ptr(new ctkRangeSliderPrivate(*this)) { Q_D(ctkRangeSlider); d->init(); } // -------------------------------------------------------------------------- ctkRangeSlider::ctkRangeSlider(ctkRangeSliderPrivate* impl, QWidget* _parent) : QSlider(_parent) , d_ptr(impl) { Q_D(ctkRangeSlider); d->init(); } // -------------------------------------------------------------------------- ctkRangeSlider::ctkRangeSlider( ctkRangeSliderPrivate* impl, Qt::Orientation o, QWidget* parentObject ) :QSlider(o, parentObject) , d_ptr(impl) { Q_D(ctkRangeSlider); d->init(); } // -------------------------------------------------------------------------- ctkRangeSlider::~ctkRangeSlider() { } // -------------------------------------------------------------------------- int ctkRangeSlider::minimumValue() const { Q_D(const ctkRangeSlider); return d->m_MinimumValue; } // -------------------------------------------------------------------------- void ctkRangeSlider::setMinimumValue( int min ) { Q_D(ctkRangeSlider); this->setValues( min, qMax(d->m_MaximumValue,min) ); } // -------------------------------------------------------------------------- int ctkRangeSlider::maximumValue() const { Q_D(const ctkRangeSlider); return d->m_MaximumValue; } // -------------------------------------------------------------------------- void ctkRangeSlider::setMaximumValue( int max ) { Q_D(ctkRangeSlider); this->setValues( qMin(d->m_MinimumValue, max), max ); } // -------------------------------------------------------------------------- void ctkRangeSlider::setValues(int l, int u) { Q_D(ctkRangeSlider); const int minValue = qBound(this->minimum(), qMin(l,u), this->maximum()); const int maxValue = qBound(this->minimum(), qMax(l,u), this->maximum()); bool emitMinValChanged = (minValue != d->m_MinimumValue); bool emitMaxValChanged = (maxValue != d->m_MaximumValue); d->m_MinimumValue = minValue; d->m_MaximumValue = maxValue; bool emitMinPosChanged = (minValue != d->m_MinimumPosition); bool emitMaxPosChanged = (maxValue != d->m_MaximumPosition); d->m_MinimumPosition = minValue; d->m_MaximumPosition = maxValue; if (isSliderDown()) { if (emitMinPosChanged || emitMaxPosChanged) { emit positionsChanged(d->m_MinimumPosition, d->m_MaximumPosition); } if (emitMinPosChanged) { emit minimumPositionChanged(d->m_MinimumPosition); } if (emitMaxPosChanged) { emit maximumPositionChanged(d->m_MaximumPosition); } } if (emitMinValChanged || emitMaxValChanged) { emit valuesChanged(d->m_MinimumValue, d->m_MaximumValue); } if (emitMinValChanged) { emit minimumValueChanged(d->m_MinimumValue); } if (emitMaxValChanged) { emit maximumValueChanged(d->m_MaximumValue); } if (emitMinPosChanged || emitMaxPosChanged || emitMinValChanged || emitMaxValChanged) { this->update(); } } // -------------------------------------------------------------------------- int ctkRangeSlider::minimumPosition() const { Q_D(const ctkRangeSlider); return d->m_MinimumPosition; } // -------------------------------------------------------------------------- int ctkRangeSlider::maximumPosition() const { Q_D(const ctkRangeSlider); return d->m_MaximumPosition; } // -------------------------------------------------------------------------- void ctkRangeSlider::setMinimumPosition(int l) { Q_D(const ctkRangeSlider); this->setPositions(l, qMax(l, d->m_MaximumPosition)); } // -------------------------------------------------------------------------- void ctkRangeSlider::setMaximumPosition(int u) { Q_D(const ctkRangeSlider); this->setPositions(qMin(d->m_MinimumPosition, u), u); } // -------------------------------------------------------------------------- void ctkRangeSlider::setPositions(int min, int max) { Q_D(ctkRangeSlider); const int minPosition = qBound(this->minimum(), qMin(min, max), this->maximum()); const int maxPosition = qBound(this->minimum(), qMax(min, max), this->maximum()); bool emitMinPosChanged = (minPosition != d->m_MinimumPosition); bool emitMaxPosChanged = (maxPosition != d->m_MaximumPosition); if (!emitMinPosChanged && !emitMaxPosChanged) { return; } d->m_MinimumPosition = minPosition; d->m_MaximumPosition = maxPosition; if (!this->hasTracking()) { this->update(); } if (isSliderDown()) { if (emitMinPosChanged) { emit minimumPositionChanged(d->m_MinimumPosition); } if (emitMaxPosChanged) { emit maximumPositionChanged(d->m_MaximumPosition); } if (emitMinPosChanged || emitMaxPosChanged) { emit positionsChanged(d->m_MinimumPosition, d->m_MaximumPosition); } } if (this->hasTracking()) { this->triggerAction(SliderMove); this->setValues(d->m_MinimumPosition, d->m_MaximumPosition); } } // -------------------------------------------------------------------------- void ctkRangeSlider::setSymmetricMoves(bool symmetry) { Q_D(ctkRangeSlider); d->m_SymmetricMoves = symmetry; } // -------------------------------------------------------------------------- bool ctkRangeSlider::symmetricMoves()const { Q_D(const ctkRangeSlider); return d->m_SymmetricMoves; } // -------------------------------------------------------------------------- void ctkRangeSlider::onRangeChanged(int _minimum, int _maximum) { Q_UNUSED(_minimum); Q_UNUSED(_maximum); Q_D(ctkRangeSlider); this->setValues(d->m_MinimumValue, d->m_MaximumValue); } // -------------------------------------------------------------------------- // Render void ctkRangeSlider::paintEvent( QPaintEvent* ) { Q_D(ctkRangeSlider); QStyleOptionSlider option; this->initStyleOption(&option); QStylePainter painter(this); option.subControls = QStyle::SC_SliderGroove; // Move to minimum to not highlight the SliderGroove. // On mac style, drawing just the slider groove also draws the handles, // therefore we give a negative (outside of view) position. option.sliderValue = this->minimum() - this->maximum(); option.sliderPosition = this->minimum() - this->maximum(); painter.drawComplexControl(QStyle::CC_Slider, option); option.sliderPosition = d->m_MinimumPosition; const QRect lr = style()->subControlRect( QStyle::CC_Slider, &option, QStyle::SC_SliderHandle, this); option.sliderPosition = d->m_MaximumPosition; const QRect ur = style()->subControlRect( QStyle::CC_Slider, &option, QStyle::SC_SliderHandle, this); QRect sr = style()->subControlRect( QStyle::CC_Slider, &option, QStyle::SC_SliderGroove, this); QRect rangeBox; if (option.orientation == Qt::Horizontal) { rangeBox = QRect( QPoint(qMin( lr.center().x(), ur.center().x() ), sr.center().y() - 2), QPoint(qMax( lr.center().x(), ur.center().x() ), sr.center().y() + 1)); } else { rangeBox = QRect( QPoint(sr.center().x() - 2, qMin( lr.center().y(), ur.center().y() )), QPoint(sr.center().x() + 1, qMax( lr.center().y(), ur.center().y() ))); } // ----------------------------- // Render the range // QRect groove = this->style()->subControlRect( QStyle::CC_Slider, &option, QStyle::SC_SliderGroove, this ); groove.adjust(0, 0, -1, 0); // Create default colors based on the transfer function. // QColor highlight = this->palette().color(QPalette::Normal, QPalette::Highlight); QLinearGradient gradient; if (option.orientation == Qt::Horizontal) { gradient = QLinearGradient( groove.center().x(), groove.top(), groove.center().x(), groove.bottom()); } else { gradient = QLinearGradient( groove.left(), groove.center().y(), groove.right(), groove.center().y()); } // TODO: Set this based on the supplied transfer function //QColor l = Qt::darkGray; //QColor u = Qt::black; gradient.setColorAt(0, highlight.darker(120)); gradient.setColorAt(1, highlight.lighter(160)); painter.setPen(QPen(highlight.darker(150), 0)); painter.setBrush(gradient); painter.drawRect( rangeBox.intersected(groove) ); // ----------------------------------- // Render the sliders // if (this->isMinimumSliderDown()) { d->drawMaximumSlider( &painter ); d->drawMinimumSlider( &painter ); } else { d->drawMinimumSlider( &painter ); d->drawMaximumSlider( &painter ); } } // -------------------------------------------------------------------------- // Standard Qt UI events void ctkRangeSlider::mousePressEvent(QMouseEvent* mouseEvent) { Q_D(ctkRangeSlider); if (minimum() == maximum() || (mouseEvent->buttons() ^ mouseEvent->button())) { mouseEvent->ignore(); return; } int mepos = this->orientation() == Qt::Horizontal ? mouseEvent->pos().x() : mouseEvent->pos().y(); QStyleOptionSlider option; this->initStyleOption( &option ); QRect handleRect; ctkRangeSliderPrivate::Handle handle_ = d->handleAtPos(mouseEvent->pos(), handleRect); if (handle_ != ctkRangeSliderPrivate::NoHandle) { d->m_SubclassPosition = (handle_ == ctkRangeSliderPrivate::MinimumHandle)? d->m_MinimumPosition : d->m_MaximumPosition; // save the position of the mouse inside the handle for later d->m_SubclassClickOffset = mepos - (this->orientation() == Qt::Horizontal ? handleRect.left() : handleRect.top()); this->setSliderDown(true); if (d->m_SelectedHandles != handle_) { d->m_SelectedHandles = handle_; this->update(handleRect); } // Accept the mouseEvent mouseEvent->accept(); return; } // if we are here, no handles have been pressed // Check if we pressed on the groove between the 2 handles QStyle::SubControl control = this->style()->hitTestComplexControl( QStyle::CC_Slider, &option, mouseEvent->pos(), this); QRect sr = style()->subControlRect( QStyle::CC_Slider, &option, QStyle::SC_SliderGroove, this); int minCenter = (this->orientation() == Qt::Horizontal ? handleRect.left() : handleRect.top()); int maxCenter = (this->orientation() == Qt::Horizontal ? handleRect.right() : handleRect.bottom()); if (control == QStyle::SC_SliderGroove && mepos > minCenter && mepos < maxCenter) { // warning lost of precision it might be fatal d->m_SubclassPosition = (d->m_MinimumPosition + d->m_MaximumPosition) / 2.; d->m_SubclassClickOffset = mepos - d->pixelPosFromRangeValue(d->m_SubclassPosition); d->m_SubclassWidth = (d->m_MaximumPosition - d->m_MinimumPosition) / 2.; qMax(d->m_SubclassPosition - d->m_MinimumPosition, d->m_MaximumPosition - d->m_SubclassPosition); this->setSliderDown(true); if (!this->isMinimumSliderDown() || !this->isMaximumSliderDown()) { d->m_SelectedHandles = QFlags(ctkRangeSliderPrivate::MinimumHandle) | QFlags(ctkRangeSliderPrivate::MaximumHandle); this->update(handleRect.united(sr)); } mouseEvent->accept(); return; } mouseEvent->ignore(); } // -------------------------------------------------------------------------- // Standard Qt UI events void ctkRangeSlider::mouseMoveEvent(QMouseEvent* mouseEvent) { Q_D(ctkRangeSlider); if (!d->m_SelectedHandles) { mouseEvent->ignore(); return; } int mepos = this->orientation() == Qt::Horizontal ? mouseEvent->pos().x() : mouseEvent->pos().y(); QStyleOptionSlider option; this->initStyleOption(&option); const int m = style()->pixelMetric( QStyle::PM_MaximumDragDistance, &option, this ); int newPosition = d->pixelPosToRangeValue(mepos - d->m_SubclassClickOffset); if (m >= 0) { const QRect r = rect().adjusted(-m, -m, m, m); if (!r.contains(mouseEvent->pos())) { newPosition = d->m_SubclassPosition; } } // Only the lower/left slider is down if (this->isMinimumSliderDown() && !this->isMaximumSliderDown()) { double newMinPos = qMin(newPosition,d->m_MaximumPosition); this->setPositions(newMinPos, d->m_MaximumPosition + (d->m_SymmetricMoves ? d->m_MinimumPosition - newMinPos : 0)); } // Only the upper/right slider is down else if (this->isMaximumSliderDown() && !this->isMinimumSliderDown()) { double newMaxPos = qMax(d->m_MinimumPosition, newPosition); this->setPositions(d->m_MinimumPosition - (d->m_SymmetricMoves ? newMaxPos - d->m_MaximumPosition: 0), newMaxPos); } // Both handles are down (the user clicked in between the handles) else if (this->isMinimumSliderDown() && this->isMaximumSliderDown()) { this->setPositions(newPosition - static_cast(d->m_SubclassWidth), newPosition + static_cast(d->m_SubclassWidth + .5)); } mouseEvent->accept(); } // -------------------------------------------------------------------------- // Standard Qt UI mouseEvents void ctkRangeSlider::mouseReleaseEvent(QMouseEvent* mouseEvent) { Q_D(ctkRangeSlider); this->QSlider::mouseReleaseEvent(mouseEvent); setSliderDown(false); d->m_SelectedHandles = 0; this->update(); } // -------------------------------------------------------------------------- bool ctkRangeSlider::isMinimumSliderDown()const { Q_D(const ctkRangeSlider); return d->m_SelectedHandles & ctkRangeSliderPrivate::MinimumHandle; } // -------------------------------------------------------------------------- bool ctkRangeSlider::isMaximumSliderDown()const { Q_D(const ctkRangeSlider); return d->m_SelectedHandles & ctkRangeSliderPrivate::MaximumHandle; } // -------------------------------------------------------------------------- void ctkRangeSlider::initMinimumSliderStyleOption(QStyleOptionSlider* option) const { this->initStyleOption(option); } // -------------------------------------------------------------------------- void ctkRangeSlider::initMaximumSliderStyleOption(QStyleOptionSlider* option) const { this->initStyleOption(option); } // -------------------------------------------------------------------------- QString ctkRangeSlider::handleToolTip()const { Q_D(const ctkRangeSlider); return d->m_HandleToolTip; } // -------------------------------------------------------------------------- void ctkRangeSlider::setHandleToolTip(const QString& _toolTip) { Q_D(ctkRangeSlider); d->m_HandleToolTip = _toolTip; } // -------------------------------------------------------------------------- bool ctkRangeSlider::event(QEvent* _event) { Q_D(ctkRangeSlider); switch(_event->type()) { case QEvent::ToolTip: { QHelpEvent* helpEvent = static_cast(_event); QStyleOptionSlider opt; // Test the MinimumHandle opt.sliderPosition = d->m_MinimumPosition; opt.sliderValue = d->m_MinimumValue; this->initStyleOption(&opt); QStyle::SubControl hoveredControl = this->style()->hitTestComplexControl( QStyle::CC_Slider, &opt, helpEvent->pos(), this); if (!d->m_HandleToolTip.isEmpty() && hoveredControl == QStyle::SC_SliderHandle) { QToolTip::showText(helpEvent->globalPos(), d->m_HandleToolTip.arg(this->minimumValue())); _event->accept(); return true; } // Test the MaximumHandle opt.sliderPosition = d->m_MaximumPosition; opt.sliderValue = d->m_MaximumValue; this->initStyleOption(&opt); hoveredControl = this->style()->hitTestComplexControl( QStyle::CC_Slider, &opt, helpEvent->pos(), this); if (!d->m_HandleToolTip.isEmpty() && hoveredControl == QStyle::SC_SliderHandle) { QToolTip::showText(helpEvent->globalPos(), d->m_HandleToolTip.arg(this->maximumValue())); _event->accept(); return true; } } default: break; } return this->Superclass::event(_event); } gqrx-2.9/src/qtgui/ctk/ctkRangeSlider.h000066400000000000000000000175241320142145500201260ustar00rootroot00000000000000/*========================================================================= Library: CTK Copyright (c) Kitware Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.txt Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. =========================================================================*/ #ifndef __ctkRangeSlider_h #define __ctkRangeSlider_h // Qt includes #include // CTK includes #include "ctkPimpl.h" class QStylePainter; class ctkRangeSliderPrivate; /// \ingroup Widgets /// /// A ctkRangeSlider is a slider that lets you input 2 values instead of one /// (see QSlider). These values are typically a lower and upper bound. /// Values are comprised between the range of the slider. See setRange(), /// minimum() and maximum(). The upper bound can't be smaller than the /// lower bound and vice-versa. /// When setting new values (setMinimumValue(), setMaximumValue() or /// setValues()), make sure they lie between the range (minimum(), maximum()) /// of the slider, they would be forced otherwised. If it is not the behavior /// you desire, you can set the range first (setRange(), setMinimum(), /// setMaximum()) /// TODO: support triggerAction(QAbstractSlider::SliderSingleStepSub) that /// moves both values at a time. /// \sa ctkDoubleRangeSlider, ctkDoubleSlider, ctkRangeWidget class ctkRangeSlider : public QSlider { Q_OBJECT Q_PROPERTY(int minimumValue READ minimumValue WRITE setMinimumValue) Q_PROPERTY(int maximumValue READ maximumValue WRITE setMaximumValue) Q_PROPERTY(int minimumPosition READ minimumPosition WRITE setMinimumPosition) Q_PROPERTY(int maximumPosition READ maximumPosition WRITE setMaximumPosition) Q_PROPERTY(bool symmetricMoves READ symmetricMoves WRITE setSymmetricMoves) Q_PROPERTY(QString handleToolTip READ handleToolTip WRITE setHandleToolTip) public: // Superclass typedef typedef QSlider Superclass; /// Constructor, builds a ctkRangeSlider that ranges from 0 to 100 and has /// a lower and upper values of 0 and 100 respectively, other properties /// are set the QSlider default properties. explicit ctkRangeSlider( Qt::Orientation o, QWidget* par= 0 ); explicit ctkRangeSlider( QWidget* par = 0 ); virtual ~ctkRangeSlider(); /// /// This property holds the slider's current minimum value. /// The slider silently forces minimumValue to be within the legal range: /// minimum() <= minimumValue() <= maximumValue() <= maximum(). /// Changing the minimumValue also changes the minimumPosition. int minimumValue() const; /// /// This property holds the slider's current maximum value. /// The slider forces the maximum value to be within the legal range: /// The slider silently forces maximumValue to be within the legal range: /// Changing the maximumValue also changes the maximumPosition. int maximumValue() const; /// /// This property holds the current slider minimum position. /// If tracking is enabled (the default), this is identical to minimumValue. int minimumPosition() const; void setMinimumPosition(int min); /// /// This property holds the current slider maximum position. /// If tracking is enabled (the default), this is identical to maximumValue. int maximumPosition() const; void setMaximumPosition(int max); /// /// Utility function that set the minimum position and /// maximum position at once. void setPositions(int min, int max); /// /// When symmetricMoves is true, moving a handle will move the other handle /// symmetrically, otherwise the handles are independent. False by default bool symmetricMoves()const; void setSymmetricMoves(bool symmetry); /// /// Controls the text to display for the handle tooltip. It is in addition /// to the widget tooltip. /// "%1" is replaced by the current value of the slider. /// Empty string (by default) means no tooltip. QString handleToolTip()const; void setHandleToolTip(const QString& toolTip); /// Returns true if the minimum value handle is down, false if it is up. /// \sa isMaximumSliderDown() bool isMinimumSliderDown()const; /// Returns true if the maximum value handle is down, false if it is up. /// \sa isMinimumSliderDown() bool isMaximumSliderDown()const; Q_SIGNALS: /// /// This signal is emitted when the slider minimum value has changed, /// with the new slider value as argument. void minimumValueChanged(int min); /// /// This signal is emitted when the slider maximum value has changed, /// with the new slider value as argument. void maximumValueChanged(int max); /// /// Utility signal that is fired when minimum or maximum values have changed. void valuesChanged(int min, int max); /// /// This signal is emitted when sliderDown is true and the slider moves. /// This usually happens when the user is dragging the minimum slider. /// The value is the new slider minimum position. /// This signal is emitted even when tracking is turned off. void minimumPositionChanged(int min); /// /// This signal is emitted when sliderDown is true and the slider moves. /// This usually happens when the user is dragging the maximum slider. /// The value is the new slider maximum position. /// This signal is emitted even when tracking is turned off. void maximumPositionChanged(int max); /// /// Utility signal that is fired when minimum or maximum positions /// have changed. void positionsChanged(int min, int max); public Q_SLOTS: /// /// This property holds the slider's current minimum value. /// The slider silently forces min to be within the legal range: /// minimum() <= min <= maximumValue() <= maximum(). /// Note: Changing the minimumValue also changes the minimumPosition. /// \sa stMaximumValue, setValues, setMinimum, setMaximum, setRange void setMinimumValue(int min); /// /// This property holds the slider's current maximum value. /// The slider silently forces max to be within the legal range: /// minimum() <= minimumValue() <= max <= maximum(). /// Note: Changing the maximumValue also changes the maximumPosition. /// \sa stMinimumValue, setValues, setMinimum, setMaximum, setRange void setMaximumValue(int max); /// /// Utility function that set the minimum value and maximum value at once. /// The slider silently forces min and max to be within the legal range: /// minimum() <= min <= max <= maximum(). /// Note: Changing the minimumValue and maximumValue also changes the /// minimumPosition and maximumPosition. /// \sa setMinimumValue, setMaximumValue, setMinimum, setMaximum, setRange void setValues(int min, int max); protected Q_SLOTS: void onRangeChanged(int minimum, int maximum); protected: ctkRangeSlider( ctkRangeSliderPrivate* impl, Qt::Orientation o, QWidget* par= 0 ); ctkRangeSlider( ctkRangeSliderPrivate* impl, QWidget* par = 0 ); // Description: // Standard Qt UI events virtual void mousePressEvent(QMouseEvent* ev); virtual void mouseMoveEvent(QMouseEvent* ev); virtual void mouseReleaseEvent(QMouseEvent* ev); // Description: // Rendering is done here. virtual void paintEvent(QPaintEvent* ev); virtual void initMinimumSliderStyleOption(QStyleOptionSlider* option) const; virtual void initMaximumSliderStyleOption(QStyleOptionSlider* option) const; // Description: // Reimplemented for the tooltips virtual bool event(QEvent* event); protected: QScopedPointer d_ptr; private: Q_DECLARE_PRIVATE(ctkRangeSlider); Q_DISABLE_COPY(ctkRangeSlider); }; #endif gqrx-2.9/src/qtgui/demod_options.cpp000066400000000000000000000077311320142145500176410ustar00rootroot00000000000000/* -*- 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" /* convert between deemphasis time constant and combo-index */ const double tau_tbl[] = { 0.0, 25.0e-6, 50.0e-6, 75.0e-6, 100.0e-6, 250.0e-6, 530.0e-6, 1.0e-3 }; const int tau_tbl_maxidx = 7; double tau_from_index(int index) { if (index < 0 || index > tau_tbl_maxidx) return 0.0; return tau_tbl[index]; } int tau_to_index(double tau) { int i; for (i = 0; i < tau_tbl_maxidx; i++) { if (tau < (tau_tbl[i] + 0.5 * (tau_tbl[i+1] - tau_tbl[i]))) return i; } return 0; } /* convert betweenFM max dev and combo index */ float maxdev_from_index(int index) { switch(index) { case 0: /* Voice 2.5k */ return 2500.0; case 1: /* Voice 5k */ return 5000.0; case 2: /* APT 17k */ return 17000.0; case 3: /* APT 25k (17k but need some margin for Doppler and freq error) */ return 25000.0; default: /* Voice 5k */ qDebug() << "Invalid max_dev index: " << index; return 5000.0; } } int maxdev_to_index(float max_dev) { if (max_dev < 3000.0) /* Voice 2.5k */ return 0; else if (max_dev < 10000.0) /* Voice 5k */ return 1; else if (max_dev < 20000.0) /* APT 17k */ return 2; else /* APT 25k */ return 3; } 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() const { return ui->demodOptions->currentIndex(); } void CDemodOptions::setCwOffset(int offset) { ui->cwOffsetSpin->setValue(offset); } int CDemodOptions::getCwOffset(void) const { return ui->cwOffsetSpin->value(); } void CDemodOptions::setMaxDev(float max_dev) { ui->maxdevSelector->setCurrentIndex(maxdev_to_index(max_dev)); } float CDemodOptions::getMaxDev(void) const { return maxdev_from_index(ui->maxdevSelector->currentIndex()); } void CDemodOptions::setEmph(double tau) { ui->emphSelector->setCurrentIndex((tau_to_index(tau))); } double CDemodOptions::getEmph(void) const { return tau_from_index(ui->emphSelector->currentIndex()); } void CDemodOptions::on_maxdevSelector_activated(int index) { emit fmMaxdevSelected(maxdev_from_index(index)); } void CDemodOptions::on_emphSelector_activated(int index) { emit fmEmphSelected(tau_from_index(index)); } void CDemodOptions::on_dcrCheckBox_toggled(bool checked) { emit amDcrToggled(checked); } void CDemodOptions::on_cwOffsetSpin_valueChanged(int value) { emit cwOffsetChanged(value); } gqrx-2.9/src/qtgui/demod_options.h000066400000000000000000000045261320142145500173050ustar00rootroot00000000000000/* -*- 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_CW_OPT = 3, PAGE_NUM = 4 }; explicit CDemodOptions(QWidget *parent = 0); ~CDemodOptions(); void closeEvent(QCloseEvent *event); void setCurrentPage(int index); int currentPage() const; void setCwOffset(int offset); int getCwOffset(void) const; void setMaxDev(float max_dev); float getMaxDev(void) const; void setEmph(double tau); double getEmph(void) const; 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); /*! \brief CW offset changed. */ void cwOffsetChanged(int offset); private slots: void on_maxdevSelector_activated(int index); void on_emphSelector_activated(int index); void on_dcrCheckBox_toggled(bool checked); void on_cwOffsetSpin_valueChanged(int value); private: Ui::CDemodOptions *ui; }; #endif // DEMOD_OPTIONS_H gqrx-2.9/src/qtgui/demod_options.ui000066400000000000000000000177031320142145500174740ustar00rootroot00000000000000 CDemodOptions 0 0 228 110 Mode options :/icons/icons/signal.svg:/icons/icons/signal.svg 0 72 0 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 0 Voice (2.5 kHz) Voice (5 kHz) APT (17 kHz) APT (25 kHz) 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 20 30 161 29 CW offset Hz 2000 700 gqrx-2.9/src/qtgui/dockaudio.cpp000066400000000000000000000310321320142145500167270ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2016 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), rx_freq(144000000) { ui->setupUi(this); #ifdef Q_OS_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 #ifdef Q_OS_LINUX // buttons can be smaller than 50x32 ui->audioStreamButton->setMinimumSize(48, 24); ui->audioRecButton->setMinimumSize(48, 24); ui->audioPlayButton->setMinimumSize(48, 24); ui->audioConfButton->setMinimumSize(48, 24); #endif audioOptions = new CAudioOptions(this); connect(audioOptions, SIGNAL(newFftSplit(int)), ui->audioSpectrum, SLOT(setPercent2DScreen(int))); connect(audioOptions, SIGNAL(newPandapterRange(int,int)), this, SLOT(setNewPandapterRange(int,int))); connect(audioOptions, SIGNAL(newWaterfallRange(int,int)), this, SLOT(setNewWaterfallRange(int,int))); 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->setFreqUnits(1000); ui->audioSpectrum->setSampleRate(48000); // Full bandwidth ui->audioSpectrum->setSpanFreq(12000); ui->audioSpectrum->setCenterFreq(0); ui->audioSpectrum->setPercent2DScreen(100); ui->audioSpectrum->setFftCenterFreq(6000); ui->audioSpectrum->setDemodCenterFreq(0); ui->audioSpectrum->setFilterBoxEnabled(false); ui->audioSpectrum->setCenterLineEnabled(false); ui->audioSpectrum->setBookmarksEnabled(false); ui->audioSpectrum->setFftRange(-80., 0.); ui->audioSpectrum->setVdivDelta(40); ui->audioSpectrum->setHdivDelta(40); 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(float *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"; } /*! Public slot to set new RX frequency in Hz. */ void DockAudio::setRxFrequency(qint64 freq) { rx_freq = freq; } /*! \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, 5, 'f', 1)); 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"); last_audio = QString("%1/%2_%3.wav").arg(rec_dir).arg(file_name).arg(rx_freq); // 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) { QFileInfo info(last_audio); if(info.exists()) { // emit signal and start timer emit audioPlayStarted(last_audio); ui->audioRecLabel->setText(info.fileName()); ui->audioPlayButton->setToolTip(tr("Stop audio playback")); ui->audioRecButton->setEnabled(false); // prevent recording while we play } else { ui->audioPlayButton->setChecked(false); ui->audioPlayButton->setEnabled(false); } } 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) { int ival, fft_min, fft_max; if (!settings) return; settings->beginGroup("audio"); settings->setValue("gain", audioGain()); ival = audioOptions->getFftSplit(); if (ival >= 0 && ival < 100) settings->setValue("fft_split", ival); else settings->remove("fft_split"); audioOptions->getPandapterRange(&fft_min, &fft_max); if (fft_min != -80) settings->setValue("pandapter_min_db", fft_min); else settings->remove("pandapter_min_db"); if (fft_max != 0) settings->setValue("pandapter_max_db", fft_max); else settings->remove("pandapter_max_db"); audioOptions->getWaterfallRange(&fft_min, &fft_max); if (fft_min != -80) settings->setValue("waterfall_min_db", fft_min); else settings->remove("waterfall_min_db"); if (fft_max != 0) settings->setValue("waterfall_max_db", fft_max); else settings->remove("waterfall_max_db"); if (rec_dir != QDir::homePath()) settings->setValue("rec_dir", rec_dir); else settings->remove("rec_dir"); if (udp_host.isEmpty()) settings->remove("udp_host"); else settings->setValue("udp_host", udp_host); if (udp_port != 7355) settings->setValue("udp_port", udp_port); else settings->remove("udp_port"); settings->endGroup(); } void DockAudio::readSettings(QSettings *settings) { int ival, fft_min, fft_max; bool conv_ok = false; if (!settings) return; settings->beginGroup("audio"); ival = settings->value("gain", QVariant(-200)).toInt(&conv_ok); if (conv_ok) setAudioGain(ival); ival = settings->value("fft_split", QVariant(100)).toInt(&conv_ok); if (conv_ok) audioOptions->setFftSplit(ival); fft_min = settings->value("pandapter_min_db", QVariant(-80)).toInt(&conv_ok); if (!conv_ok) fft_min = -80; fft_max = settings->value("pandapter_max_db", QVariant(0)).toInt(&conv_ok); if (!conv_ok) fft_max = 0; audioOptions->setPandapterRange(fft_min, fft_max); fft_min = settings->value("waterfall_min_db", QVariant(-80)).toInt(&conv_ok); if (!conv_ok) fft_min = -80; fft_max = settings->value("waterfall_max_db", QVariant(0)).toInt(&conv_ok); if (!conv_ok) fft_max = 0; audioOptions->setWaterfallRange(fft_min, fft_max); // Location of audio recordings rec_dir = settings->value("rec_dir", QDir::homePath()).toString(); audioOptions->setRecDir(rec_dir); // Audio streaming host and port udp_host = settings->value("udp_host", "localhost").toString(); udp_port = settings->value("udp_port", 7355).toInt(&conv_ok); if (!conv_ok) udp_port = 7355; audioOptions->setUdpHost(udp_host); audioOptions->setUdpPort(udp_port); settings->endGroup(); } void DockAudio::setNewPandapterRange(int min, int max) { ui->audioSpectrum->setPandapterRange(min, max); } void DockAudio::setNewWaterfallRange(int min, int max) { ui->audioSpectrum->setWaterfallRange(min, max); } /*! \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-2.9/src/qtgui/dockaudio.h000066400000000000000000000074461320142145500164100ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2016 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(float *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); void setRxFrequency(qint64 freq); 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 setNewPandapterRange(int min, int max); void setNewWaterfallRange(int min, int max); 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. */ qint64 rx_freq; /*! RX frequency used in filenames. */ }; #endif // DOCKAUDIO_H gqrx-2.9/src/qtgui/dockaudio.ui000066400000000000000000000231271320142145500165700ustar00rootroot00000000000000 DockAudio 0 0 220 200 0 0 220 200 :/icons/icons/signal.svg:/icons/icons/signal.svg Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea Audio 0 0 0 0 50 50 16777215 16777215 Audio spectrum QFrame::StyledPanel QFrame::Raised 6 0 0 Gain: Qt::AlignCenter 0 0 0 22 Audio gain Audio gain -800 500 -200 Qt::Horizontal 0 0 -20.0 dB Qt::AlignCenter 0 1 0 50 32 16777215 16777215 Stream raw audio over UDP Stream raw audio over UDP UDP true 0 0 50 32 16777215 16777215 Record audio Record audio Rec 16 16 true false 0 0 50 32 16777215 16777215 Playback previously recorded audio Playback previously recorded audio Play 16 16 true true 0 0 50 32 16777215 16777215 Audio settings Audio settings ... 16 16 0 0 Current audio source (DSP or WAV file) <i>DSP</i> Qt::AlignCenter CPlotter QFrame
    qtgui/plotter.h
    1
    gqrx-2.9/src/qtgui/dockbookmarks.cpp000066400000000000000000000222121320142145500176160ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2013 Christian Lindner DL2VCL, Stefano Leucci. * * 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 "bookmarks.h" #include "bookmarkstaglist.h" #include "dockbookmarks.h" #include "ui_dockbookmarks.h" #include "qtcolorpicker.h" #include "dockrxopt.h" DockBookmarks::DockBookmarks(QWidget *parent) : QDockWidget(parent), ui(new Ui::DockBookmarks) { ui->setupUi(this); bookmarksTableModel = new BookmarksTableModel(); // Frequency List ui->tableViewFrequencyList->setModel(bookmarksTableModel); ui->tableViewFrequencyList->setColumnWidth( BookmarksTableModel::COL_NAME, ui->tableViewFrequencyList->columnWidth(BookmarksTableModel::COL_NAME)*2 ); ui->tableViewFrequencyList->setSelectionBehavior(QAbstractItemView::SelectRows); ui->tableViewFrequencyList->setSelectionMode(QAbstractItemView::SingleSelection); ui->tableViewFrequencyList->installEventFilter(this); // Demod Selection in Frequency List Table. ComboBoxDelegateModulation* delegateModulation = new ComboBoxDelegateModulation(this); ui->tableViewFrequencyList->setItemDelegateForColumn(2, delegateModulation); // Bookmarks Context menu contextmenu = new QMenu(this); // MenuItem Delete { QAction* action = new QAction("Delete Bookmark", this); contextmenu->addAction(action); connect(action, SIGNAL(triggered()), this, SLOT(DeleteSelectedBookmark())); } // MenuItem Add { actionAddBookmark = new QAction("Add Bookmark", this); contextmenu->addAction(actionAddBookmark); } ui->tableViewFrequencyList->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui->tableViewFrequencyList, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(ShowContextMenu(const QPoint&))); // Update GUI Bookmarks::Get().load(); bookmarksTableModel->update(); m_currentFrequency=0; m_updating=false; // TagList updateTags(); connect(ui->tableViewFrequencyList, SIGNAL(activated(const QModelIndex &)), this, SLOT(activated(const QModelIndex &))); connect(ui->tableViewFrequencyList, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(doubleClicked(const QModelIndex &))); connect(bookmarksTableModel, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(onDataChanged(const QModelIndex &, const QModelIndex &))); connect(&Bookmarks::Get(), SIGNAL(TagListChanged()), ui->tableWidgetTagList, SLOT(updateTags())); connect(&Bookmarks::Get(), SIGNAL(BookmarksChanged()), bookmarksTableModel, SLOT(update())); } DockBookmarks::~DockBookmarks() { delete bookmarksTableModel; bookmarksTableModel = 0; } void DockBookmarks::activated(const QModelIndex & index ) { BookmarkInfo *info = bookmarksTableModel->getBookmarkAtRow(index.row()); emit newBookmarkActivated(info->frequency, info->modulation, info->bandwidth); } void DockBookmarks::setNewFrequency(qint64 rx_freq) { ui->tableViewFrequencyList->clearSelection(); const int iRowCount = bookmarksTableModel->rowCount(); for(int row=0; rowgetBookmarkAtRow(row)); if( std::abs(rx_freq - info.frequency) <= ((info.bandwidth/2)+1) ) { ui->tableViewFrequencyList->selectRow(row); ui->tableViewFrequencyList->scrollTo( ui->tableViewFrequencyList->currentIndex(), QAbstractItemView::EnsureVisible ); break; } } m_currentFrequency=rx_freq; } void DockBookmarks::updateTags() { m_updating=true; ui->tableWidgetTagList->updateTags(); m_updating=false; } void DockBookmarks::updateBookmarks() { bookmarksTableModel->update(); } //Data has been edited void DockBookmarks::onDataChanged(const QModelIndex&, const QModelIndex &) { updateTags(); Bookmarks::Get().save(); } void DockBookmarks::on_tableWidgetTagList_itemChanged(QTableWidgetItem *item) { // we only want to react on changed by the user, not changes by the program itself. if(ui->tableWidgetTagList->m_bUpdating) return; int col = item->column(); if (col != 1) return; QString strText = item->text(); Bookmarks::Get().setTagChecked(strText, (item->checkState() == Qt::Checked)); } bool DockBookmarks::eventFilter(QObject* object, QEvent* event) { if (event->type()==QEvent::KeyPress) { QKeyEvent* pKeyEvent=static_cast(event); if (pKeyEvent->key() == Qt::Key_Delete && ui->tableViewFrequencyList->hasFocus()) { return DeleteSelectedBookmark(); } } return QWidget::eventFilter(object, event); } bool DockBookmarks::DeleteSelectedBookmark() { QModelIndexList selected = ui->tableViewFrequencyList->selectionModel()->selectedRows(); if(selected.empty()) { return true; } if(QMessageBox::question(this, "Delete bookmark", "Really delete?", QMessageBox::Yes|QMessageBox::No)==QMessageBox::Yes) { int iIndex = bookmarksTableModel->GetBookmarksIndexForRow(selected.first().row()); Bookmarks::Get().remove(iIndex); bookmarksTableModel->update(); } return true; } void DockBookmarks::ShowContextMenu(const QPoint& pos) { contextmenu->popup(ui->tableViewFrequencyList->viewport()->mapToGlobal(pos)); } void DockBookmarks::doubleClicked(const QModelIndex & index) { if(index.column() == BookmarksTableModel::COL_TAGS) { changeBookmarkTags(index.row(), index.column()); } } ComboBoxDelegateModulation::ComboBoxDelegateModulation(QObject *parent) :QItemDelegate(parent) { } QWidget *ComboBoxDelegateModulation::createEditor(QWidget *parent, const QStyleOptionViewItem &/* option */, const QModelIndex &index) const { QComboBox* comboBox = new QComboBox(parent); for(int i = 0; i < DockRxOpt::ModulationStrings.size(); ++i) { comboBox->addItem(DockRxOpt::ModulationStrings[i]); } setEditorData(comboBox, index); return comboBox; } void ComboBoxDelegateModulation::setEditorData(QWidget *editor, const QModelIndex &index) const { QComboBox *comboBox = static_cast(editor); QString value = index.model()->data(index, Qt::EditRole).toString(); int iModulation = DockRxOpt::GetEnumForModulationString(value); comboBox->setCurrentIndex(iModulation); } void ComboBoxDelegateModulation::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { QComboBox *comboBox = static_cast(editor); model->setData(index, comboBox->currentText(), Qt::EditRole); } void DockBookmarks::changeBookmarkTags(int row, int /*column*/) { bool ok=false; QString tags; // list of tags separated by comma int iIdx = bookmarksTableModel->GetBookmarksIndexForRow(row); BookmarkInfo& bmi = Bookmarks::Get().getBookmark(iIdx); // Create and show the Dialog for a new Bookmark. // Write the result into variabe 'tags'. { QDialog dialog(this); dialog.setWindowTitle("Change Bookmark Tags"); BookmarksTagList* taglist = new BookmarksTagList(&dialog, false); taglist->updateTags(); taglist->setSelectedTags(bmi.tags); taglist->DeleteTag(TagInfo::strUntagged); QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(buttonBox, SIGNAL(accepted()), &dialog, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), &dialog, SLOT(reject())); QVBoxLayout *mainLayout = new QVBoxLayout(&dialog); mainLayout->addWidget(taglist); mainLayout->addWidget(buttonBox); ok = dialog.exec(); if (ok) { tags = taglist->getSelectedTagsAsString(); // list of selected tags is now in string 'tags'. // Change Tags of Bookmark QStringList listTags = tags.split(",",QString::SkipEmptyParts); bmi.tags.clear(); if(listTags.size()==0) { bmi.tags.append(&Bookmarks::Get().findOrAddTag("")); // "Untagged" } for(int i=0; i #include #include "qtgui/bookmarkstablemodel.h" #include namespace Ui { class DockBookmarks; } class ComboBoxDelegateModulation : public QItemDelegate { Q_OBJECT public: ComboBoxDelegateModulation(QObject *parent = 0); QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const; void setEditorData(QWidget *editor, const QModelIndex &index) const; void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const; }; #if 0 class DelegateTags : public QItemDelegate { Q_OBJECT public: DelegateTags(QObject *parent = 0); QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const; void setEditorData(QWidget *editor, const QModelIndex &index) const; void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const; }; #endif class DockBookmarks : public QDockWidget { Q_OBJECT private: Ui::DockBookmarks *ui; QMenu* contextmenu; qint64 m_currentFrequency; bool m_updating; bool eventFilter(QObject* object, QEvent* event); public: explicit DockBookmarks(QWidget *parent = 0); ~DockBookmarks(); // ui->tableViewFrequencyList // ui->tableWidgetTagList BookmarksTableModel *bookmarksTableModel; QAction* actionAddBookmark; void updateTags(); void updateBookmarks(); void changeBookmarkTags(int row, int /*column*/); signals: void newBookmarkActivated(qint64, QString, int); public slots: void setNewFrequency(qint64 rx_freq); private slots: void activated(const QModelIndex & index ); void onDataChanged (const QModelIndex & topLeft, const QModelIndex & bottomRight); //void on_addButton_clicked(); //void on_delButton_clicked(); void on_tableWidgetTagList_itemChanged(QTableWidgetItem* item); void ShowContextMenu(const QPoint&pos); bool DeleteSelectedBookmark(); void doubleClicked(const QModelIndex & index); }; #endif // DOCKFREQTABLE_H gqrx-2.9/src/qtgui/dockbookmarks.ui000066400000000000000000000071241320142145500174560ustar00rootroot00000000000000 DockBookmarks 0 0 585 248 200 0 322 117 Qt::AllDockWidgetAreas Bookmarks Qt::Horizontal 1 0 100 10 QAbstractItemView::AnyKeyPressed|QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked true false 0 0 100 0 16777215 16777215 0 0 QAbstractItemView::EditKeyPressed true QAbstractItemView::SingleSelection QAbstractItemView::SelectRows 2 false true false BookmarksTagList QTableWidget
    qtgui/bookmarkstaglist.h
    gqrx-2.9/src/qtgui/dockfft.cpp000066400000000000000000000345701320142145500164170ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2016 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 "dockfft.h" #include "ui_dockfft.h" #define DEFAULT_FFT_MAX_DB -0 #define DEFAULT_FFT_MIN_DB -135 #define DEFAULT_FFT_RATE 25 #define DEFAULT_FFT_SIZE 8192 #define DEFAULT_FFT_SPLIT 35 #define DEFAULT_FFT_AVG 75 DockFft::DockFft(QWidget *parent) : QDockWidget(parent), ui(new Ui::DockFft) { ui->setupUi(this); #ifdef Q_OS_MAC // Is this really only needed on Mac to make the color picker button appear square like the other buttons? ui->fillButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); #if QT_VERSION < 0x050000 // Workaround for Mac, see http://stackoverflow.com/questions/3978889/why-is-qhboxlayout-causing-widgets-to-overlap // Fixed in Qt 5? ui->resetButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); ui->centerButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); ui->demodButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); #endif #endif #ifdef Q_OS_LINUX // buttons can be smaller than 50x32 ui->peakDetectionButton->setMinimumSize(48, 24); ui->peakHoldButton->setMinimumSize(48, 24); ui->lockButton->setMinimumSize(48, 24); ui->resetButton->setMinimumSize(48, 24); ui->centerButton->setMinimumSize(48, 24); ui->demodButton->setMinimumSize(48, 24); ui->fillButton->setMinimumSize(48, 24); ui->colorPicker->setMinimumSize(48, 24); #endif m_sample_rate = 0.f; m_pand_last_modified = false; // 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); updateInfoLabels(); 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); updateInfoLabels(); return fftSize(); } void DockFft::setSampleRate(float sample_rate) { if (sample_rate < 0.1f) return; m_sample_rate = sample_rate; updateInfoLabels(); } /** * @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; } /** 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"); // dB ranges intval = ui->pandRangeSlider->minimumValue(); if (intval == DEFAULT_FFT_MIN_DB) settings->remove("pandapter_min_db"); else settings->setValue("pandapter_min_db", intval); intval = ui->pandRangeSlider->maximumValue(); if (intval == DEFAULT_FFT_MAX_DB) settings->remove("pandapter_max_db"); else settings->setValue("pandapter_max_db", intval); intval = ui->wfRangeSlider->minimumValue(); if (intval == DEFAULT_FFT_MIN_DB) settings->remove("waterfall_min_db"); else settings->setValue("waterfall_min_db", intval); intval = ui->wfRangeSlider->maximumValue(); if (intval == DEFAULT_FFT_MAX_DB) settings->remove("waterfall_max_db"); else settings->setValue("waterfall_max_db", intval); // pandapter and waterfall ranges locked together if (ui->lockButton->isChecked()) settings->setValue("db_ranges_locked", true); else settings->remove("db_ranges_locked"); settings->endGroup(); } /** Read FFT settings. */ void DockFft::readSettings(QSettings *settings) { int intval; int fft_min, fft_max; 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); // delete old dB settings from config if (settings->contains("reference_level")) settings->remove("reference_level"); if (settings->contains("fft_range")) settings->remove("fft_range"); fft_max = settings->value("pandapter_max_db", DEFAULT_FFT_MAX_DB).toInt(); fft_min = settings->value("pandapter_min_db", DEFAULT_FFT_MIN_DB).toInt(); setPandapterRange(fft_min, fft_max); emit pandapterRangeChanged((float) fft_min, (float) fft_max); fft_max = settings->value("waterfall_max_db", DEFAULT_FFT_MAX_DB).toInt(); fft_min = settings->value("waterfall_min_db", DEFAULT_FFT_MIN_DB).toInt(); setWaterfallRange(fft_min, fft_max); emit waterfallRangeChanged((float) fft_min, (float) fft_max); bool_val = settings->value("db_ranges_locked", false).toBool(); ui->lockButton->setChecked(bool_val); settings->endGroup(); } void DockFft::setPandapterRange(float min, float max) { ui->pandRangeSlider->blockSignals(true); ui->pandRangeSlider->setValues((int) min, (int) max); if (ui->lockButton->isChecked()) ui->wfRangeSlider->setValues((int) min, (int) max); m_pand_last_modified = true; ui->pandRangeSlider->blockSignals(false); } void DockFft::setWaterfallRange(float min, float max) { ui->wfRangeSlider->blockSignals(true); ui->wfRangeSlider->setValues((int) min, (int) max); if (ui->lockButton->isChecked()) ui->pandRangeSlider->setValues((int) min, (int) max); m_pand_last_modified = false; ui->wfRangeSlider->blockSignals(false); } void DockFft::setZoomLevel(float level) { ui->fftZoomSlider->blockSignals(true); ui->fftZoomSlider->setValue((int) level); ui->zoomLevelLabel->setText(QString("%1x").arg((int) level)); ui->fftZoomSlider->blockSignals(false); } /** FFT size changed. */ void DockFft::on_fftSizeComboBox_currentIndexChanged(const QString &text) { int value = text.toInt(); emit fftSizeChanged(value); updateInfoLabels(); } /** FFT rate changed. */ void DockFft::on_fftRateComboBox_currentIndexChanged(const QString & text) { int fps = fftRate(); Q_UNUSED(text); emit fftRateChanged(fps); updateInfoLabels(); } static const quint64 wf_span_table[] = { 0, // Auto 1*60*1000, // 1 minute 2*60*1000, // 2 minutes 5*60*1000, // 5 minutes 10*60*1000, // 10 minutes 15*60*1000, // 15 minutes 20*60*1000, // 20 minutes 30*60*1000, // 30 minutes 1*60*60*1000, // 1 hour 2*60*60*1000, // 2 hours 5*60*60*1000, // 5 hours 10*60*60*1000, // 10 hours 16*60*60*1000, // 16 hours 24*60*60*1000, // 24 hours 48*60*60*1000 // 48 hours }; /** Waterfall time span changed. */ void DockFft::on_wfSpanComboBox_currentIndexChanged(int index) { if (index < 0 || index > 14) return; emit wfSpanChanged(wf_span_table[index]); } /** Set waterfall time resolution. */ void DockFft::setWfResolution(quint64 msec_per_line) { float res = 1.0e-3 * (float)msec_per_line; ui->wfResLabel->setText(QString("Res: %1 s").arg(res, 0, 'f', 2)); } /** * @brief Split between waterfall and pandapter changed. * @param value The percentage of the waterfall. */ void DockFft::on_fftSplitSlider_valueChanged(int value) { emit fftSplitChanged(value); } /** FFT filter gain changed. */ void DockFft::on_fftAvgSlider_valueChanged(int value) { float avg = 1.0 - 1.0e-2 * ((float)value); emit fftAvgChanged(avg); } /** FFT zoom level changed */ void DockFft::on_fftZoomSlider_valueChanged(int level) { ui->zoomLevelLabel->setText(QString("%1x").arg(level)); emit fftZoomChanged((float)level); } void DockFft::on_pandRangeSlider_valuesChanged(int min, int max) { if (ui->lockButton->isChecked()) ui->wfRangeSlider->setValues(min, max); m_pand_last_modified = true; emit pandapterRangeChanged((float) min, (float) max); } void DockFft::on_wfRangeSlider_valuesChanged(int min, int max) { if (ui->lockButton->isChecked()) ui->pandRangeSlider->setValues(min, max); m_pand_last_modified = false; emit waterfallRangeChanged((float) min, (float) max); } void DockFft::on_resetButton_clicked(void) { ui->zoomLevelLabel->setText(QString("1x")); ui->fftZoomSlider->setValue(0); 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); } /** lock button toggled */ void DockFft::on_lockButton_toggled(bool checked) { if (checked) { if (m_pand_last_modified) { int min = ui->pandRangeSlider->minimumValue(); int max = ui->pandRangeSlider->maximumValue(); ui->wfRangeSlider->setPositions(min, max); } else { int min = ui->wfRangeSlider->minimumValue(); int max = ui->wfRangeSlider->maximumValue(); ui->pandRangeSlider->setPositions(min, max); } } } /** Update RBW and FFT overlab labels */ void DockFft::updateInfoLabels(void) { float rate; float size; float rbw; float ovr; float sps; if (m_sample_rate == 0.f) return; rate = fftRate(); size = fftSize(); rbw = m_sample_rate / size; if (rbw < 1.e3f) ui->fftRbwLabel->setText(QString("RBW: %1 Hz").arg(rbw, 0, 'f', 1)); else if (rbw < 1.e6f) ui->fftRbwLabel->setText(QString("RBW: %1 kHz").arg(1.e-3 * rbw, 0, 'f', 1)); else ui->fftRbwLabel->setText(QString("RBW: %1 MHz").arg(1.e-6 * rbw, 0, 'f', 1)); sps = size * rate; if (sps <= m_sample_rate) ovr = 0; else ovr = 100 * (sps / m_sample_rate - 1.f); ui->fftOvrLabel->setText(QString("Overlap: %1%").arg(ovr, 0, 'f', 0)); } gqrx-2.9/src/qtgui/dockfft.h000066400000000000000000000076301320142145500160610ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2016 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 setSampleRate(float sample_rate); void saveSettings(QSettings *settings); void readSettings(QSettings *settings); signals: void fftSizeChanged(int size); /*! FFT size changed. */ void fftRateChanged(int fps); /*! FFT rate changed. */ void wfSpanChanged(quint64 span_ms); /*! Waterfall span changed. */ void fftSplitChanged(int pct); /*! Split between pandapter and waterfall changed. */ void fftZoomChanged(float level); /*! Zoom level slider changed. */ void fftAvgChanged(float gain); /*! FFT video filter gain has changed. */ void pandapterRangeChanged(float min, float max); void waterfallRangeChanged(float min, float max); 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 */ public slots: void setPandapterRange(float min, float max); void setWaterfallRange(float min, float max); void setWfResolution(quint64 msec_per_line); void setZoomLevel(float level); private slots: void on_fftSizeComboBox_currentIndexChanged(const QString & text); void on_fftRateComboBox_currentIndexChanged(const QString & text); void on_wfSpanComboBox_currentIndexChanged(int index); void on_fftSplitSlider_valueChanged(int value); void on_fftAvgSlider_valueChanged(int value); void on_fftZoomSlider_valueChanged(int level); void on_pandRangeSlider_valuesChanged(int min, int max); void on_wfRangeSlider_valuesChanged(int min, int max); 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); void on_lockButton_toggled(bool checked); private: void updateInfoLabels(void); private: Ui::DockFft * ui; // float m_maximumFftDb; // float m_minimumFftDb; float m_sample_rate; bool m_pand_last_modified; /* Flag to indicate which slider was changed last */ }; #endif // DOCKFFT_H gqrx-2.9/src/qtgui/dockfft.ui000066400000000000000000001061551320142145500162510ustar00rootroot00000000000000 DockFft 0 0 320 386 0 0 200 200 524287 524287 :/icons/icons/signal.svg:/icons/icons/signal.svg FFT performance and display settings Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea FFT Settings 5 5 5 5 0 0 QFrame::NoFrame QFrame::Sunken Qt::ScrollBarAsNeeded QAbstractScrollArea::AdjustToContentsOnFirstShow true 0 0 310 357 0 0 0 0 0 QLayout::SetDefaultConstraint 0 0 0 0 6 0 0 0 0 Set pandapter dB range Set pandapter dB range -160 0 Qt::Horizontal 0 0 Current zoom level on the frequency axis Current zoom level on the frequency axis 1x Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 2 QLayout::SetDefaultConstraint 1 0 0 0 50 32 16777215 16777215 Enable peak detection in FFT Detect true 0 0 50 32 16777215 16777215 Toggle peak hold in FFT Hold true FFT size Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Waterfall Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 0 0 0 22 Set zoom level on the frequency axis Set zoom level on the frequency axis 1 50 5 Qt::Horizontal false false QSlider::NoTicks Waterfall time resolution. Res: - s Set zoom level on the frequency axis Set zoom level on the frequency axis Freq zoom Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 0 22 FFT averaging gain 100 50 Qt::Horizontal false false Color for the FFT plot Color Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 50 32 Click to select color for the FFT plot Click to select color for the FFT plot true 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 7 15 QComboBox::InsertAlphabetically 1048576 524288 262144 131072 65536 32768 16384 8192 4096 3840 2048 1024 768 512 Set pandapter dB range Set pandapter dB range Pand. dB Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Pandapter Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 FFT averaging gain Averaging Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter FFT buffer overlap between two consecutive FFT calculations. Overlap: 0% 0 Set waterfall dB range Set waterfall dB range Wf. dB Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 The vertical time span on the waterfall. Auto 1 min 2 min 5 min 10 min 15 min 20 min 30 min 1 hour 2 hours 5 hours 10 hours 16 hours 24 hours 48 hours 0 0 0 0 FFT refresh rate 3 60 fps 50 fps 30 fps 25 fps 20 fps 17 fps 15 fps 10 fps 5 fps 1 fps 0 fps The vertical time span on the waterfall. Time span Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 2 0 0 50 32 16777215 16777215 Reset zoom level to 1x Reset 0 0 50 32 16777215 16777215 Center FFT around original center frequency Center 0 0 50 32 16777215 16777215 Center FFT around demodulator frequency Demod 0 0 50 32 16777215 16777215 Fill the area below the FFT plot with a gradient Fill the area below the FFT plot with a gradient Fill true Peak Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Rate Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Set waterfall dB range Set waterfall dB range -160 0 Qt::Horizontal Resolution bandwidth RBW: 0 kHz 0 0 0 22 Spatial distribution between pandapter and waterfall 0 100 50 Qt::Horizontal Qt::Vertical 253 68 true 0 0 50 32 Lock panadapter and waterfall sliders together Lock panadapter and waterfall sliders together Lock true ctkRangeSlider QSlider
    qtgui/ctk/ctkRangeSlider.h
    QtColorPicker QPushButton
    qtgui/qtcolorpicker.h
    gqrx-2.9/src/qtgui/dockinputctl.cpp000066400000000000000000000372541320142145500175040ustar00rootroot00000000000000/* -*- 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) { qint64 lnb_lo; bool conv_ok; bool bool_val; qint64 ppm_corr = settings->value("input/corr_freq", 0).toLongLong(&conv_ok); setFreqCorr(((double)ppm_corr)/1.0e6); 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()); lnb_lo = settings->value("input/lnb_lo", 0).toLongLong(&conv_ok); if (conv_ok) { setLnbLo(((double)lnb_lo)/1.0e6); emit lnbLoChanged(ui->lnbSpinBox->value()); } 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); emit autoGainChanged(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 if (settings->contains("input/gains")) { QMap allgains; QString gain_name; double gain_value; 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); } } // misc GUI settings bool_val = settings->value("gui/fctl_reset_digits", true).toBool(); emit freqCtrlResetChanged(bool_val); ui->freqCtrlResetButton->setChecked(bool_val); } 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); qint64 ppm_corr = (qint64)(ui->freqCorrSpinBox->value()*1.e6); if (ppm_corr) settings->setValue("input/corr_freq", ppm_corr); 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"); // Remember state of freqReset button. Default is checked. if (!ui->freqCtrlResetButton->isChecked()) settings->setValue("gui/fctl_reset_digits", false); else settings->remove("gui/fctl_reset_digits"); } void DockInputCtl::readLnbLoFromSettings(QSettings * settings) { qint64 lnb_lo; bool conv_ok; lnb_lo = settings->value("input/lnb_lo", 0).toLongLong(&conv_ok); if (conv_ok) { setLnbLo(((double)lnb_lo) / 1.0e6); emit lnbLoChanged(ui->lnbSpinBox->value()); } } 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; } /** * 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(); } /** * Set new frequency correction. * @param corr The new frequency correction in PPM. */ void DockInputCtl::setFreqCorr(double corr) { ui->freqCorrSpinBox->setValue(corr); } /** Get current frequency correction. */ double DockInputCtl::freqCorr() { return ui->freqCorrSpinBox->value(); } /** Enasble/disable I/Q swapping. */ void DockInputCtl::setIqSwap(bool reversed) { ui->iqSwapButton->setChecked(reversed); } /** Get current I/Q swapping. */ bool DockInputCtl::iqSwap(void) { return ui->iqSwapButton->isChecked(); } /** Enable automatic DC removal. */ void DockInputCtl::setDcCancel(bool enabled) { ui->dcCancelButton->setChecked(enabled); } /** Get current DC remove status. */ bool DockInputCtl::dcCancel(void) { return ui->dcCancelButton->isChecked(); } /** Enable automatic IQ balance. */ void DockInputCtl::setIqBalance(bool enabled) { ui->iqBalanceButton->setChecked(enabled); } /** Get current IQ balance status. */ bool DockInputCtl::iqBalance(void) { return ui->iqBalanceButton->isChecked(); } /** Enasble/disable ignoring hardware limits. */ void DockInputCtl::setIgnoreLimits(bool reversed) { ui->ignoreButton->setChecked(reversed); } /** Get current status of whether limits should be ignored or not. */ bool DockInputCtl::ignoreLimits(void) { return ui->ignoreButton->isChecked(); } /** 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())); } } /** Select antenna. */ void DockInputCtl::setAntenna(const QString &antenna) { int index = ui->antSelector->findText(antenna, Qt::MatchExactly); if (index != -1) ui->antSelector->setCurrentIndex(index); } /** Enable/disable resetting lower digits on freqCtrl widgets */ void DockInputCtl::setFreqCtrlReset(bool enabled) { ui->freqCtrlResetButton->setChecked(enabled); } /** * 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() << "********************"; } /** * Load all gains from the settings. * * Can be used for restoring the manual gains after auto-gain has been * disabled. */ void DockInputCtl::restoreManualGains(QSettings *settings) { // 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); } } } /** LNB LO value has changed. */ void DockInputCtl::on_lnbSpinBox_valueChanged(double value) { emit lnbLoChanged(value); } /** 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); } /** * Frequency correction changed. * @param value The new frequency correction in ppm. */ void DockInputCtl::on_freqCorrSpinBox_valueChanged(double value) { emit freqCorrChanged(value); } /** * 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); } /** * DC removal checkbox changed. * @param checked True if DC removal is enabled, false otherwise */ void DockInputCtl::on_dcCancelButton_toggled(bool checked) { emit dcCancelChanged(checked); } /** * 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); } /** Antenna selection has changed. */ void DockInputCtl::on_antSelector_currentIndexChanged(const QString &antenna) { emit antennaSelected(antenna); } /** Reset box has changed */ void DockInputCtl::on_freqCtrlResetButton_toggled(bool checked) { emit freqCtrlResetChanged(checked); } /** 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; } } /** * 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); } /** * 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)); } /** * Get all gains. * @param gains Pointer to a map where the gains and their 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-2.9/src/qtgui/dockinputctl.h000066400000000000000000000104761320142145500171460ustar00rootroot00000000000000/* -*- 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); double lnbLo(); void readLnbLoFromSettings(QSettings * settings); void setGain(QString &name, double value); double gain(QString &name); void setAgc(bool enabled); bool agc(); void setFreqCorr(double corr); double 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); void restoreManualGains(QSettings *settings); void setFreqCtrlReset(bool enabled); signals: void gainChanged(QString name, double value); void autoGainChanged(bool enabled); void freqCorrChanged(double 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); void freqCtrlResetChanged(bool enabled); public slots: void setLnbLo(double freq_mhz); private slots: void on_lnbSpinBox_valueChanged(double value); void on_agcButton_toggled(bool checked); void on_freqCorrSpinBox_valueChanged(double 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 on_freqCtrlResetButton_toggled(bool checked); 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-2.9/src/qtgui/dockinputctl.ui000066400000000000000000000176001320142145500173300ustar00rootroot00000000000000 DockInputCtl 0 0 240 240 240 240 :/icons/icons/flash.svg:/icons/icons/flash.svg false Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea Input controls 6 5 5 5 5 LNB LO -1 0 0 Local oscillator of up or down converter Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter MHz 6 -500000.000000000000000 500000.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 remove Enable automatic I/Q balance (requires gr-iqbal component) IQ balance Freq. correction Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 ppm 1 -500.000000000000000 500.000000000000000 0.000000000000000 Select active antenna connector Antenna 0 0 Select active antenna connector Qt::Horizontal <html><head/><body><p>Reset subordinate digits in main frequency control widget</p></body></html> Reset frequency controller digits Qt::Vertical 5 0 gqrx-2.9/src/qtgui/dockrds.cpp000066400000000000000000000070031320142145500164170ustar00rootroot00000000000000/* -*- 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 "dockrds.h" #include "ui_dockrds.h" DockRDS::DockRDS(QWidget *parent) : QDockWidget(parent), ui(new Ui::DockRDS) { ui->setupUi(this); #if QT_VERSION >= 0x050200 ui->scrollArea->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContentsOnFirstShow); #endif } DockRDS::~DockRDS() { delete ui; } void DockRDS::updateRDS(QString text, int type) { std::string str, out; /* type 0 = PI * type 1 = PS * type 2 = PTY * type 3 = flagstring: TP, TA, MuSp, MoSt, AH, CMP, stPTY * type 4 = RadioText * type 5 = ClockTime * type 6 = Alternative Frequencies */ switch (type) { case 0: ui->program_information->setText(text); break; case 1: ui->station_name->setText(text); break; case 2: ui->program_type->setText(text); break; case 3: str = text.toStdString(); out = ""; if (str.at(0) == '1') out.append("TP "); if (str.at(1) == '1') out.append("TA "); if (str.at(2) == '0') out.append("Speech "); if (str.at(2) == '1') out.append("Music "); if (str.at(3) == '0') out.append("Stereo "); if (str.at(3) == '1') out.append("Mono "); if (str.at(4) == '1') out.append("AH "); if (str.at(5) == '1') out.append("CMP "); if (str.at(6) == '1') out.append("stPTY "); ui->flags->setText(QString::fromStdString(out)); break; case 4: ui->radiotext->setText(text); break; case 5: ui->clocktime->setText(text); break; case 6: ui->alt_freq->setText(text); break; default: // nothing to do break; } } void DockRDS::ClearTextFields() { ui->program_information->setText(""); ui->station_name->setText(""); ui->program_type->setText(""); ui->flags->setText(""); ui->radiotext->setText(""); ui->clocktime->setText(""); ui->alt_freq->setText(""); } void DockRDS::showEnabled() { ClearTextFields(); if (!ui->rdsCheckbox->isChecked()) { ui->rdsCheckbox->blockSignals(true); ui->rdsCheckbox->setChecked(true); ui->rdsCheckbox->blockSignals(false); } } void DockRDS::showDisabled() { ClearTextFields(); } void DockRDS::setDisabled() { ui->rdsCheckbox->setDisabled(true); ui->rdsCheckbox->blockSignals(true); ui->rdsCheckbox->setChecked(false); ui->rdsCheckbox->blockSignals(false); } void DockRDS::setEnabled() { ui->rdsCheckbox->setDisabled(false); } /** Enable/disable RDS decoder */ void DockRDS::on_rdsCheckbox_toggled(bool checked) { emit rdsDecoderToggled(checked); } gqrx-2.9/src/qtgui/dockrds.h000066400000000000000000000011671320142145500160710ustar00rootroot00000000000000#ifndef DOCKRDS_H #define DOCKRDS_H #include #include namespace Ui { class DockRDS; } class DockRDS : public QDockWidget { Q_OBJECT public: explicit DockRDS(QWidget *parent = 0); ~DockRDS(); public slots: void updateRDS(QString text, int type); void showEnabled(); void showDisabled(); void setEnabled(); void setDisabled(); private: void ClearTextFields(); signals: void rdsDecoderToggled(bool); private slots: void on_rdsCheckbox_toggled(bool checked); private: Ui::DockRDS *ui; /*! The Qt designer UI file. */ }; #endif // DOCKRDS_H gqrx-2.9/src/qtgui/dockrds.ui000066400000000000000000000234361320142145500162620ustar00rootroot00000000000000 DockRDS true 0 0 215 220 89 104 Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea RDS 5 5 0 5 0 QFrame::NoFrame QFrame::Sunken true 0 0 205 201 QFormLayout::ExpandingFieldsGrow Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 5 5 5 5 Station Name: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 0 0 Program Type: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 0 0 Program ID: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 0 0 0 0 Radio Text: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 0 0 Clock Time: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 16777215 16777215 Alt. Frequencies: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 16777215 16777215 Flags: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 16777215 16777215 0 0 true Qt::StrongFocus Enable or disable decoding of RDS data Enable RDS gqrx-2.9/src/qtgui/dockrxopt.cpp000066400000000000000000000522151320142145500170100ustar00rootroot00000000000000/* -*- 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" QStringList DockRxOpt::ModulationStrings; // Filter preset table per mode, preset and lo/hi static const int filter_preset_table[DockRxOpt::MODE_LAST][3][2] = { // WIDE NORMAL NARROW {{ 0, 0}, { 0, 0}, { 0, 0}}, // MODE_OFF {{ -15000, 15000}, { -5000, 5000}, { -1000, 1000}}, // MODE_RAW {{ -10000, 10000}, { -5000, 5000}, { -2500, 2500}}, // MODE_AM {{ -10000, 10000}, { -5000, 5000}, { -2500, 2500}}, // MODE_NFM {{-100000, 100000}, {-80000, 80000}, {-60000, 60000}}, // MODE_WFM_MONO {{-100000, 100000}, {-80000, 80000}, {-60000, 60000}}, // MODE_WFM_STEREO {{ -4000, -100}, { -2800, -100}, { -2400, -300}}, // MODE_LSB {{ 100, 4000}, { 100, 2800}, { 300, 2400}}, // MODE_USB {{ -1000, 1000}, { -250, 250}, { -100, 100}}, // MODE_CWL {{ -1000, 1000}, { -250, 250}, { -100, 100}}, // MODE_CWU {{-100000, 100000}, {-80000, 80000}, {-60000, 60000}} // MODE_WFM_STEREO_OIRT }; DockRxOpt::DockRxOpt(qint64 filterOffsetRange, QWidget *parent) : QDockWidget(parent), ui(new Ui::DockRxOpt), agc_is_on(true), hw_freq_hz(144500000) { ui->setupUi(this); if (ModulationStrings.size() == 0) { // Keep in sync with rxopt_mode_idx ModulationStrings.append("Demod Off"); ModulationStrings.append("Raw I/Q"); ModulationStrings.append("AM"); ModulationStrings.append("Narrow FM"); ModulationStrings.append("WFM (mono)"); ModulationStrings.append("WFM (stereo)"); ModulationStrings.append("LSB"); ModulationStrings.append("USB"); ModulationStrings.append("CW-L"); ModulationStrings.append("CW-U"); ModulationStrings.append("WFM (oirt)"); } ui->modeSelector->addItems(ModulationStrings); #ifdef Q_OS_MAC // Workaround for Mac, see http://stackoverflow.com/questions/3978889/why-is-qhboxlayout-causing-widgets-to-overlap // Might be fixed in Qt 5? ui->modeButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); ui->agcButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); ui->autoSquelchButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); ui->resetSquelchButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); #endif #ifdef Q_OS_LINUX ui->modeButton->setMinimumSize(32, 24); ui->agcButton->setMinimumSize(32, 24); ui->autoSquelchButton->setMinimumSize(32, 24); ui->resetSquelchButton->setMinimumSize(32, 24); ui->nbOptButton->setMinimumSize(32, 24); ui->nb2Button->setMinimumSize(32, 24); ui->nb1Button->setMinimumSize(32, 24); #endif ui->filterFreq->setup(7, -filterOffsetRange/2, filterOffsetRange/2, 1, FCTL_UNIT_KHZ); ui->filterFreq->setFrequency(0); // use same slot for filteCombo and filterShapeCombo connect(ui->filterShapeCombo, SIGNAL(activated(int)), this, SLOT(on_filterCombo_activated(int))); // 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))); connect(demodOpt, SIGNAL(cwOffsetChanged(int)), this, SLOT(demodOpt_cwOffsetChanged(int))); // 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, FCTL_UNIT_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(); } /** 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, ' ')); } /** * Get filter index from filter LO / HI values. * @param lo The filter low cut frequency. * @param hi The filter high cut frequency. * * Given filter low and high cut frequencies, this function checks whether the * filter settings correspond to one of the presets in filter_preset_table and * returns the corresponding index to ui->filterCombo; */ unsigned int DockRxOpt::filterIdxFromLoHi(int lo, int hi) const { int mode_index = ui->modeSelector->currentIndex(); if (lo == filter_preset_table[mode_index][FILTER_PRESET_WIDE][0] && hi == filter_preset_table[mode_index][FILTER_PRESET_WIDE][1]) return FILTER_PRESET_WIDE; else if (lo == filter_preset_table[mode_index][FILTER_PRESET_NORMAL][0] && hi == filter_preset_table[mode_index][FILTER_PRESET_NORMAL][1]) return FILTER_PRESET_NORMAL; else if (lo == filter_preset_table[mode_index][FILTER_PRESET_NARROW][0] && hi == filter_preset_table[mode_index][FILTER_PRESET_NARROW][1]) return FILTER_PRESET_NARROW; return FILTER_PRESET_USER; } /** * @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) { int filter_index = filterIdxFromLoHi(lo, hi); ui->filterCombo->setCurrentIndex(filter_index); if (filter_index == FILTER_PRESET_USER) { float width_f; width_f = fabs((hi-lo)/1000.f); ui->filterCombo->setItemText(FILTER_PRESET_USER, QString("User (%1 k)") .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() const { return ui->filterCombo->currentIndex(); } /** Select filter shape */ void DockRxOpt::setCurrentFilterShape(int index) { ui->filterShapeCombo->setCurrentIndex(index); } int DockRxOpt::currentFilterShape() const { return ui->filterShapeCombo->currentIndex(); } /** * @brief Select new demodulator. * @param demod Demodulator index corresponding to receiver::demod. */ void DockRxOpt::setCurrentDemod(int demod) { if ((demod >= MODE_OFF) && (demod < MODE_LAST)) { ui->modeSelector->setCurrentIndex(demod); updateDemodOptPage(demod); } } /** * @brief Get current demodulator selection. * @return The current demodulator corresponding to receiver::demod. */ int DockRxOpt::currentDemod() const { return ui->modeSelector->currentIndex(); } QString DockRxOpt::currentDemodAsString() { return GetStringForModulationIndex(currentDemod()); } float DockRxOpt::currentMaxdev() const { return demodOpt->getMaxDev(); } double DockRxOpt::currentEmph() const { return demodOpt->getEmph(); } /** * @brief Set squelch level. * @param level Squelch level in dBFS */ void DockRxOpt::setSquelchLevel(double level) { ui->sqlSpinBox->setValue(level); } /** * @brief Get the current squelch level * @returns The current squelch setting in dBFS */ double DockRxOpt::currentSquelchLevel() const { return ui->sqlSpinBox->value(); } /** Get filter lo/hi for a given mode and preset */ void DockRxOpt::getFilterPreset(int mode, int preset, int * lo, int * hi) const { if (mode < 0 || mode >= MODE_LAST) { qDebug() << __func__ << ": Invalid mode:" << mode; mode = MODE_AM; } else if (preset < 0 || preset > 2) { qDebug() << __func__ << ": Invalid preset:" << preset; preset = FILTER_PRESET_NORMAL; } *lo = filter_preset_table[mode][preset][0]; *hi = filter_preset_table[mode][preset][1]; } int DockRxOpt::getCwOffset() const { return demodOpt->getCwOffset(); } /** Read receiver configuration from settings data. */ void DockRxOpt::readSettings(QSettings *settings) { bool conv_ok; int int_val; double dbl_val; bool bool_val = settings->value("gui/fctl_reset_digits", true).toBool(); ui->filterFreq->setResetLowerDigits(bool_val); int_val = settings->value("receiver/cwoffset", 700).toInt(&conv_ok); if (conv_ok) demodOpt->setCwOffset(int_val); int_val = settings->value("receiver/fm_maxdev", 2500).toInt(&conv_ok); if (conv_ok) demodOpt->setMaxDev(int_val); dbl_val = settings->value("receiver/fm_deemph", 75).toDouble(&conv_ok); if (conv_ok && dbl_val >= 0) demodOpt->setEmph(1.0e-6 * dbl_val); // was stored as usec qint64 offs = settings->value("receiver/offset", 0).toInt(&conv_ok); if (offs) { setFilterOffset(offs); emit filterOffsetChanged(offs); } dbl_val = settings->value("receiver/sql_level", 1.0).toDouble(&conv_ok); if (conv_ok && dbl_val < 1.0) ui->sqlSpinBox->setValue(dbl_val); // AGC settings int_val = settings->value("receiver/agc_threshold", -100).toInt(&conv_ok); if (conv_ok) agcOpt->setThreshold(int_val); int_val = settings->value("receiver/agc_decay", 500).toInt(&conv_ok); if (conv_ok) { agcOpt->setDecay(int_val); if (int_val == 100) ui->agcPresetCombo->setCurrentIndex(0); else if (int_val == 500) ui->agcPresetCombo->setCurrentIndex(1); else if (int_val == 2000) ui->agcPresetCombo->setCurrentIndex(2); else ui->agcPresetCombo->setCurrentIndex(3); } int_val = settings->value("receiver/agc_slope", 0).toInt(&conv_ok); if (conv_ok) agcOpt->setSlope(int_val); int_val = settings->value("receiver/agc_gain", 0).toInt(&conv_ok); if (conv_ok) agcOpt->setGain(int_val); agcOpt->setHang(settings->value("receiver/agc_usehang", false).toBool()); if (settings->value("receiver/agc_off", false).toBool()) ui->agcPresetCombo->setCurrentIndex(4); int_val = MODE_AM; if (settings->contains("receiver/demod")) int_val = settings->value("receiver/demod").toInt(&conv_ok); setCurrentDemod(int_val); emit demodSelected(int_val); } /** Save receiver configuration to settings. */ void DockRxOpt::saveSettings(QSettings *settings) { int int_val; settings->setValue("receiver/demod", ui->modeSelector->currentIndex()); int cwofs = demodOpt->getCwOffset(); if (cwofs == 700) settings->remove("receiver/cwoffset"); else settings->setValue("receiver/cwoffset", cwofs); // currently we do not need the decimal int_val = (int)demodOpt->getMaxDev(); if (int_val == 2500) settings->remove("receiver/fm_maxdev"); else settings->setValue("receiver/fm_maxdev", int_val); // save as usec int_val = (int)(1.0e6 * demodOpt->getEmph()); if (int_val == 75) settings->remove("receiver/fm_deemph"); else settings->setValue("receiver/fm_deemph", int_val); 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"); // AGC settings int_val = agcOpt->threshold(); if (int_val != -100) settings->setValue("receiver/agc_threshold", int_val); else settings->remove("receiver/agc_threshold"); int_val = agcOpt->decay(); if (int_val != 500) settings->setValue("receiver/agc_decay", int_val); else settings->remove("receiver/agc_decay"); int_val = agcOpt->slope(); if (int_val != 0) settings->setValue("receiver/agc_slope", int_val); else settings->remove("receiver/agc_slope"); int_val = agcOpt->gain(); if (int_val != 0) settings->setValue("receiver/agc_gain", int_val); else settings->remove("receiver/agc_gain"); if (agcOpt->hang()) settings->setValue("receiver/agc_usehang", true); else settings->remove("receiver/agc_usehang"); // AGC Off if (ui->agcPresetCombo->currentIndex() == 4) settings->setValue("receiver/agc_off", true); else settings->remove("receiver/agc_off"); } /** RX frequency changed through spin box */ void DockRxOpt::on_freqSpinBox_valueChanged(double freq) { emit rxFreqChanged(1.e3 * freq); } void DockRxOpt::setRxFreq(qint64 freq_hz) { ui->freqSpinBox->blockSignals(true); ui->freqSpinBox->setValue(1.e-3 * (double)freq_hz); ui->freqSpinBox->blockSignals(false); } void DockRxOpt::setRxFreqRange(qint64 min_hz, qint64 max_hz) { ui->freqSpinBox->setRange(1.e-3 * (double)min_hz, 1.e-3 * (double)max_hz); } /** * @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) { updateHwFreq(); emit filterOffsetChanged(freq); } /** * 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(); qDebug() << " shape:" << ui->filterShapeCombo->currentIndex(); emit demodSelected(ui->modeSelector->currentIndex()); } /** * @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) { updateDemodOptPage(index); emit demodSelected(index); } void DockRxOpt::updateDemodOptPage(int demod) { // update demodulator option widget if (demod == MODE_NFM) demodOpt->setCurrentPage(CDemodOptions::PAGE_FM_OPT); else if (demod == MODE_AM) demodOpt->setCurrentPage(CDemodOptions::PAGE_AM_OPT); else if (demod == MODE_CWL || demod == MODE_CWU) demodOpt->setCurrentPage(CDemodOptions::PAGE_CW_OPT); else demodOpt->setCurrentPage(CDemodOptions::PAGE_NO_OPT); } /** Show demodulator options. */ void DockRxOpt::on_modeButton_clicked() { demodOpt->show(); } /** 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() { double newval = sqlAutoClicked(); // FIXME: We rely on signal only being connected to one slot ui->sqlSpinBox->setValue(newval); } void DockRxOpt::on_resetSquelchButton_clicked() { ui->sqlSpinBox->setValue(-150.0); } /** AGC preset has changed. */ void DockRxOpt::on_agcPresetCombo_currentIndexChanged(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) { emit agcHangToggled(checked); } /** * @brief AGC threshold ("knee") changed. * @param value The new AGC threshold in dB. */ void DockRxOpt::agcOpt_thresholdChanged(int value) { emit agcThresholdChanged(value); } /** * @brief AGC slope factor changed. * @param value The new slope factor in dB. */ void DockRxOpt::agcOpt_slopeChanged(int value) { emit agcSlopeChanged(value); } /** * @brief AGC decay changed. * @param value The new decay rate in ms (tbc). */ void DockRxOpt::agcOpt_decayChanged(int value) { emit agcDecayChanged(value); } /** * @brief AGC manual gain changed. * @param gain The new gain in dB. */ void DockRxOpt::agcOpt_gainChanged(int 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); } void DockRxOpt::demodOpt_cwOffsetChanged(int offset) { emit cwOffsetChanged(offset); } /** Noise blanker 1 button has been toggled. */ void DockRxOpt::on_nb1Button_toggled(bool checked) { emit noiseBlankerChanged(1, checked, (float) nbOpt->nbThreshold(1)); } /** Noise blanker 2 button has been toggled. */ void DockRxOpt::on_nb2Button_toggled(bool checked) { emit noiseBlankerChanged(2, checked, (float) nbOpt->nbThreshold(2)); } /** 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(); } int DockRxOpt::GetEnumForModulationString(QString param) { int iModulation = -1; for(int i=0; i #include #include "qtgui/agc_options.h" #include "qtgui/demod_options.h" #include "qtgui/nb_options.h" #define FILTER_PRESET_WIDE 0 #define FILTER_PRESET_NORMAL 1 #define FILTER_PRESET_NARROW 2 #define FILTER_PRESET_USER 3 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: /** * Mode selector entries. * * @note If you change this enum, remember to update the TCP interface. * @note Keep in same order as the Strings in ModulationStrings, see * DockRxOpt.cpp constructor. */ 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. */ MODE_WFM_STEREO_OIRT = 10, /*!< Broadcast FM (stereo oirt). */ MODE_LAST = 11 }; 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() const; void setCurrentFilterShape(int index); int currentFilterShape() const; void setHwFreq(qint64 freq_hz); void setRxFreqRange(qint64 min_hz, qint64 max_hz); int currentDemod() const; QString currentDemodAsString(); float currentMaxdev() const; double currentEmph() const; double currentSquelchLevel() const; void getFilterPreset(int mode, int preset, int * lo, int * hi) const; int getCwOffset() const; static QStringList ModulationStrings; static QString GetStringForModulationIndex(int iModulationIndex); static int GetEnumForModulationString(QString param); static bool IsModulationValid(QString strModulation); public slots: void setRxFreq(qint64 freq_hz); void setCurrentDemod(int demod); void setFilterOffset(qint64 freq_hz); void setSquelchLevel(double level); private: void updateHwFreq(); void updateDemodOptPage(int demod); unsigned int filterIdxFromLoHi(int lo, int hi) const; signals: /** Signal emitted when receiver frequency has changed */ void rxFreqChanged(qint64 freq_hz); /** Signal emitted when the channel filter frequency has changed. */ void filterOffsetChanged(qint64 freq_hz); /** Signal emitted when new demodulator is selected. */ void demodSelected(int demod); /** Signal emitted when new FM deviation is selected. */ void fmMaxdevSelected(float max_dev); /** Signal emitted when new FM de-emphasis constant is selected. */ void fmEmphSelected(double tau); /** Signal emitted when AM DCR status is toggled. */ void amDcrToggled(bool enabled); /** Signal emitted when baseband gain has changed. Gain is in dB. */ //void bbGainChanged(float gain); /** Signal emitted when squelch level has changed. Level is in dBFS. */ void sqlLevelChanged(double level); /** * Signal emitted when auto squelch level is clicked. * * @note Need current signal/noise level returned */ double sqlAutoClicked(); /** Signal emitted when AGC is togglen ON/OFF. */ void agcToggled(bool agc_on); /** Signal emitted when AGC hang is toggled. */ void agcHangToggled(bool use_hang); /** Signal emitted when AGC threshold has changed. Threshold in dB. */ void agcThresholdChanged(int value); /** Signal emitted when AGC slope has changed. Slope is in dB.*/ void agcSlopeChanged(int slope); /** Signal emitted when AGC decay has changed. Decay is in millisec.*/ void agcDecayChanged(int decay); /** Signal emitted when AGC manual gain has changed. Gain is in dB.*/ void agcGainChanged(int gain); /** Signal emitted when noise blanker status has changed. */ void noiseBlankerChanged(int nbid, bool on, float threshold); void cwOffsetChanged(int offset); private slots: void on_freqSpinBox_valueChanged(double freq); void on_filterFreq_newFrequency(qint64 freq); void on_filterCombo_activated(int index); void on_modeSelector_activated(int index); void on_modeButton_clicked(); void on_agcButton_clicked(); void on_autoSquelchButton_clicked(); void on_resetSquelchButton_clicked(); //void on_agcPresetCombo_activated(int index); void on_agcPresetCombo_currentIndexChanged(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); void demodOpt_cwOffsetChanged(int offset); // 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-2.9/src/qtgui/dockrxopt.ui000066400000000000000000000566531320142145500166550ustar00rootroot00000000000000 DockRxOpt 0 0 305 335 0 0 305 335 524287 524287 :/icons/icons/signal.svg:/icons/icons/signal.svg Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea Receiver Options 0 0 0 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 QLayout::SetDefaultConstraint 0 0 The frequency of the hardware (not including LNB) Hardware freq: Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 0 0 The frequency of the hardware (not including LNB) 144.500000 MHz Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 10 5 0 0 Squelch level in dB below full scale Squelch Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 50 30 16777215 16777215 AGC options AGC options ... 16 16 false 0 0 Demodulator type (mode) Demodulator type (mode) Mode -1 30 0 0 50 30 16777215 16777215 Noise blanker options Noise blanker options Noise blanker options ... true 0 0 Apply mode dependent filter preset Filter width 1 Wide Normal Narrow User 0 0 Mode Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 -1 0 0 Noise blanker settings Noise blanker Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Filter shape Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 50 30 16777215 16777215 Demodulator options Mode options ... 16 16 false 0 0 Filter width Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 AGC Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter true 0 0 AGC presets AGC 1 Fast Medium Slow User Off 0 0 Select the filter shape Filter shape 1 Soft Normal Sharp 5 true 0 0 50 30 16777215 16777215 Noise blanker for static type noise Noise blanker 1 NB1 true true 0 0 50 30 16777215 16777215 Noise blanker for pulse type noise Noise blanker 2 NB2 true Frequency Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Receiver frequency Frequency Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 3 24000.000000000000000 2200000.000000000000000 144500.000000000000000 kHz 0 0 50 30 16777215 16777215 Reset squelch to its default value Auto squelch R 16 16 0 0 Squelch level in dB below full scale Squelch Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter true dB 1 -150.000000000000000 0.000000000000000 1.000000000000000 -150.000000000000000 0 0 50 30 16777215 16777215 Set squelch to the current signal or noise level Auto squelch A 16 16 Qt::Vertical 10 0 CFreqCtrl QFrame
    qtgui/freqctrl.h
    1
    freqSpinBox filterCombo filterShapeCombo modeSelector modeButton agcPresetCombo agcButton sqlSpinBox autoSquelchButton resetSquelchButton nb1Button nb2Button nbOptButton
    gqrx-2.9/src/qtgui/freqctrl.cpp000066400000000000000000000562411320142145500166200ustar00rootroot00000000000000/* * Frequency controller widget (originally from CuteSDR) * * This file is part of gqrx sdr. * * Copyright 2010 Moe Wheatley AE4JY * Copyright 2012-2017 Alexandru Csete OZ9AEC * All rights reserved. * * This software is released under the "Simplified BSD License". * * 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT HOLDER 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. */ #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 #define STATUS_TIP \ "Scroll or left-click to increase/decrease digit. " \ "Right-click to clear digits." CFreqCtrl::CFreqCtrl(QWidget *parent) : QFrame(parent) { setAutoFillBackground(false); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setFocusPolicy(Qt::StrongFocus); setMouseTracking(true); m_BkColor = QColor(0x1F, 0x1D, 0x1D, 0xFF); m_DigitColor = QColor(0xFF, 0xFF, 0xFF, 0xFF); m_HighlightColor = QColor(0x5A, 0x5A, 0x5A, 0xFF); m_UnitsColor = Qt::gray; m_freq = 146123456; setup(0, 1, 4000000000U, 1, FCTL_UNIT_NONE); m_Oldfreq = 0; m_LastLeadZeroPos = 0; m_LRMouseFreqSel = false; m_ActiveEditDigit = -1; m_ResetLowerDigits = true; m_UnitsFont = QFont("Arial", 12, QFont::Normal); m_DigitFont = QFont("Arial", 12, QFont::Normal); setStatusTip(tr(STATUS_TIP)); } CFreqCtrl::~CFreqCtrl() { } QSize CFreqCtrl::minimumSizeHint() const { return QSize(100, 20); } QSize CFreqCtrl::sizeHint() const { return QSize(100, 20); } 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; } static int fmax_to_numdigits(qint64 fmax) { if (fmax < 10e6) return 7; else if (fmax < 100e6) return 8; else if (fmax < 1e9) return 9; else if (fmax < 10e9) return 10; else if (fmax < 100e9) return 11; return 12; } void CFreqCtrl::setup(int NumDigits, qint64 Minf, qint64 Maxf, int MinStep, FctlUnit unit) { int i; qint64 pwr = 1; m_LastEditDigit = 0; m_Oldfreq = -1; m_NumDigits = NumDigits ? NumDigits : fmax_to_numdigits(Maxf); if (m_NumDigits > FCTL_MAX_DIGITS) m_NumDigits = FCTL_MAX_DIGITS; if (m_NumDigits < FCTL_MIN_DIGITS) m_NumDigits = FCTL_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; setUnit(unit); 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; } 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; } } // If the sign changed and the frequency is less than 1 unit, // redraw the leading zero to get the correct sign. if ((m_Oldfreq ^ m_freq) < 0 && m_DigitInfo[m_LeadZeroPos - 1].val == 0) m_DigitInfo[m_LeadZeroPos - 1].modified = true; // 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. If that position is zero, // it will be checked in the drawing method, drawDigits(). /** 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; } void CFreqCtrl::setDigitColor(QColor col) { m_UpdateAll = true; m_DigitColor = col; for (int i = m_DigStart; i < m_NumDigits; i++) m_DigitInfo[i].modified = true; updateCtrl(true); } void CFreqCtrl::setUnit(FctlUnit unit) { switch (unit) { case FCTL_UNIT_NONE: m_DecPos = 0; m_UnitString = QString(); break; case FCTL_UNIT_HZ: m_DecPos = 0; m_UnitString = "Hz "; break; case FCTL_UNIT_KHZ: m_DecPos = 3; m_UnitString = "kHz"; break; case FCTL_UNIT_MHZ: m_DecPos = 6; m_UnitString = "MHz"; break; case FCTL_UNIT_GHZ: m_DecPos = 9; m_UnitString = "GHz"; break; case FCTL_UNIT_SEC: m_DecPos = 6; m_UnitString = "Sec"; break; case FCTL_UNIT_MSEC: m_DecPos = 3; m_UnitString = "mS "; break; case FCTL_UNIT_USEC: m_DecPos = 0; m_UnitString = "uS "; break; case FCTL_UNIT_NSEC: m_DecPos = 0; m_UnitString = "nS "; break; } m_Unit = unit; m_UpdateAll = true; updateCtrl(true); } void CFreqCtrl::setBgColor(QColor col) { m_UpdateAll = true; m_BkColor = col; for (int i = m_DigStart; i < m_NumDigits; i++) m_DigitInfo[i].modified = true; updateCtrl(true); } void CFreqCtrl::setUnitsColor(QColor col) { m_UpdateAll = true; m_UnitsColor = col; updateCtrl(true); } void CFreqCtrl::setHighlightColor(QColor col) { m_UpdateAll = true; m_HighlightColor = col; updateCtrl(true); } void CFreqCtrl::updateCtrl(bool all) { if (all) { m_UpdateAll = true; for (int i = m_DigStart; i < m_NumDigits; i++) m_DigitInfo[i].modified = true; } update(); } void CFreqCtrl::resizeEvent(QResizeEvent *) { // qDebug() <= 0) { if (m_DigitInfo[m_ActiveEditDigit].editmode) { m_DigitInfo[m_ActiveEditDigit].editmode = false; m_DigitInfo[m_ActiveEditDigit].modified = true; m_ActiveEditDigit = -1; updateCtrl(false); } } } 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?) } 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); } } 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 { clearFreq(); } } } } } 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(); } } } 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); } 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 (m_Unit == FCTL_UNIT_NONE) { if (m_LeadZeroPos > i) dgsep = '.'; else dgsep = ' '; } else { if (i == m_DecPos) dgsep = '.'; else dgsep = ' '; } Painter.drawText(m_SepRect[i], Qt::AlignHCenter | Qt::AlignVCenter, QChar(dgsep)); } else { m_SepRect[i].setCoords(0, 0, 0, 0); } m_DigitInfo[i].dQRect.setCoords(digpos - cellwidth, rect.top(), digpos, rect.bottom()); digpos -= cellwidth; } } // Draws just the Digits that have been modified void CFreqCtrl::drawDigits(QPainter &Painter) { Painter.setFont(m_DigitFont); m_FirstEditableDigit = m_DigStart; for (int i = m_DigStart; i < m_NumDigits; i++) { if (m_DigitInfo[i].incval == 0) m_FirstEditableDigit++; if (m_DigitInfo[i].modified || m_DigitInfo[i].editmode) { if (m_DigitInfo[i].editmode && m_DigitInfo[i].incval != 0) Painter.fillRect(m_DigitInfo[i].dQRect, m_HighlightColor); else Painter.fillRect(m_DigitInfo[i].dQRect, m_BkColor); if (i >= m_LeadZeroPos) Painter.setPen(m_BkColor); else Painter.setPen(m_DigitColor); if (m_freq < 0 && i == m_LeadZeroPos - 1 && m_DigitInfo[i].val == 0) Painter.drawText(m_DigitInfo[i].dQRect, Qt::AlignHCenter | Qt::AlignVCenter, QString("-0")); else 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; } } } // Clear the selected digit and the digits below (i.e. set them to 0) void CFreqCtrl::clearFreq() { if (m_ActiveEditDigit >= 0) { if (m_DigitInfo[m_ActiveEditDigit].editmode) { m_freq -= m_DigitInfo[m_ActiveEditDigit].val * m_DigitInfo[m_ActiveEditDigit].incval; /* digits below the active one are reset to 0 */ 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-2.9/src/qtgui/freqctrl.h000066400000000000000000000072471320142145500162670ustar00rootroot00000000000000/* * Frequency controller widget (originally from CuteSDR) */ #pragma once #include #include #include enum FctlUnit { FCTL_UNIT_NONE, // Freq displayed without unit: 14.236.000 FCTL_UNIT_HZ, FCTL_UNIT_KHZ, FCTL_UNIT_MHZ, FCTL_UNIT_GHZ, FCTL_UNIT_SEC, FCTL_UNIT_MSEC, FCTL_UNIT_USEC, FCTL_UNIT_NSEC }; #define FCTL_MAX_DIGITS 12 #define FCTL_MIN_DIGITS 4 class CFreqCtrl : public QFrame { Q_OBJECT public: explicit CFreqCtrl(QWidget *parent = 0); ~CFreqCtrl(); QSize minimumSizeHint() const; QSize sizeHint() const; // Use NumDigits=0 for auto void setup(int NumDigits, qint64 Minf, qint64 Maxf, int MinStep, FctlUnit unit); void setUnit(FctlUnit unit); void setDigitColor(QColor col); void setBgColor(QColor col); void setUnitsColor(QColor col); void setHighlightColor(QColor col); qint64 getFrequency() const { return m_freq; } void setResetLowerDigits(bool reset) { m_ResetLowerDigits = reset; } signals: void newFrequency(qint64 freq); // emitted when frequency has changed public slots: void setFrequency(qint64 freq); protected: 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 clearFreq(); 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; FctlUnit m_Unit; QRect m_rectCtrl; // main control rectangle QRect m_UnitsRect; // rectangle where Units text goes QRect m_SepRect[FCTL_MAX_DIGITS]; // separation rectangles for commas, decimal point, 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[FCTL_MAX_DIGITS]; }; gqrx-2.9/src/qtgui/ioconfig.cpp000066400000000000000000000572071320142145500165760ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2014 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 WITH_PORTAUDIO #include "portaudio/device_list.h" #elif defined(GQRX_OS_MACX) #include "osxaudio/device_list.h" #endif #include "qtgui/ioconfig.h" #include "ui_ioconfig.h" CIoConfig::CIoConfig(QSettings * settings, std::map &devList, QWidget *parent) : QDialog(parent), ui(new Ui::CIoConfig), m_settings(settings) { unsigned int i=0; QString devstr; bool cfgmatch=false; //flag to indicate that device from config was found ui->setupUi(this); QString indev = settings->value("input/device", "").toString(); // insert the device list in device combo box std::map::iterator I = devList.begin(); i = 0; while (I != devList.end()) { devstr = (*I).second.toString(); ui->inDevCombo->addItem((*I).first, devstr); // is this the device stored in config? if (indev == devstr) { ui->inDevCombo->setCurrentIndex(i); ui->inDevEdit->setText(devstr); cfgmatch = true; } ++I; ++i; } 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); } } // input rate updateInputSampleRates(settings->value("input/sample_rate", 0).toInt()); // decimation int idx = decim2idx(settings->value("input/decimation", 0).toInt()); ui->decimCombo->setCurrentIndex(idx); decimationChanged(idx); // 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(); // get list of audio output devices #ifdef WITH_PULSEAUDIO 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 WITH_PORTAUDIO portaudio_device_list devices; outDevList = devices.get_output_devices(); for (i = 0; i < outDevList.size(); i++) { 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); } //ui->outDevCombo->setEditable(true); #elif defined(GQRX_OS_MACX) osxaudio_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->addItem(settings->value("output/device", "Default").toString(), settings->value("output/device", "Default").toString()); 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))); connect(ui->inSrCombo, SIGNAL(editTextChanged(QString)), this, SLOT(inputRateChanged(QString))); connect(ui->decimCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(decimationChanged(int))); } CIoConfig::~CIoConfig() { delete ui; } /** * @brief get the list of devices */ void CIoConfig::getDeviceList(std::map &devList) { unsigned int i=0; QString devstr; QString devlabel; #if defined(GQRX_OS_MACX) // automatic discovery of FCD does not work on Mac // so we do it ourselves osxaudio_device_list devices; vector 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'"; devList.insert(std::pair(QString("FUNcube Dongle V1.0"), QVariant(devstr))); } else if (this_dev.find("FUNcube Dongle V1_0") != string::npos) // since OS X 10.11.4 { devstr = "fcd,type=1,device='FUNcube Dongle V1_0'"; devList.insert(std::pair(QString("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'"; devList.insert(std::pair(QString("FUNcube Dongle V2.0"), QVariant(devstr))); } else if (this_dev.find("FUNcube Dongle V2_0") != string::npos) // since OS X 10.11.4 { devstr = "fcd,type=2,device='FUNcube Dongle V2_0'"; devList.insert(std::pair(QString("FUNcube Dongle V2_0"), QVariant(devstr))); } } #endif // Get list of input devices discovered by gr-osmosdr and store them in // the device list 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()); devList.insert(std::pair(devlabel, devstr)); qDebug() << " " << i << ":" << devlabel; ++i; } } /** Save configuration. */ void CIoConfig::saveConfig() { int idx; int int_val; bool conv_ok; qDebug() << __FUNCTION__; idx = ui->outDevCombo->currentIndex(); #if defined(WITH_PULSEAUDIO) || defined(GQRX_OS_MACX) if (idx > 0) { 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())); } #else if (idx > 0 || ui->outDevCombo->currentText() != "Default") { qDebug() << "Output device:" << ui->outDevCombo->currentText(); m_settings->setValue("output/device", ui->outDevCombo->currentText()); } #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); else m_settings->remove("input/lnb_lo"); int_val = ui->inSrCombo->currentText().toInt(&conv_ok); if (conv_ok) m_settings->setValue("input/sample_rate", int_val); else m_settings->remove("input/sample_rate"); idx = ui->decimCombo->currentIndex(); int_val = idx2decim(idx); if (int_val < 2) m_settings->remove("input/decimation"); else m_settings->setValue("input/decimation", int_val); } /** * @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; if (ui->inDevEdit->text().contains("fcd")) { if (ui->inDevCombo->currentText().contains("V2")) // V2.0 or V2_0 { ui->inSrCombo->addItem("192000"); } else { ui->inSrCombo->addItem("96000"); } } else if (ui->inDevEdit->text().contains("rtl") || ui->inDevEdit->text().contains("rtl_tcp")) { ui->inSrCombo->addItem("240000"); ui->inSrCombo->addItem("300000"); ui->inSrCombo->addItem("960000"); ui->inSrCombo->addItem("1152000"); ui->inSrCombo->addItem("1200000"); ui->inSrCombo->addItem("1440000"); ui->inSrCombo->addItem("1600000"); ui->inSrCombo->addItem("1800000"); ui->inSrCombo->addItem("1920000"); ui->inSrCombo->addItem("2400000"); ui->inSrCombo->addItem("2880000"); ui->inSrCombo->addItem("3200000"); if (rate > 0) { ui->inSrCombo->addItem(QString("%1").arg(rate)); ui->inSrCombo->setCurrentIndex(12); } else { ui->inSrCombo->setCurrentIndex(7); } } 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")) { 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"); if (rate > 0) { ui->inSrCombo->insertItem(0, QString("%1").arg(rate)); ui->inSrCombo->setCurrentIndex(0); } else ui->inSrCombo->setCurrentIndex(4); // select 111.111 kHz } else if (ui->inDevEdit->text().contains("sdr-ip")) { 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"); if (rate > 0) { ui->inSrCombo->insertItem(0, QString("%1").arg(rate)); ui->inSrCombo->setCurrentIndex(0); } else ui->inSrCombo->setCurrentIndex(11); // select 250 kHz } else if (ui->inDevEdit->text().contains("netsdr")) { 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"); if (rate > 0) { ui->inSrCombo->insertItem(0, QString("%1").arg(rate)); ui->inSrCombo->setCurrentIndex(0); } else ui->inSrCombo->setCurrentIndex(11); // select 250 kHz } else if (ui->inDevEdit->text().contains("cloudiq")) { ui->inSrCombo->addItem("48000"); ui->inSrCombo->addItem("61440"); ui->inSrCombo->addItem("96000"); ui->inSrCombo->addItem("122880"); ui->inSrCombo->addItem("240000"); ui->inSrCombo->addItem("256000"); ui->inSrCombo->addItem("370120"); ui->inSrCombo->addItem("495483"); ui->inSrCombo->addItem("512000"); ui->inSrCombo->addItem("614400"); ui->inSrCombo->addItem("1024000"); ui->inSrCombo->addItem("1228800"); ui->inSrCombo->addItem("1807058"); if (rate > 0) { ui->inSrCombo->insertItem(0, QString("%1").arg(rate)); ui->inSrCombo->setCurrentIndex(0); } else ui->inSrCombo->setCurrentIndex(4); // select 240 kHz } else if (ui->inDevEdit->text().contains("airspyhf")) { ui->inSrCombo->addItem("768000"); } // NB: must list airspyhf first else if (ui->inDevEdit->text().contains("airspy")) { if (rate > 0) ui->inSrCombo->addItem(QString("%1").arg(rate)); ui->inSrCombo->addItem("2500000"); ui->inSrCombo->addItem("3000000"); ui->inSrCombo->addItem("6000000"); ui->inSrCombo->addItem("10000000"); } else if (ui->inDevEdit->text().contains("redpitaya")) { ui->inSrCombo->addItem("20000"); ui->inSrCombo->addItem("50000"); ui->inSrCombo->addItem("100000"); ui->inSrCombo->addItem("250000"); ui->inSrCombo->addItem("500000"); ui->inSrCombo->addItem("1250000"); if (rate > 0) { ui->inSrCombo->insertItem(0, QString("%1").arg(rate)); ui->inSrCombo->setCurrentIndex(0); } else ui->inSrCombo->setCurrentIndex(3); // select 250 kHz } else if (ui->inDevEdit->text().contains("sdrplay")) { ui->inSrCombo->addItem("222222"); ui->inSrCombo->addItem("333333"); ui->inSrCombo->addItem("428571"); ui->inSrCombo->addItem("500000"); ui->inSrCombo->addItem("571429"); ui->inSrCombo->addItem("750000"); ui->inSrCombo->addItem("875000"); ui->inSrCombo->addItem("1000000"); ui->inSrCombo->addItem("1536000"); ui->inSrCombo->addItem("2048000"); ui->inSrCombo->addItem("5000000"); ui->inSrCombo->addItem("6000000"); ui->inSrCombo->addItem("7000000"); ui->inSrCombo->addItem("8000000"); ui->inSrCombo->addItem("9000000"); ui->inSrCombo->addItem("10000000"); ui->inSrCombo->addItem("11000000"); ui->inSrCombo->addItem("12000000"); if (rate > 0) { ui->inSrCombo->insertItem(0, QString("%1").arg(rate)); ui->inSrCombo->setCurrentIndex(0); } else ui->inSrCombo->setCurrentIndex(9); // select 2048 kHz } else if (ui->inDevEdit->text().contains("lime")) { ui->inSrCombo->addItem("100000"); ui->inSrCombo->addItem("500000"); ui->inSrCombo->addItem("1000000"); ui->inSrCombo->addItem("2500000"); ui->inSrCombo->addItem("5000000"); ui->inSrCombo->addItem("10000000"); ui->inSrCombo->addItem("20000000"); ui->inSrCombo->addItem("50000000"); if (rate > 0) { ui->inSrCombo->insertItem(0, QString("%1").arg(rate)); ui->inSrCombo->setCurrentIndex(0); } else ui->inSrCombo->setCurrentIndex(4); // select 5 MHz } else if (ui->inDevEdit->text().contains("plutosdr")) { ui->inSrCombo->addItem("600000"); ui->inSrCombo->addItem("1000000"); ui->inSrCombo->addItem("1500000"); ui->inSrCombo->addItem("2000000"); ui->inSrCombo->addItem("3000000"); ui->inSrCombo->addItem("6000000"); ui->inSrCombo->addItem("16000000"); ui->inSrCombo->addItem("20000000"); ui->inSrCombo->addItem("56000000"); if (rate > 0) { ui->inSrCombo->insertItem(0, QString("%1").arg(rate)); ui->inSrCombo->setCurrentIndex(0); } else ui->inSrCombo->setCurrentIndex(2); // select 2 MHz } else { if (rate > 0) ui->inSrCombo->addItem(QString("%1").arg(rate)); } updateDecimations(); } /** * @brief Update available decimations according to the current sample rate. * * This function will repopulate the decimation selector combo box to only * include decimations up to a meaningful maximum value, so that the quadrature * rate doesn't get below 48 ksps. */ void CIoConfig::updateDecimations(void) { bool ok; int rate; // get current sample rate from combo box rate= ui->inSrCombo->currentText().toInt(&ok); if (!ok || rate < 0) return; ui->decimCombo->clear(); ui->decimCombo->addItem("None", 0); if (rate >= 96000) ui->decimCombo->addItem("2", 0); if (rate >= 192000) ui->decimCombo->addItem("4", 0); if (rate >= 384000) ui->decimCombo->addItem("8", 0); if (rate >= 768000) ui->decimCombo->addItem("16", 0); if (rate >= 1536000) ui->decimCombo->addItem("32", 0); if (rate >= 3072000) ui->decimCombo->addItem("64", 0); if (rate >= 6144000) ui->decimCombo->addItem("128", 0); // if (rate >= 12288000) // ui->decimCombo->addItem("256", 0); // if (rate >= 24576000) // ui->decimCombo->addItem("512", 0); decimationChanged(0); } /** * @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); } /** * @brief Sample changed, either by selection of direct entry. * @param text The new sample rate. */ void CIoConfig::inputRateChanged(const QString &text) { (void) text; updateDecimations(); } /** * @brief New decimation rate selected. * @param index The index of the selected item in the combo box. * * This function calculates the quadrature rate and updates the sample rate * label just below the decimation combo box. */ void CIoConfig::decimationChanged(int index) { float quad_rate; int input_rate; int decim; bool ok; decim = idx2decim(index); input_rate = ui->inSrCombo->currentText().toInt(&ok); if (!ok) return; quad_rate = (float)input_rate / (float)decim; if (quad_rate > 1.e6f) ui->sampRateLabel->setText(QString(" %1 Msps"). arg(quad_rate * 1.e-6, 0, 'f', 3)); else ui->sampRateLabel->setText(QString(" %1 ksps"). arg(quad_rate * 1.e-3, 0, 'f', 3)); } /** Convert a combo box index to decimation. */ int CIoConfig::idx2decim(int idx) const { if (idx < 1) return 1; return (1 << idx); } /** Convert a decimation to a combobox index */ int CIoConfig::decim2idx(int decim) const { int idx; if (decim == 0) return 0; idx = 0; while (decim >>= 1) ++idx; return idx; } gqrx-2.9/src/qtgui/ioconfig.h000066400000000000000000000042071320142145500162330ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2014 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 WITH_PORTAUDIO #include "portaudio/device_list.h" #elif defined(GQRX_OS_MACX) #include "osxaudio/device_list.h" #endif namespace Ui { class CIoConfig; } /** @brief Inout/output device configurator. */ class CIoConfig : public QDialog { Q_OBJECT public: explicit CIoConfig(QSettings *settings, std::map &devList, QWidget *parent = 0); virtual ~CIoConfig(); static void getDeviceList(std::map &devList); private slots: void saveConfig(); void inputDeviceSelected(int index); void inputDevstrChanged(const QString &text); void inputRateChanged(const QString &text); void decimationChanged(int index); private: void updateInputSampleRates(int rate); void updateDecimations(void); int idx2decim(int idx) const; int decim2idx(int decim) const; private: Ui::CIoConfig *ui; QSettings *m_settings; #ifdef WITH_PULSEAUDIO vector outDevList; #elif WITH_PORTAUDIO vector outDevList; #elif defined(GQRX_OS_MACX) vector outDevList; #endif }; #endif // IOCONFIG_H gqrx-2.9/src/qtgui/ioconfig.ui000066400000000000000000000266571320142145500164360ustar00rootroot00000000000000 CIoConfig 0 0 303 413 Configure I/O devices :/icons/icons/flash.svg:/icons/icons/flash.svg 50 false I/Q input Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter false QFormLayout::AllNonFixedFieldsGrow Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Device Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter true Select an input device QComboBox::AdjustToMinimumContentsLength 15 Device arguments Device string Input 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 -500000.000000000000000 500000.000000000000000 <html><head/><body><p>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.</p><p>Examples (some arguments may be optional):</p><p>fcd=0<br/>rtl=0<br/>rtl=0,rtl_xtal=28.80001e6,tuner_xtal=26e6,buffers=64 ...<br/>rtl_tcp=127.0.0.1:1234,psize=16384<br/>plutosdr,uri=usb:6.36.5<br/>file=/path/to/file.ext,freq=428e6,rate=1e6,repeat=true,throttle=true ... </p><p>You can test the device strings in gnuradio-companion.</p></body></html> Analog bandwidth (leave at 0 for default) MHz 6 100.000000000000000 0.100000000000000 Analog bandwidth (leave at 0 for default) Bandwidth Decimation Input decimation None 2 4 8 16 32 64 128 256 512 Sample rate 50 false The sample rate after decimation 0 Msps Qt::Horizontal Audio output Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 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 inDevCombo inDevEdit inSrCombo decimCombo bwSpinBox loSpinBox outDevCombo outSrCombo buttonBox accepted() CIoConfig accept() 248 254 157 274 buttonBox rejected() CIoConfig reject() 316 260 286 274 gqrx-2.9/src/qtgui/iq_tool.cpp000066400000000000000000000261511320142145500164410ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2014 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 "iq_tool.h" #include "ui_iq_tool.h" CIqTool::CIqTool(QWidget *parent) : QDialog(parent), ui(new Ui::CIqTool) { ui->setupUi(this); is_recording = false; is_playing = false; bytes_per_sample = 8; sample_rate = 192000; rec_len = 0; plot_spp = 1; //ui->recDirEdit->setText(QDir::currentPath()); recdir = new QDir(QDir::homePath(), "*.raw"); error_palette = new QPalette(); error_palette->setColor(QPalette::Text, Qt::red); timer = new QTimer(this); connect(timer, SIGNAL(timeout()), this, SLOT(timeoutFunction())); } CIqTool::~CIqTool() { timer->stop(); delete timer; delete ui; delete recdir; delete error_palette; } /*! \brief Set new sample rate. */ void CIqTool::setSampleRate(qint64 sr) { sample_rate = sr; if (!current_file.isEmpty()) { // Get duration of selected recording and update label QFileInfo info(*recdir, current_file); rec_len = (int)(info.size() / (sample_rate * bytes_per_sample)); refreshTimeWidgets(); } } /*! \brief Slot activated when the user selects a file. */ void CIqTool::on_listWidget_currentTextChanged(const QString ¤tText) { current_file = currentText; QFileInfo info(*recdir, current_file); // Get duration of selected recording and update label sample_rate = sampleRateFromFileName(currentText); rec_len = (int)(info.size() / (sample_rate * bytes_per_sample)); refreshTimeWidgets(); } /*! \brief Start/stop playback */ void CIqTool::on_playButton_clicked(bool checked) { is_playing = checked; if (checked) { if (current_file.isEmpty()) { QMessageBox msg_box; msg_box.setIcon(QMessageBox::Critical); if (ui->listWidget->count() == 0) { msg_box.setText(tr("There are no I/Q files in the current directory.")); } else { msg_box.setText(tr("Please select a file to play.")); } msg_box.exec(); ui->playButton->setChecked(false); // will not trig clicked() } else { ui->listWidget->setEnabled(false); ui->recButton->setEnabled(false); emit startPlayback(recdir->absoluteFilePath(current_file), (float)sample_rate); } } else { emit stopPlayback(); ui->listWidget->setEnabled(true); ui->recButton->setEnabled(true); ui->slider->setValue(0); } } /*! \brief Cancel playback. * * This slot can be activated to cancel an ongoing playback. * * This slot should be used to signal that a playback could not be started. */ void CIqTool::cancelPlayback() { ui->playButton->setChecked(false); ui->listWidget->setEnabled(true); ui->recButton->setEnabled(true); is_playing = false; } /*! \brief Plot waveform. */ void CIqTool::on_plotButton_clicked() { if (current_file.isEmpty()) { QMessageBox msg_box; msg_box.setIcon(QMessageBox::Critical); if (ui->listWidget->count() == 0) { msg_box.setText(tr("There are no I/Q files in the current directory.")); } else { msg_box.setText(tr("Please select a file to play.")); } msg_box.exec(); return; } QFile *file = new QFile(recdir->filePath(current_file)); if (!file->open(QIODevice::ReadOnly)) { return; } // number of data points with 1 point / second int num_points = rec_len / plot_spp; int chunk_size = sample_rate / plot_spp * bytes_per_sample; char *readbuf = (char*)malloc(chunk_size); struct iqt_cplx *cplxbuf = (iqt_cplx *)readbuf; // FIXME: We assume gr_complex qDebug() << "*** NUM POINTS:" << num_points; qDebug() << "*** CHUNK SIZE:" << chunk_size; for (int i = 0; i < num_points; i++) { // read data chunk if (!file->seek(i * chunk_size)) { continue; } qint64 read = file->read(readbuf, chunk_size); // calculate average and max float val, avg=0.0, max=0.0; for (int j = 0; j < read/bytes_per_sample; j++) { val = fabs(cplxbuf[j].re); avg += val; if (val > max) max = val; } avg /= read/bytes_per_sample; qDebug() << i << " AVG:" << avg << " MAX:" << max; // store data in plot buffer } // plot data // clean up file->close(); delete file; free(readbuf); } /*! \brief Slider value (seek position) has changed. */ void CIqTool::on_slider_valueChanged(int value) { refreshTimeWidgets(); qint64 seek_pos = (qint64)(value)*sample_rate; emit seek(seek_pos); } /*! \brief Start/stop recording */ void CIqTool::on_recButton_clicked(bool checked) { is_recording = checked; if (checked) { ui->playButton->setEnabled(false); //ui->plotButton->setEnabled(false); emit startRecording(recdir->path()); refreshDir(); ui->listWidget->setCurrentRow(ui->listWidget->count()-1); } else { ui->playButton->setEnabled(true); //ui->plotButton->setEnabled(true); emit stopRecording(); } } /*! \brief Cancel a recording. * * This slot can be activated to cancel an ongoing recording. Cancelling an * ongoing recording will stop the recording and delete the recorded file, if * any. * * This slot should be used to signal that a recording could not be started. */ void CIqTool::cancelRecording() { ui->recButton->setChecked(false); ui->playButton->setEnabled(true); is_recording = false; } /*! \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 CIqTool::closeEvent(QCloseEvent *event) { timer->stop(); hide(); event->ignore(); } /*! \brief Catch window show events. */ void CIqTool::showEvent(QShowEvent * event) { Q_UNUSED(event); refreshDir(); refreshTimeWidgets(); timer->start(1000); } void CIqTool::saveSettings(QSettings *settings) { if (!settings) return; // Location of baseband recordings QString dir = recdir->path(); if (dir != QDir::homePath()) settings->setValue("baseband/rec_dir", dir); else settings->remove("baseband/rec_dir"); } void CIqTool::readSettings(QSettings *settings) { if (!settings) return; // Location of baseband recordings QString dir = settings->value("baseband/rec_dir", QDir::homePath()).toString(); ui->recDirEdit->setText(dir); } /*! \brief Slot called when the recordings directory has changed either * because of user input or programmatically. */ void CIqTool::on_recDirEdit_textChanged(const QString &dir) { if (recdir->exists(dir)) { ui->recDirEdit->setPalette(QPalette()); // Clear custom color recdir->setPath(dir); recdir->cd(dir); //emit newRecDirSelected(dir); } else { ui->recDirEdit->setPalette(*error_palette); // indicate error } } /*! \brief Slot called when the user clicks on the "Select" button. */ void CIqTool::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); } void CIqTool::timeoutFunction(void) { refreshDir(); if (is_playing) { // advance slider with one second int val = ui->slider->value(); if (val < ui->slider->maximum()) { ui->slider->blockSignals(true); ui->slider->setValue(val+1); ui->slider->blockSignals(false); refreshTimeWidgets(); } } if (is_recording) refreshTimeWidgets(); } /*! \brief Refresh list of files in current working directory. */ void CIqTool::refreshDir() { int selection = ui->listWidget->currentRow(); recdir->refresh(); QStringList files = recdir->entryList(); ui->listWidget->blockSignals(true); ui->listWidget->clear(); ui->listWidget->insertItems(0, files); ui->listWidget->setCurrentRow(selection); ui->listWidget->blockSignals(false); if (is_recording) { // update rec_len; if the file being recorded is the one selected // in the list, the length will update periodically QFileInfo info(*recdir, current_file); rec_len = (int)(info.size() / (sample_rate * bytes_per_sample)); } } /*! \brief Refresh time labels and slider position * * \note Safe for recordings > 24 hours */ void CIqTool::refreshTimeWidgets(void) { ui->slider->setMaximum(rec_len); // duration int len = rec_len; int lh, lm, ls; lh = len / 3600; len = len % 3600; lm = len / 60; ls = len % 60; // current position int pos = ui->slider->value(); int ph, pm, ps; ph = pos / 3600; pos = pos % 3600; pm = pos / 60; ps = pos % 60; ui->timeLabel->setText(QString("%1:%2:%3 / %4:%5:%6") .arg(ph, 2, 10, QChar('0')) .arg(pm, 2, 10, QChar('0')) .arg(ps, 2, 10, QChar('0')) .arg(lh, 2, 10, QChar('0')) .arg(lm, 2, 10, QChar('0')) .arg(ls, 2, 10, QChar('0'))); } /*! \brief Extract sample rate from file name */ qint64 CIqTool::sampleRateFromFileName(const QString &filename) { bool ok; qint64 sr; QStringList list = filename.split('_'); if (list.size() < 5) return sample_rate; // gqrx_yymmdd_hhmmss_freq_samprate_fc.raw sr = list.at(4).toLongLong(&ok); if (ok) return sr; else return sample_rate; // return current rate } gqrx-2.9/src/qtgui/iq_tool.h000066400000000000000000000055121320142145500161040ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2014 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 IQ_TOOL_H #define IQ_TOOL_H #include #include #include #include #include #include #include #include namespace Ui { class CIqTool; } struct iqt_cplx { float re; float im; }; /*! \brief User interface for I/Q recording and playback. */ class CIqTool : public QDialog { Q_OBJECT public: explicit CIqTool(QWidget *parent = 0); ~CIqTool(); void setSampleRate(qint64 sr); void closeEvent(QCloseEvent *event); void showEvent(QShowEvent * event); void saveSettings(QSettings *settings); void readSettings(QSettings *settings); signals: void startRecording(const QString recdir); void stopRecording(); void startPlayback(const QString filename, float samprate); void stopPlayback(); void seek(qint64 seek_pos); public slots: void cancelRecording(); void cancelPlayback(); private slots: void on_recDirEdit_textChanged(const QString &text); void on_recDirButton_clicked(); void on_recButton_clicked(bool checked); void on_playButton_clicked(bool checked); void on_plotButton_clicked(); void on_slider_valueChanged(int value); void on_listWidget_currentTextChanged(const QString ¤tText); void timeoutFunction(void); private: void refreshDir(void); void refreshTimeWidgets(void); qint64 sampleRateFromFileName(const QString &filename); private: Ui::CIqTool *ui; QDir *recdir; QTimer *timer; QPalette *error_palette; /*!< Palette used to indicate an error. */ QString current_file; /*!< Selected file in file browser. */ bool is_recording; bool is_playing; int bytes_per_sample; /*!< Bytes per sample (fc = 4) */ int sample_rate; /*!< Current sample rate. */ int rec_len; /*!< Length of a recording in seconds */ int plot_spp; /*!< [seconds / datapoint] */ }; #endif // IQ_TOOL_H gqrx-2.9/src/qtgui/iq_tool.ui000066400000000000000000000164431320142145500162770ustar00rootroot00000000000000 CIqTool 0 0 482 327 0 0 0 32 I/Q recording and replay tool :/icons/icons/signal.svg:/icons/icons/signal.svg Location: true Enter a different location or use the select button true 0 0 Select a different location Select false true true false 32 32 16777215 16777215 Plot the I/Q data. &Plot :/icons/icons/signal.svg:/icons/icons/signal.svg false false true 32 32 16777215 16777215 <html><head/><body><p>Button disabled.</p><p>Please use the record button in the main window.</p></body></html> &Rec :/icons/icons/record.svg:/icons/icons/record.svg true false 32 32 16777215 16777215 Start playing selected file &Play :/icons/icons/play.svg:/icons/icons/play.svg true false Qt::Horizontal 40 20 00:00:00 / 00:00:00 Qt::AlignCenter Qt::Horizontal 40 20 Seek forward and backward in I/Q file 600 10 60 Qt::Horizontal 0 50 QFrame::Box QFrame::Sunken gqrx-2.9/src/qtgui/meter.cpp000066400000000000000000000161621320142145500161100ustar00rootroot00000000000000/* -*- 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 // vertical position of horizontal axis #define CTRL_NEEDLE_TOP 0.4 // vertical 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() { } QSize CMeter::minimumSizeHint() const { return QSize(20, 10); } QSize CMeter::sizeHint() const { return QSize(100, 30); } 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(); } 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.f - d_alpha_decay) + dbfs * d_alpha_decay; else level = level * (1.f - d_alpha_rise) + dbfs * d_alpha_rise; m_dBm = (int)level; qreal w = (qreal)m_2DPixmap.width(); w -= 2 * 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(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"); int y = (h) / 4; Font.setPixelSize(y); Font.setWeight(QFont::Normal); painter.setFont(Font); painter.setPen(QColor(0xDA, 0xDA, 0xDA, 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(0x1F, 0x1D, 0x1D, 0xFF)); // 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 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"); 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); } } gqrx-2.9/src/qtgui/meter.h000066400000000000000000000051131320142145500155470ustar00rootroot00000000000000/* -*- 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();} public slots: void setLevel(float dbfs); protected: 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-2.9/src/qtgui/nb_options.cpp000066400000000000000000000033131320142145500171400ustar00rootroot00000000000000/* -*- 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-2.9/src/qtgui/nb_options.h000066400000000000000000000026251320142145500166120ustar00rootroot00000000000000/* -*- 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-2.9/src/qtgui/nb_options.ui000066400000000000000000000043701320142145500167770ustar00rootroot00000000000000 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-2.9/src/qtgui/plotter.cpp000066400000000000000000001512721320142145500164670ustar00rootroot00000000000000/* -*- 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 #ifndef _MSC_VER #include #else #include #include int gettimeofday(struct timeval * tp, struct timezone * tzp) { // Note: some broken versions only have 8 trailing zero's, the correct epoch has 9 trailing zero's static const uint64_t EPOCH = ((uint64_t) 116444736000000000ULL); SYSTEMTIME system_time; FILETIME file_time; uint64_t time; GetSystemTime( &system_time ); SystemTimeToFileTime( &system_time, &file_time ); time = ((uint64_t)file_time.dwLowDateTime ) ; time += ((uint64_t)file_time.dwHighDateTime) << 32; tp->tv_sec = (long) ((time - EPOCH) / 10000000L); tp->tv_usec = (long) (system_time.wMilliseconds * 1000); return 0; } #endif #include #include #include #include #include #include #include #include "plotter.h" #include "bookmarks.h" // Comment out to enable plotter debug messages //#define PLOTTER_DEBUG #define CUR_CUT_DELTA 5 //cursor capture delta in pixels #define FFT_MIN_DB -160.f #define FFT_MAX_DB 0.f // Colors of type QRgb in 0xAARRGGBB format (unsigned int) #define PLOTTER_BGD_COLOR 0xFF1F1D1D #define PLOTTER_GRID_COLOR 0xFF444242 #define PLOTTER_TEXT_COLOR 0xFFDADADA #define PLOTTER_CENTER_LINE_COLOR 0xFF788296 #define PLOTTER_FILTER_LINE_COLOR 0xFFFF7171 #define PLOTTER_FILTER_BOX_COLOR 0xFFA0A0A4 // FIXME: Should cache the QColors also static inline bool val_is_out_of_range(float val, float min, float max) { return (val < min || val > max); } static inline bool out_of_range(float min, float max) { return (val_is_out_of_range(min, FFT_MIN_DB, FFT_MAX_DB) || val_is_out_of_range(max, FFT_MIN_DB, FFT_MAX_DB) || max < min + 10.f); } /** Current time in milliseconds since Epoch */ static inline quint64 time_ms(void) { struct timeval tval; gettimeofday(&tval, NULL); return 1e3 * tval.tv_sec + 1e-3 * tval.tv_usec; } #define STATUS_TIP \ "Click, drag or scroll on spectrum to tune. " \ "Drag and scroll X and Y axes for pan and zoom. " \ "Drag filter edges to adjust filter." 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); setTooltipsEnabled(false); setStatusTip(tr(STATUS_TIP)); // 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_BookmarksEnabled = true; m_Span = 96000; m_SampleFreq = 96000; m_HorDivs = 12; m_VerDivs = 6; m_PandMaxdB = m_WfMaxdB = 0.f; m_PandMindB = m_WfMindB = -150.f; m_FreqUnits = 1000000; m_CursorCaptured = NOCAP; 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 = 30; //percent of screen used for 2D display m_VdivDelta = 30; m_HdivDelta = 70; m_FreqDigits = 3; m_Peaks = QMap(); setPeakDetection(false, 2); m_PeakHoldValid = false; setFftPlotColor(QColor(0xFF,0xFF,0xFF,0xFF)); setFftFill(false); // always update waterfall tlast_wf_ms = 0; msec_per_wfline = 0; wf_span = 0; fft_rate = 15; memset(m_wfbuf, 255, MAX_SCREENSIZE); } CPlotter::~CPlotter() { } QSize CPlotter::minimumSizeHint() const { return QSize(50, 50); } QSize CPlotter::sizeHint() const { return QSize(180, 180); } 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) { bool onTag = false; if(pt.y() < 15 * 10) // FIXME { for(int i = 0; i < m_BookmarkTags.size() && !onTag; i++) { if (m_BookmarkTags[i].first.contains(event->pos())) onTag = true; } } // if no mouse button monitor grab regions and change cursor icon if (onTag) { setCursor(QCursor(Qt::PointingHandCursor)); m_CursorCaptured = BOOKMARK; } else 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; if (m_TooltipsEnabled) QToolTip::showText(event->globalPos(), QString("Demod: %1 kHz") .arg(m_DemodCenterFreq/1.e3f, 0, 'f', 3), this); } 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; if (m_TooltipsEnabled) QToolTip::showText(event->globalPos(), QString("High cut: %1 Hz") .arg(m_DemodHiCutFreq), this); } 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; if (m_TooltipsEnabled) QToolTip::showText(event->globalPos(), QString("Low cut: %1 Hz") .arg(m_DemodLowCutFreq), this); } else if (isPointCloseTo(pt.x(), m_YAxisWidth/2, m_YAxisWidth/2)) { if (YAXIS != m_CursorCaptured) setCursor(QCursor(Qt::OpenHandCursor)); m_CursorCaptured = YAXIS; if (m_TooltipsEnabled) QToolTip::hideText(); } else if (isPointCloseTo(pt.y(), m_XAxisYCenter, m_CursorCaptureDelta+5)) { if (XAXIS != m_CursorCaptured) setCursor(QCursor(Qt::OpenHandCursor)); m_CursorCaptured = XAXIS; if (m_TooltipsEnabled) QToolTip::hideText(); } else { //if not near any grab boundaries if (NOCAP != m_CursorCaptured) { setCursor(QCursor(Qt::ArrowCursor)); m_CursorCaptured = NOCAP; } if (m_TooltipsEnabled) QToolTip::showText(event->globalPos(), QString("F: %1 kHz") .arg(freqFromX(pt.x())/1.e3f, 0, 'f', 3), this); } m_GrabPosition = 0; } } else { // not in Overlay region if (event->buttons() == Qt::NoButton) { if (NOCAP != m_CursorCaptured) setCursor(QCursor(Qt::ArrowCursor)); m_CursorCaptured = NOCAP; m_GrabPosition = 0; } if (m_TooltipsEnabled) { QDateTime tt; tt.setMSecsSinceEpoch(msecFromY(pt.y())); QToolTip::showText(event->globalPos(), QString("%1\n%2 kHz") .arg(tt.toString("yyyy.MM.dd hh:mm:ss.zzz")) .arg(freqFromX(pt.x())/1.e3f, 0, 'f', 3), this); } } // 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 float delta_px = m_Yzero - pt.y(); float delta_db = delta_px * fabs(m_PandMindB - m_PandMaxdB) / (float)m_OverlayPixmap.height(); m_PandMindB -= delta_db; m_PandMaxdB -= delta_db; if (out_of_range(m_PandMindB, m_PandMaxdB)) { m_PandMindB += delta_db; m_PandMaxdB += delta_db; } else { emit pandapterRangeChanged(m_PandMindB, m_PandMaxdB); if (m_Running) m_DrawOverlay = true; else drawOverlay(); m_PeakHoldValid = false; 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); } updateOverlay(); m_PeakHoldValid = false; 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; else drawOverlay(); } 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 = NOCAP; } } 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); updateOverlay(); } 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 = NOCAP; } } 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); updateOverlay(); m_PeakHoldValid = false; } 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 = NOCAP; } } else { // cursor not captured m_GrabPosition = 0; } if (!this->rect().contains(pt)) { if (NOCAP != m_CursorCaptured) setCursor(QCursor(Qt::ArrowCursor)); m_CursorCaptured = NOCAP; } } 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); float dist = 1.0e10; 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; float d = powf(y - pt.y(), 2) + powf(x - pt.x(), 2); if (d < dist) { dist = d; best = x; } } return best; } /** Set waterfall span in milliseconds */ void CPlotter::setWaterfallSpan(quint64 span_ms) { wf_span = span_ms; msec_per_wfline = wf_span / m_WaterfallPixmap.height(); clearWaterfall(); } void CPlotter::clearWaterfall() { m_WaterfallPixmap.fill(Qt::black); memset(m_wfbuf, 255, MAX_SCREENSIZE); } /** * @brief Save waterfall to a graphics file * @param filename * @return TRUE if the save successful, FALSE if an erorr occurred. * * We assume that frequency strings are up to date */ bool CPlotter::saveWaterfall(const QString & filename) const { QBrush axis_brush(QColor(0x00, 0x00, 0x00, 0x70), Qt::SolidPattern); QPixmap pixmap(m_WaterfallPixmap); QPainter painter(&pixmap); QRect rect; QDateTime tt; QFont font("sans-serif"); QFontMetrics font_metrics(font); float pixperdiv; int x, y, w, h; int hxa, wya = 85; int i; w = pixmap.width(); h = pixmap.height(); hxa = font_metrics.height() + 5; // height of X axis y = h - hxa; pixperdiv = (float) w / (float) m_HorDivs; painter.setBrush(axis_brush); painter.setPen(QColor(0x0, 0x0, 0x0, 0x70)); painter.drawRect(0, y, w, hxa); painter.drawRect(0, 0, wya, h - hxa - 1); painter.setFont(font); painter.setPen(QColor(0xFF, 0xFF, 0xFF, 0xFF)); // skip last frequency entry for (i = 2; i < m_HorDivs - 1; i++) { // frequency tick marks x = (int)((float)i * pixperdiv); painter.drawLine(x, y, x, y + 5); // frequency strings x = (int)((float)i * pixperdiv - pixperdiv / 2.0); rect.setRect(x, y, (int)pixperdiv, hxa); painter.drawText(rect, Qt::AlignHCenter|Qt::AlignBottom, m_HDivText[i]); } rect.setRect(w - pixperdiv - 10, y, pixperdiv, hxa); painter.drawText(rect, Qt::AlignRight|Qt::AlignBottom, tr("MHz")); quint64 msec; int tdivs = h / 70 + 1; pixperdiv = (float) h / (float) tdivs; tt.setTimeSpec(Qt::OffsetFromUTC); for (i = 1; i < tdivs; i++) { y = (int)((float)i * pixperdiv); if (msec_per_wfline > 0) msec = tlast_wf_ms - y * msec_per_wfline; else msec = tlast_wf_ms - y * 1000 / fft_rate; tt.setMSecsSinceEpoch(msec); rect.setRect(0, y - font_metrics.height(), wya - 5, font_metrics.height()); painter.drawText(rect, Qt::AlignRight|Qt::AlignVCenter, tt.toString("yyyy.MM.dd")); painter.drawLine(wya - 5, y, wya, y); rect.setRect(0, y, wya - 5, font_metrics.height()); painter.drawText(rect, Qt::AlignRight|Qt::AlignVCenter, tt.toString("hh:mm:ss")); } return pixmap.save(filename, 0, -1); } /** Get waterfall time resolution in milleconds / line. */ quint64 CPlotter::getWfTimeRes(void) { if (msec_per_wfline) return msec_per_wfline; else return 1000 * fft_rate / m_WaterfallPixmap.height(); // Auto mode } void CPlotter::setFftRate(int rate_hz) { fft_rate = rate_hz; clearWaterfall(); } // Called when a mouse button is pressed void CPlotter::mousePressEvent(QMouseEvent * event) { QPoint pt = event->pos(); if (NOCAP == m_CursorCaptured) { if (isPointCloseTo(pt.x(), m_DemodFreqX, m_CursorCaptureDelta)) { // 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; 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); drawOverlay(); } else if (event->buttons() == Qt::RightButton) { // reset frequency zoom resetHorizontalZoom(); } } } else { if (m_CursorCaptured == YAXIS) // get ready for moving Y axis m_Yzero = pt.y(); else if (m_CursorCaptured == XAXIS) { m_Xzero = pt.x(); if (event->buttons() == Qt::RightButton) { // reset frequency zoom resetHorizontalZoom(); } } else if (m_CursorCaptured == BOOKMARK) { for (int i = 0; i < m_BookmarkTags.size(); i++) { if (m_BookmarkTags[i].first.contains(event->pos())) { m_DemodCenterFreq = m_BookmarkTags[i].second; emit newDemodFreq(m_DemodCenterFreq, m_DemodCenterFreq - m_CenterFreq); break; } } } } } void CPlotter::mouseReleaseEvent(QMouseEvent * event) { QPoint pt = event->pos(); if (!m_OverlayPixmap.rect().contains(pt)) { // not in Overlay region if (NOCAP != m_CursorCaptured) setCursor(QCursor(Qt::ArrowCursor)); m_CursorCaptured = NOCAP; 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; } } } // Make a single zoom step on the X axis. void CPlotter::zoomStepX(float step, int x) { // calculate new range shown on FFT float new_range = qBound(10.0f, (float)(m_Span) * step, (float)(m_SampleFreq) * 10.0f); // Frequency where event occured is kept fixed under mouse float ratio = (float)x / (float)m_OverlayPixmap.width(); float fixed_hz = freqFromX(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); float factor = (float)m_SampleFreq / (float)m_Span; emit newZoomLevel(factor); qDebug() << QString("Spectrum zoom: %1x").arg(factor, 0, 'f', 1); m_PeakHoldValid = false; } // Zoom on X axis (absolute level) void CPlotter::zoomOnXAxis(float level) { float current_level = (float)m_SampleFreq / (float)m_Span; zoomStepX(current_level / level, xFromFreq(m_DemodCenterFreq)); } // 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 = m_PandMaxdB - m_PandMindB; float y_range = (float)m_OverlayPixmap.height(); float db_per_pix = db_range / y_range; float fixed_db = m_PandMaxdB - pt.y() * db_per_pix; db_range = qBound(10.f, db_range * zoom_fac, FFT_MAX_DB - FFT_MIN_DB); m_PandMaxdB = fixed_db + ratio * db_range; if (m_PandMaxdB > FFT_MAX_DB) m_PandMaxdB = FFT_MAX_DB; m_PandMindB = m_PandMaxdB - db_range; m_PeakHoldValid = false; emit pandapterRangeChanged(m_PandMindB, m_PandMaxdB); } else if (m_CursorCaptured == XAXIS) { zoomStepX(event->delta() < 0 ? 1.1 : 0.9, pt.x()); } 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); } updateOverlay(); } // 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 int fft_plot_height; m_Size = size(); fft_plot_height = m_Percent2DScreen * m_Size.height() / 100; m_OverlayPixmap = QPixmap(m_Size.width(), fft_plot_height); m_OverlayPixmap.fill(Qt::black); m_2DPixmap = QPixmap(m_Size.width(), fft_plot_height); 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; if (wf_span > 0) msec_per_wfline = wf_span / height; memset(m_wfbuf, 255, MAX_SCREENSIZE); } 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); } // 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; } 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) { quint64 tnow_ms = time_ms(); // get scaled FFT data n = qMin(w, MAX_SCREENSIZE); getScreenIntegerFFTData(255, n, m_WfMaxdB, m_WfMindB, m_FftCenter - (qint64)m_Span / 2, m_FftCenter + (qint64)m_Span / 2, m_wfData, m_fftbuf, &xmin, &xmax); if (msec_per_wfline > 0) { // not in "auto" mode, so accumulate waterfall data for (i = 0; i < n; i++) { // average //m_wfbuf[i] = (m_wfbuf[i] + m_fftbuf[i]) / 2; // peak (0..255 where 255 is min) if (m_fftbuf[i] < m_wfbuf[i]) m_wfbuf[i] = m_fftbuf[i]; } } // is it time to update waterfall? if (tnow_ms - tlast_wf_ms >= msec_per_wfline) { tlast_wf_ms = tnow_ms; // 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); // 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); if (msec_per_wfline > 0) { // user set time span for (i = xmin; i < xmax; i++) { painter1.setPen(m_ColorTbl[255 - m_wfbuf[i]]); painter1.drawPoint(i, 0); m_wfbuf[i] = 255; } } else { 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_PandMaxdB, m_PandMindB, 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) { painter2.setBrush(QBrush(m_FftFillCol, Qt::SolidPattern)); 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(); float mean = 0; float 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; float 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 float 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(); } /** * 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 both the * pandapter and the waterfall. */ void CPlotter::setNewFttData(float *fftData, int size) { /** FIXME **/ if (!m_Running) m_Running = true; m_wfData = fftData; m_fftData = fftData; m_fftDataSize = size; draw(); } /** * 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(float *fftData, float *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, float maxdB, float mindB, qint64 startFreq, qint64 stopFreq, float *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; float *m_pFFTAveBuf = inBuf; float dBGainFactor = ((float)plotHeight) / fabs(maxdB - mindB); qint32* m_pTranslateTbl = new qint32[qMax(m_FFTSize, plotWidth)]; /** FIXME: qint64 -> qint32 **/ m_BinMin = (qint32)((float)startFreq * (float)m_FFTSize / m_SampleFreq); m_BinMin += (m_FFTSize/2); m_BinMax = (qint32)((float)stopFreq * (float)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] = ((qint64)(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 if(i < 0 || i >= m_FFTSize) y = plotHeight; else y = (qint32)(dBGainFactor*(maxdB-m_pFFTAveBuf[i])); if (y > plotHeight) y = plotHeight; else if (y < 0) y = 0; outBuf[x] = y; } } delete [] m_pTranslateTbl; } void CPlotter::setFftRange(float min, float max) { setWaterfallRange(min, max); setPandapterRange(min, max); } void CPlotter::setPandapterRange(float min, float max) { if (out_of_range(min, max)) return; m_PandMindB = min; m_PandMaxdB = max; updateOverlay(); m_PeakHoldValid = false; } void CPlotter::setWaterfallRange(float min, float max) { if (out_of_range(min, max)) return; m_WfMindB = min; m_WfMaxdB = max; // no overlay change is necessary } // 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; float adjoffset; float dbstepsize; float mindbadj; QRect rect; QFontMetrics metrics(m_Font); QPainter painter(&m_OverlayPixmap); painter.initFrom(this); painter.setFont(m_Font); // solid background painter.setBrush(Qt::SolidPattern); painter.fillRect(0, 0, w, h, QColor(PLOTTER_BGD_COLOR)); #define HOR_MARGIN 5 #define VER_MARGIN 5 // X and Y axis areas m_YAxisWidth = metrics.width("XXXX") + 2 * HOR_MARGIN; m_XAxisYCenter = h - metrics.height()/2; int xAxisHeight = metrics.height() + 2 * VER_MARGIN; int xAxisTop = h - xAxisHeight; int fLabelTop = xAxisTop + VER_MARGIN; if (m_BookmarksEnabled) { m_BookmarkTags.clear(); static const QFontMetrics fm(painter.font()); static const int fontHeight = fm.ascent() + 1; static const int slant = 5; static const int levelHeight = fontHeight + 5; static const int nLevels = 10; QList bookmarks = Bookmarks::Get().getBookmarksInRange(m_CenterFreq + m_FftCenter - m_Span / 2, m_CenterFreq + m_FftCenter + m_Span / 2); int tagEnd[nLevels] = {0}; for (int i = 0; i < bookmarks.size(); i++) { x = xFromFreq(bookmarks[i].frequency); #if defined(_WIN16) || defined(_WIN32) || defined(_WIN64) int nameWidth = fm.width(bookmarks[i].name); #else int nameWidth = fm.boundingRect(bookmarks[i].name).width(); #endif int level = 0; while(level < nLevels && tagEnd[level] > x) level++; if(level == nLevels) level = 0; tagEnd[level] = x + nameWidth + slant - 1; m_BookmarkTags.append(qMakePair(QRect(x, level * levelHeight, nameWidth + slant, fontHeight), bookmarks[i].frequency)); QColor color = QColor(bookmarks[i].GetColor()); color.setAlpha(0x60); // Vertical line painter.setPen(QPen(color, 1, Qt::DashLine)); painter.drawLine(x, level * levelHeight + fontHeight + slant, x, xAxisTop); // Horizontal line painter.setPen(QPen(color, 1, Qt::SolidLine)); painter.drawLine(x + slant, level * levelHeight + fontHeight, x + nameWidth + slant - 1, level * levelHeight + fontHeight); // Diagonal line painter.drawLine(x + 1, level * levelHeight + fontHeight + slant - 1, x + slant - 1, level * levelHeight + fontHeight + 1); color.setAlpha(0xFF); painter.setPen(QPen(color, 2, Qt::SolidLine)); painter.drawText(x + slant, level * levelHeight, nameWidth, fontHeight, Qt::AlignVCenter | Qt::AlignHCenter, bookmarks[i].name); } } if (m_CenterLineEnabled) { x = xFromFreq(m_CenterFreq); if (x > 0 && x < w) { painter.setPen(QColor(PLOTTER_CENTER_LINE_COLOR)); painter.drawLine(x, 0, x, xAxisTop); } } // Frequency grid qint64 StartFreq = m_CenterFreq + m_FftCenter - m_Span / 2; QString label; label.setNum(float((StartFreq + m_Span) / m_FreqUnits), 'f', m_FreqDigits); calcDivSize(StartFreq, StartFreq + m_Span, qMin(w/(metrics.width(label) + metrics.width("O")), HORZ_DIVS_MAX), m_StartFreqAdj, m_FreqPerDiv, m_HorDivs); pixperdiv = (float)w * (float) m_FreqPerDiv / (float) m_Span; adjoffset = pixperdiv * float (m_StartFreqAdj - StartFreq) / (float) m_FreqPerDiv; painter.setPen(QPen(QColor(PLOTTER_GRID_COLOR), 1, Qt::DotLine)); for (int i = 0; i <= m_HorDivs; i++) { x = (int)((float)i * pixperdiv + adjoffset); if (x > m_YAxisWidth) painter.drawLine(x, 0, x, xAxisTop); } // draw frequency values (x axis) makeFrequencyStrs(); painter.setPen(QColor(PLOTTER_TEXT_COLOR)); for (int i = 0; i <= m_HorDivs; i++) { int tw = metrics.width(m_HDivText[i]); x = (int)((float)i*pixperdiv + adjoffset); if (x > m_YAxisWidth) { rect.setRect(x - tw/2, fLabelTop, tw, metrics.height()); painter.drawText(rect, Qt::AlignHCenter|Qt::AlignBottom, m_HDivText[i]); } } // Level grid qint64 mindBAdj64 = 0; qint64 dbDivSize = 0; calcDivSize((qint64) m_PandMindB, (qint64) m_PandMaxdB, qMax(h/m_VdivDelta, VERT_DIVS_MIN), mindBAdj64, dbDivSize, m_VerDivs); dbstepsize = (float) dbDivSize; mindbadj = mindBAdj64; pixperdiv = (float) h * (float) dbstepsize / (m_PandMaxdB - m_PandMindB); adjoffset = (float) h * (mindbadj - m_PandMindB) / (m_PandMaxdB - m_PandMindB); #ifdef PLOTTER_DEBUG qDebug() << "minDb =" << m_PandMindB << "maxDb =" << m_PandMaxdB << "mindbadj =" << mindbadj << "dbstepsize =" << dbstepsize << "pixperdiv =" << pixperdiv << "adjoffset =" << adjoffset; #endif painter.setPen(QPen(QColor(PLOTTER_GRID_COLOR), 1, Qt::DotLine)); for (int i = 0; i <= m_VerDivs; i++) { y = h - (int)((float) i * pixperdiv + adjoffset); if (y < h - xAxisHeight) painter.drawLine(m_YAxisWidth, y, w, y); } // draw amplitude values (y axis) int dB = m_PandMaxdB; m_YAxisWidth = metrics.width("-120 "); painter.setPen(QColor(PLOTTER_TEXT_COLOR)); for (int i = 0; i < m_VerDivs; i++) { y = h - (int)((float) i * pixperdiv + adjoffset); int th = metrics.height(); if (y < h -xAxisHeight) { dB = mindbadj + dbstepsize * i; rect.setRect(HOR_MARGIN, y - th / 2, m_YAxisWidth, th); painter.drawText(rect, Qt::AlignRight|Qt::AlignVCenter, QString::number(dB)); } } // Draw demod filter box if (m_FilterBoxEnabled) { 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.setOpacity(0.3); painter.fillRect(m_DemodLowCutFreqX, 0, dw, h, QColor(PLOTTER_FILTER_BOX_COLOR)); painter.setOpacity(1.0); painter.setPen(QColor(PLOTTER_FILTER_LINE_COLOR)); painter.drawLine(m_DemodFreqX, 0, m_DemodFreqX, h); } 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(); } painter.end(); } // Create frequency division strings based on start frequency, span frequency, // and frequency units. // Places in QString array m_HDivText // Keeps all strings the same fractional length void CPlotter::makeFrequencyStrs() { qint64 StartFreq = m_StartFreqAdj; 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 += m_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 += m_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_StartFreqAdj; for (i = 0; i <= m_HorDivs; i++) { freq = (float)StartFreq/(float)m_FreqUnits; m_HDivText[i].setNum(freq,'f', max); StartFreq += m_FreqPerDiv; } } // Convert from screen coordinate 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; } // Convert from frequency to screen coordinate 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; } /** Calculate time offset of a given line on the waterfall */ quint64 CPlotter::msecFromY(int y) { // ensure we are in the waterfall region if (y < m_OverlayPixmap.height()) return 0; int dy = y - m_OverlayPixmap.height(); if (msec_per_wfline > 0) return tlast_wf_ms - dy * msec_per_wfline; else return tlast_wf_ms - dy * 1000 / fft_rate; } // 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); } // Clamp 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(); updateOverlay(); } void CPlotter::setCenterFreq(quint64 f) { if((quint64)m_CenterFreq == f) return; qint64 offset = m_CenterFreq - m_DemodCenterFreq; m_CenterFreq = f; m_DemodCenterFreq = m_CenterFreq - offset; updateOverlay(); m_PeakHoldValid = false; } // Ensure overlay is updated by either scheduling or forcing a redraw void CPlotter::updateOverlay() { if (m_Running) m_DrawOverlay = true; else drawOverlay(); } /** Reset horizontal zoom to 100% and centered around 0. */ void CPlotter::resetHorizontalZoom(void) { setFftCenterFreq(0); setSpanFreq((qint32)m_SampleFreq); } /** Center FFT plot around 0 (corresponds to center freq). */ void CPlotter::moveToCenterFreq(void) { setFftCenterFreq(0); updateOverlay(); m_PeakHoldValid = false; } /** Center FFT plot around the demodulator frequency. */ void CPlotter::moveToDemodFreq(void) { setFftCenterFreq(m_DemodCenterFreq-m_CenterFreq); updateOverlay(); m_PeakHoldValid = false; } /** Set FFT plot color. */ void CPlotter::setFftPlotColor(const QColor color) { m_FftColor = color; m_FftFillCol = color; m_FftFillCol.setAlpha(0x1A); m_PeakHoldColor = color; m_PeakHoldColor.setAlpha(60); } /** Enable/disable filling the area below the FFT plot. */ void CPlotter::setFftFill(bool enabled) { m_FftFill = enabled; } /** Set peak hold on or off. */ void CPlotter::setPeakHold(bool enabled) { m_PeakHoldActive = enabled; m_PeakHoldValid = false; } /** * 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, float c) { if(!enabled || c <= 0) m_PeakDetection = -1; else m_PeakDetection = c; } void CPlotter::calcDivSize (qint64 low, qint64 high, int divswanted, qint64 &adjlow, qint64 &step, int& divs) { #ifdef PLOTTER_DEBUG qDebug() << "low: " << low; qDebug() << "high: " << high; qDebug() << "divswanted: " << divswanted; #endif if (divswanted == 0) return; static const qint64 stepTable[] = { 1, 2, 5 }; static const int stepTableSize = sizeof (stepTable) / sizeof (stepTable[0]); qint64 multiplier = 1; step = 1; divs = high - low; int index = 0; while (divs > divswanted) { step = stepTable[index] * multiplier; divs = int ((high - low) / step); adjlow = (low / step) * step; index = index + 1; if (index == stepTableSize) { index = 0; multiplier = multiplier * 10; } } if (adjlow < low) adjlow += step; #ifdef PLOTTER_DEBUG qDebug() << "adjlow: " << adjlow; qDebug() << "step: " << step; qDebug() << "divs: " << divs; #endif } gqrx-2.9/src/qtgui/plotter.h000066400000000000000000000220501320142145500161230ustar00rootroot00000000000000/* -*- c++ -*- */ #ifndef PLOTTER_H #define PLOTTER_H #include #include #include #include #include #include #define HORZ_DIVS_MAX 12 //50 #define VERT_DIVS_MIN 5 #define MAX_SCREENSIZE 16384 #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 setTooltipsEnabled(bool enabled) { m_TooltipsEnabled = enabled; } void setBookmarksEnabled(bool enabled) { m_BookmarksEnabled = enabled; } void setNewFttData(float *fftData, int size); void setNewFttData(float *fftData, float *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; } int getFilterBw() { return m_DemodHiCutFreq - m_DemodLowCutFreq; } void setHiLowCutFrequencies(int LowCut, int HiCut) { m_DemodLowCutFreq = LowCut; m_DemodHiCutFreq = HiCut; drawOverlay(); } void getHiLowCutFrequencies(int *LowCut, int *HiCut) { *LowCut = m_DemodLowCutFreq; *HiCut = m_DemodHiCutFreq; } 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 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(float rate) { if (rate > 0.0) { m_SampleFreq = rate; drawOverlay(); } } float 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); void setWaterfallSpan(quint64 span_ms); quint64 getWfTimeRes(void); void setFftRate(int rate_hz); void clearWaterfall(void); bool saveWaterfall(const QString & filename) const; 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); /* substitute for NewLow / NewHigh */ void pandapterRangeChanged(float min, float max); void newZoomLevel(float level); public slots: // zoom functions void resetHorizontalZoom(void); void moveToCenterFreq(void); void moveToDemodFreq(void); void zoomOnXAxis(float level); // other FFT slots void setFftPlotColor(const QColor color); void setFftFill(bool enabled); void setPeakHold(bool enabled); void setFftRange(float min, float max); void setPandapterRange(float min, float max); void setWaterfallRange(float min, float max); void setPeakDetection(bool enabled, float c); void updateOverlay(); void setPercent2DScreen(int percent) { m_Percent2DScreen = percent; m_Size = QSize(0,0); resizeEvent(NULL); } 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 { NOCAP, LEFT, CENTER, RIGHT, YAXIS, XAXIS, BOOKMARK }; void drawOverlay(); void makeFrequencyStrs(); int xFromFreq(qint64 freq); qint64 freqFromX(int x); void zoomStepX(float factor, int x); qint64 roundFreq(qint64 freq, int resolution); quint64 msecFromY(int y); void clampDemodParameters(); bool isPointCloseTo(int x, int xr, int delta) { return ((x > (xr - delta)) && (x < (xr + delta))); } void getScreenIntegerFFTData(qint32 plotHeight, qint32 plotWidth, float maxdB, float mindB, qint64 startFreq, qint64 stopFreq, float *inBuf, qint32 *outBuf, qint32 *maxbin, qint32 *minbin); void calcDivSize (qint64 low, qint64 high, int divswanted, qint64 &adjlow, qint64 &step, int& divs); bool m_PeakHoldActive; bool m_PeakHoldValid; qint32 m_fftbuf[MAX_SCREENSIZE]; quint8 m_wfbuf[MAX_SCREENSIZE]; // used for accumulating waterfall data at high time spans qint32 m_fftPeakHoldBuf[MAX_SCREENSIZE]; float *m_fftData; /*! pointer to incoming FFT data */ float *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; // The HW frequency qint64 m_FftCenter; // Center freq in the -span ... +span range qint64 m_DemodCenterFreq; qint64 m_StartFreqAdj; qint64 m_FreqPerDiv; bool m_CenterLineEnabled; /*!< Distinguish center line. */ bool m_FilterBoxEnabled; /*!< Draw filter box. */ bool m_TooltipsEnabled; /*!< Tooltips enabled */ bool m_BookmarksEnabled; /*!< Show/hide bookmarks on spectrum */ 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. */ float m_PandMindB; float m_PandMaxdB; float m_WfMindB; float m_WfMaxdB; qint64 m_Span; float 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. */ QFont m_Font; /*!< Font used for plotter (system font) */ 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_FftFillCol, m_PeakHoldColor; bool m_FftFill; float m_PeakDetection; QMap m_Peaks; QList< QPair > m_BookmarkTags; // Waterfall averaging quint64 tlast_wf_ms; // last time waterfall has been updated quint64 msec_per_wfline; // milliseconds between waterfall updates quint64 wf_span; // waterfall span in milliseconds (0 = auto) int fft_rate; // expected FFT rate (needed when WF span is auto) }; #endif // PLOTTER_H gqrx-2.9/src/qtgui/qtcolorpicker.cpp000066400000000000000000000663621320142145500176640ustar00rootroot00000000000000/**************************************************************************** ** ** 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-2.9/src/qtgui/qtcolorpicker.h000066400000000000000000000076241320142145500173250ustar00rootroot00000000000000/**************************************************************************** ** ** 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-2.9/src/receivers/000077500000000000000000000000001320142145500151205ustar00rootroot00000000000000gqrx-2.9/src/receivers/CMakeLists.txt000066400000000000000000000004031320142145500176550ustar00rootroot00000000000000####################################################################################################################### # Add the source files to SRCS_LIST add_source_files(SRCS_LIST nbrx.cpp nbrx.h receiver_base.cpp receiver_base.h wfmrx.cpp wfmrx.h ) gqrx-2.9/src/receivers/nbrx.cpp000066400000000000000000000140751320142145500166040ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2016 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 "receivers/nbrx.h" // NB: Remeber to adjust filter ranges in MainWindow #define PREF_QUAD_RATE 96000.f 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, 0, 500, false); sql = gr::analog::simple_squelch_cc::make(-150.0, 0.001); meter = make_rx_meter_c(DETECTOR_TYPE_RMS); demod_raw = gr::blocks::complex_to_float::make(1); demod_ssb = gr::blocks::complex_to_real::make(1); demod_fm = make_rx_demod_fm(PREF_QUAD_RATE, 5000.0, 75.0e-6); demod_am = make_rx_demod_am(PREF_QUAD_RATE, true); audio_rr.reset(); if (d_audio_rate != PREF_QUAD_RATE) { std::cout << "Resampling audio " << PREF_QUAD_RATE << " -> " << d_audio_rate << std::endl; audio_rr = make_resampler_ff(d_audio_rate/PREF_QUAD_RATE); } demod = demod_fm; 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, 0); if (audio_rr) { connect(demod, 0, audio_rr, 0); connect(audio_rr, 0, self(), 0); // left channel connect(audio_rr, 0, self(), 1); // right channel } else { connect(demod, 0, self(), 0); connect(demod, 0, self(), 1); } } 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 (std::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; std::cout << "**** FIXME: nbrx::set_audio_rate() not implemented" << std::endl; } void nbrx::set_filter(double low, double high, double tw) { filter->set_param(low, high, tw); } void nbrx::set_cw_offset(double offset) { filter->set_cw_offset(offset); } 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; } disconnect(agc, 0, demod, 0); if (audio_rr) disconnect(demod, 0, audio_rr, 0); else { disconnect(demod, 0, self(), 0); disconnect(demod, 0, self(), 1); } switch (rx_demod) { case NBRX_DEMOD_NONE: d_demod = NBRX_DEMOD_NONE; demod = demod_raw; break; case NBRX_DEMOD_SSB: d_demod = NBRX_DEMOD_SSB; demod = demod_ssb; break; case NBRX_DEMOD_AM: d_demod = NBRX_DEMOD_AM; demod = demod_am; break; case NBRX_DEMOD_FM: default: d_demod = NBRX_DEMOD_FM; demod = demod_fm; break; } connect(agc, 0, demod, 0); if (audio_rr) { // FIXME: DEMOD_NONE has two outputs. connect(demod, 0, audio_rr, 0); } else if (d_demod == NBRX_DEMOD_NONE) { connect(demod, 0, self(), 0); connect(demod, 1, self(), 1); } else { connect(demod, 0, self(), 0); connect(demod, 0, self(), 1); } } 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-2.9/src/receivers/nbrx.h000066400000000000000000000104561320142145500162500ustar00rootroot00000000000000/* -*- c++ -*- */ /* * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt * http://gqrx.dk/ * * Copyright 2011-2016 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 #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); virtual ~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); void set_cw_offset(double offset); 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_float::sptr demod_raw; /*!< Raw I/Q passthrough. */ 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. */ gr::basic_block_sptr demod; // dummy pointer used for simplifying reconf }; #endif // NBRX_H gqrx-2.9/src/receivers/receiver_base.cpp000066400000000000000000000061301320142145500204220ustar00rootroot00000000000000/* -*- 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; } void receiver_base_cf::get_rds_data(std::string &outbuff, int &num) { (void) outbuff; (void) num; } void receiver_base_cf::start_rds_decoder() { } void receiver_base_cf::stop_rds_decoder() { } void receiver_base_cf::reset_rds_parser() { } bool receiver_base_cf::is_rds_decoder_active() { return false; } gqrx-2.9/src/receivers/receiver_base.h000066400000000000000000000057601320142145500200770ustar00rootroot00000000000000/* -*- 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); virtual ~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 void set_cw_offset(double offset) = 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); virtual void get_rds_data(std::string &outbuff, int &num); virtual void start_rds_decoder(); virtual void stop_rds_decoder(); virtual void reset_rds_parser(); virtual bool is_rds_decoder_active(); }; #endif // RECEIVER_BASE_H gqrx-2.9/src/receivers/wfmrx.cpp000066400000000000000000000157031320142145500167750ustar00rootroot00000000000000/* -*- 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 "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, 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); stereo_oirt = make_stereo_demod(PREF_MIDLE_RATE, d_audio_rate, true, true); mono = make_stereo_demod(PREF_MIDLE_RATE, d_audio_rate, false); /* create rds blocks but dont connect them */ rds = make_rx_rds(PREF_QUAD_RATE); rds_decoder = gr::rds::decoder::make(0, 0); rds_parser = gr::rds::parser::make(0, 0); rds_store = make_rx_rds_store(); rds_enabled = 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 (std::abs(d_quad_rate-quad_rate) > 0.5) { #ifndef QT_NO_DEBUG_OUTPUT std::cerr << "Changing WFM 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: disconnect(midle_rr, 0, stereo, 0); disconnect(stereo, 0, self(), 0); // left channel disconnect(stereo, 1, self(), 1); // right channel break; case WFMRX_DEMOD_STEREO_UKW: disconnect(midle_rr, 0, stereo_oirt, 0); disconnect(stereo_oirt, 0, self(), 0); // left channel disconnect(stereo_oirt, 1, self(), 1); // right channel break; } 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: connect(midle_rr, 0, stereo, 0); connect(stereo, 0, self(), 0); // left channel connect(stereo, 1, self(), 1); // right channel break; case WFMRX_DEMOD_STEREO_UKW: connect(midle_rr, 0, stereo_oirt, 0); connect(stereo_oirt, 0, self(), 0); // left channel connect(stereo_oirt, 1, self(), 1); // right channel break; } 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); } void wfmrx::get_rds_data(std::string &outbuff, int &num) { rds_store->get_message(outbuff, num); } void wfmrx::start_rds_decoder() { connect(demod_fm, 0, rds, 0); connect(rds, 0, rds_decoder, 0); msg_connect(rds_decoder, "out", rds_parser, "in"); msg_connect(rds_parser, "out", rds_store, "store"); rds_enabled=true; } void wfmrx::stop_rds_decoder() { lock(); disconnect(demod_fm, 0, rds, 0); disconnect(rds, 0, rds_decoder, 0); msg_disconnect(rds_decoder, "out", rds_parser, "in"); msg_disconnect(rds_parser, "out", rds_store, "store"); unlock(); rds_enabled=false; } void wfmrx::reset_rds_parser() { rds_parser->reset(); } bool wfmrx::is_rds_decoder_active() { return rds_enabled; } gqrx-2.9/src/receivers/wfmrx.h000066400000000000000000000104771320142145500164450ustar00rootroot00000000000000/* -*- 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" #include "dsp/rx_rds.h" #include "dsp/rds/decoder.h" #include "dsp/rds/parser.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); void set_cw_offset(double offset) { (void)offset; } 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); void get_rds_data(std::string &outbuff, int &num); void start_rds_decoder(); void stop_rds_decoder(); void reset_rds_parser(); bool is_rds_decoder_active(); 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 stereo_oirt; /*!< FM stereo oirt demodulator. */ stereo_demod_sptr mono; /*!< FM stereo demodulator OFF. */ rx_rds_sptr rds; /*!< RDS decoder */ rx_rds_store_sptr rds_store; /*!< RDS decoded messages */ gr::rds::decoder::sptr rds_decoder; gr::rds::parser::sptr rds_parser; bool rds_enabled; }; #endif // WFMRX_H