├── .clang-format ├── .gitignore ├── .travis.yml ├── AUTHORS ├── CMakeLists.txt ├── FindTinyXML.cmake ├── Findlibusb-1.0.cmake ├── Findrtlsdr.cmake ├── LICENSE.md ├── README.md ├── TODO ├── debian ├── changelog.in ├── compat ├── control ├── copyright ├── rules └── source │ └── format ├── depends └── common │ ├── libusb │ ├── 0001-make-private-libs-public.patch │ ├── CMakeLists.txt │ └── libusb.txt │ ├── rtl-sdr │ ├── 0001-static-only.patch │ ├── deps.txt │ └── rtl-sdr.txt │ └── tinyxml │ ├── CMakeLists.txt │ ├── tinyxml.sha256 │ └── tinyxml.txt ├── pvr.rtl.radiofm ├── PVRFMRadioAddonSettings.xml ├── addon.xml.in └── resources │ ├── fanart.jpg │ ├── language │ ├── resource.language.de_de │ │ └── strings.po │ └── resource.language.en_gb │ │ └── strings.po │ └── skins │ ├── skin.confluence │ └── 720p │ │ └── ChannelTuner.xml │ └── skin.estuary │ └── xml │ └── ChannelTuner.xml └── src ├── ChannelSettings.cpp ├── ChannelSettings.h ├── Definitions.h ├── DownConvert.cpp ├── DownConvert.h ├── Filter.cpp ├── Filter.h ├── FirFilter.cpp ├── FirFilter.h ├── FmDecode.cpp ├── FmDecode.h ├── FreqShift.cpp ├── FreqShift.h ├── IirFilter.cpp ├── IirFilter.h ├── RDSGroupDecoder.cpp ├── RDSGroupDecoder.h ├── RDSProcess.cpp ├── RDSProcess.h ├── RTL_SDR_Source.cpp ├── RTL_SDR_Source.h ├── RadioReceiver.cpp ├── RadioReceiver.h ├── Utils.cpp ├── Utils.h ├── XMLUtils.h └── filtercoef.h /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | # BasedOnStyle: LLVM 3 | AccessModifierOffset: -2 4 | AlignAfterOpenBracket: Align 5 | AlignConsecutiveAssignments: false 6 | AlignConsecutiveDeclarations: false 7 | AlignEscapedNewlines: DontAlign 8 | AlignOperands: true 9 | AlignTrailingComments: false 10 | AllowAllParametersOfDeclarationOnNextLine: true 11 | AllowShortBlocksOnASingleLine: false 12 | AllowShortCaseLabelsOnASingleLine: false 13 | AllowShortFunctionsOnASingleLine: InlineOnly 14 | AllowShortIfStatementsOnASingleLine: false 15 | AllowShortLoopsOnASingleLine: false 16 | AlwaysBreakAfterDefinitionReturnType: None 17 | AlwaysBreakAfterReturnType: None 18 | AlwaysBreakBeforeMultilineStrings: false 19 | AlwaysBreakTemplateDeclarations: true 20 | BinPackArguments: true 21 | BinPackParameters: false 22 | BreakBeforeBinaryOperators: None 23 | BreakBeforeBraces: Allman 24 | BreakBeforeTernaryOperators: true 25 | BreakConstructorInitializersBeforeComma: false 26 | BreakConstructorInitializers: BeforeColon 27 | BreakAfterJavaFieldAnnotations: false 28 | BreakStringLiterals: true 29 | ColumnLimit: 100 30 | CommentPragmas: '^ IWYU pragma:' 31 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 32 | ConstructorInitializerIndentWidth: 2 33 | ContinuationIndentWidth: 4 34 | Cpp11BracedListStyle: true 35 | DerivePointerAlignment: false 36 | DisableFormat: false 37 | ExperimentalAutoDetectBinPacking: false 38 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 39 | IncludeBlocks: Regroup 40 | IncludeCategories: 41 | - Regex: '^<[a-z0-9_]+>$' 42 | Priority: 3 43 | - Regex: '^<(assert|complex|ctype|errno|fenv|float|inttypes|iso646|limits|locale|math|setjmp|signal|stdalign|stdarg|stdatomic|stdbool|stddef|stdint|stdio|stdlib|stdnoreturn|string|tgmath|threads|time|uchar|wchar|wctype)\.h>$' 44 | Priority: 3 45 | - Regex: '^<' 46 | Priority: 3 47 | - Regex: '^["<](kodi|p8-platform)\/.*\.h[">]$' 48 | Priority: 2 49 | - Regex: '.*' 50 | Priority: 1 51 | IncludeIsMainRegex: '$' 52 | IndentCaseLabels: true 53 | IndentWidth: 2 54 | IndentWrappedFunctionNames: false 55 | JavaScriptQuotes: Leave 56 | JavaScriptWrapImports: true 57 | KeepEmptyLinesAtTheStartOfBlocks: true 58 | MacroBlockBegin: '' 59 | MacroBlockEnd: '' 60 | MaxEmptyLinesToKeep: 2 61 | NamespaceIndentation: None 62 | ObjCBlockIndentWidth: 2 63 | ObjCSpaceAfterProperty: false 64 | ObjCSpaceBeforeProtocolList: true 65 | PenaltyBreakBeforeFirstCallParameter: 19 66 | PenaltyBreakComment: 300 67 | PenaltyBreakFirstLessLess: 120 68 | PenaltyBreakString: 1000 69 | PenaltyExcessCharacter: 1000000 70 | PenaltyReturnTypeOnItsOwnLine: 60000 71 | PointerAlignment: Left 72 | ReflowComments: false 73 | SortIncludes: true 74 | SpaceAfterCStyleCast: false 75 | SpaceAfterTemplateKeyword: false 76 | SpaceBeforeAssignmentOperators: true 77 | SpaceBeforeParens: ControlStatements 78 | SpaceInEmptyParentheses: false 79 | SpacesBeforeTrailingComments: 1 80 | SpacesInAngles: false 81 | SpacesInContainerLiterals: true 82 | SpacesInCStyleCastParentheses: false 83 | SpacesInParentheses: false 84 | SpacesInSquareBrackets: false 85 | Standard: Cpp11 86 | TabWidth: 8 87 | UseTab: Never 88 | ... 89 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # build artifacts 2 | build/ 3 | pvr.*/addon.xml 4 | 5 | # Debian build files 6 | debian/changelog 7 | debian/files 8 | debian/*.log 9 | debian/*.substvars 10 | debian/.debhelper/ 11 | debian/tmp/ 12 | debian/kodi-pvr-*/ 13 | obj-x86_64-linux-gnu/ 14 | 15 | # commonly used editors 16 | # vim 17 | *.swp 18 | 19 | # Eclipse 20 | *.project 21 | *.cproject 22 | .classpath 23 | *.sublime-* 24 | .settings/ 25 | 26 | # KDevelop 4 27 | *.kdev4 28 | 29 | # gedit 30 | *~ 31 | 32 | # CLion 33 | /.idea 34 | 35 | # clion 36 | .idea/ 37 | 38 | # to prevent add after a "git format-patch VALUE" and "git add ." call 39 | /*.patch 40 | 41 | # Visual Studio Code 42 | .vscode 43 | 44 | # to prevent add if project code opened by Visual Studio over CMake file 45 | .vs/ 46 | 47 | # General MacOS 48 | .DS_Store 49 | .AppleDouble 50 | .LSOverride 51 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | 3 | env: 4 | global: 5 | - app_id=pvr.rtl.radiofm 6 | 7 | matrix: 8 | include: 9 | - os: linux 10 | dist: xenial 11 | sudo: required 12 | compiler: gcc 13 | - os: linux 14 | dist: xenial 15 | sudo: required 16 | compiler: clang 17 | - os: linux 18 | dist: bionic 19 | sudo: required 20 | compiler: gcc 21 | env: DEBIAN_BUILD=true 22 | - os: linux 23 | dist: focal 24 | sudo: required 25 | compiler: gcc 26 | env: DEBIAN_BUILD=true 27 | - os: osx 28 | osx_image: xcode10.2 29 | 30 | before_install: 31 | - if [[ $DEBIAN_BUILD != true ]] && [[ $TRAVIS_OS_NAME == linux ]]; then sudo apt-get install -y libudev-dev; fi 32 | - if [[ $DEBIAN_BUILD == true ]]; then sudo add-apt-repository -y ppa:team-xbmc/xbmc-nightly; fi 33 | - if [[ $DEBIAN_BUILD == true ]]; then sudo apt-get update; fi 34 | - if [[ $DEBIAN_BUILD == true ]]; then sudo apt-get install fakeroot; fi 35 | 36 | # 37 | # The addon source is automatically checked out in $TRAVIS_BUILD_DIR, 38 | # we'll put the Kodi source on the same level 39 | # 40 | before_script: 41 | - if [[ $DEBIAN_BUILD != true ]]; then cd $TRAVIS_BUILD_DIR/..; fi 42 | - if [[ $DEBIAN_BUILD != true ]]; then git clone --branch master --depth=1 https://github.com/xbmc/xbmc.git; fi 43 | - if [[ $DEBIAN_BUILD != true ]]; then mkdir -p $TRAVIS_BUILD_DIR/../xbmc/cmake/addons/${app_id}; fi 44 | - if [[ $DEBIAN_BUILD != true ]]; then echo "${app_id} . ." > $TRAVIS_BUILD_DIR/../xbmc/cmake/addons/${app_id}/${app_id}.txt; fi 45 | - if [[ $DEBIAN_BUILD != true ]]; then echo "all" > $TRAVIS_BUILD_DIR/../xbmc/cmake/addons/${app_id}/platforms.txt; fi 46 | - if [[ $DEBIAN_BUILD != true ]]; then cd ${app_id} && mkdir build && cd build; fi 47 | - if [[ $DEBIAN_BUILD != true ]]; then mkdir -p definition/${app_id}; fi 48 | - if [[ $DEBIAN_BUILD != true ]]; then echo ${app_id} $TRAVIS_BUILD_DIR $TRAVIS_COMMIT > definition/${app_id}/${app_id}.txt; fi 49 | - if [[ $DEBIAN_BUILD != true ]]; then cmake -DADDONS_TO_BUILD=${app_id} -DADDON_SRC_PREFIX=$TRAVIS_BUILD_DIR/.. -DADDONS_DEFINITION_DIR=$TRAVIS_BUILD_DIR/build/definition -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=$TRAVIS_BUILD_DIR/../xbmc/addons -DPACKAGE_ZIP=1 $TRAVIS_BUILD_DIR/../xbmc/cmake/addons; fi 50 | - if [[ $DEBIAN_BUILD == true ]]; then wget https://raw.githubusercontent.com/xbmc/xbmc/master/xbmc/addons/kodi-dev-kit/tools/debian-addon-package-test.sh && chmod +x ./debian-addon-package-test.sh; fi 51 | - if [[ $DEBIAN_BUILD == true ]]; then sudo apt-get build-dep $TRAVIS_BUILD_DIR; fi 52 | 53 | script: 54 | - if [[ $DEBIAN_BUILD != true ]]; then make; fi 55 | - if [[ $DEBIAN_BUILD == true ]]; then ./debian-addon-package-test.sh $TRAVIS_BUILD_DIR; fi 56 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Team XBMC 2 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(pvr.rtl.radiofm) 3 | 4 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}) 5 | 6 | find_package(rtlsdr REQUIRED) 7 | find_package(TinyXML REQUIRED) 8 | find_package(Kodi REQUIRED) 9 | find_package(libusb-1.0 REQUIRED) 10 | 11 | include_directories(${rtlsdr_INCLUDE_DIRS} 12 | ${KODI_INCLUDE_DIR}/.. 13 | ${TINYXML_INCLUDE_DIRS} 14 | ${LIBUSB_1_INCLUDE_DIRS}) 15 | 16 | set(BASIC_SOURCES src/RadioReceiver.cpp 17 | src/RTL_SDR_Source.cpp 18 | src/Filter.cpp 19 | src/FirFilter.cpp 20 | src/IirFilter.cpp 21 | src/FmDecode.cpp 22 | src/DownConvert.cpp 23 | src/FreqShift.cpp 24 | src/RDSGroupDecoder.cpp 25 | src/RDSProcess.cpp 26 | src/ChannelSettings.cpp 27 | src/Utils.cpp) 28 | 29 | set(BASIC_HEADERS src/ChannelSettings.h 30 | src/Definitions.h 31 | src/DownConvert.h 32 | src/Filter.h 33 | src/FirFilter.h 34 | src/FmDecode.h 35 | src/FreqShift.h 36 | src/IirFilter.h 37 | src/RadioReceiver.h 38 | src/RDSGroupDecoder.h 39 | src/RDSProcess.h 40 | src/RTL_SDR_Source.h 41 | src/Utils.h 42 | src/XMLUtils.h 43 | src/filtercoef.h) 44 | 45 | if(CORE_SYSTEM_NAME STREQUAL osx OR 46 | CORE_SYSTEM_NAME STREQUAL darwin_embedded) 47 | find_library(COREFOUNDATION CoreFoundation) 48 | find_library(IOK IOKit) 49 | 50 | list(APPEND DEPLIBS ${COREFOUNDATION} ${IOK}) 51 | set(CMAKE_EXE_LINKER_FLAGS "-lobjc -framework IOKit -framework CoreFoundation") 52 | endif() 53 | 54 | list(APPEND DEPLIBS ${TINYXML_LIBRARIES} 55 | ${RTLSDR_LIBRARIES} 56 | ${LIBUSB_1_LIBRARIES}) 57 | 58 | addon_version(pvr.rtl.radiofm RTL_RADIOFM) 59 | add_definitions(-DRTL_RADIOFM_VERSION=${RTL_RADIOFM_VERSION}) 60 | 61 | build_addon(pvr.rtl.radiofm BASIC DEPLIBS) 62 | 63 | include(CPack) 64 | -------------------------------------------------------------------------------- /FindTinyXML.cmake: -------------------------------------------------------------------------------- 1 | # - Find TinyXML 2 | # Find the native TinyXML includes and library 3 | # 4 | # TINYXML_FOUND - True if TinyXML found. 5 | # TINYXML_INCLUDE_DIR - where to find tinyxml.h, etc. 6 | # TINYXML_LIBRARIES - List of libraries when using TinyXML. 7 | # 8 | 9 | IF( TINYXML_INCLUDE_DIR ) 10 | # Already in cache, be silent 11 | SET( TinyXML_FIND_QUIETLY TRUE ) 12 | ENDIF( TINYXML_INCLUDE_DIR ) 13 | 14 | FIND_PATH( TINYXML_INCLUDE_DIR "tinyxml.h" 15 | PATH_SUFFIXES "tinyxml" ) 16 | 17 | FIND_LIBRARY( TINYXML_LIBRARIES 18 | NAMES "tinyxml" 19 | PATH_SUFFIXES "tinyxml" ) 20 | 21 | # handle the QUIETLY and REQUIRED arguments and set TINYXML_FOUND to TRUE if 22 | # all listed variables are TRUE 23 | INCLUDE( "FindPackageHandleStandardArgs" ) 24 | FIND_PACKAGE_HANDLE_STANDARD_ARGS( "TinyXML" DEFAULT_MSG TINYXML_INCLUDE_DIR TINYXML_LIBRARIES ) 25 | 26 | MARK_AS_ADVANCED( TINYXML_INCLUDE_DIR TINYXML_LIBRARIES ) 27 | -------------------------------------------------------------------------------- /Findlibusb-1.0.cmake: -------------------------------------------------------------------------------- 1 | find_package(PkgConfig) 2 | if(PKG_CONFIG_FOUND) 3 | pkg_check_modules(PC_LIBUSB_1 libusb-1.0 QUIET) 4 | endif() 5 | 6 | find_path(LIBUSB_1_INCLUDE_DIRS libusb.h 7 | PATHS ${PC_LIBUSB_1_INCLUDEDIR} 8 | PATH_SUFFIXES libusb-1.0) 9 | find_library(LIBUSB_1_LIBRARIES usb-1.0 10 | PATHS ${PC_LIBUSB_1_LIBDIR}) 11 | 12 | include(FindPackageHandleStandardArgs) 13 | find_package_handle_standard_args(libusb-1.0 REQUIRED_VARS LIBUSB_1_LIBRARIES LIBUSB_1_INCLUDE_DIRS) 14 | 15 | mark_as_advanced(LIBUSB_1_INCLUDE_DIRS LIBUSB_1_LIBRARIES) 16 | -------------------------------------------------------------------------------- /Findrtlsdr.cmake: -------------------------------------------------------------------------------- 1 | find_package(PkgConfig) 2 | if(PKG_CONFIG_FOUND) 3 | pkg_check_modules(PC_RTLSDR rtlsdr QUIET) 4 | endif() 5 | 6 | find_path(RTLSDR_INCLUDE_DIRS rtl-sdr.h 7 | PATHS ${PC_RTLSDR_INCLUDEDIR}) 8 | find_library(RTLSDR_LIBRARIES rtlsdr 9 | PATHS ${PC_RTLSDR_LIBDIR}) 10 | 11 | include(FindPackageHandleStandardArgs) 12 | find_package_handle_standard_args(rtlsdr REQUIRED_VARS RTLSDR_LIBRARIES RTLSDR_INCLUDE_DIRS) 13 | 14 | mark_as_advanced(RTLSDR_INCLUDE_DIRS RTLSDR_LIBRARIES) 15 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The GNU General Public License, Version 2, June 1991 (GPLv2) 2 | ============================================================ 3 | 4 | > Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | > 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 6 | 7 | Everyone is permitted to copy and distribute verbatim copies of this license 8 | document, but changing it is not allowed. 9 | 10 | 11 | Preamble 12 | -------- 13 | 14 | The licenses for most software are designed to take away your freedom to share 15 | and change it. By contrast, the GNU General Public License is intended to 16 | guarantee your freedom to share and change free software--to make sure the 17 | software is free for all its users. This General Public License applies to most 18 | of the Free Software Foundation's software and to any other program whose 19 | authors commit to using it. (Some other Free Software Foundation software is 20 | covered by the GNU Lesser General Public License instead.) You can apply it to 21 | your programs, too. 22 | 23 | When we speak of free software, we are referring to freedom, not price. Our 24 | General Public Licenses are designed to make sure that you have the freedom to 25 | distribute copies of free software (and charge for this service if you wish), 26 | that you receive source code or can get it if you want it, that you can change 27 | the software or use pieces of it in new free programs; and that you know you can 28 | do these things. 29 | 30 | To protect your rights, we need to make restrictions that forbid anyone to deny 31 | you these rights or to ask you to surrender the rights. These restrictions 32 | translate to certain responsibilities for you if you distribute copies of the 33 | software, or if you modify it. 34 | 35 | For example, if you distribute copies of such a program, whether gratis or for a 36 | fee, you must give the recipients all the rights that you have. You must make 37 | sure that they, too, receive or can get the source code. And you must show them 38 | these terms so they know their rights. 39 | 40 | We protect your rights with two steps: (1) copyright the software, and (2) offer 41 | you this license which gives you legal permission to copy, distribute and/or 42 | modify the software. 43 | 44 | Also, for each author's protection and ours, we want to make certain that 45 | everyone understands that there is no warranty for this free software. If the 46 | software is modified by someone else and passed on, we want its recipients to 47 | know that what they have is not the original, so that any problems introduced by 48 | others will not reflect on the original authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software patents. We wish 51 | to avoid the danger that redistributors of a free program will individually 52 | obtain patent licenses, in effect making the program proprietary. To prevent 53 | this, we have made it clear that any patent must be licensed for everyone's free 54 | use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and modification 57 | follow. 58 | 59 | 60 | Terms And Conditions For Copying, Distribution And Modification 61 | --------------------------------------------------------------- 62 | 63 | **0.** This License applies to any program or other work which contains a notice 64 | placed by the copyright holder saying it may be distributed under the terms of 65 | this General Public License. The "Program", below, refers to any such program or 66 | work, and a "work based on the Program" means either the Program or any 67 | derivative work under copyright law: that is to say, a work containing the 68 | Program or a portion of it, either verbatim or with modifications and/or 69 | translated into another language. (Hereinafter, translation is included without 70 | limitation in the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not covered by 73 | this License; they are outside its scope. The act of running the Program is not 74 | restricted, and the output from the Program is covered only if its contents 75 | constitute a work based on the Program (independent of having been made by 76 | running the Program). Whether that is true depends on what the Program does. 77 | 78 | **1.** You may copy and distribute verbatim copies of the Program's source code 79 | as you receive it, in any medium, provided that you conspicuously and 80 | appropriately publish on each copy an appropriate copyright notice and 81 | disclaimer of warranty; keep intact all the notices that refer to this License 82 | and to the absence of any warranty; and give any other recipients of the Program 83 | a copy of this License along with the Program. 84 | 85 | You may charge a fee for the physical act of transferring a copy, and you may at 86 | your option offer warranty protection in exchange for a fee. 87 | 88 | **2.** You may modify your copy or copies of the Program or any portion of it, 89 | thus forming a work based on the Program, and copy and distribute such 90 | modifications or work under the terms of Section 1 above, provided that you also 91 | meet all of these conditions: 92 | 93 | * **a)** You must cause the modified files to carry prominent notices stating 94 | that you changed the files and the date of any change. 95 | 96 | * **b)** You must cause any work that you distribute or publish, that in whole 97 | or in part contains or is derived from the Program or any part thereof, to 98 | be licensed as a whole at no charge to all third parties under the terms of 99 | this License. 100 | 101 | * **c)** If the modified program normally reads commands interactively when 102 | run, you must cause it, when started running for such interactive use in the 103 | most ordinary way, to print or display an announcement including an 104 | appropriate copyright notice and a notice that there is no warranty (or 105 | else, saying that you provide a warranty) and that users may redistribute 106 | the program under these conditions, and telling the user how to view a copy 107 | of this License. (Exception: if the Program itself is interactive but does 108 | not normally print such an announcement, your work based on the Program is 109 | not required to print an announcement.) 110 | 111 | These requirements apply to the modified work as a whole. If identifiable 112 | sections of that work are not derived from the Program, and can be reasonably 113 | considered independent and separate works in themselves, then this License, and 114 | its terms, do not apply to those sections when you distribute them as separate 115 | works. But when you distribute the same sections as part of a whole which is a 116 | work based on the Program, the distribution of the whole must be on the terms of 117 | this License, whose permissions for other licensees extend to the entire whole, 118 | and thus to each and every part regardless of who wrote it. 119 | 120 | Thus, it is not the intent of this section to claim rights or contest your 121 | rights to work written entirely by you; rather, the intent is to exercise the 122 | right to control the distribution of derivative or collective works based on the 123 | Program. 124 | 125 | In addition, mere aggregation of another work not based on the Program with the 126 | Program (or with a work based on the Program) on a volume of a storage or 127 | distribution medium does not bring the other work under the scope of this 128 | License. 129 | 130 | **3.** You may copy and distribute the Program (or a work based on it, under 131 | Section 2) in object code or executable form under the terms of Sections 1 and 2 132 | above provided that you also do one of the following: 133 | 134 | * **a)** Accompany it with the complete corresponding machine-readable source 135 | code, which must be distributed under the terms of Sections 1 and 2 above on 136 | a medium customarily used for software interchange; or, 137 | 138 | * **b)** Accompany it with a written offer, valid for at least three years, to 139 | give any third party, for a charge no more than your cost of physically 140 | performing source distribution, a complete machine-readable copy of the 141 | corresponding source code, to be distributed under the terms of Sections 1 142 | and 2 above on a medium customarily used for software interchange; or, 143 | 144 | * **c)** Accompany it with the information you received as to the offer to 145 | distribute corresponding source code. (This alternative is allowed only for 146 | noncommercial distribution and only if you received the program in object 147 | code or executable form with such an offer, in accord with Subsection b 148 | above.) 149 | 150 | The source code for a work means the preferred form of the work for making 151 | modifications to it. For an executable work, complete source code means all the 152 | source code for all modules it contains, plus any associated interface 153 | definition files, plus the scripts used to control compilation and installation 154 | of the executable. However, as a special exception, the source code distributed 155 | need not include anything that is normally distributed (in either source or 156 | binary form) with the major components (compiler, kernel, and so on) of the 157 | operating system on which the executable runs, unless that component itself 158 | accompanies the executable. 159 | 160 | If distribution of executable or object code is made by offering access to copy 161 | from a designated place, then offering equivalent access to copy the source code 162 | from the same place counts as distribution of the source code, even though third 163 | parties are not compelled to copy the source along with the object code. 164 | 165 | **4.** You may not copy, modify, sublicense, or distribute the Program except as 166 | expressly provided under this License. Any attempt otherwise to copy, modify, 167 | sublicense or distribute the Program is void, and will automatically terminate 168 | your rights under this License. However, parties who have received copies, or 169 | rights, from you under this License will not have their licenses terminated so 170 | long as such parties remain in full compliance. 171 | 172 | **5.** You are not required to accept this License, since you have not signed 173 | it. However, nothing else grants you permission to modify or distribute the 174 | Program or its derivative works. These actions are prohibited by law if you do 175 | not accept this License. Therefore, by modifying or distributing the Program (or 176 | any work based on the Program), you indicate your acceptance of this License to 177 | do so, and all its terms and conditions for copying, distributing or modifying 178 | the Program or works based on it. 179 | 180 | **6.** Each time you redistribute the Program (or any work based on the 181 | Program), the recipient automatically receives a license from the original 182 | licensor to copy, distribute or modify the Program subject to these terms and 183 | conditions. You may not impose any further restrictions on the recipients' 184 | exercise of the rights granted herein. You are not responsible for enforcing 185 | compliance by third parties to this License. 186 | 187 | **7.** If, as a consequence of a court judgment or allegation of patent 188 | infringement or for any other reason (not limited to patent issues), conditions 189 | are imposed on you (whether by court order, agreement or otherwise) that 190 | contradict the conditions of this License, they do not excuse you from the 191 | conditions of this License. If you cannot distribute so as to satisfy 192 | simultaneously your obligations under this License and any other pertinent 193 | obligations, then as a consequence you may not distribute the Program at all. 194 | For example, if a patent license would not permit royalty-free redistribution of 195 | the Program by all those who receive copies directly or indirectly through you, 196 | then the only way you could satisfy both it and this License would be to refrain 197 | entirely from distribution of the Program. 198 | 199 | If any portion of this section is held invalid or unenforceable under any 200 | particular circumstance, the balance of the section is intended to apply and the 201 | section as a whole is intended to apply in other circumstances. 202 | 203 | It is not the purpose of this section to induce you to infringe any patents or 204 | other property right claims or to contest validity of any such claims; this 205 | section has the sole purpose of protecting the integrity of the free software 206 | distribution system, which is implemented by public license practices. Many 207 | people have made generous contributions to the wide range of software 208 | distributed through that system in reliance on consistent application of that 209 | system; it is up to the author/donor to decide if he or she is willing to 210 | distribute software through any other system and a licensee cannot impose that 211 | choice. 212 | 213 | This section is intended to make thoroughly clear what is believed to be a 214 | consequence of the rest of this License. 215 | 216 | **8.** If the distribution and/or use of the Program is restricted in certain 217 | countries either by patents or by copyrighted interfaces, the original copyright 218 | holder who places the Program under this License may add an explicit 219 | geographical distribution limitation excluding those countries, so that 220 | distribution is permitted only in or among countries not thus excluded. In such 221 | case, this License incorporates the limitation as if written in the body of this 222 | License. 223 | 224 | **9.** The Free Software Foundation may publish revised and/or new versions of 225 | the General Public License from time to time. Such new versions will be similar 226 | in spirit to the present version, but may differ in detail to address new 227 | problems or concerns. 228 | 229 | Each version is given a distinguishing version number. If the Program specifies 230 | a version number of this License which applies to it and "any later version", 231 | you have the option of following the terms and conditions either of that version 232 | or of any later version published by the Free Software Foundation. If the 233 | Program does not specify a version number of this License, you may choose any 234 | version ever published by the Free Software Foundation. 235 | 236 | **10.** If you wish to incorporate parts of the Program into other free programs 237 | whose distribution conditions are different, write to the author to ask for 238 | permission. For software which is copyrighted by the Free Software Foundation, 239 | write to the Free Software Foundation; we sometimes make exceptions for this. 240 | Our decision will be guided by the two goals of preserving the free status of 241 | all derivatives of our free software and of promoting the sharing and reuse of 242 | software generally. 243 | 244 | 245 | No Warranty 246 | ----------- 247 | 248 | **11.** BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR 249 | THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE 250 | STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM 251 | "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, 252 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 253 | PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 254 | PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 255 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 256 | 257 | **12.** IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 258 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE 259 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 260 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR 261 | INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA 262 | BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 263 | FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER 264 | OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 265 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![License: GPL-2.0-or-later](https://img.shields.io/badge/License-GPL%20v2+-blue.svg)](LICENSE.md) 2 | [![Build Status](https://travis-ci.com/AlwinEsch/pvr.rtl.radiofm.svg?branch=Matrix)](https://travis-ci.com/AlwinEsch/pvr.rtl.radiofm/branches) 3 | 4 | # pvr.rtl.radiofm 5 | FM Radio receiver based upon RTL-SDR as pvr addon for KODI 6 | 7 | Add-on is usable since Kodi 15.0. RDS support comes with 16.0. 8 | 9 | It use a from RTL-SDR supported receiver with Realtek RTL2832U as source (see http://sdr.osmocom.org/trac/wiki/rtl-sdr) 10 | to make FM radio signal receive and send the audio stream in float format to KODI and send also RDS signal translated 11 | to UECP in it. 12 | 13 | On linux make sure the kernel part ```dvb_usb_rtl28xxu``` for DVB-T is not loaded, otherwise is it not usable for RTL-SDR! 14 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | TODO list (also included things for KODI not todo with this addon): 2 | 3 | - Cleanup and improvement 4 | - Handle of RDS alternate Frequency list? 5 | - Improve RDS process to become data in better quality 6 | - Fix and check in KODI following (codec id for teletext and then compare???): 7 | m_streams[i] = st; 8 | st->m_parser_split = true; 9 | } 10 | else if (props.stream[i].iCodecId == AV_CODEC_ID_DVB_TELETEXT) 11 | { 12 | if (stm) 13 | { 14 | if (stm->codec != (AVCodecID)props.stream[i].iCodecId) 15 | DisposeStream(i); 16 | } 17 | if (!m_streams[i]) 18 | m_streams[i] = new CDemuxStreamTeletext(); 19 | } 20 | else if (props.stream[i].iCodecType == XBMC_CODEC_TYPE_RDS && 21 | CSettings::Get().GetBool("pvrplayback.enableradiords")) 22 | { 23 | - Finish KODI RDS support 24 | - Pull request for VDR plugin and related addon to add RDS 25 | -------------------------------------------------------------------------------- /debian/changelog.in: -------------------------------------------------------------------------------- 1 | kodi-pvr-rtl-radiofm (#PACKAGEVERSION#-#TAGREV#~#DIST#) #DIST#; urgency=low 2 | 3 | [ kodi ] 4 | * autogenerated dummy changelog 5 | 6 | -- Alwin Esch Wed, 07 Jan 2015 11:40:22 +0100 7 | 8 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: kodi-pvr-rtl-radiofm 2 | Priority: extra 3 | Maintainer: Alwin Esch 4 | Build-Depends: debhelper (>= 9.0.0), cmake, kodi-addon-dev, 5 | librtlsdr-dev, libtinyxml-dev 6 | Standards-Version: 4.1.2 7 | Section: libs 8 | Homepage: https://kodi.tv 9 | 10 | Package: kodi-pvr-rtl-radiofm-template 11 | Section: libs 12 | Architecture: any 13 | Depends: ${shlibs:Depends}, ${misc:Depends} 14 | Description: PVR client to connect RTL-SDR as FM Radio to KODI 15 | 16 | Package: kodi-pvr-rtl-radiofm-template-dbg 17 | Section: debug 18 | Architecture: any 19 | Depends: ${shlibs:Depends}, ${misc:Depends} 20 | Description: debug symbols for RTL-SDR FM radio Receiver for KODI 21 | contains the debug symbols for kodi-pvr-rtl-radiofm-template 22 | 23 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://dep.debian.net/deps/dep5 2 | Upstream-Name: pvr.rtl.radiofm 3 | 4 | Files: * 5 | Copyright: Alwin Esch 6 | License: GPL-2+ 7 | This package is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; either version 2 of the License, or 10 | (at your option) any later version. 11 | . 12 | This package is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | . 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see 19 | . 20 | On Debian systems, the complete text of the GNU General 21 | Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". 22 | 23 | Severel parts are based upon SoftFM from Joris van Rantwijk Copyright (C) 2013. 24 | and CuteSDR from Moe Wheatley (under "Simplified BSD License") Copyright (C) 2010 25 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # -*- makefile -*- 3 | # Sample debian/rules that uses debhelper. 4 | # This file was originally written by Joey Hess and Craig Small. 5 | # As a special exception, when this file is copied by dh-make into a 6 | # dh-make output file, you may use that output file without restriction. 7 | # This special exception was added by Craig Small in version 0.37 of dh-make. 8 | 9 | # Uncomment this to turn on verbose mode. 10 | #export DH_VERBOSE=1 11 | 12 | %: 13 | dh $@ 14 | 15 | override_dh_auto_configure: 16 | dh_auto_configure -- -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_SHARED_LIBS=1 -DUSE_LTO=1 17 | 18 | override_dh_installdocs: 19 | dh_installdocs --link-doc=kodi-rtl-radiofm 20 | 21 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /depends/common/libusb/0001-make-private-libs-public.patch: -------------------------------------------------------------------------------- 1 | --- a/libusb-1.0.pc.in 2 | +++ b/libusb-1.0.pc.in 3 | @@ -6,6 +6,5 @@ includedir=@includedir@ 4 | Name: libusb-1.0 5 | Description: C API for USB device access from Linux, Mac OS X, Windows and OpenBSD/NetBSD userspace 6 | Version: @VERSION@ 7 | -Libs: -L${libdir} -lusb-1.0 8 | -Libs.private: @LIBS@ 9 | +Libs: -L${libdir} -lusb-1.0 @LIBS@ 10 | Cflags: -I${includedir}/libusb-1.0 11 | -------------------------------------------------------------------------------- /depends/common/libusb/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(libusb) 3 | 4 | include(ExternalProject) 5 | 6 | ExternalProject_Add( 7 | libusb 8 | SOURCE_DIR ${CMAKE_SOURCE_DIR} 9 | CONFIGURE_COMMAND /autogen.sh 10 | --prefix=${OUTPUT_DIR} 11 | --enable-static 12 | --disable-shared 13 | --disable-examples-build 14 | --disable-tests-build 15 | --with-pic 16 | INSTALL_COMMAND "" 17 | BUILD_IN_SOURCE 1 18 | ) 19 | 20 | install(CODE "execute_process(COMMAND make install WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})") 21 | -------------------------------------------------------------------------------- /depends/common/libusb/libusb.txt: -------------------------------------------------------------------------------- 1 | libusb https://github.com/libusb/libusb v1.0.20 2 | -------------------------------------------------------------------------------- /depends/common/rtl-sdr/0001-static-only.patch: -------------------------------------------------------------------------------- 1 | From 7436f660b2aa2886f32354b4a6df9a21942ee87a Mon Sep 17 00:00:00 2001 2 | From: Alwin Esch 3 | Date: Sun, 8 Nov 2020 21:52:59 +0100 4 | Subject: [PATCH] static only 5 | 6 | --- 7 | src/CMakeLists.txt | 117 ++++----------------------------------------- 8 | 1 file changed, 9 insertions(+), 108 deletions(-) 9 | 10 | diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt 11 | index 7b47309..74a9820 100644 12 | --- a/src/CMakeLists.txt 13 | +++ b/src/CMakeLists.txt 14 | @@ -15,42 +15,24 @@ 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | -######################################################################## 19 | -# Setup shared library variant 20 | -######################################################################## 21 | -add_library(rtlsdr SHARED librtlsdr.c 22 | - tuner_e4k.c tuner_fc0012.c tuner_fc0013.c tuner_fc2580.c tuner_r82xx.c) 23 | -target_link_libraries(rtlsdr ${LIBUSB_LIBRARIES} ${THREADS_PTHREADS_LIBRARY}) 24 | -target_include_directories(rtlsdr PUBLIC 25 | - $ 26 | - $ # /include 27 | - ${LIBUSB_INCLUDE_DIRS} 28 | - ${THREADS_PTHREADS_INCLUDE_DIR} 29 | - ) 30 | -set_target_properties(rtlsdr PROPERTIES DEFINE_SYMBOL "rtlsdr_EXPORTS") 31 | -set_target_properties(rtlsdr PROPERTIES OUTPUT_NAME rtlsdr) 32 | -set_target_properties(rtlsdr PROPERTIES SOVERSION ${MAJOR_VERSION}) 33 | -set_target_properties(rtlsdr PROPERTIES VERSION ${LIBVER}) 34 | -generate_export_header(rtlsdr) 35 | - 36 | ######################################################################## 37 | # Setup static library variant 38 | ######################################################################## 39 | -add_library(rtlsdr_static STATIC librtlsdr.c 40 | +add_library(rtlsdr STATIC librtlsdr.c 41 | tuner_e4k.c tuner_fc0012.c tuner_fc0013.c tuner_fc2580.c tuner_r82xx.c) 42 | target_link_libraries(rtlsdr ${LIBUSB_LIBRARIES} ${THREADS_PTHREADS_LIBRARY}) 43 | -target_include_directories(rtlsdr_static PUBLIC 44 | +target_include_directories(rtlsdr PUBLIC 45 | $ 46 | $ # /include 47 | ${LIBUSB_INCLUDE_DIRS} 48 | ${THREADS_PTHREADS_INCLUDE_DIR} 49 | ) 50 | -set_property(TARGET rtlsdr_static APPEND PROPERTY COMPILE_DEFINITIONS "rtlsdr_STATIC" ) 51 | +set_property(TARGET rtlsdr APPEND PROPERTY COMPILE_DEFINITIONS "rtlsdr" ) 52 | if(NOT WIN32) 53 | # Force same library filename for static and shared variants of the library 54 | -set_target_properties(rtlsdr_static PROPERTIES OUTPUT_NAME rtlsdr) 55 | +set_target_properties(rtlsdr PROPERTIES OUTPUT_NAME rtlsdr) 56 | endif() 57 | -generate_export_header(rtlsdr_static) 58 | +generate_export_header(rtlsdr) 59 | 60 | ######################################################################## 61 | # Set up Windows DLL resource files 62 | @@ -63,109 +45,28 @@ IF(MSVC) 63 | ${CMAKE_CURRENT_BINARY_DIR}/rtlsdr.rc 64 | @ONLY) 65 | target_sources(rtlsdr PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/rtlsdr.rc) 66 | - target_sources(rtlsdr_static PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/rtlsdr.rc) 67 | ENDIF(MSVC) 68 | 69 | ######################################################################## 70 | # Setup libraries used in executables 71 | ######################################################################## 72 | -add_library(convenience_static STATIC 73 | +add_library(convenience STATIC 74 | convenience/convenience.c 75 | ) 76 | -target_include_directories(convenience_static 77 | +target_include_directories(convenience 78 | PRIVATE ${CMAKE_SOURCE_DIR}/include) 79 | if(WIN32) 80 | -add_library(libgetopt_static STATIC 81 | +add_library(libgetopt STATIC 82 | getopt/getopt.c 83 | ) 84 | -target_link_libraries(convenience_static 85 | +target_link_libraries(convenience 86 | rtlsdr 87 | ) 88 | endif() 89 | 90 | -######################################################################## 91 | -# Build utility 92 | -######################################################################## 93 | -add_executable(rtl_sdr rtl_sdr.c) 94 | -add_executable(rtl_tcp rtl_tcp.c) 95 | -add_executable(rtl_test rtl_test.c) 96 | -add_executable(rtl_fm rtl_fm.c) 97 | -add_executable(rtl_eeprom rtl_eeprom.c) 98 | -add_executable(rtl_adsb rtl_adsb.c) 99 | -add_executable(rtl_power rtl_power.c) 100 | -add_executable(rtl_biast rtl_biast.c) 101 | -set(INSTALL_TARGETS rtlsdr rtlsdr_static rtl_sdr rtl_tcp rtl_test rtl_fm rtl_eeprom rtl_adsb rtl_power rtl_biast) 102 | - 103 | -target_link_libraries(rtl_sdr rtlsdr convenience_static 104 | - ${LIBUSB_LIBRARIES} 105 | - ${CMAKE_THREAD_LIBS_INIT} 106 | -) 107 | -target_link_libraries(rtl_tcp rtlsdr convenience_static 108 | - ${LIBUSB_LIBRARIES} 109 | - ${CMAKE_THREAD_LIBS_INIT} 110 | -) 111 | -target_link_libraries(rtl_test rtlsdr convenience_static 112 | - ${LIBUSB_LIBRARIES} 113 | - ${CMAKE_THREAD_LIBS_INIT} 114 | -) 115 | -target_link_libraries(rtl_fm rtlsdr convenience_static 116 | - ${LIBUSB_LIBRARIES} 117 | - ${CMAKE_THREAD_LIBS_INIT} 118 | -) 119 | -target_link_libraries(rtl_eeprom rtlsdr convenience_static 120 | - ${LIBUSB_LIBRARIES} 121 | - ${CMAKE_THREAD_LIBS_INIT} 122 | -) 123 | -target_link_libraries(rtl_adsb rtlsdr convenience_static 124 | - ${LIBUSB_LIBRARIES} 125 | - ${CMAKE_THREAD_LIBS_INIT} 126 | -) 127 | -target_link_libraries(rtl_power rtlsdr convenience_static 128 | - ${LIBUSB_LIBRARIES} 129 | - ${CMAKE_THREAD_LIBS_INIT} 130 | -) 131 | -target_link_libraries(rtl_biast rtlsdr convenience_static 132 | - ${LIBUSB_LIBRARIES} 133 | - ${CMAKE_THREAD_LIBS_INIT} 134 | -) 135 | -if(UNIX) 136 | -target_link_libraries(rtl_fm m) 137 | -target_link_libraries(rtl_adsb m) 138 | -target_link_libraries(rtl_power m) 139 | -if(APPLE OR CMAKE_SYSTEM MATCHES "OpenBSD") 140 | - target_link_libraries(rtl_test m) 141 | -else() 142 | - target_link_libraries(rtl_test m rt) 143 | -endif() 144 | -endif() 145 | - 146 | -if(WIN32) 147 | -target_link_libraries(rtl_sdr libgetopt_static) 148 | -target_link_libraries(rtl_tcp ws2_32 libgetopt_static) 149 | -target_link_libraries(rtl_test libgetopt_static) 150 | -target_link_libraries(rtl_fm libgetopt_static) 151 | -target_link_libraries(rtl_eeprom libgetopt_static) 152 | -target_link_libraries(rtl_adsb libgetopt_static) 153 | -target_link_libraries(rtl_power libgetopt_static) 154 | -target_link_libraries(rtl_biast libgetopt_static) 155 | -set_property(TARGET rtl_sdr APPEND PROPERTY COMPILE_DEFINITIONS "rtlsdr_STATIC" ) 156 | -set_property(TARGET rtl_tcp APPEND PROPERTY COMPILE_DEFINITIONS "rtlsdr_STATIC" ) 157 | -set_property(TARGET rtl_test APPEND PROPERTY COMPILE_DEFINITIONS "rtlsdr_STATIC" ) 158 | -set_property(TARGET rtl_fm APPEND PROPERTY COMPILE_DEFINITIONS "rtlsdr_STATIC" ) 159 | -set_property(TARGET rtl_eeprom APPEND PROPERTY COMPILE_DEFINITIONS "rtlsdr_STATIC" ) 160 | -set_property(TARGET rtl_adsb APPEND PROPERTY COMPILE_DEFINITIONS "rtlsdr_STATIC" ) 161 | -set_property(TARGET rtl_power APPEND PROPERTY COMPILE_DEFINITIONS "rtlsdr_STATIC" ) 162 | -set_property(TARGET rtl_biast APPEND PROPERTY COMPILE_DEFINITIONS "rtlsdr_STATIC" ) 163 | -endif() 164 | ######################################################################## 165 | # Install built library files & utilities 166 | ######################################################################## 167 | install(TARGETS rtlsdr EXPORT RTLSDR-export 168 | - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} # .so/.dylib file 169 | - ) 170 | -install(TARGETS rtlsdr_static EXPORT RTLSDR-export 171 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} # .so/.dylib file 172 | ) 173 | -install(TARGETS rtl_sdr rtl_tcp rtl_test rtl_fm rtl_eeprom rtl_adsb rtl_power rtl_biast 174 | - DESTINATION ${CMAKE_INSTALL_BINDIR} 175 | - ) 176 | -- 177 | 2.25.1 178 | 179 | -------------------------------------------------------------------------------- /depends/common/rtl-sdr/deps.txt: -------------------------------------------------------------------------------- 1 | libusb -------------------------------------------------------------------------------- /depends/common/rtl-sdr/rtl-sdr.txt: -------------------------------------------------------------------------------- 1 | rtl-sdr https://github.com/osmocom/rtl-sdr 0847e93e0869feab50fd27c7afeb85d78ca04631 2 | -------------------------------------------------------------------------------- /depends/common/tinyxml/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(tinyxml) 3 | 4 | set(SOURCES src/tinystr.cpp 5 | src/tinyxml.cpp 6 | src/tinyxmlerror.cpp 7 | src/tinyxmlparser.cpp) 8 | 9 | if(WIN32) 10 | add_definitions(-DWIN32 -D_LIB) 11 | endif() 12 | add_definitions(-DTIXML_USE_STL) 13 | 14 | add_library(tinyxml ${SOURCES}) 15 | 16 | include_directories(${PROJECT_SOURCE_DIR}/include) 17 | 18 | set(HEADERS ${PROJECT_SOURCE_DIR}/include/tinystr.h 19 | ${PROJECT_SOURCE_DIR}/include/tinyxml.h) 20 | 21 | install(FILES ${HEADERS} DESTINATION ${CMAKE_INSTALL_PREFIX}/include) 22 | install(TARGETS tinyxml DESTINATION ${CMAKE_INSTALL_PREFIX}/lib) 23 | -------------------------------------------------------------------------------- /depends/common/tinyxml/tinyxml.sha256: -------------------------------------------------------------------------------- 1 | 8164c9ad48b9028667768a584d62f7760cfbfb90d0dd6214ad174403058da10c 2 | -------------------------------------------------------------------------------- /depends/common/tinyxml/tinyxml.txt: -------------------------------------------------------------------------------- 1 | tinyxml http://mirrors.kodi.tv/build-deps/sources/tinyxml-2.6.2_2.tar.gz 2 | -------------------------------------------------------------------------------- /pvr.rtl.radiofm/PVRFMRadioAddonSettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 0 5 | 6 | 7 | 8 | 21 9 | 10 | 11 | 12 | 11 13 | 94200000.000000 14 | bigFM Saarland 15 | 16 | 17 | 18 | 12 19 | 99600000.000000 20 | CityRadio Saarbrücken 21 | 22 | 23 | 24 | 13 25 | 92900000.000000 26 | Classic Rock Radio 27 | 28 | 29 | 30 | 14 31 | 90100000.000000 32 | Deutschlandfunk 33 | 34 | 35 | 36 | 15 37 | 107500000.000000 38 | Deutschlandfunk Kultur 39 | 40 | 41 | 42 | 16 43 | 101700000.000000 44 | RADIO SALUE 45 | 46 | 47 | 48 | 17 49 | 102600000.000000 50 | RPR1 51 | 52 | 53 | 54 | 18 55 | 88000000.000000 56 | SR 1 EuropaWelle 57 | 58 | 59 | 60 | 19 61 | 91300000.000000 62 | SR 2 KulturRadio 63 | 64 | 65 | 66 | 20 67 | 95500000.000000 68 | SR 3 69 | 70 | 71 | 72 | 21 73 | 99100000.000000 74 | SWR 1 75 | 76 | 77 | 78 | 22 79 | 95500000.000000 80 | SWR 2 81 | 82 | 83 | 84 | 23 85 | 101100000.000000 86 | SWR 3 87 | 88 | 89 | 90 | 24 91 | 105600000.000000 92 | SWR 4 93 | 94 | 95 | 96 | 25 97 | 103700000.000000 98 | UNSERDING 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /pvr.rtl.radiofm/addon.xml.in: -------------------------------------------------------------------------------- 1 | 2 | 7 | @ADDON_DEPENDS@ 8 | 11 | 12 | PVR-Client zum Verbinden von RTL-SDR als FM-Radio mit Kodi 13 | PVR client to connect RTL-SDR as FM Radio to Kodi 14 | RTL-SDR-Frontend; Unterstützung des Streamings von FM-Radio. 15 | 16 | DVB-T-Dongles, die auf dem Realtek RTL2832U basieren, können als billiger SDR verwendet werden, da der Chip die Übertragung der rohen I/Q-Samples auf den Host ermöglicht, der hier offiziell für die FM-Demodulation verwendet wird. 17 | 18 | ACHTUNG: Die vollständige Verarbeitung erfolgt mit der CPU und erfordert eine bessere Leistung! 19 | RTL-SDR frontend; supporting streaming of FM Radio. 20 | 21 | DVB-T dongles based on the Realtek RTL2832U can be used as a cheap SDR, since the chip allows transferring the raw I/Q samples to the host, which is officially used for FM demodulation here. 22 | 23 | ATTENTION: Complete processing is performed with CPU and need better performance of it! 24 | Das ist instabile Software! Die Autoren sind in keiner Weise für ausgefallene Kanäle oder andere unerwünschte Effekte verantwortlich. 25 | This is unstable software! The authors are in no way responsible for failed channels or any other undesirable effects. 26 | all 27 | GPL-2.0-or-later 28 | https://github.com/AlwinEsch/pvr.rtl.radiofm 29 | 30 | resources/fanart.jpg 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /pvr.rtl.radiofm/resources/fanart.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlwinEsch/pvr.rtl.radiofm/bfe8357c8827faed3740d50d16f1469f4c3b4fc6/pvr.rtl.radiofm/resources/fanart.jpg -------------------------------------------------------------------------------- /pvr.rtl.radiofm/resources/language/resource.language.de_de/strings.po: -------------------------------------------------------------------------------- 1 | # XBMC Media Center language file 2 | # Addon Name: RTL SDR FM Radio Client 3 | # Addon id: pvr.rtl.radiofm 4 | # Addon Provider: Alwin Esch, Team Kodi 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: pvr.rtl.radiofm\n" 8 | "Report-Msgid-Bugs-To: https://github.com/AlwinEsch/pvr.rtl.radiofm/issues\n" 9 | "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" 10 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 11 | "Last-Translator: Kodi Translation Team\n" 12 | "Language-Team: German (Germany) (http://www.transifex.com/projects/p/xbmc-addons/language/de_DE/)\n" 13 | "MIME-Version: 1.0\n" 14 | "Content-Type: text/plain; charset=UTF-8\n" 15 | "Content-Transfer-Encoding: 8bit\n" 16 | "Language: de_DE\n" 17 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 18 | 19 | #settings labels 20 | 21 | msgctxt "#30010" 22 | msgid "Channel tuner" 23 | msgstr "Kanaltuner" 24 | 25 | msgctxt "#30011" 26 | msgid "OK" 27 | msgstr "OK" 28 | 29 | msgctxt "#30012" 30 | msgid "Cancel" 31 | msgstr "Abbrechen" 32 | 33 | msgctxt "#30020" 34 | msgid "+0.01" 35 | msgstr "+0.01" 36 | 37 | msgctxt "#30021" 38 | msgid "-0.01" 39 | msgstr "-0.01" 40 | 41 | msgctxt "#30022" 42 | msgid "+0.1" 43 | msgstr "+0.1" 44 | 45 | msgctxt "#30023" 46 | msgid "-0.1" 47 | msgstr "-0.1" 48 | 49 | msgctxt "#30024" 50 | msgid "-1.0" 51 | msgstr "-1.0" 52 | 53 | msgctxt "#30025" 54 | msgid "+1.0" 55 | msgstr "+1.0" 56 | 57 | msgctxt "#30026" 58 | msgid "<<" 59 | msgstr "<<" 60 | 61 | msgctxt "#30027" 62 | msgid ">>" 63 | msgstr ">>" 64 | 65 | msgctxt "#30031" 66 | msgid "Signal: %i %%" 67 | msgstr "Signal: %i %%" 68 | -------------------------------------------------------------------------------- /pvr.rtl.radiofm/resources/language/resource.language.en_gb/strings.po: -------------------------------------------------------------------------------- 1 | # XBMC Media Center language file 2 | # Addon Name: RTL SDR FM Radio Client 3 | # Addon id: pvr.rtl.radiofm 4 | # Addon Provider: Alwin Esch, Team Kodi 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: pvr.rtl.radiofm\n" 8 | "Report-Msgid-Bugs-To: https://github.com/AlwinEsch/pvr.rtl.radiofm/issues\n" 9 | "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" 10 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 11 | "Last-Translator: Kodi Translation Team\n" 12 | "Language-Team: English (United Kingdom) (http://www.transifex.com/projects/p/xbmc-addons/language/en_GB/)\n" 13 | "MIME-Version: 1.0\n" 14 | "Content-Type: text/plain; charset=UTF-8\n" 15 | "Content-Transfer-Encoding: 8bit\n" 16 | "Language: en_GB\n" 17 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 18 | 19 | #settings labels 20 | 21 | msgctxt "#30010" 22 | msgid "Channel tuner" 23 | msgstr "" 24 | 25 | msgctxt "#30011" 26 | msgid "OK" 27 | msgstr "" 28 | 29 | msgctxt "#30012" 30 | msgid "Cancel" 31 | msgstr "" 32 | 33 | msgctxt "#30020" 34 | msgid "+0.01" 35 | msgstr "" 36 | 37 | msgctxt "#30021" 38 | msgid "-0.01" 39 | msgstr "" 40 | 41 | msgctxt "#30022" 42 | msgid "+0.1" 43 | msgstr "" 44 | 45 | msgctxt "#30023" 46 | msgid "-0.1" 47 | msgstr "" 48 | 49 | msgctxt "#30024" 50 | msgid "-1.0" 51 | msgstr "" 52 | 53 | msgctxt "#30025" 54 | msgid "+1.0" 55 | msgstr "" 56 | 57 | msgctxt "#30026" 58 | msgid "<<" 59 | msgstr "" 60 | 61 | msgctxt "#30027" 62 | msgid ">>" 63 | msgstr "" 64 | 65 | msgctxt "#30031" 66 | msgid "Signal: %i %%" 67 | msgstr "" 68 | -------------------------------------------------------------------------------- /pvr.rtl.radiofm/resources/skins/skin.confluence/720p/ChannelTuner.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 3 | yes 4 | 5 | CommonSettingsBackground 6 | CommonMediaPlayingBackground 7 | 8 | 90 9 | 250 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 0 20 | 0 21 | 1100 22 | 240 23 | DialogBack.png 24 | 25 | 26 | LOGO 27 | 30 28 | 15 29 | 220 30 | 80 31 | keep 32 | Confluence_Logo.png 33 | 34 | 35 | 268 36 | 10 37 | 790 38 | 218 39 | black-back2.png 40 | 41 | 42 | 268 43 | 10 44 | 804 45 | 70 46 | stretch 47 | GlassTitleBar.png 48 | 49 | 50 | header label 51 | 300 52 | 20 53 | 740 54 | 30 55 | font16caps 56 | 57 | left 58 | center 59 | white 60 | black 61 | 62 | 63 | OK 64 | 280 65 | 160 66 | 250 67 | 50 68 | 13 69 | 70 | font13_title 71 | center 72 | center 73 | floor_button.png 74 | floor_buttonFO.png 75 | 6 76 | 37 77 | 31 78 | 31 79 | 80 | 81 | Channel Name 82 | 540 83 | 160 84 | 245 85 | 50 86 | 13 87 | 88 | font35_title 89 | center 90 | center 91 | MenuItemNF.png 92 | MenuItemFO.png 93 | 5 94 | 6 95 | 37 96 | 37 97 | 98 | 99 | Cancel 100 | 795 101 | 160 102 | 250 103 | 50 104 | 13 105 | 106 | font13_title 107 | center 108 | center 109 | floor_button.png 110 | floor_buttonFO.png 111 | 37 112 | 5 113 | 35 114 | 35 115 | 116 | 117 | Auto tune down 118 | 280 119 | 80 120 | 80 121 | 50 122 | 13 123 | 124 | font13_title 125 | center 126 | center 127 | floor_button.png 128 | floor_buttonFO.png 129 | 36 130 | 31 131 | 5 132 | 5 133 | 134 | 135 | Decrease 1MHz 136 | 365 137 | 80 138 | 80 139 | 50 140 | 13 141 | 142 | font13_title 143 | center 144 | center 145 | floor_button.png 146 | floor_buttonFO.png 147 | 30 148 | 32 149 | 5 150 | 5 151 | 152 | 153 | Decrease 0.1MHz 154 | 450 155 | 80 156 | 80 157 | 50 158 | 13 159 | 160 | font13_title 161 | center 162 | center 163 | floor_button.png 164 | floor_buttonFO.png 165 | 31 166 | 34 167 | 5 168 | 5 169 | 170 | 171 | Current freq 172 | 540 173 | 85 174 | 245 175 | 50 176 | font35_title 177 | 178 | center 179 | center 180 | white 181 | black 182 | 183 | 184 | Current Level 185 | 540 186 | 110 187 | 245 188 | 50 189 | font12_title 190 | 191 | center 192 | center 193 | white 194 | black 195 | 196 | 197 | Increase 0.1MHz 198 | 795 199 | 80 200 | 80 201 | 50 202 | 13 203 | 204 | font13_title 205 | center 206 | center 207 | floor_button.png 208 | floor_buttonFO.png 209 | 32 210 | 35 211 | 6 212 | 6 213 | 214 | 215 | Increase 1MHz 216 | 880 217 | 80 218 | 80 219 | 50 220 | 13 221 | 222 | font13_title 223 | center 224 | center 225 | floor_button.png 226 | floor_buttonFO.png 227 | 34 228 | 36 229 | 6 230 | 6 231 | 232 | 233 | Auto Tune up 234 | 965 235 | 80 236 | 80 237 | 50 238 | 13 239 | 240 | font13_title 241 | center 242 | center 243 | floor_button.png 244 | floor_buttonFO.png 245 | 35 246 | 30 247 | 6 248 | 6 249 | 250 | 251 | BehindDialogFadeOut 252 | 253 | 60 254 | 0 255 | WindowClose 256 | WindowOpen 257 | 258 | 0 259 | 0 260 | 250 261 | 35 262 | header.png 263 | 264 | 265 | WindowTitleHomeButton 266 | Clock 267 | 268 | 269 | -------------------------------------------------------------------------------- /pvr.rtl.radiofm/resources/skins/skin.estuary/xml/ChannelTuner.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 3 | background 4 | no 5 | 6 | DefaultBackground 7 | 8 | 260 9 | 350 10 | 11 | OpenClose_Right 12 | 13 | Menu Panel 14 | 200 15 | 0 16 | 1000 17 | 340 18 | dialogs/dialog-bg.png 19 | 20 | 21 | Dialog Header image 22 | 220 23 | 20 24 | 960 25 | 70 26 | colors/white70.png 27 | 28 | 29 | header label 30 | 255 31 | 40 32 | 740 33 | 30 34 | font13_title 35 | 36 | left 37 | center 38 | white 39 | black 40 | 41 | 42 | OK 43 | 240 44 | 220 45 | 290 46 | 100 47 | 48 | center 49 | 6 50 | 37 51 | 31 52 | 31 53 | 54 | 55 | Channel Name 56 | 400 57 | 220 58 | 600 59 | 100 60 | 61 | buttons/dialogbutton-nofo.png 62 | buttons/dialogbutton-nofo.png 63 | center 64 | 5 65 | 6 66 | 37 67 | 37 68 | 69 | 70 | Cancel 71 | 875 72 | 220 73 | 290 74 | 100 75 | 76 | center 77 | 37 78 | 5 79 | 35 80 | 35 81 | 82 | 83 | Auto tune down 84 | 240 85 | 140 86 | 120 87 | 100 88 | 89 | center 90 | 36 91 | 31 92 | 5 93 | 5 94 | 95 | 96 | Decrease 1MHz 97 | 325 98 | 140 99 | 120 100 | 100 101 | 102 | center 103 | 30 104 | 32 105 | 5 106 | 5 107 | 108 | 109 | Decrease 0.1MHz 110 | 410 111 | 140 112 | 120 113 | 100 114 | 115 | center 116 | 31 117 | 34 118 | 5 119 | 5 120 | 121 | 122 | Current freq 123 | 570 124 | 100 125 | 260 126 | 50 127 | font35_title 128 | 129 | center 130 | white 131 | black 132 | 133 | 134 | Current Level 135 | 520 136 | 180 137 | 360 138 | 50 139 | font16_title 140 | 141 | center 142 | white 143 | black 144 | 145 | 146 | Increase 0.1MHz 147 | 875 148 | 140 149 | 120 150 | 100 151 | 152 | center 153 | 32 154 | 35 155 | 6 156 | 6 157 | 158 | 159 | Increase 1MHz 160 | 960 161 | 140 162 | 120 163 | 100 164 | 165 | center 166 | 34 167 | 36 168 | 6 169 | 6 170 | 171 | 172 | Auto Tune up 173 | 1045 174 | 140 175 | 120 176 | 100 177 | 178 | center 179 | 35 180 | 30 181 | 6 182 | 6 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | BottomBar 191 | 192 | 193 | -------------------------------------------------------------------------------- /src/ChannelSettings.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015-2020, Alwin Esch (Team KODI) 3 | * 4 | * SPDX-License-Identifier: GPL-2.0-or-later 5 | * See LICENSE.md for more information. 6 | */ 7 | 8 | #include "ChannelSettings.h" 9 | 10 | #include "RadioReceiver.h" 11 | #include "Utils.h" 12 | 13 | #include 14 | #include 15 | 16 | #define BUTTON_OK 5 17 | #define BUTTON_CANCEL 6 18 | 19 | #define CONTROL_BUTTON_RUN_DOWN 30 20 | #define CONTROL_BUTTON_DECREASE_1 31 21 | #define CONTROL_BUTTON_DECREASE_2 32 22 | #define CONTROL_LABEL_FREQ 33 23 | #define CONTROL_BUTTON_INCREASE_2 34 24 | #define CONTROL_BUTTON_INCREASE_1 35 25 | #define CONTROL_BUTTON_RUN_UP 36 26 | #define CONTROL_BUTTON_CHANNEL_NAME 37 27 | #define CONTROL_LABEL_LEVEL 38 28 | 29 | 30 | cChannelSettings::cChannelSettings() 31 | : kodi::gui::CWindow("ChannelTuner.xml", "skin.estuary", true, true) 32 | { 33 | } 34 | 35 | cChannelSettings::~cChannelSettings() 36 | { 37 | m_running = false; 38 | if (m_thread.joinable()) 39 | m_thread.join(); 40 | } 41 | 42 | PVR_ERROR cChannelSettings::Open(const kodi::addon::PVRChannel& channel, 43 | cRadioReceiver* source, 44 | bool addAdjust) 45 | { 46 | m_Source = source; 47 | m_addAdjust = addAdjust; 48 | m_WasSaved = false; 49 | 50 | m_ChannelIndex = -1; 51 | if (m_addAdjust) 52 | { 53 | if (m_Source->IsActive()) 54 | m_CurrentFreq = 55 | m_Source->GetSource()->GetFrequency() - 0.25 * m_Source->GetSource()->GetSampleRate(); 56 | else 57 | m_CurrentFreq = 87500000; 58 | } 59 | else 60 | { 61 | std::vector* channels = m_Source->GetChannelData(); 62 | for (unsigned int iChannelPtr = 0; iChannelPtr < channels->size(); iChannelPtr++) 63 | { 64 | if (channels->at(iChannelPtr).iUniqueId == channel.GetUniqueId()) 65 | { 66 | m_ChannelIndex = iChannelPtr; 67 | m_CurrentFreq = channels->at(iChannelPtr).fChannelFreq; 68 | break; 69 | } 70 | } 71 | if (m_ChannelIndex == -1) 72 | { 73 | kodi::Log(ADDON_LOG_ERROR, "channel '%s' id %i unknown", channel.GetChannelName().c_str(), 74 | channel.GetUniqueId()); 75 | return PVR_ERROR_INVALID_PARAMETERS; 76 | } 77 | } 78 | 79 | if (m_Source->IsActive()) 80 | m_PrevFreq = 81 | m_Source->GetSource()->GetFrequency() - 0.25 * m_Source->GetSource()->GetSampleRate(); 82 | else 83 | m_PrevFreq = m_CurrentFreq; 84 | 85 | DoModal(); 86 | 87 | m_running = false; 88 | if (m_thread.joinable()) 89 | m_thread.join(); 90 | 91 | m_Source->RegisterDialog(nullptr); 92 | 93 | return PVR_ERROR_NO_ERROR; 94 | } 95 | 96 | void cChannelSettings::Process() 97 | { 98 | m_AutoTuneIgnore = false; 99 | while (m_running) 100 | { 101 | if (m_AutoTuneUp) 102 | { 103 | m_CurrentFreq += 100000; 104 | if (m_CurrentFreq > 108000000) 105 | m_CurrentFreq = 87500000; 106 | 107 | UpdateFreq(m_CurrentFreq); 108 | } 109 | else if (m_AutoTuneDown) 110 | { 111 | m_CurrentFreq -= 100000; 112 | if (m_CurrentFreq < 87500000) 113 | m_CurrentFreq = 108000000; 114 | 115 | UpdateFreq(m_CurrentFreq); 116 | } 117 | 118 | std::this_thread::sleep_for(std::chrono::milliseconds(1250)); 119 | if (!m_Source->IsActive()) 120 | break; 121 | 122 | float interfaceLevel; 123 | float audioLevel; 124 | bool stereo; 125 | if (m_Source->GetSignalStatus(interfaceLevel, audioLevel, stereo) == PVR_ERROR_NO_ERROR) 126 | { 127 | SetControlLabel(CONTROL_LABEL_LEVEL, StringUtils::Format("IF=%+5.1fdB Audio=%+5.1fdB", 128 | interfaceLevel, audioLevel)); 129 | 130 | if ((m_AutoTuneUp || m_AutoTuneDown) && !m_AutoTuneIgnore) 131 | { 132 | if (stereo) 133 | { 134 | m_AutoTuneUp = false; 135 | m_AutoTuneDown = false; 136 | } 137 | } 138 | } 139 | else 140 | { 141 | m_AutoTuneUp = false; 142 | m_AutoTuneDown = false; 143 | } 144 | m_AutoTuneIgnore = false; 145 | } 146 | return; 147 | } 148 | 149 | bool cChannelSettings::OnClick(int controlId) 150 | { 151 | switch (controlId) 152 | { 153 | case CONTROL_BUTTON_RUN_DOWN: 154 | m_AutoTuneDown = !m_AutoTuneDown; 155 | m_AutoTuneUp = false; 156 | m_AutoTuneIgnore = true; 157 | break; 158 | case CONTROL_BUTTON_DECREASE_1: 159 | m_CurrentFreq -= 1000000; 160 | if (m_CurrentFreq < 87500000) 161 | m_CurrentFreq = 108000000; 162 | UpdateFreq(m_CurrentFreq); 163 | m_AutoTuneDown = false; 164 | m_AutoTuneUp = false; 165 | break; 166 | case CONTROL_BUTTON_DECREASE_2: 167 | m_CurrentFreq -= 100000; 168 | if (m_CurrentFreq < 87500000) 169 | m_CurrentFreq = 108000000; 170 | UpdateFreq(m_CurrentFreq); 171 | m_AutoTuneDown = false; 172 | m_AutoTuneUp = false; 173 | break; 174 | case CONTROL_BUTTON_INCREASE_2: 175 | m_CurrentFreq += 100000; 176 | if (m_CurrentFreq > 108000000) 177 | m_CurrentFreq = 87500000; 178 | UpdateFreq(m_CurrentFreq); 179 | m_AutoTuneDown = false; 180 | m_AutoTuneUp = false; 181 | break; 182 | case CONTROL_BUTTON_INCREASE_1: 183 | m_CurrentFreq += 1000000; 184 | if (m_CurrentFreq > 108000000) 185 | m_CurrentFreq = 87500000; 186 | UpdateFreq(m_CurrentFreq); 187 | m_AutoTuneDown = false; 188 | m_AutoTuneUp = false; 189 | break; 190 | case CONTROL_BUTTON_RUN_UP: 191 | m_AutoTuneUp = !m_AutoTuneUp; 192 | m_AutoTuneDown = false; 193 | m_AutoTuneIgnore = true; 194 | break; 195 | case CONTROL_BUTTON_CHANNEL_NAME: 196 | { 197 | } 198 | break; 199 | case BUTTON_OK: 200 | if (m_addAdjust) 201 | { 202 | FMRadioChannel channel; 203 | channel.iUniqueId = m_Source->CreateNewUniqueId(); 204 | channel.fChannelFreq = m_CurrentFreq; 205 | channel.strChannelName = m_Name; 206 | channel.strChannelName = m_Source->CreateChannelName(channel); 207 | channel.strIconPath = ""; 208 | m_Source->GetChannelData()->push_back(channel); 209 | } 210 | else 211 | { 212 | m_Source->GetChannelData()->at(m_ChannelIndex).fChannelFreq = m_CurrentFreq; 213 | 214 | if (m_Source->IsChannelActive(m_ChannelIndex)) 215 | m_PrevFreq = m_CurrentFreq; 216 | } 217 | 218 | m_Source->SaveChannelData(); 219 | UpdateFreq(m_PrevFreq); 220 | Close(); 221 | m_WasSaved = true; 222 | break; 223 | case BUTTON_CANCEL: 224 | UpdateFreq(m_PrevFreq); 225 | Close(); 226 | break; 227 | default: 228 | break; 229 | } 230 | 231 | return true; 232 | } 233 | 234 | bool cChannelSettings::OnFocus(int controlId) 235 | { 236 | return true; 237 | } 238 | 239 | bool cChannelSettings::OnInit() 240 | { 241 | UpdateFreq(m_CurrentFreq); 242 | UpdateName("-"); 243 | 244 | m_Source->RegisterDialog(this); 245 | 246 | m_AutoTuneUp = false; 247 | m_AutoTuneDown = false; 248 | 249 | if (m_Source->IsActive()) 250 | { 251 | m_running = true; 252 | m_thread = std::thread([&] { Process(); }); 253 | } 254 | return true; 255 | } 256 | 257 | bool cChannelSettings::OnAction(ADDON_ACTION actionId) 258 | { 259 | bool ret = false; 260 | 261 | switch (actionId) 262 | { 263 | case ADDON_ACTION_MOUSE_WHEEL_UP: 264 | ret = OnClick(CONTROL_BUTTON_INCREASE_2); 265 | break; 266 | case ADDON_ACTION_MOUSE_WHEEL_DOWN: 267 | ret = OnClick(CONTROL_BUTTON_DECREASE_2); 268 | break; 269 | case ADDON_ACTION_PREVIOUS_MENU: 270 | case ADDON_ACTION_NAV_BACK: 271 | ret = OnClick(BUTTON_CANCEL); 272 | break; 273 | default: 274 | break; 275 | } 276 | 277 | return ret; 278 | } 279 | 280 | void cChannelSettings::UpdateFreq(uint32_t freq) 281 | { 282 | uint32_t newFreq = freq + 0.25 * m_Source->GetSource()->GetSampleRate(); 283 | 284 | SetControlLabel(CONTROL_LABEL_FREQ, StringUtils::Format("%.01f MHz", (float)freq / 1000000)); 285 | if (m_Source->IsActive()) 286 | m_Source->GetSource()->SetFrequency(newFreq); 287 | 288 | UpdateName("-"); 289 | } 290 | 291 | void cChannelSettings::UpdateName(std::string name) 292 | { 293 | m_Name = name; 294 | SetControlLabel(CONTROL_BUTTON_CHANNEL_NAME, m_Name); 295 | } 296 | -------------------------------------------------------------------------------- /src/ChannelSettings.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015-2020, Alwin Esch (Team KODI) 3 | * 4 | * SPDX-License-Identifier: GPL-2.0-or-later 5 | * See LICENSE.md for more information. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | class cRadioReceiver; 18 | 19 | class ATTRIBUTE_HIDDEN cChannelSettings : public kodi::gui::CWindow 20 | { 21 | public: 22 | cChannelSettings(); 23 | virtual ~cChannelSettings(); 24 | 25 | PVR_ERROR Open(const kodi::addon::PVRChannel& channel, cRadioReceiver* source, bool addAdjust); 26 | 27 | void UpdateName(std::string name); 28 | 29 | bool OnClick(int controlId) override; 30 | bool OnFocus(int controlId) override; 31 | bool OnInit() override; 32 | bool OnAction(ADDON_ACTION actionId) override; 33 | 34 | protected: 35 | void Process(); 36 | 37 | private: 38 | void UpdateFreq(uint32_t freq); 39 | 40 | std::atomic m_running = {false}; 41 | std::thread m_thread; 42 | 43 | int m_ChannelIndex; 44 | uint32_t m_CurrentFreq; 45 | uint32_t m_PrevFreq; 46 | cRadioReceiver* m_Source; 47 | bool m_addAdjust; 48 | bool m_AutoTuneIgnore; 49 | bool m_AutoTuneUp; 50 | bool m_AutoTuneDown; 51 | std::string m_Name; 52 | bool m_WasSaved; 53 | }; 54 | -------------------------------------------------------------------------------- /src/Definitions.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015-2020, Alwin Esch (Team KODI) 3 | * 4 | * SPDX-License-Identifier: GPL-2.0-or-later 5 | * See LICENSE.md for more information. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | 13 | #define DEVICE_RESTART_TRIES 5 14 | 15 | #define OUTPUT_SAMPLERATE 48000 16 | 17 | #undef USE_DOUBLE_PRECISION 18 | 19 | typedef enum eFilterType 20 | { 21 | ftLP, 22 | ftHP, 23 | ftBP, 24 | ftBR, 25 | ftConst 26 | } eFilterType; 27 | 28 | #ifdef USE_DOUBLE_PRECISION 29 | typedef std::complex ComplexType; 30 | typedef double RealType; 31 | 32 | #define MSIN(x) sin(x) 33 | #define MCOS(x) cos(x) 34 | #define MPOW(x, y) pow(x, y) 35 | #define MEXP(x) exp(x) 36 | #define MFABS(x) fabs(x) 37 | #define MLOG(x) log(x) 38 | #define MLOG10(x) log10(x) 39 | #define MSQRT(x) sqrt(x) 40 | #define MATAN(x) atan(x) 41 | #define MFMOD(x, y) fmod(x, y) 42 | #define MATAN2(x, y) atan2(x, y) 43 | #else 44 | typedef std::complex ComplexType; 45 | typedef float RealType; 46 | 47 | #define MSIN(x) sinf(x) 48 | #define MCOS(x) cosf(x) 49 | #define MPOW(x, y) powf(x, y) 50 | #define MEXP(x) expf(x) 51 | #define MFABS(x) fabsf(x) 52 | #define MLOG(x) logf(x) 53 | #define MLOG10(x) log10f(x) 54 | #define MSQRT(x) sqrtf(x) 55 | #define MATAN(x) atanf(x) 56 | #define MFMOD(x, y) fmodf(x, y) 57 | #define MATAN2(x, y) atan2f(x, y) 58 | #endif 59 | 60 | #define K_2PI (2.0 * 3.14159265358979323846) 61 | #define K_PI (3.14159265358979323846) 62 | #define K_PI4 (K_PI / 4.0) 63 | #define K_PI2 (K_PI / 2.0) 64 | #define K_3PI4 (3.0 * K_PI4) 65 | 66 | #define Hz(x) (x) 67 | #define Khz(x) (x * 1000) 68 | #define KHz(x) (x * 1000) 69 | #define Mhz(x) (Khz(x) * 1000) 70 | #define MHz(x) (KHz(x) * 1000) 71 | 72 | #include 73 | 74 | static inline void sincos_LP(RealType x, RealType& sin, RealType& cos) 75 | { 76 | //always wrap input angle to -PI..PI 77 | if (x < -3.14159265) 78 | x += 6.28318531; 79 | else if (x > 3.14159265) 80 | x -= 6.28318531; 81 | 82 | //compute sine 83 | if (x < 0) 84 | sin = 1.27323954 * x + .405284735 * x * x; 85 | else 86 | sin = 1.27323954 * x - 0.405284735 * x * x; 87 | 88 | //compute cosine: sin(x + PI/2) = cos(x) 89 | x += 1.57079632; 90 | if (x > 3.14159265) 91 | x -= 6.28318531; 92 | 93 | if (x < 0) 94 | cos = 1.27323954 * x + 0.405284735 * x * x; 95 | else 96 | cos = 1.27323954 * x - 0.405284735 * x * x; 97 | } 98 | 99 | static inline void sincos_HP(RealType x, RealType& sin, RealType& cos) 100 | { 101 | //always wrap input angle to -PI..PI 102 | if (x < -3.14159265) 103 | x += 6.28318531; 104 | else if (x > 3.14159265) 105 | x -= 6.28318531; 106 | 107 | //compute sine 108 | if (x < 0) 109 | { 110 | sin = 1.27323954 * x + .405284735 * x * x; 111 | if (sin < 0) 112 | sin = .225 * (sin * -sin - sin) + sin; 113 | else 114 | sin = .225 * (sin * sin - sin) + sin; 115 | } 116 | else 117 | { 118 | sin = 1.27323954 * x - 0.405284735 * x * x; 119 | if (sin < 0) 120 | sin = .225 * (sin * -sin - sin) + sin; 121 | else 122 | sin = .225 * (sin * sin - sin) + sin; 123 | } 124 | 125 | //compute cosine: sin(x + PI/2) = cos(x) 126 | x += 1.57079632; 127 | if (x > 3.14159265) 128 | x -= 6.28318531; 129 | 130 | if (x < 0) 131 | { 132 | cos = 1.27323954 * x + 0.405284735 * x * x; 133 | if (cos < 0) 134 | cos = .225 * (cos * -cos - cos) + cos; 135 | else 136 | cos = .225 * (cos * cos - cos) + cos; 137 | } 138 | else 139 | { 140 | cos = 1.27323954 * x - 0.405284735 * x * x; 141 | if (cos < 0) 142 | cos = .225 * (cos * -cos - cos) + cos; 143 | else 144 | cos = .225 * (cos * cos - cos) + cos; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/DownConvert.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013, Joris van Rantwijk. 3 | * Copyright (C) 2015-2020, Alwin Esch (Team KODI) 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "Definitions.h" 12 | 13 | #include 14 | 15 | /*! 16 | * Downsampler with low-pass FIR filter for real-valued signals. 17 | * 18 | * Step 1: Low-pass filter based on Lanczos FIR filter 19 | * Step 2: (optional) Decimation by an arbitrary factor (integer or float) 20 | */ 21 | class ATTRIBUTE_HIDDEN cDownsampleFilter 22 | { 23 | public: 24 | /*! 25 | * Construct low-pass filter with optional downsampling. 26 | * 27 | * filter_order :: FIR filter order 28 | * cutoff :: Cutoff frequency relative to the full input sample rate 29 | * (valid range 0.0 .. 0.5) 30 | * downsample :: Decimation factor (>= 1) or 1 to disable 31 | * integer_factor :: Enables a faster and more precise algorithm that 32 | * only works for integer downsample factors. 33 | * 34 | * The output sample rate is (input_sample_rate / downsample) 35 | */ 36 | cDownsampleFilter(unsigned int filter_order, 37 | double cutoff, 38 | double downsample = 1, 39 | bool integer_factor = true); 40 | virtual ~cDownsampleFilter(); 41 | 42 | void Reset(); 43 | 44 | /*! Process samples. */ 45 | unsigned int Process(const RealType* samples_in, RealType* samples_out, unsigned int length); 46 | unsigned int Process(const ComplexType* samples_in, 47 | ComplexType* samples_out, 48 | unsigned int length); 49 | 50 | private: 51 | double m_downsample; 52 | unsigned int m_downsample_int; 53 | unsigned int m_pos_int; 54 | RealType m_pos_frac; 55 | RealType* m_coeff; 56 | 57 | unsigned int m_stateOrderSize; 58 | RealType* m_stateReal; 59 | ComplexType* m_stateComplex; 60 | }; 61 | 62 | 63 | #define MAX_DECSTAGES 10 //one more than max to make sure is a null at end of list 64 | 65 | ////////////////////////////////////////////////////////////////////////////////// 66 | // Main Downconverter Class 67 | ////////////////////////////////////////////////////////////////////////////////// 68 | class CRDSDownConvert 69 | { 70 | public: 71 | CRDSDownConvert(); 72 | virtual ~CRDSDownConvert(); 73 | void SetFrequency(RealType NcoFreq); 74 | void SetCwOffset(RealType offset) { m_CW_Offset = offset; } 75 | int ProcessData(int InLength, ComplexType* pInData, ComplexType* pOutData); 76 | RealType SetDataRate(RealType InRate, RealType MaxBW); 77 | RealType SetWfmDataRate(RealType InRate, RealType MaxBW); 78 | 79 | private: 80 | //////////// 81 | //pure abstract base class for all the different types of decimate by 2 stages 82 | //DecBy2 function is defined in derived classes 83 | //////////// 84 | class CDec2 85 | { 86 | public: 87 | CDec2() {} 88 | virtual ~CDec2() {} 89 | virtual int DecBy2(int InLength, ComplexType* pInData, ComplexType* pOutData) = 0; 90 | }; 91 | 92 | //////////// 93 | //private class for the Half Band decimate by 2 stages 94 | //////////// 95 | class CHalfBandDecimateBy2 : public CDec2 96 | { 97 | public: 98 | CHalfBandDecimateBy2(int len, const RealType* pCoef); 99 | ~CHalfBandDecimateBy2() 100 | { 101 | if (m_pHBFirBuf) 102 | delete m_pHBFirBuf; 103 | } 104 | int DecBy2(int InLength, ComplexType* pInData, ComplexType* pOutData); 105 | ComplexType* m_pHBFirBuf; 106 | int m_FirLength; 107 | const RealType* m_pCoef; 108 | }; 109 | 110 | 111 | //////////// 112 | //private class for the fixed 11 tap Half Band decimate by 2 stages 113 | //////////// 114 | class CHalfBand11TapDecimateBy2 : public CDec2 115 | { 116 | public: 117 | CHalfBand11TapDecimateBy2(); 118 | ~CHalfBand11TapDecimateBy2() {} 119 | int DecBy2(int InLength, ComplexType* pInData, ComplexType* pOutData); 120 | RealType H0; //unwrapped coeeficients 121 | RealType H2; 122 | RealType H4; 123 | RealType H5; 124 | RealType H6; 125 | RealType H8; 126 | RealType H10; 127 | ComplexType d0; //unwrapped delay buffer 128 | ComplexType d1; 129 | ComplexType d2; 130 | ComplexType d3; 131 | ComplexType d4; 132 | ComplexType d5; 133 | ComplexType d6; 134 | ComplexType d7; 135 | ComplexType d8; 136 | ComplexType d9; 137 | }; 138 | 139 | //////////// 140 | //private class for the N=3 CIC decimate by 2 stages 141 | //////////// 142 | class CCicN3DecimateBy2 : public CDec2 143 | { 144 | public: 145 | CCicN3DecimateBy2(); 146 | ~CCicN3DecimateBy2() {} 147 | int DecBy2(int InLength, ComplexType* pInData, ComplexType* pOutData); 148 | ComplexType m_Xodd; 149 | ComplexType m_Xeven; 150 | }; 151 | 152 | private: 153 | //private helper functions 154 | void DeleteFilters(); 155 | 156 | RealType m_OutputRate; 157 | RealType m_NcoFreq; 158 | RealType m_CW_Offset; 159 | RealType m_NcoInc; 160 | RealType m_NcoTime; 161 | RealType m_InRate; 162 | RealType m_MaxBW; 163 | ComplexType m_Osc1; 164 | RealType m_OscCos; 165 | RealType m_OscSin; 166 | std::mutex m_Mutex; //for keeping threads from stomping on each other 167 | //array of pointers for performing decimate by 2 stages 168 | CDec2* m_pDecimatorPtrs[MAX_DECSTAGES]; 169 | }; 170 | -------------------------------------------------------------------------------- /src/Filter.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "Filter.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace std; 11 | 12 | 13 | /** Prepare Lanczos FIR filter coefficients. */ 14 | template 15 | static void make_lanczos_coeff(unsigned int filter_order, double cutoff, vector& coeff) 16 | { 17 | coeff.resize(filter_order + 1); 18 | 19 | // Prepare Lanczos FIR filter. 20 | // t[i] = (i - order/2) 21 | // coeff[i] = Sinc(2 * cutoff * t[i]) * Sinc(t[i] / (order/2 + 1)) 22 | // coeff /= sum(coeff) 23 | 24 | double ysum = 0.0; 25 | 26 | // Calculate filter kernel. 27 | for (int i = 0; i <= (int)filter_order; i++) 28 | { 29 | int t2 = 2 * i - filter_order; 30 | 31 | double y; 32 | if (t2 == 0) 33 | { 34 | y = 1.0; 35 | } 36 | else 37 | { 38 | double x1 = cutoff * t2; 39 | double x2 = t2 / double(filter_order + 2); 40 | y = (sin(M_PI * x1) / M_PI / x1) * (sin(M_PI * x2) / M_PI / x2); 41 | } 42 | 43 | coeff[i] = y; 44 | ysum += y; 45 | } 46 | 47 | // Apply correction factor to ensure unit gain at DC. 48 | for (unsigned i = 0; i <= filter_order; i++) 49 | { 50 | coeff[i] /= ysum; 51 | } 52 | } 53 | 54 | 55 | /* **************** class FineTuner **************** */ 56 | 57 | // Construct finetuner. 58 | FineTuner::FineTuner(unsigned int table_size, int freq_shift) : m_index(0), m_table(table_size) 59 | { 60 | double phase_step = 2.0 * M_PI / double(table_size); 61 | for (unsigned int i = 0; i < table_size; i++) 62 | { 63 | double phi = (((int64_t)freq_shift * i) % table_size) * phase_step; 64 | double pcos = cos(phi); 65 | double psin = sin(phi); 66 | m_table[i] = IQSample(pcos, psin); 67 | } 68 | } 69 | 70 | 71 | // Process samples. 72 | void FineTuner::process(const IQSampleVector& samples_in, IQSampleVector& samples_out) 73 | { 74 | unsigned int tblidx = m_index; 75 | unsigned int tblsiz = m_table.size(); 76 | unsigned int n = samples_in.size(); 77 | 78 | samples_out.resize(n); 79 | 80 | for (unsigned int i = 0; i < n; i++) 81 | { 82 | samples_out[i] = samples_in[i] * m_table[tblidx]; 83 | tblidx++; 84 | if (tblidx == tblsiz) 85 | tblidx = 0; 86 | } 87 | 88 | m_index = tblidx; 89 | } 90 | 91 | 92 | /* **************** class LowPassFilterFirIQ **************** */ 93 | 94 | // Construct low-pass filter. 95 | LowPassFilterFirIQ::LowPassFilterFirIQ(unsigned int filter_order, double cutoff) 96 | : m_state(filter_order) 97 | { 98 | make_lanczos_coeff(filter_order, cutoff, m_coeff); 99 | } 100 | 101 | 102 | // Process samples. 103 | void LowPassFilterFirIQ::process(const IQSampleVector& samples_in, IQSampleVector& samples_out) 104 | { 105 | unsigned int order = m_state.size(); 106 | unsigned int n = samples_in.size(); 107 | 108 | samples_out.resize(n); 109 | 110 | if (n == 0) 111 | return; 112 | 113 | // NOTE: We use m_coeff the wrong way around because it is slightly 114 | // faster to scan forward through the array. The result is still correct 115 | // because the coefficients are symmetric. 116 | 117 | // The first few samples need data from m_state. 118 | unsigned int i = 0; 119 | for (; i < n && i < order; i++) 120 | { 121 | IQSample y = 0; 122 | for (unsigned int j = 0; j < order - i; j++) 123 | y += m_state[i + j] * m_coeff[j]; 124 | for (unsigned int j = order - i; j <= order; j++) 125 | y += samples_in[i - order + j] * m_coeff[j]; 126 | samples_out[i] = y; 127 | } 128 | 129 | // Remaining samples only need data from samples_in. 130 | for (; i < n; i++) 131 | { 132 | IQSample y = 0; 133 | IQSampleVector::const_iterator inp = samples_in.begin() + i - order; 134 | for (unsigned int j = 0; j <= order; j++) 135 | y += inp[j] * m_coeff[j]; 136 | samples_out[i] = y; 137 | } 138 | 139 | // Update m_state. 140 | if (n < order) 141 | { 142 | copy(m_state.begin() + n, m_state.end(), m_state.begin()); 143 | copy(samples_in.begin(), samples_in.end(), m_state.end() - n); 144 | } 145 | else 146 | { 147 | copy(samples_in.end() - order, samples_in.end(), m_state.begin()); 148 | } 149 | } 150 | 151 | 152 | /* **************** class DownsampleFilter **************** */ 153 | 154 | // Construct low-pass filter with optional downsampling. 155 | DownsampleFilter::DownsampleFilter(unsigned int filter_order, 156 | double cutoff, 157 | double downsample, 158 | bool integer_factor) 159 | : m_downsample(downsample), 160 | m_downsample_int(integer_factor ? lrint(downsample) : 0), 161 | m_pos_int(0), 162 | m_pos_frac(0), 163 | m_state(filter_order) 164 | { 165 | assert(downsample >= 1); 166 | assert(filter_order > 1); 167 | 168 | // Force the first coefficient to zero and append an extra zero at the 169 | // end of the array. This ensures we can always obtain (filter_order+1) 170 | // coefficients by linear interpolation between adjacent array elements. 171 | make_lanczos_coeff(filter_order - 1, cutoff, m_coeff); 172 | m_coeff.insert(m_coeff.begin(), 0); 173 | m_coeff.push_back(0); 174 | } 175 | 176 | 177 | // Process samples. 178 | void DownsampleFilter::process(const SampleVector& samples_in, SampleVector& samples_out) 179 | { 180 | unsigned int order = m_state.size(); 181 | unsigned int n = samples_in.size(); 182 | 183 | if (m_downsample_int != 0) 184 | { 185 | 186 | // Integer downsample factor, no linear interpolation. 187 | // This is relatively simple. 188 | 189 | unsigned int p = m_pos_int; 190 | unsigned int pstep = m_downsample_int; 191 | 192 | samples_out.resize((n - p + pstep - 1) / pstep); 193 | 194 | // The first few samples need data from m_state. 195 | unsigned int i = 0; 196 | for (; p < n && p < order; p += pstep, i++) 197 | { 198 | Sample y = 0; 199 | for (unsigned int j = 1; j <= p; j++) 200 | y += samples_in[p - j] * m_coeff[j]; 201 | for (unsigned int j = p + 1; j <= order; j++) 202 | y += m_state[order + p - j] * m_coeff[j]; 203 | samples_out[i] = y; 204 | } 205 | 206 | // Remaining samples only need data from samples_in. 207 | for (; p < n; p += pstep, i++) 208 | { 209 | Sample y = 0; 210 | for (unsigned int j = 1; j <= order; j++) 211 | y += samples_in[p - j] * m_coeff[j]; 212 | samples_out[i] = y; 213 | } 214 | 215 | assert(i == samples_out.size()); 216 | 217 | // Update index of start position in text sample block. 218 | m_pos_int = p - n; 219 | } 220 | else 221 | { 222 | 223 | // Fractional downsample factor via linear interpolation of 224 | // the FIR coefficient table. This is a bitch. 225 | 226 | // Estimate number of output samples we can produce in this run. 227 | Sample p = m_pos_frac; 228 | Sample pstep = m_downsample; 229 | unsigned int n_out = int(2 + n / pstep); 230 | 231 | samples_out.resize(n_out); 232 | 233 | // Produce output samples. 234 | unsigned int i = 0; 235 | Sample pf = p; 236 | unsigned int pi = int(pf); 237 | while (pi < n) 238 | { 239 | Sample k1 = pf - pi; 240 | Sample k0 = 1 - k1; 241 | 242 | Sample y = 0; 243 | for (unsigned int j = 0; j <= order; j++) 244 | { 245 | Sample k = m_coeff[j] * k0 + m_coeff[j + 1] * k1; 246 | Sample s = (j <= pi) ? samples_in[pi - j] : m_state[order + pi - j]; 247 | y += k * s; 248 | } 249 | samples_out[i] = y; 250 | 251 | i++; 252 | pf = p + i * pstep; 253 | pi = int(pf); 254 | } 255 | 256 | // We may overestimate the number of samples by 1 or 2. 257 | assert(i <= n_out && i + 2 >= n_out); 258 | samples_out.resize(i); 259 | 260 | // Update fractional index of start position in text sample block. 261 | // Limit to 0 to avoid catastrophic results of rounding errors. 262 | m_pos_frac = pf - n; 263 | if (m_pos_frac < 0) 264 | m_pos_frac = 0; 265 | } 266 | 267 | // Update m_state. 268 | if (n < order) 269 | { 270 | copy(m_state.begin() + n, m_state.end(), m_state.begin()); 271 | copy(samples_in.begin(), samples_in.end(), m_state.end() - n); 272 | } 273 | else 274 | { 275 | copy(samples_in.end() - order, samples_in.end(), m_state.begin()); 276 | } 277 | } 278 | 279 | 280 | /* **************** class LowPassFilterRC **************** */ 281 | 282 | // Construct 1st order low-pass IIR filter. 283 | LowPassFilterRC::LowPassFilterRC(double timeconst) : m_timeconst(timeconst), m_y1(0) 284 | { 285 | } 286 | 287 | 288 | // Process samples. 289 | void LowPassFilterRC::process(const SampleVector& samples_in, SampleVector& samples_out) 290 | { 291 | /* 292 | * Continuous domain: 293 | * H(s) = 1 / (1 - s * timeconst) 294 | * 295 | * Discrete domain: 296 | * H(z) = (1 - exp(-1/timeconst)) / (1 - exp(-1/timeconst) / z) 297 | */ 298 | Sample a1 = -exp(-1 / m_timeconst); 299 | ; 300 | Sample b0 = 1 + a1; 301 | 302 | unsigned int n = samples_in.size(); 303 | samples_out.resize(n); 304 | 305 | Sample y = m_y1; 306 | for (unsigned int i = 0; i < n; i++) 307 | { 308 | Sample x = samples_in[i]; 309 | y = b0 * x - a1 * y; 310 | samples_out[i] = y; 311 | } 312 | 313 | m_y1 = y; 314 | } 315 | 316 | 317 | // Process samples in-place. 318 | void LowPassFilterRC::process_inplace(SampleVector& samples) 319 | { 320 | Sample a1 = -exp(-1 / m_timeconst); 321 | ; 322 | Sample b0 = 1 + a1; 323 | 324 | unsigned int n = samples.size(); 325 | 326 | Sample y = m_y1; 327 | for (unsigned int i = 0; i < n; i++) 328 | { 329 | Sample x = samples[i]; 330 | y = b0 * x - a1 * y; 331 | samples[i] = y; 332 | } 333 | 334 | m_y1 = y; 335 | } 336 | 337 | 338 | /* **************** class LowPassFilterIir **************** */ 339 | 340 | // Construct 4th order low-pass IIR filter. 341 | LowPassFilterIir::LowPassFilterIir(double cutoff) : y1(0), y2(0), y3(0), y4(0) 342 | { 343 | typedef complex CDbl; 344 | 345 | // Angular cutoff frequency. 346 | double w = 2 * M_PI * cutoff; 347 | 348 | // Poles 1 and 4 are a conjugate pair, and poles 2 and 3 are another pair. 349 | // Continuous domain: 350 | // p_k = w * exp( (2*k + n - 1) / (2*n) * pi * j) 351 | CDbl p1s = w * exp((2 * 1 + 4 - 1) / double(2 * 4) * CDbl(0, M_PI)); 352 | CDbl p2s = w * exp((2 * 2 + 4 - 1) / double(2 * 4) * CDbl(0, M_PI)); 353 | 354 | // Map poles to discrete-domain via matched Z transform. 355 | CDbl p1z = exp(p1s); 356 | CDbl p2z = exp(p2s); 357 | 358 | // Discrete-domain transfer function: 359 | // H(z) = b0 / ( (1 - p1/z) * (1 - p4/z) * (1 - p2/z) * (1 - p3/z) ) 360 | // = b0 / ( (1 - (p1+p4)/z + p1*p4/z**2) * 361 | // (1 - (p2+p3)/z + p2*p3/z**2) ) 362 | // = b0 / (1 - (p1 + p4 + p2 + p3)/z 363 | // + (p1*p4 + p2*p3 + (p1+p4)*(p2+p3))/z**2 364 | // - ((p1+p4)*p2*p3 + (p2+p3)*p1*p4)/z**3 365 | // + p1*p4*p2*p3/z**4 366 | // 367 | // Note that p3 = conj(p2), p4 = conj(p1) 368 | // Therefore p1+p4 == 2*real(p1), p1*p4 == abs(p1*p1) 369 | // 370 | a1 = -(2 * real(p1z) + 2 * real(p2z)); 371 | a2 = (abs(p1z * p1z) + abs(p2z * p2z) + 2 * real(p1z) * 2 * real(p2z)); 372 | a3 = -(2 * real(p1z) * abs(p2z * p2z) + 2 * real(p2z) * abs(p1z * p1z)); 373 | a4 = abs(p1z * p1z) * abs(p2z * p2z); 374 | 375 | // Choose b0 to get unit DC gain. 376 | b0 = 1 + a1 + a2 + a3 + a4; 377 | } 378 | 379 | 380 | // Process samples. 381 | void LowPassFilterIir::process(const SampleVector& samples_in, SampleVector& samples_out) 382 | { 383 | unsigned int n = samples_in.size(); 384 | 385 | samples_out.resize(n); 386 | 387 | for (unsigned int i = 0; i < n; i++) 388 | { 389 | Sample x = samples_in[i]; 390 | Sample y = b0 * x - a1 * y1 - a2 * y2 - a3 * y3 - a4 * y4; 391 | y4 = y3; 392 | y3 = y2; 393 | y2 = y1; 394 | y1 = y; 395 | samples_out[i] = y; 396 | } 397 | } 398 | 399 | 400 | /* **************** class HighPassFilterIir **************** */ 401 | 402 | // Construct 2nd order high-pass IIR filter. 403 | HighPassFilterIir::HighPassFilterIir(double cutoff) : x1(0), x2(0), y1(0), y2(0) 404 | { 405 | typedef complex CDbl; 406 | 407 | // Angular cutoff frequency. 408 | double w = 2 * M_PI * cutoff; 409 | 410 | // Poles 1 and 2 are a conjugate pair. 411 | // Continuous-domain: 412 | // p_k = w / exp( (2*k + n - 1) / (2*n) * pi * j) 413 | CDbl p1s = w / exp((2 * 1 + 2 - 1) / double(2 * 2) * CDbl(0, M_PI)); 414 | 415 | // Map poles to discrete-domain via matched Z transform. 416 | CDbl p1z = exp(p1s); 417 | 418 | // Both zeros are located in s = 0, z = 1. 419 | 420 | // Discrete-domain transfer function: 421 | // H(z) = g * (1 - 1/z) * (1 - 1/z) / ( (1 - p1/z) * (1 - p2/z) ) 422 | // = g * (1 - 2/z + 1/z**2) / (1 - (p1+p2)/z + (p1*p2)/z**2) 423 | // 424 | // Note that z2 = conj(z1). 425 | // Therefore p1+p2 == 2*real(p1), p1*2 == abs(p1*p1), z4 = conj(z1) 426 | // 427 | b0 = 1; 428 | b1 = -2; 429 | b2 = 1; 430 | a1 = -2 * real(p1z); 431 | a2 = abs(p1z * p1z); 432 | 433 | // Adjust b coefficients to get unit gain at Nyquist frequency (z=-1). 434 | double g = (b0 - b1 + b2) / (1 - a1 + a2); 435 | b0 /= g; 436 | b1 /= g; 437 | b2 /= g; 438 | } 439 | 440 | 441 | // Process samples. 442 | void HighPassFilterIir::process(const SampleVector& samples_in, SampleVector& samples_out) 443 | { 444 | unsigned int n = samples_in.size(); 445 | 446 | samples_out.resize(n); 447 | 448 | for (unsigned int i = 0; i < n; i++) 449 | { 450 | Sample x = samples_in[i]; 451 | Sample y = b0 * x + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2; 452 | x2 = x1; 453 | x1 = x; 454 | y2 = y1; 455 | y1 = y; 456 | samples_out[i] = y; 457 | } 458 | } 459 | 460 | 461 | // Process samples in-place. 462 | void HighPassFilterIir::process_inplace(SampleVector& samples) 463 | { 464 | unsigned int n = samples.size(); 465 | 466 | for (unsigned int i = 0; i < n; i++) 467 | { 468 | Sample x = samples[i]; 469 | Sample y = b0 * x + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2; 470 | x2 = x1; 471 | x1 = x; 472 | y2 = y1; 473 | y1 = y; 474 | samples[i] = y; 475 | } 476 | } 477 | 478 | /* end */ 479 | -------------------------------------------------------------------------------- /src/Filter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Utils.h" 4 | 5 | #include 6 | 7 | /** Fine tuner which shifts the frequency of an IQ signal by a fixed offset. */ 8 | class FineTuner 9 | { 10 | public: 11 | /** 12 | * Construct fine tuner. 13 | * 14 | * table_size :: Size of internal sin/cos tables, determines the resolution 15 | * of the frequency shift. 16 | * 17 | * freq_shift :: Frequency shift. Signal frequency will be shifted by 18 | * (sample_rate * freq_shift / table_size). 19 | */ 20 | FineTuner(unsigned int table_size, int freq_shift); 21 | 22 | /** Process samples. */ 23 | void process(const IQSampleVector& samples_in, IQSampleVector& samples_out); 24 | 25 | private: 26 | unsigned int m_index; 27 | IQSampleVector m_table; 28 | }; 29 | 30 | 31 | /** Low-pass filter for IQ samples, based on Lanczos FIR filter. */ 32 | class LowPassFilterFirIQ 33 | { 34 | public: 35 | /** 36 | * Construct low-pass filter. 37 | * 38 | * filter_order :: FIR filter order. 39 | * cutoff :: Cutoff frequency relative to the full sample rate 40 | * (valid range 0.0 ... 0.5). 41 | */ 42 | LowPassFilterFirIQ(unsigned int filter_order, double cutoff); 43 | 44 | /** Process samples. */ 45 | void process(const IQSampleVector& samples_in, IQSampleVector& samples_out); 46 | 47 | private: 48 | std::vector m_coeff; 49 | IQSampleVector m_state; 50 | }; 51 | 52 | 53 | /** 54 | * Downsampler with low-pass FIR filter for real-valued signals. 55 | * 56 | * Step 1: Low-pass filter based on Lanczos FIR filter 57 | * Step 2: (optional) Decimation by an arbitrary factor (integer or float) 58 | */ 59 | class DownsampleFilter 60 | { 61 | public: 62 | /** 63 | * Construct low-pass filter with optional downsampling. 64 | * 65 | * filter_order :: FIR filter order 66 | * cutoff :: Cutoff frequency relative to the full input sample rate 67 | * (valid range 0.0 .. 0.5) 68 | * downsample :: Decimation factor (>= 1) or 1 to disable 69 | * integer_factor :: Enables a faster and more precise algorithm that 70 | * only works for integer downsample factors. 71 | * 72 | * The output sample rate is (input_sample_rate / downsample) 73 | */ 74 | DownsampleFilter(unsigned int filter_order, 75 | double cutoff, 76 | double downsample = 1, 77 | bool integer_factor = true); 78 | 79 | /** Process samples. */ 80 | void process(const SampleVector& samples_in, SampleVector& samples_out); 81 | 82 | private: 83 | double m_downsample; 84 | unsigned int m_downsample_int; 85 | unsigned int m_pos_int; 86 | Sample m_pos_frac; 87 | SampleVector m_coeff; 88 | SampleVector m_state; 89 | }; 90 | 91 | 92 | /** First order low-pass IIR filter for real-valued signals. */ 93 | class LowPassFilterRC 94 | { 95 | public: 96 | /** 97 | * Construct 1st order low-pass IIR filter. 98 | * 99 | * timeconst :: RC time constant in seconds (1 / (2 * PI * cutoff_freq) 100 | */ 101 | LowPassFilterRC(double timeconst); 102 | 103 | /** Process samples. */ 104 | void process(const SampleVector& samples_in, SampleVector& samples_out); 105 | 106 | /** Process samples in-place. */ 107 | void process_inplace(SampleVector& samples); 108 | 109 | private: 110 | double m_timeconst; 111 | Sample m_y1; 112 | }; 113 | 114 | 115 | /** Low-pass filter for real-valued signals based on Butterworth IIR filter. */ 116 | class LowPassFilterIir 117 | { 118 | public: 119 | /** 120 | * Construct 4th order low-pass IIR filter. 121 | * 122 | * cutoff :: Low-pass cutoff relative to the sample frequency 123 | * (valid range 0.0 .. 0.5, 0.5 = Nyquist) 124 | */ 125 | LowPassFilterIir(double cutoff); 126 | 127 | /** Process samples. */ 128 | void process(const SampleVector& samples_in, SampleVector& samples_out); 129 | 130 | private: 131 | Sample b0, a1, a2, a3, a4; 132 | Sample y1, y2, y3, y4; 133 | }; 134 | 135 | 136 | /** High-pass filter for real-valued signals based on Butterworth IIR filter. */ 137 | class HighPassFilterIir 138 | { 139 | public: 140 | /** 141 | * Construct 2nd order high-pass IIR filter. 142 | * 143 | * cutoff :: High-pass cutoff relative to the sample frequency 144 | * (valid range 0.0 .. 0.5, 0.5 = Nyquist) 145 | */ 146 | HighPassFilterIir(double cutoff); 147 | 148 | /** Process samples. */ 149 | void process(const SampleVector& samples_in, SampleVector& samples_out); 150 | 151 | /** Process samples in-place. */ 152 | void process_inplace(SampleVector& samples); 153 | 154 | private: 155 | Sample b0, b1, b2, a1, a2; 156 | Sample x1, x2, y1, y2; 157 | }; 158 | -------------------------------------------------------------------------------- /src/FirFilter.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2013, Moe Wheatley 3 | * Copyright (C) 2015-2020, Alwin Esch (Team KODI) 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | /*! 10 | * This class implements a FIR filter using a dual flat coefficient 11 | * array to eliminate testing for buffer wrap around. 12 | * 13 | * Filter coefficients can be from a fixed table or this class will create 14 | * a lowpass or highpass filter from frequency and attenuation specifications 15 | * using a Kaiser-Bessel windowed sinc algorithm 16 | * 17 | * History of Moe Wheatley: 18 | * 2011-01-29 Initial creation MSW 19 | * 2011-03-27 Initial release 20 | * 2011-08-07 Modified FIR filter initialization to force fixed size 21 | * 2013-07-28 Added single/double precision math macros 22 | */ 23 | 24 | #include "FirFilter.h" 25 | 26 | #define MAX_HALF_BAND_BUFSIZE 8192 27 | 28 | cFirFilter::cFirFilter() 29 | { 30 | m_NumTaps = 1; 31 | m_State = 0; 32 | } 33 | 34 | /*! 35 | * private helper function to Compute Modified Bessel function I0(x) 36 | * using a series approximation. 37 | * I0(x) = 1.0 + { sum from k=1 to infinity ----> [(x/2)^k / k!]^2 } 38 | */ 39 | RealType cFirFilter::Izero(RealType x) 40 | { 41 | RealType x2 = x / 2.0f; 42 | RealType sum = 1.0f; 43 | RealType ds = 1.0f; 44 | RealType di = 1.0f; 45 | RealType errorlimit = 1e-9; 46 | RealType tmp; 47 | 48 | do 49 | { 50 | tmp = x2 / di; 51 | tmp *= tmp; 52 | ds *= tmp; 53 | sum += ds; 54 | di += 1.0; 55 | } while (ds >= errorlimit * sum); 56 | 57 | return (sum); 58 | } 59 | 60 | /*! 61 | * Create a FIR Low Pass filter with scaled amplitude 'Scale' 62 | * NumTaps if non-zero, forces filter design to be this number of taps 63 | * Scale is linear amplitude scale factor. 64 | * Astop = Stopband Atenuation in dB (ie 40dB is 40dB stopband attenuation) 65 | * Fpass = Lowpass passband frequency in Hz 66 | * Fstop = Lowpass stopband frequency in Hz 67 | * Fsamprate = Sample Rate in Hz 68 | * 69 | * ------------- 70 | * | 71 | * | 72 | * | 73 | * | 74 | * Astop --------------- 75 | * Fpass Fstop 76 | * 77 | */ 78 | int cFirFilter::InitLPFilter(unsigned int NumTaps, 79 | RealType Scale, 80 | RealType Astop, 81 | RealType Fpass, 82 | RealType Fstop, 83 | RealType Fsamprate) 84 | { 85 | RealType Beta; 86 | m_SampleRate = Fsamprate; 87 | //! create normalized frequency parameters 88 | RealType normFpass = Fpass / Fsamprate; 89 | RealType normFstop = Fstop / Fsamprate; 90 | RealType normFcut = (normFstop + normFpass) / 2.0f; //!< low pass filter 6dB cutoff 91 | 92 | //! calculate Kaiser-Bessel window shape factor, Beta, from stopband attenuation 93 | if (Astop < 20.96f) 94 | Beta = 0; 95 | else if (Astop >= 50.0f) 96 | Beta = .1102 * (Astop - 8.71f); 97 | else 98 | Beta = .5842 * MPOW((Astop - 20.96f), 0.4) + .07886f * (Astop - 20.96f); 99 | 100 | //! Now Estimate number of filter taps required based on filter specs 101 | m_NumTaps = (Astop - 8.0f) / (2.285f * K_2PI * (normFstop - normFpass)) + 1; 102 | 103 | //! clamp range of filter taps 104 | if (m_NumTaps > MAX_NUMCOEF) 105 | m_NumTaps = MAX_NUMCOEF; 106 | if (m_NumTaps < 3) 107 | m_NumTaps = 3; 108 | 109 | if (NumTaps) //!< if need to force to to a number of taps 110 | m_NumTaps = NumTaps; 111 | 112 | RealType fCenter = .5 * (RealType)(m_NumTaps - 1); 113 | RealType izb = Izero(Beta); //!< precalculate denominator since is same for all points 114 | for (unsigned int n = 0; n < m_NumTaps; ++n) 115 | { 116 | RealType x = (RealType)n - fCenter; 117 | RealType c; 118 | //! create ideal Sinc() LP filter with normFcut 119 | if ((RealType)n == fCenter) //!< deal with odd size filter singularity where sin(0)/0==1 120 | c = 2.0 * normFcut; 121 | else 122 | c = MSIN(K_2PI * x * normFcut) / (K_PI * x); 123 | //! calculate Kaiser window and multiply to get coefficient 124 | x = ((RealType)n - ((RealType)m_NumTaps - 1.0f) / 2.0f) / (((RealType)m_NumTaps - 1.0f) / 2.0f); 125 | m_Coef[n] = Scale * c * Izero(Beta * MSQRT(1 - (x * x))) / izb; 126 | } 127 | 128 | //! make a 2x length array for FIR flat calculation efficiency 129 | for (unsigned int n = 0; n < m_NumTaps; ++n) 130 | m_Coef[n + m_NumTaps] = m_Coef[n]; 131 | 132 | //! copy into complex coef buffers 133 | for (unsigned int n = 0; n < m_NumTaps * 2; ++n) 134 | { 135 | m_ICoef[n] = m_Coef[n]; 136 | m_QCoef[n] = m_Coef[n]; 137 | } 138 | 139 | //! Initialize the FIR buffers and state 140 | for (unsigned int i = 0; i < m_NumTaps; i++) 141 | { 142 | m_rZBuf[i] = 0.0; 143 | m_cZBuf[i] = 0.0; 144 | } 145 | m_State = 0; 146 | 147 | return m_NumTaps; 148 | } 149 | 150 | /*! 151 | * function to convert LP filter coefficients into complex hilbert bandpass 152 | * filter coefficients. 153 | * Hbpreal(n)= 2*Hlp(n)*cos( 2PI*FreqOffset*(n-(N-1)/2)/samplerate ); 154 | * Hbpimaj(n)= 2*Hlp(n)*sin( 2PI*FreqOffset*(n-(N-1)/2)/samplerate ); 155 | */ 156 | void cFirFilter::GenerateHBFilter(RealType FreqOffset) 157 | { 158 | for (unsigned int n = 0; n < m_NumTaps; ++n) 159 | { 160 | //! apply complex frequency shift transform to low pass filter coefficients 161 | RealType psin; 162 | RealType pcos; 163 | sincos_HP((K_2PI * FreqOffset / m_SampleRate) * 164 | ((RealType)n - ((RealType)(m_NumTaps - 1) / 2.0)), 165 | psin, pcos); 166 | m_ICoef[n] = 2.0f * m_Coef[n] * pcos; 167 | m_QCoef[n] = 2.0f * m_Coef[n] * psin; 168 | } 169 | 170 | //! make a 2x length array for FIR flat calculation efficiency 171 | for (unsigned int n = 0; n < m_NumTaps; ++n) 172 | { 173 | m_ICoef[n + m_NumTaps] = m_ICoef[n]; 174 | m_QCoef[n + m_NumTaps] = m_QCoef[n]; 175 | } 176 | } 177 | 178 | /*! 179 | * Create a FIR high Pass filter with scaled amplitude 'Scale' 180 | * NumTaps if non-zero, forces filter design to be this number of taps 181 | * Astop = Stopband Atenuation in dB (ie 40dB is 40dB stopband attenuation) 182 | * Fpass = Highpass passband frequency in Hz 183 | * Fstop = Highpass stopband frequency in Hz 184 | * Fsamprate = Sample Rate in Hz 185 | * 186 | * Apass ------------- 187 | * / 188 | * / 189 | * / 190 | * / 191 | * Astop --------- 192 | * Fstop Fpass 193 | */ 194 | int cFirFilter::InitHPFilter(unsigned int NumTaps, 195 | RealType Scale, 196 | RealType Astop, 197 | RealType Fpass, 198 | RealType Fstop, 199 | RealType Fsamprate) 200 | { 201 | RealType Beta; 202 | m_SampleRate = Fsamprate; 203 | //! create normalized frequency parameters 204 | RealType normFpass = Fpass / Fsamprate; 205 | RealType normFstop = Fstop / Fsamprate; 206 | RealType normFcut = (normFstop + normFpass) / 2.0; //high pass filter 6dB cutoff 207 | 208 | //! calculate Kaiser-Bessel window shape factor, Beta, from stopband attenuation 209 | if (Astop < 20.96f) 210 | Beta = 0; 211 | else if (Astop >= 50.0f) 212 | Beta = .1102f * (Astop - 8.71f); 213 | else 214 | Beta = .5842f * MPOW((Astop - 20.96f), 0.4f) + .07886f * (Astop - 20.96f); 215 | 216 | //! Now Estimate number of filter taps required based on filter specs 217 | m_NumTaps = (Astop - 8.0f) / (2.285f * K_2PI * (normFpass - normFstop)) + 1; 218 | 219 | //! clamp range of filter taps 220 | if (m_NumTaps > (MAX_NUMCOEF - 1)) 221 | m_NumTaps = MAX_NUMCOEF - 1; 222 | if (m_NumTaps < 3) 223 | m_NumTaps = 3; 224 | 225 | m_NumTaps |= 1; //!< force to next odd number 226 | 227 | if (NumTaps) //!< if need to force to to a number of taps 228 | m_NumTaps = NumTaps; 229 | 230 | RealType izb = Izero(Beta); //!< precalculate denominator since is same for all points 231 | RealType fCenter = .5f * (RealType)(m_NumTaps - 1); 232 | for (unsigned int n = 0; n < m_NumTaps; n++) 233 | { 234 | RealType x = (RealType)n - (RealType)(m_NumTaps - 1) / 2.0; 235 | RealType c; 236 | //! create ideal Sinc() HP filter with normFcut 237 | if ((RealType)n == fCenter) //!< deal with odd size filter singularity where sin(0)/0==1 238 | c = 1.0 - 2.0 * normFcut; 239 | else 240 | c = MSIN(K_PI * x) / (K_PI * x) - MSIN(K_2PI * x * normFcut) / (K_PI * x); 241 | 242 | //! calculate Kaiser window and multiply to get coefficient 243 | x = ((RealType)n - ((RealType)m_NumTaps - 1.0f) / 2.0f) / (((RealType)m_NumTaps - 1.0f) / 2.0f); 244 | m_Coef[n] = Scale * c * Izero(Beta * MSQRT(1 - (x * x))) / izb; 245 | } 246 | 247 | //! make a 2x length array for FIR flat calculation efficiency 248 | for (unsigned int n = 0; n < m_NumTaps; ++n) 249 | m_Coef[n + m_NumTaps] = m_Coef[n]; 250 | 251 | //! copy into complex coef buffers 252 | for (unsigned int n = 0; n < m_NumTaps * 2; ++n) 253 | { 254 | m_ICoef[n] = m_Coef[n]; 255 | m_QCoef[n] = m_Coef[n]; 256 | } 257 | 258 | //! Initialize the FIR buffers and state 259 | for (unsigned int i = 0; i < m_NumTaps; ++i) 260 | { 261 | m_rZBuf[i] = 0.0; 262 | m_cZBuf[i] = 0.0; 263 | } 264 | m_State = 0; 265 | 266 | return m_NumTaps; 267 | } 268 | 269 | /*! 270 | * Initializes a pre-designed complex FIR filter with fixed coefficients 271 | * Iniitalize FIR variables and clear out buffers. 272 | */ 273 | void cFirFilter::InitConstFir(unsigned int NumTaps, 274 | const RealType* pICoef, 275 | const RealType* pQCoef, 276 | RealType Fsamprate) 277 | { 278 | m_SampleRate = Fsamprate; 279 | if (NumTaps > MAX_NUMCOEF) 280 | m_NumTaps = MAX_NUMCOEF; 281 | else 282 | m_NumTaps = NumTaps; 283 | for (unsigned int i = 0; i < m_NumTaps; i++) 284 | { 285 | m_ICoef[i] = pICoef[i]; 286 | m_ICoef[m_NumTaps + i] = pICoef[i]; //!< create duplicate for calculation efficiency 287 | m_QCoef[i] = pQCoef[i]; 288 | m_QCoef[m_NumTaps + i] = pQCoef[i]; //!< create duplicate for calculation efficiency 289 | } 290 | for (unsigned int i = 0; i < m_NumTaps; i++) 291 | { //! zero input buffers 292 | m_rZBuf[i] = 0.0; 293 | m_cZBuf[i] = 0.0; 294 | } 295 | m_State = 0; //!< zero filter state variable 296 | } 297 | 298 | /*! 299 | * Initializes a pre-designed FIR filter with fixed coefficients 300 | * Iniitalize FIR variables and clear out buffers. 301 | */ 302 | void cFirFilter::InitConstFir(unsigned int NumTaps, const RealType* pCoef, RealType Fsamprate) 303 | { 304 | m_SampleRate = Fsamprate; 305 | if (NumTaps > MAX_NUMCOEF) 306 | m_NumTaps = MAX_NUMCOEF; 307 | else 308 | m_NumTaps = NumTaps; 309 | for (unsigned int i = 0; i < m_NumTaps; ++i) 310 | { 311 | m_Coef[i] = pCoef[i]; 312 | m_Coef[m_NumTaps + i] = pCoef[i]; //!< create duplicate for calculation efficiency 313 | } 314 | for (unsigned int i = 0; i < m_NumTaps; ++i) 315 | { //! zero input buffers 316 | m_rZBuf[i] = 0.0; 317 | m_cZBuf[i] = 0.0; 318 | } 319 | m_State = 0; //!< zero filter state variable 320 | } 321 | 322 | /*! 323 | * Process InLength InBuf[] samples and place in OutBuf[] 324 | * Note the Coefficient array is twice the length and has a duplicated set 325 | * in order to eliminate testing for buffer wrap in the inner loop 326 | * ex: if 3 tap FIR with coefficients{21,-43,15} is made into a array of 6 entries 327 | * {21, -43, 15, 21, -43, 15 } 328 | * Complex single buffer version 329 | */ 330 | void cFirFilter::Process(ComplexType* buffer, unsigned int length) 331 | { 332 | ComplexType acc; 333 | RealType* HIptr; 334 | RealType* HQptr; 335 | 336 | for (unsigned int i = 0; i < length; ++i) 337 | { 338 | m_cZBuf[m_State] = buffer[i]; 339 | HIptr = m_ICoef + m_NumTaps - m_State; 340 | HQptr = m_QCoef + m_NumTaps - m_State; 341 | 342 | acc = ComplexType((*HIptr++ * m_cZBuf[0].real()), (*HQptr++ * m_cZBuf[0].imag())); 343 | for (unsigned int j = 1; j < m_NumTaps; j++) 344 | acc += ComplexType((*HIptr++ * m_cZBuf[j].real()), (*HQptr++ * m_cZBuf[j].imag())); 345 | 346 | if (--m_State < 0) 347 | m_State += m_NumTaps; 348 | buffer[i] = acc; 349 | } 350 | } 351 | 352 | /*! 353 | * Process InLength InBuf[] samples and place in OutBuf[] 354 | * Note the Coefficient array is twice the length and has a duplicated set 355 | * in order to eliminate testing for buffer wrap in the inner loop 356 | * ex: if 3 tap FIR with coefficients{21,-43,15} is made into a array of 6 entries 357 | * {21, -43, 15, 21, -43, 15 } 358 | * Real single buffer version 359 | */ 360 | void cFirFilter::Process(RealType* buffer, unsigned int length) 361 | { 362 | RealType acc; 363 | const RealType* Hptr; 364 | 365 | for (unsigned int i = 0; i < length; ++i) 366 | { 367 | m_rZBuf[m_State] = buffer[i]; 368 | Hptr = &m_Coef[m_NumTaps - m_State]; 369 | acc = Hptr[0] * m_rZBuf[0]; //do the 1st MAC 370 | for (unsigned int j = 1; j < m_NumTaps; ++j) 371 | acc += Hptr[j] * m_rZBuf[j]; //do the remaining MACs 372 | 373 | if (--m_State < 0) 374 | m_State += m_NumTaps; 375 | buffer[i] = acc; 376 | } 377 | } 378 | 379 | /*! 380 | * Process InLength InBuf[] samples and place in OutBuf[] 381 | * Note the Coefficient array is twice the length and has a duplicated set 382 | * in order to eliminate testing for buffer wrap in the inner loop 383 | * ex: if 3 tap FIR with coefficients{21,-43,15} is made into a array of 6 entries 384 | * {21, -43, 15, 21, -43, 15 } 385 | * Real single buffer version with two streams 386 | */ 387 | void cFirFilter::ProcessTwo(RealType* bufferA, RealType* bufferB, unsigned int length) 388 | { 389 | RealType valueA; 390 | RealType valueB; 391 | RealType* HIptr; 392 | RealType* HQptr; 393 | 394 | for (unsigned int i = 0; i < length; ++i) 395 | { 396 | m_cZBuf[m_State] = ComplexType(bufferA[i], bufferB[i]); 397 | HIptr = m_ICoef + m_NumTaps - m_State; 398 | HQptr = m_QCoef + m_NumTaps - m_State; 399 | 400 | valueA = (*HIptr++ * m_cZBuf[0].real()); //do the first MAC 401 | valueB = (*HQptr++ * m_cZBuf[0].imag()); 402 | for (unsigned int j = 1; j < m_NumTaps; ++j) 403 | { 404 | valueA += (*HIptr++ * m_cZBuf[j].real()); //do the remaining MACs 405 | valueB += (*HQptr++ * m_cZBuf[j].imag()); 406 | } 407 | 408 | if (--m_State < 0) 409 | m_State += m_NumTaps; 410 | bufferA[i] = valueA; 411 | bufferB[i] = valueB; 412 | } 413 | } 414 | 415 | /*! 416 | * Process InLength InBuf[] samples and place in OutBuf[] 417 | * Note the Coefficient array is twice the length and has a duplicated set 418 | * in order to eliminate testing for buffer wrap in the inner loop 419 | * ex: if 3 tap FIR with coefficients{21,-43,15} is made into a array of 6 entries 420 | * {21, -43, 15, 21, -43, 15 } 421 | * Real double in / out buffer version 422 | */ 423 | void cFirFilter::Process(ComplexType* InBuf, ComplexType* OutBuf, unsigned int length) 424 | { 425 | ComplexType acc; 426 | RealType* HIptr; 427 | RealType* HQptr; 428 | 429 | for (unsigned int i = 0; i < length; ++i) 430 | { 431 | m_cZBuf[m_State] = InBuf[i]; 432 | HIptr = m_ICoef + m_NumTaps - m_State; 433 | HQptr = m_QCoef + m_NumTaps - m_State; 434 | 435 | acc = ComplexType((*HIptr++ * m_cZBuf[0].real()), (*HQptr++ * m_cZBuf[0].imag())); 436 | for (unsigned int j = 1; j < m_NumTaps; ++j) 437 | acc += ComplexType((*HIptr++ * m_cZBuf[j].real()), (*HQptr++ * m_cZBuf[j].imag())); 438 | 439 | if (--m_State < 0) 440 | m_State += m_NumTaps; 441 | OutBuf[i] = acc; 442 | } 443 | } 444 | 445 | /*! 446 | * Process InLength InBuf[] samples and place in OutBuf[] 447 | * Note the Coefficient array is twice the length and has a duplicated set 448 | * in order to eliminate testing for buffer wrap in the inner loop 449 | * ex: if 3 tap FIR with coefficients{21,-43,15} is made into a array of 6 entries 450 | * {21, -43, 15, 21, -43, 15 } 451 | * Complex double in / out buffer version (for Hilbert filter pair) 452 | */ 453 | void cFirFilter::Process(RealType* InBuf, ComplexType* OutBuf, unsigned int length) 454 | { 455 | ComplexType acc; 456 | RealType* HIptr; 457 | RealType* HQptr; 458 | 459 | for (unsigned int i = 0; i < length; ++i) 460 | { 461 | m_cZBuf[m_State] = ComplexType(InBuf[i], InBuf[i]); 462 | HIptr = m_ICoef + m_NumTaps - m_State; 463 | HQptr = m_QCoef + m_NumTaps - m_State; 464 | 465 | acc = ComplexType(*HIptr++ * m_cZBuf[0].real(), *HQptr++ * m_cZBuf[0].imag()); 466 | for (unsigned int j = 1; j < m_NumTaps; ++j) 467 | acc = ComplexType(*HIptr++ * m_cZBuf[j].real(), 468 | *HQptr++ * m_cZBuf[j].imag()); //do the remaining MACs 469 | 470 | if (--m_State < 0) 471 | m_State += m_NumTaps; 472 | 473 | OutBuf[i] = acc; 474 | } 475 | } 476 | -------------------------------------------------------------------------------- /src/FirFilter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2013, Moe Wheatley 3 | * Copyright (C) 2015-2020, Alwin Esch (Team KODI) 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "Definitions.h" 12 | 13 | #include 14 | 15 | #define MAX_NUMCOEF 75 16 | 17 | class ATTRIBUTE_HIDDEN cFirFilter 18 | { 19 | public: 20 | cFirFilter(); 21 | virtual ~cFirFilter() {} 22 | 23 | void InitConstFir(unsigned int NumTaps, const RealType* pCoef, RealType Fsamprate); 24 | void InitConstFir(unsigned int NumTaps, 25 | const RealType* pICoef, 26 | const RealType* pQCoef, 27 | RealType Fsamprate); 28 | int InitLPFilter(unsigned int NumTaps, 29 | RealType Scale, 30 | RealType Astop, 31 | RealType Fpass, 32 | RealType Fstop, 33 | RealType Fsamprate); 34 | int InitHPFilter(unsigned int NumTaps, 35 | RealType Scale, 36 | RealType Astop, 37 | RealType Fpass, 38 | RealType Fstop, 39 | RealType Fsamprate); 40 | void GenerateHBFilter(RealType FreqOffset); 41 | 42 | void Process(ComplexType* buffer, unsigned int length); 43 | void Process(RealType* buffer, unsigned int length); 44 | void Process(RealType* InBuf, ComplexType* OutBuf, unsigned int length); 45 | void Process(ComplexType* InBuf, ComplexType* OutBuf, unsigned int length); 46 | 47 | void ProcessTwo(RealType* bufferA, RealType* bufferB, unsigned int length); 48 | 49 | private: 50 | RealType Izero(RealType x); 51 | 52 | RealType m_SampleRate; 53 | unsigned int m_NumTaps; 54 | int m_State; 55 | RealType m_Coef[MAX_NUMCOEF * 2]; 56 | RealType m_ICoef[MAX_NUMCOEF * 2]; 57 | RealType m_QCoef[MAX_NUMCOEF * 2]; 58 | RealType m_rZBuf[MAX_NUMCOEF]; 59 | ComplexType m_cZBuf[MAX_NUMCOEF]; 60 | }; 61 | -------------------------------------------------------------------------------- /src/FmDecode.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013, Joris van Rantwijk. 3 | * Copyright (C) 2015-2020, Alwin Esch (Team KODI) 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "DownConvert.h" 12 | #include "FirFilter.h" 13 | #include "IirFilter.h" 14 | #include "RDSProcess.h" 15 | 16 | #include 17 | #include 18 | 19 | #define PHZBUF_SIZE 16384 20 | 21 | #define DEFAULT_DEEMPHASIS 50.0 22 | #define DEFAULT_BANDWIDTH_IF 100000.0 23 | #define DEFAULT_FREQ_DEV 60000.0 24 | #define DEFAULT_BANDWIDTH_PCM 15000.0 25 | #define PILOT_FREQ 19000.0 26 | 27 | class cRadioReceiver; 28 | 29 | /** Fine tuner which shifts the frequency of an IQ signal by a fixed offset. */ 30 | class ATTRIBUTE_HIDDEN cFineTuner 31 | { 32 | public: 33 | /*! 34 | * Construct fine tuner. 35 | * 36 | * table_size :: Size of internal sin/cos tables, determines the resolution 37 | * of the frequency shift. 38 | * 39 | * freq_shift :: Frequency shift. Signal frequency will be shifted by 40 | * (sample_rate * freq_shift / table_size). 41 | */ 42 | cFineTuner(unsigned int table_size, int freq_shift); 43 | virtual ~cFineTuner(); 44 | 45 | /*! Process samples. */ 46 | void Process(const ComplexType* samples_in, ComplexType* samples_out, unsigned int samples); 47 | 48 | private: 49 | unsigned int m_index; 50 | ComplexType* m_table; 51 | unsigned int m_tableSize; 52 | }; 53 | 54 | /*! Phase-locked loop for stereo pilot. */ 55 | class ATTRIBUTE_HIDDEN cPilotPhaseLock 56 | { 57 | public: 58 | /*! 59 | * Construct phase-locked loop. 60 | * 61 | * freq :: 19 kHz center frequency relative to sample freq 62 | * (0.5 is Nyquist) 63 | * bandwidth :: bandwidth relative to sample frequency 64 | * minsignal :: minimum pilot amplitude 65 | */ 66 | cPilotPhaseLock(RealType freq, RealType bandwidth, RealType minsignal); 67 | 68 | /*! 69 | * Process samples and extract 19 kHz pilot tone. 70 | * Generate phase-locked 38 kHz tone with unit amplitude. 71 | */ 72 | bool Process(const RealType* samples_in, RealType* samples_out, unsigned int length); 73 | 74 | /*! Return detected amplitude of pilot signal. */ 75 | RealType GetPilotLevel() const { return 2 * m_pilotLevel; } 76 | 77 | private: 78 | RealType m_minfreq, m_maxfreq; 79 | RealType m_phasor_b0, m_phasor_a1, m_phasor_a2; 80 | RealType m_phasor_i1, m_phasor_i2, m_phasor_q1, m_phasor_q2; 81 | RealType m_loopfilter_b0, m_loopfilter_b1; 82 | RealType m_loopfilter_x1; 83 | RealType m_freq, m_phase; 84 | RealType m_minsignal; 85 | RealType m_pilotLevel; 86 | int m_lock_delay; 87 | int m_lock_cnt; 88 | }; 89 | 90 | /*! Complete decoder for FM broadcast signal. */ 91 | class ATTRIBUTE_HIDDEN cFmDecoder 92 | { 93 | public: 94 | /*! 95 | * Construct FM decoder. 96 | * 97 | * sample_rate_if :: IQ sample rate in Hz. 98 | * tuning_offset :: Frequency offset in Hz of radio station with respect 99 | * to receiver LO frequency (positive value means 100 | * station is at higher frequency than LO). 101 | * sample_rate_pcm :: Audio sample rate. 102 | * stereo :: True to enable stereo decoding. 103 | * freq_dev :: Full scale carrier frequency deviation 104 | * (75 kHz for broadcast FM) 105 | * bandwidth_pcm :: Half bandwidth of audio signal in Hz 106 | * (15 kHz for broadcast FM) 107 | * downsample :: Downsampling factor to apply after FM demodulation. 108 | * Set to 1 to disable. 109 | */ 110 | cFmDecoder(cRadioReceiver* proc, 111 | double sample_rate_if, 112 | double tuning_offset, 113 | double sample_rate_pcm, 114 | double bandwidth_pcm = DEFAULT_BANDWIDTH_PCM, 115 | unsigned int downsample = 1, 116 | bool USver = false); 117 | 118 | virtual ~cFmDecoder(); 119 | 120 | void Reset(); 121 | 122 | /*! 123 | * Process IQ samples and return audio samples. 124 | * 125 | * If the decoder is set in stereo mode, samples for left and right 126 | * channels are interleaved in the output vector (even if no stereo 127 | * signal is detected). If the decoder is set in mono mode, the output 128 | * vector only contains samples for one channel. 129 | * @param samples_in array which include recieved signal 130 | * @param samples the amount of samples in data 131 | * @param audio pointer to return data array (is allocated with samples_in 132 | * size and is always enough) 133 | * @return the amount of present samples in audio 134 | */ 135 | unsigned int ProcessStream(const ComplexType* samples_in, unsigned int samples, float* audio); 136 | 137 | /*! 138 | * Return true if a stereo signal is detected. 139 | */ 140 | bool StereoDetected() const { return m_StereoDetected; } 141 | 142 | /*! 143 | * Return actual frequency offset in Hz with respect to receiver LO. 144 | * @return frequency 145 | */ 146 | RealType GetTuningOffset() const 147 | { 148 | RealType tuned = -m_TuningShift * m_SampleRate_Interface / RealType(m_TuningTableSize); 149 | return tuned + m_BasebandMean * m_FrequencyDev; 150 | } 151 | 152 | /*! 153 | * Return RMS IF level (where full scale IQ signal is 1.0). 154 | */ 155 | RealType GetInterfaceLevel() const { return m_InterfaceLevel; } 156 | 157 | /*! 158 | * Return RMS baseband signal level (where nominal level is 0.707). 159 | */ 160 | RealType GetBasebandLevel() const { return m_BasebandLevel; } 161 | 162 | /*! 163 | * Return amplitude of stereo pilot (nominal level is 0.1). 164 | */ 165 | RealType GetPilotLevel() const { return m_PilotPLL.GetPilotLevel(); } 166 | 167 | private: 168 | void InitDeemphasis(RealType Time, RealType SampleRate); 169 | 170 | inline void SamplesMeanRMS(const RealType* samples, 171 | RealType& mean, 172 | RealType& rms, 173 | unsigned int n); 174 | inline ComplexType::value_type RMSLevelApprox(const ComplexType* samples, unsigned int length); 175 | inline void ProcessDeemphasisFilter(RealType* bufferA, RealType* bufferB, unsigned int length); 176 | inline void PhaseLockedLoop(ComplexType* signal, RealType* out, unsigned int dataSize); 177 | 178 | cRadioReceiver* const m_proc; 179 | const RealType m_SampleRate_Interface; 180 | const RealType m_SampleRate_Baseband; 181 | const int m_TuningTableSize; 182 | const int m_TuningShift; 183 | const RealType m_FrequencyDev; 184 | const unsigned int m_Downsample; 185 | const RealType m_BandwidthInterface; 186 | 187 | bool m_StereoDetected; 188 | RealType m_InterfaceLevel; 189 | RealType m_BasebandMean; 190 | RealType m_BasebandLevel; 191 | RealType m_AudioLevel; 192 | RealType m_FMDeModGain; 193 | 194 | ComplexType* m_BufferIfTuned; 195 | ComplexType* m_BufferDemod; 196 | RealType* m_BufferBaseband; 197 | RealType* m_BufferMono; 198 | RealType* m_BufferStereo; 199 | RealType* m_BufferRawStereo; 200 | 201 | cFineTuner m_FineTuner; 202 | cPilotPhaseLock m_PilotPLL; 203 | cDownsampleFilter m_ReSampleInput; 204 | cDownsampleFilter m_ReSampleMono; 205 | cDownsampleFilter m_ReSampleStereo; 206 | cRDSRxSignalProcessor m_RDSProcess; 207 | cIirFilter m_DCBlock; 208 | cIirFilter m_NotchFilter; 209 | cFirFilter m_LPFilter; 210 | cFirFilter m_InputLPFilter; 211 | 212 | cFirFilter m_HilbertFilter; 213 | 214 | RealType m_DeemphasisAveRe; 215 | RealType m_DeemphasisAveIm; 216 | RealType m_DeemphasisAlpha; 217 | 218 | RealType m_NcoPhase; 219 | RealType m_NcoPhaseIncr; 220 | RealType m_NcoHLimit; 221 | RealType m_NcoLLimit; 222 | RealType m_PLLAlpha; 223 | RealType m_PLLBeta; 224 | RealType m_DemodDCOffset; 225 | }; 226 | -------------------------------------------------------------------------------- /src/FreqShift.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015-2020, Alwin Esch (Team KODI) 3 | * 4 | * SPDX-License-Identifier: GPL-2.0-or-later 5 | * See LICENSE.md for more information. 6 | */ 7 | 8 | #include "FreqShift.h" 9 | 10 | cFreqShift::cFreqShift(RealType NcoFreq, RealType InRate) 11 | { 12 | m_NcoTime = 0.0; 13 | m_InRate = InRate; 14 | m_NcoFreq = NcoFreq; 15 | m_NcoInc = K_2PI * m_NcoFreq / m_InRate; 16 | } 17 | 18 | void cFreqShift::Reset() 19 | { 20 | m_NcoTime = 0.0; 21 | } 22 | 23 | void cFreqShift::Process(ComplexType* pInData, unsigned int InLength) 24 | { 25 | ComplexType dtmp; 26 | ComplexType Osc; 27 | 28 | #if (defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined(TARGET_WINDOWS)) 29 | RealType dPhaseAcc = m_NcoTime; 30 | RealType dASMCos = 0.0; 31 | RealType dASMSin = 0.0; 32 | #endif 33 | #if TARGET_WINDOWS 34 | RealType* pdCosAns = &dASMCos; 35 | RealType* pdSinAns = &dASMSin; 36 | #endif 37 | 38 | //263uS using sin/cos or 70uS using quadrature osc or 200uS using _asm 39 | for (unsigned int i = 0; i < InLength; ++i) 40 | { 41 | dtmp = pInData[i]; 42 | #if TARGET_WINDOWS 43 | _asm 44 | { 45 | fld QWORD PTR [dPhaseAcc] 46 | fsincos 47 | mov ebx,[pdCosAns] ; get the pointer into ebx 48 | fstp QWORD PTR [ebx] ; store the result through the pointer 49 | mov ebx,[pdSinAns] 50 | fstp QWORD PTR [ebx] 51 | } 52 | dPhaseAcc += m_NcoInc; 53 | Osc.re = dASMCos; 54 | Osc.im = dASMSin; 55 | #elif (defined(__i386__) || defined(__x86_64__)) 56 | asm volatile("fsincos" : "=%&t"(dASMCos), "=%&u"(dASMSin) : "0"(dPhaseAcc)); 57 | dPhaseAcc += m_NcoInc; 58 | Osc = ComplexType(dASMCos, dASMSin); 59 | #elif defined(__arm__) 60 | sincos_LP(m_NcoTime, dASMSin, dASMCos); 61 | Osc = ComplexType(dASMCos, dASMSin); 62 | #else 63 | Osc = ComplexType(MCOS(m_NcoTime), MSIN(m_NcoTime)); 64 | m_NcoTime += m_NcoInc; 65 | #endif 66 | 67 | //Cpx multiply by shift frequency 68 | pInData[i] = ComplexType((dtmp.real() * Osc.real()) - (dtmp.imag() * Osc.imag()), 69 | (dtmp.real() * Osc.imag()) + (dtmp.imag() * Osc.real())); 70 | } 71 | #if (defined(__i386__) || defined(__x86_64__) || defined(TARGET_WINDOWS)) 72 | m_NcoTime = dPhaseAcc; 73 | #else 74 | m_NcoTime = MFMOD(m_NcoTime, K_2PI); //keep radian counter bounded 75 | #endif 76 | } 77 | -------------------------------------------------------------------------------- /src/FreqShift.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015-2020, Alwin Esch (Team KODI) 3 | * 4 | * SPDX-License-Identifier: GPL-2.0-or-later 5 | * See LICENSE.md for more information. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include "Definitions.h" 11 | 12 | class ATTRIBUTE_HIDDEN cFreqShift 13 | { 14 | public: 15 | cFreqShift(RealType NcoFreq, RealType InRate); 16 | virtual ~cFreqShift() = default; 17 | 18 | void Reset(); 19 | 20 | void Process(ComplexType* pInData, unsigned int InLength); 21 | 22 | private: 23 | RealType m_NcoFreq; 24 | RealType m_NcoInc; 25 | RealType m_NcoTime; 26 | RealType m_InRate; 27 | }; 28 | -------------------------------------------------------------------------------- /src/IirFilter.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2013, Moe Wheatley 3 | * Copyright (C) 2015-2020, Alwin Esch (Team KODI) 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #include "IirFilter.h" 10 | 11 | bool cIirFilter::Init(eFilterType type, RealType F0Freq, RealType FilterQ, RealType SampleRate) 12 | { 13 | bool ret = true; 14 | 15 | RealType w0 = K_2PI * F0Freq / SampleRate; //normalized corner frequency 16 | RealType alpha = MSIN(w0) / (2.0 * FilterQ); 17 | RealType A = 1.0 / (1.0 + alpha); //scale everything by 1/A0 for direct form 2 18 | 19 | switch (type) 20 | { 21 | case ftLP: 22 | m_B0 = A * ((1.0 - MCOS(w0)) / 2.0); 23 | m_B1 = A * (1.0 - MCOS(w0)); 24 | m_B2 = A * ((1.0 - MCOS(w0)) / 2.0); 25 | m_A1 = A * (-2.0 * MCOS(w0)); 26 | m_A2 = A * (1.0 - alpha); 27 | break; 28 | case ftHP: 29 | m_B0 = A * ((1.0 + MCOS(w0)) / 2.0); 30 | m_B1 = -A * (1.0 + MCOS(w0)); 31 | m_B2 = A * ((1.0 + MCOS(w0)) / 2.0); 32 | m_A1 = A * (-2.0 * MCOS(w0)); 33 | m_A2 = A * (1.0 - alpha); 34 | break; 35 | case ftBP: 36 | m_B0 = A * alpha; 37 | m_B1 = 0.0; 38 | m_B2 = A * -alpha; 39 | m_A1 = A * (-2.0 * MCOS(w0)); 40 | m_A2 = A * (1.0 - alpha); 41 | break; 42 | case ftBR: 43 | m_B0 = A * 1.0; 44 | m_B1 = A * (-2.0 * MCOS(w0)); 45 | m_B2 = A * 1.0; 46 | m_A1 = A * (-2.0 * MCOS(w0)); 47 | m_A2 = A * (1.0 - alpha); 48 | break; 49 | default: 50 | ret = false; 51 | break; 52 | } 53 | 54 | m_w1a = 0.0; 55 | m_w2a = 0.0; 56 | m_w1b = 0.0; 57 | m_w2b = 0.0; 58 | 59 | return ret; 60 | } 61 | 62 | void cIirFilter::Process(ComplexType* buffer, unsigned int length) 63 | { 64 | for (unsigned int i = 0; i < length; ++i) 65 | { 66 | RealType w0a = buffer[i].real() - m_A1 * m_w1a - m_A2 * m_w2a; 67 | RealType w0b = buffer[i].imag() - m_A1 * m_w1b - m_A2 * m_w2b; 68 | buffer[i] = ComplexType(m_B0 * w0a + m_B1 * m_w1a + m_B2 * m_w2a, 69 | m_B0 * w0b + m_B1 * m_w1b + m_B2 * m_w2b); 70 | m_w2a = m_w1a; 71 | m_w1a = w0a; 72 | 73 | m_w2b = m_w1b; 74 | m_w1b = w0b; 75 | } 76 | } 77 | 78 | void cIirFilter::Process(RealType* buffer, unsigned int length) 79 | { 80 | for (unsigned int i = 0; i < length; ++i) 81 | { 82 | RealType w0 = buffer[i] - m_A1 * m_w1a - m_A2 * m_w2a; 83 | buffer[i] = m_B0 * w0 + m_B1 * m_w1a + m_B2 * m_w2a; 84 | m_w2a = m_w1a; 85 | m_w1a = w0; 86 | } 87 | } 88 | 89 | void cIirFilter::ProcessTwo(RealType* bufferA, RealType* bufferB, unsigned int length) 90 | { 91 | for (unsigned int i = 0; i < length; ++i) 92 | { 93 | RealType w0a = bufferA[i] - m_A1 * m_w1a - m_A2 * m_w2a; 94 | RealType w0b = bufferB[i] - m_A1 * m_w1b - m_A2 * m_w2b; 95 | 96 | bufferA[i] = m_B0 * w0a + m_B1 * m_w1a + m_B2 * m_w2a; 97 | bufferB[i] = m_B0 * w0b + m_B1 * m_w1b + m_B2 * m_w2b; 98 | 99 | m_w2a = m_w1a; 100 | m_w1a = w0a; 101 | 102 | m_w2b = m_w1b; 103 | m_w1b = w0b; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/IirFilter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2013, Moe Wheatley 3 | * Copyright (C) 2015-2020, Alwin Esch (Team KODI) 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "Definitions.h" 12 | 13 | class ATTRIBUTE_HIDDEN cIirFilter 14 | { 15 | public: 16 | cIirFilter() = default; 17 | virtual ~cIirFilter() = default; 18 | 19 | bool Init(eFilterType type, RealType F0Freq, RealType FilterQ, RealType SampleRate); 20 | 21 | void Process(ComplexType* buffer, unsigned int length); 22 | void Process(RealType* buffer, unsigned int length); 23 | void ProcessTwo(RealType* bufferA, RealType* bufferB, unsigned int length); 24 | 25 | private: 26 | RealType m_A1; //!< direct form 2 coefficients 27 | RealType m_A2; 28 | RealType m_B0; 29 | RealType m_B1; 30 | RealType m_B2; 31 | 32 | RealType m_w1a; //!< biquad delay storage 33 | RealType m_w2a; 34 | RealType m_w1b; //!< biquad delay storage 35 | RealType m_w2b; 36 | }; 37 | -------------------------------------------------------------------------------- /src/RDSGroupDecoder.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015-2020, Alwin Esch (Team KODI) 3 | * 4 | * SPDX-License-Identifier: GPL-2.0-or-later 5 | * See LICENSE.md for more information. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #define BLOCK__A 0 //indexes for the four blocks 16 | #define BLOCK__B 1 17 | #define BLOCK__C 2 18 | #define BLOCK__D 3 19 | 20 | class cRadioReceiver; 21 | 22 | #define RT_MEL 66 23 | 24 | class ATTRIBUTE_HIDDEN cRDSGroupDecoder 25 | { 26 | public: 27 | cRDSGroupDecoder(cRadioReceiver* proc); 28 | virtual ~cRDSGroupDecoder(){}; 29 | 30 | void DecodeRDS(uint16_t* blockData); 31 | void Reset(); 32 | 33 | private: 34 | enum eGroupType 35 | { 36 | RDS_GROUP_TYPE__0A = 0x00, 37 | RDS_GROUP_TYPE__0B = 0x01, 38 | RDS_GROUP_TYPE__1A = 0x02, 39 | RDS_GROUP_TYPE__1B = 0x03, 40 | RDS_GROUP_TYPE__2A = 0x04, 41 | RDS_GROUP_TYPE__2B = 0x05, 42 | RDS_GROUP_TYPE__3A = 0x06, 43 | RDS_GROUP_TYPE__3B = 0x07, 44 | RDS_GROUP_TYPE__4A = 0x08, 45 | RDS_GROUP_TYPE__4B = 0x09, 46 | RDS_GROUP_TYPE__5A = 0x0A, 47 | RDS_GROUP_TYPE__5B = 0x0B, 48 | RDS_GROUP_TYPE__6A = 0x0C, 49 | RDS_GROUP_TYPE__6B = 0x0D, 50 | RDS_GROUP_TYPE__7A = 0x0E, 51 | RDS_GROUP_TYPE__7B = 0x0F, 52 | RDS_GROUP_TYPE__8A = 0x10, 53 | RDS_GROUP_TYPE__8B = 0x11, 54 | RDS_GROUP_TYPE__9A = 0x12, 55 | RDS_GROUP_TYPE__9B = 0x13, 56 | RDS_GROUP_TYPE_10A = 0x14, 57 | RDS_GROUP_TYPE_10B = 0x15, 58 | RDS_GROUP_TYPE_11A = 0x16, 59 | RDS_GROUP_TYPE_11B = 0x17, 60 | RDS_GROUP_TYPE_12A = 0x18, 61 | RDS_GROUP_TYPE_12B = 0x19, 62 | RDS_GROUP_TYPE_13A = 0x1A, 63 | RDS_GROUP_TYPE_13B = 0x1B, 64 | RDS_GROUP_TYPE_14A = 0x1C, 65 | RDS_GROUP_TYPE_14B = 0x1D, 66 | RDS_GROUP_TYPE_15A = 0x1E, 67 | RDS_GROUP_TYPE_15B = 0x1F 68 | }; 69 | 70 | cRadioReceiver* m_RadioProc; 71 | 72 | uint8_t m_UECPDataFrameSeqCnt; 73 | int m_UECPStuffingPtr; 74 | uint8_t m_UECPDataFrame[263]; 75 | std::vector m_UECPDataFrame2; 76 | 77 | int m_ODATypeMap[32]; 78 | 79 | int m_PTY; 80 | 81 | int m_TA_TP; 82 | 83 | char m_PTYN[9]; 84 | bool m_PTYN_ABFlag; 85 | int m_PTYN_SetFlag; 86 | 87 | uint8_t m_DI; 88 | uint8_t m_DI_Prev; 89 | int m_DI_Finished; 90 | 91 | uint8_t m_MS; 92 | uint8_t m_MS_Prev; 93 | 94 | char m_PS_Name[9]; ///< Programme service name 95 | int m_PS_SetFlag; 96 | 97 | uint16_t m_PIN; 98 | uint16_t m_ProgramIdentCode; 99 | 100 | char m_RadioText_Temp[RT_MEL]; 101 | bool m_RadioText_FirstPtr; 102 | int m_RadioText_LastPtr; 103 | int m_RadioText_ABFlag; 104 | uint32_t m_RadioText_SegmentRegister; 105 | int m_RadioText_Count; 106 | 107 | int m_RTPlus_templateNo; 108 | int m_RTPlus_scb; 109 | int m_RTPlus_cbflag; 110 | int m_RTPlus_rfu; 111 | bool m_RTPlus_Ready; 112 | 113 | bool m_AF_MethodStarted; 114 | bool m_AF_MethodFinished; 115 | bool m_AF_LowFreqFollow; 116 | int m_AF_CalcCount; 117 | bool m_AF_MethodAB; 118 | struct sAFMethod 119 | { 120 | uint8_t Value; 121 | bool Regional; 122 | float Frequency; 123 | }; 124 | std::vector m_AF_AltFrequencies; 125 | 126 | void ClearUECPFrame(); 127 | void AddStuffingValue(uint8_t value); 128 | void SendUECPFrame(); 129 | 130 | void Decode_PI(uint16_t identifier); 131 | void Decode_PTY(int pty); 132 | void Decode_Type0___PS_DI_MS(const uint16_t* msgElement, bool versionCode); 133 | void Decode_Type1___ProgItemNum_SlowLabelCodes(const uint16_t* msgElement, bool versionCode); 134 | void Decode_Type2___Radiotext(const uint16_t* msgElement, bool versionCode); 135 | void Decode_Type3A__AppIdentOpenData(const uint16_t* msgElement); 136 | void Decode_Type4A__Clock(const uint16_t* msgElement); 137 | void Decode_Type5___TransparentDataChannels(const uint16_t* msgElement, bool versionCode); 138 | void Decode_Type6___InHouseApplications(const uint16_t* msgElement, bool versionCode); 139 | void Decode_Type7A__RadioPaging(const uint16_t* msgElement); 140 | void Decode_Type8A__TrafficMessageChannel(const uint16_t* msgElement); 141 | void Decode_Type9A__EmergencyWarningSystem(const uint16_t* msgElement); 142 | void Decode_Type10A_PTYN(const uint16_t* msgElement); 143 | void Decode_Type13A_EnhancedRadioPaging(const uint16_t* msgElement); 144 | void Decode_Type14__EnhancedOtherNetworksInfo(const uint16_t* msgElement, bool versionCode); 145 | void Decode_Type15A_RBDS(const uint16_t* msgElement); 146 | void Decode_Type15B_FastSwitchingInfo(const uint16_t* msgElement); 147 | void Decode_Type____ODA(const uint16_t* msgElement, int odaFunction); 148 | void Decode_TypeRTPlus(const uint16_t* msgElement); 149 | }; 150 | -------------------------------------------------------------------------------- /src/RDSProcess.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2013, Moe Wheatley 3 | * Copyright (C) 2015-2020, Alwin Esch (Team KODI) 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #include "RDSProcess.h" 10 | 11 | #include "FmDecode.h" 12 | 13 | const int BLK_OFFSET_TBL[8] = {OFFSET_SYNDROME_BLOCK_A, OFFSET_SYNDROME_BLOCK_B, 14 | OFFSET_SYNDROME_BLOCK_C, OFFSET_SYNDROME_BLOCK_D, 15 | // 16 | OFFSET_SYNDROME_BLOCK_A, OFFSET_SYNDROME_BLOCK_B, 17 | OFFSET_SYNDROME_BLOCK_CP, OFFSET_SYNDROME_BLOCK_D}; 18 | 19 | /*! 20 | * Parity check matrix from RDS spec for FEC 21 | * Takes into account that the (26,16)code is a shortened cyclic block code 22 | * from the original (341,331) length cyclic code. 23 | */ 24 | const uint32_t PARCKH[16] = { 25 | 0x2DC, // 10 1101 1100 26 | 0x16E, // 01 0110 1110 27 | 0x0B7, // 00 1011 0111 28 | 0x287, // 10 1000 0111 29 | 0x39F, // 11 1001 1111 30 | 0x313, // 11 0001 0011 31 | 0x355, // 11 0101 0101 32 | 0x376, // 11 0111 0110 33 | 0x1BB, // 01 1011 1011 34 | 0x201, // 10 0000 0001 35 | 0x3DC, // 11 1101 1100 36 | 0x1EE, // 01 1110 1110 37 | 0x0F7, // 00 1111 0111 38 | 0x2A7, // 10 1010 0111 39 | 0x38F, // 11 1000 1111 40 | 0x31B // 11 0001 1011 41 | }; 42 | 43 | cRDSRxSignalProcessor::cRDSRxSignalProcessor(cRadioReceiver* proc, RealType SampleRate) 44 | : m_SampleRate(SampleRate), m_Decoder(proc) 45 | { 46 | m_ProcessRate = m_DownConvert.SetDataRate(m_SampleRate, 8000.0); 47 | m_DownConvert.SetFrequency( 48 | -RDS_FREQUENCY); //set up to shift 57KHz RDS down to baseband and decimate 49 | 50 | RealType downConvertFactor = m_SampleRate / m_ProcessRate; 51 | m_ProcessArrayIn = (ComplexType*)calloc(MAX_RDS_ARRAY_SIZE, sizeof(ComplexType)); 52 | m_RdsRaw = (ComplexType*)calloc(MAX_RDS_ARRAY_SIZE / downConvertFactor, sizeof(ComplexType)); 53 | m_RdsMag = (RealType*)calloc(MAX_RDS_ARRAY_SIZE / downConvertFactor, sizeof(RealType)); 54 | m_RdsData = (RealType*)calloc(MAX_RDS_ARRAY_SIZE / downConvertFactor, sizeof(RealType)); 55 | 56 | RealType norm = K_2PI / m_ProcessRate; //to normalize Hz to radians 57 | //initialize the PLL that is used to de-rotate the rds DSB signal 58 | m_RdsNcoLLimit = (m_RdsNcoFreq - RDSPLL_RANGE) * norm; //clamp RDS PLL NCO 59 | m_RdsNcoHLimit = (m_RdsNcoFreq + RDSPLL_RANGE) * norm; 60 | m_RdsPllAlpha = 2.0 * RDSPLL_ZETA * RDSPLL_BW * norm; 61 | m_RdsPllBeta = (m_RdsPllAlpha * m_RdsPllAlpha) / (4.0 * RDSPLL_ZETA * RDSPLL_ZETA); 62 | //create matched filter to extract bi-phase bits 63 | // This is basically the time domain shape of a single bi-phase bit 64 | // as defined for RDS and is close to a single cycle sine wave in shape 65 | m_RdsMatchCoefLength = m_ProcessRate / RDS_BITRATE; 66 | m_RdsMatchCoef = (RealType*)calloc(m_RdsMatchCoefLength * 2 + 1, sizeof(RealType)); 67 | for (int i = 0; i <= m_RdsMatchCoefLength; i++) 68 | { 69 | RealType t = (RealType)i / (m_ProcessRate); 70 | RealType x = t * RDS_BITRATE; 71 | RealType x64 = 64.0 * x; 72 | m_RdsMatchCoef[i + m_RdsMatchCoefLength] = 73 | .75 * MCOS(2.0 * K_2PI * x) * ((1.0 / (1.0 / x - x64)) - (1.0 / (9.0 / x - x64))); 74 | m_RdsMatchCoef[m_RdsMatchCoefLength - i] = 75 | -.75 * MCOS(2.0 * K_2PI * x) * ((1.0 / (1.0 / x - x64)) - (1.0 / (9.0 / x - x64))); 76 | } 77 | m_RdsMatchCoefLength *= 2; 78 | 79 | //! initialize a bunch of variables pertaining to the rds decoder 80 | Reset(); 81 | } 82 | 83 | cRDSRxSignalProcessor::~cRDSRxSignalProcessor() 84 | { 85 | free(m_ProcessArrayIn); 86 | free(m_RdsRaw); 87 | free(m_RdsMag); 88 | free(m_RdsData); 89 | free(m_RdsMatchCoef); 90 | } 91 | 92 | void cRDSRxSignalProcessor::Reset() 93 | { 94 | m_Decoder.Reset(); 95 | 96 | m_RdsNcoPhase = 0.0; 97 | m_RdsNcoFreq = 0.0; //freq offset to bring to baseband 98 | 99 | m_RdsLPFilter.InitLPFilter(0, 1.0, 40.0, 2400.0, 1.3 * 2400.0, m_ProcessRate); 100 | 101 | //! load the matched filter coef into FIR filter 102 | m_RdsMatchedFilter.InitConstFir(m_RdsMatchCoefLength, m_RdsMatchCoef, m_ProcessRate); 103 | 104 | //! create Hi-Q resonator at the bit rate to recover bit sync position Q==500 105 | m_RdsBitSyncFilter.Init(ftBP, RDS_BITRATE, 500, m_ProcessRate); 106 | 107 | m_RdsLastSync = 0.0; 108 | m_RdsLastSyncSlope = 0.0; 109 | m_RdsLastBit = 0; 110 | m_CurrentBitPosition = 0; 111 | m_CurrentBlock = BLOCK__A; 112 | m_DecodeState = STATE_BITSYNC; 113 | m_BGroupOffset = 0; 114 | m_ClockInput = false; 115 | m_DataInputPrev = 0; 116 | m_ClockLastValue = 0.0; 117 | m_RdsLastData = 0.0; 118 | } 119 | 120 | void cRDSRxSignalProcessor::Process(const RealType* inputStream, unsigned int inLength) 121 | { 122 | for (unsigned int i = 0; i < inLength; i++) 123 | m_ProcessArrayIn[i] = inputStream[i]; //* 2.0;//ComplexType(0, inputStream[i] /** 2.0*/); 124 | 125 | //! translate 57KHz RDS signal to baseband and decimate RDS complex signal 126 | unsigned int length = m_DownConvert.ProcessData(inLength, m_ProcessArrayIn, m_RdsRaw); 127 | 128 | m_RdsLPFilter.Process(m_RdsRaw, length); 129 | 130 | //! PLL to remove any rotation since may not be phase locked to 19KHz Pilot or may not even have pilot 131 | ProcessRdsPll(m_RdsRaw, m_RdsData, length); 132 | 133 | //! run matched filter correlator to extract the bi-phase data bits 134 | m_RdsMatchedFilter.Process(m_RdsData, length); 135 | 136 | //! create bit sync signal in m_RdsMag[] by squaring data 137 | for (unsigned int i = 0; i < length; i++) 138 | m_RdsMag[i] = 139 | m_RdsData[i] * m_RdsData[i]; //has high energy at the bit clock rate and 2x bit rate 140 | 141 | //! run Hi-Q resonator filter that create a sin wave that will lock to BitRate clock and not 2X rate 142 | m_RdsBitSyncFilter.Process(m_RdsMag, length); 143 | 144 | for (unsigned int i = 0; i < length; i++) 145 | { 146 | RealType Data = m_RdsData[i]; 147 | RealType SyncVal = m_RdsMag[i]; 148 | 149 | //the best bit sync position is at the positive peak of the sync sine wave 150 | RealType Slope = SyncVal - m_RdsLastSync; //current slope 151 | m_RdsLastSync = SyncVal; 152 | 153 | //see if at the top of the sine wave 154 | if ((Slope < 0.0) && (m_RdsLastSyncSlope * Slope) < 0.0) 155 | { //are at sample time so read previous bit time since we are one sample behind in sync position 156 | int bit; 157 | if (m_RdsLastData >= 0) 158 | { 159 | bit = 1; 160 | m_RdsRaw[i].real(m_RdsLastData); 161 | } 162 | else 163 | { 164 | bit = 0; 165 | m_RdsRaw[i].real(m_RdsLastData); 166 | } 167 | //need to XOR with previous bit to get actual data bit value 168 | ProcessNewRdsBit(bit ^ m_RdsLastBit); //go process new RDS Bit 169 | m_RdsLastBit = bit; 170 | } 171 | else 172 | { 173 | m_RdsRaw[i].real(0); 174 | } 175 | 176 | m_RdsLastData = Data; //keep last bit since is differential data 177 | m_RdsLastSyncSlope = Slope; 178 | m_RdsRaw[i].imag(Data); 179 | } 180 | } 181 | 182 | /*! 183 | * Less acurate but somewhat faster atan2() function 184 | * |error| < 0.005 185 | * Useful for plls but not for main FM demod if best audio quality desired. 186 | */ 187 | inline RealType arctan2(RealType y, RealType x) 188 | { 189 | if (x == 0.0) 190 | { //! avoid divide by zero and just return angle 191 | if (y > 0.0) 192 | return K_PI2; 193 | if (y == 0.0) 194 | return 0.0; 195 | return -K_PI2; 196 | } 197 | 198 | RealType angle; 199 | RealType z = y / x; 200 | if (MFABS(z) < 1.0) 201 | { 202 | angle = z / (1.0 + 0.2854 * z * z); 203 | if (x < 0.0) 204 | { 205 | if (y < 0.0) 206 | return angle - K_PI; 207 | return angle + K_PI; 208 | } 209 | } 210 | else 211 | { 212 | angle = K_PI2 - z / (z * z + 0.2854); 213 | if (y < 0.0) 214 | return angle - K_PI; 215 | } 216 | return angle; 217 | } 218 | 219 | /*! 220 | * Process I/Q RDS baseband stream to lock PLL 221 | */ 222 | void cRDSRxSignalProcessor::ProcessRdsPll(ComplexType* pInData, 223 | RealType* pOutData, 224 | unsigned int InLength) 225 | { 226 | RealType Sin; 227 | RealType Cos; 228 | ComplexType tmp; 229 | 230 | for (int i = 0; i < InLength; i++) 231 | { 232 | #if TARGET_WINDOWS 233 | RealType* pdCosAns = &Cos; 234 | RealType* pdSinAns = &Sin; 235 | _asm 236 | { 237 | fld QWORD PTR [m_RdsNcoPhase] 238 | fsincos 239 | mov ebx,[pdCosAns] ; get the pointer into ebx 240 | fstp QWORD PTR [ebx] ; store the result through the pointer 241 | mov ebx,[pdSinAns] 242 | fstp QWORD PTR [ebx] 243 | } 244 | #elif (defined(__i386__) || defined(__x86_64__)) 245 | asm volatile("fsincos" : "=%&t"(Cos), "=%&u"(Sin) : "0"(m_RdsNcoPhase)); //126nS 246 | #elif defined(__arm__) 247 | sincos_LP(m_RdsNcoPhase, Sin, Cos); 248 | #else 249 | Sin = MSIN(m_RdsNcoPhase); //178ns for sin/cos calc 250 | Cos = MCOS(m_RdsNcoPhase); 251 | #endif 252 | //complex multiply input sample by NCO's sin and cos 253 | tmp = ComplexType(Cos * pInData[i].real() - Sin * pInData[i].imag(), 254 | Cos * pInData[i].imag() + Sin * pInData[i].real()); 255 | //find current sample phase after being shifted by NCO frequency 256 | RealType phzerror = -arctan2(tmp.imag(), tmp.real()); 257 | //create new NCO frequency term 258 | m_RdsNcoFreq += (m_RdsPllBeta * phzerror); // radians per sampletime 259 | //clamp NCO frequency so doesn't get out of lock range 260 | if (m_RdsNcoFreq > m_RdsNcoHLimit) 261 | m_RdsNcoFreq = m_RdsNcoHLimit; 262 | else if (m_RdsNcoFreq < m_RdsNcoLLimit) 263 | m_RdsNcoFreq = m_RdsNcoLLimit; 264 | //update NCO phase with new value 265 | m_RdsNcoPhase += (m_RdsNcoFreq + m_RdsPllAlpha * phzerror); 266 | pOutData[i] = tmp.imag(); 267 | ; 268 | } 269 | m_RdsNcoPhase = MFMOD(m_RdsNcoPhase, K_2PI); //keep radian counter bounded 270 | } 271 | 272 | void cRDSRxSignalProcessor::ProcessNewRdsBit(int bit) 273 | { 274 | m_InBitStream = (m_InBitStream << 1) | bit; //shift in new bit 275 | switch (m_DecodeState) 276 | { 277 | case STATE_BITSYNC: //looking at each bit position till we find a "good" block A 278 | if (!CheckBlock(OFFSET_SYNDROME_BLOCK_A, false)) 279 | { //got initial good chkword on Block A not using FEC 280 | m_CurrentBitPosition = 0; 281 | m_BGroupOffset = 0; 282 | m_BlockData[BLOCK__A] = m_InBitStream >> NUMBITS_CRC; 283 | m_CurrentBlock = BLOCK__B; 284 | m_DecodeState = STATE_BLOCKSYNC; //next state is looking for blocks B,C, and D in sequence 285 | } 286 | break; 287 | 288 | case STATE_BLOCKSYNC: //Looking for 4 blocks in correct sequence to have good probability bit position is good 289 | m_CurrentBitPosition++; 290 | if (m_CurrentBitPosition >= NUMBITS_BLOCK) 291 | { 292 | m_CurrentBitPosition = 0; 293 | if (CheckBlock(BLK_OFFSET_TBL[m_CurrentBlock + m_BGroupOffset], false)) 294 | { //bad chkword so go look for bit sync again 295 | m_DecodeState = STATE_BITSYNC; 296 | } 297 | else 298 | { //good chkword so save data and setup for next block 299 | m_BlockData[m_CurrentBlock] = m_InBitStream >> NUMBITS_CRC; //save msg data 300 | //see if is group A or Group B 301 | if ((BLOCK__B == m_CurrentBlock) && (m_BlockData[m_CurrentBlock] & GROUPB_BIT)) 302 | m_BGroupOffset = 4; 303 | else 304 | m_BGroupOffset = 0; 305 | 306 | if (m_CurrentBlock >= BLOCK__D) 307 | { //good chkword on all 4 blocks in correct sequence so are sure of bit position 308 | m_CurrentBlock = BLOCK__A; 309 | m_BlockErrors = 0; 310 | m_DecodeState = STATE_GROUPDECODE; 311 | 312 | m_Decoder.DecodeRDS(m_BlockData); 313 | } 314 | else 315 | m_CurrentBlock++; 316 | } 317 | } 318 | break; 319 | 320 | case STATE_GROUPDECODE: //here after getting a good sequence of blocks 321 | m_CurrentBitPosition++; 322 | if (m_CurrentBitPosition >= NUMBITS_BLOCK) 323 | { 324 | m_CurrentBitPosition = 0; 325 | if (CheckBlock(BLK_OFFSET_TBL[m_CurrentBlock + m_BGroupOffset], USE_FEC)) 326 | { 327 | m_BlockErrors++; 328 | if (m_BlockErrors > BLOCK_ERROR_LIMIT) 329 | { 330 | m_DecodeState = STATE_BITSYNC; 331 | } 332 | else 333 | { 334 | m_CurrentBlock++; 335 | if (m_CurrentBlock > BLOCK__D) 336 | m_CurrentBlock = BLOCK__A; 337 | if (BLOCK__A != m_CurrentBlock) //skip remaining blocks of this group if error 338 | m_DecodeState = STATE_GROUPRESYNC; 339 | } 340 | } 341 | else 342 | { //good block so save and get ready for next one 343 | m_BlockData[m_CurrentBlock] = m_InBitStream >> NUMBITS_CRC; //save msg data 344 | //see if is group A or Group B 345 | if ((BLOCK__B == m_CurrentBlock) && (m_BlockData[m_CurrentBlock] & GROUPB_BIT)) 346 | m_BGroupOffset = 4; 347 | else 348 | m_BGroupOffset = 0; 349 | m_CurrentBlock++; 350 | if (m_CurrentBlock > BLOCK__D) 351 | { 352 | m_CurrentBlock = BLOCK__A; 353 | m_BlockErrors = 0; 354 | 355 | m_Decoder.DecodeRDS(m_BlockData); 356 | } 357 | } 358 | } 359 | break; 360 | 361 | case STATE_GROUPRESYNC: //ignor blocks until start of next group 362 | m_CurrentBitPosition++; 363 | if (m_CurrentBitPosition >= NUMBITS_BLOCK) 364 | { 365 | m_CurrentBitPosition = 0; 366 | m_CurrentBlock++; 367 | if (m_CurrentBlock > BLOCK__D) 368 | { 369 | m_CurrentBlock = BLOCK__A; 370 | m_DecodeState = STATE_GROUPDECODE; 371 | } 372 | } 373 | break; 374 | } 375 | } 376 | 377 | uint32_t cRDSRxSignalProcessor::CheckBlock(uint32_t SyndromeOffset, int UseFec) 378 | { 379 | //! First calculate syndrome for current 26 m_InBitStream bits 380 | uint32_t testblock = (0x3FFFFFF & m_InBitStream); //!< isolate bottom 26 bits 381 | 382 | /*! 383 | * copy top 10 bits of block into 10 syndrome bits since first 10 rows 384 | * of the check matrix is just an identity matrix(diagonal one's) 385 | */ 386 | uint32_t syndrome = testblock >> 16; 387 | for (int i = 0; i < NUMBITS_MSG; i++) 388 | { 389 | //! do the 16 remaining bits of the check matrix multiply 390 | if (testblock & 0x8000) 391 | syndrome ^= PARCKH[i]; 392 | testblock <<= 1; 393 | } 394 | syndrome ^= SyndromeOffset; //!< add depending on desired block 395 | 396 | if (syndrome && UseFec) //!< if errors and can use FEC 397 | { 398 | uint32_t correctedbits = 0; 399 | uint32_t correctmask = (1 << (NUMBITS_BLOCK - 1)); //!< start pointing to msg msb 400 | 401 | //! Run Meggitt FEC algorithm to correct up to 5 consecutive burst errors 402 | for (int i = 0; i < NUMBITS_MSG; i++) 403 | { 404 | if (syndrome & 0x200) //!< chk msbit of syndrome for error state 405 | { 406 | //! is possible bit error at current position 407 | if (0 == (syndrome & 0x1F)) //!< bottom 5 bits == 0 tell it is correctable 408 | { //! Correct i-th bit 409 | m_InBitStream ^= correctmask; 410 | correctedbits++; 411 | syndrome <<= 1; //!< shift syndrome to next msb 412 | } 413 | else 414 | { 415 | syndrome <<= 1; //!< shift syndrome to next msb 416 | syndrome ^= 417 | CRC_POLY; //!< recalculate new syndrome if bottom 5 bits not zero and syndrome msb bit was a one 418 | } 419 | } 420 | else 421 | { 422 | //! no error at this bit position so just shift to next position 423 | syndrome <<= 1; //!< shift syndrome to next msb 424 | } 425 | correctmask >>= 1; //!< advance correctable bit position 426 | } 427 | syndrome &= 0x3FF; //!< isolate syndrome bits if non-zero then still an error 428 | } 429 | 430 | return syndrome; 431 | } 432 | -------------------------------------------------------------------------------- /src/RDSProcess.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2013, Moe Wheatley 3 | * Copyright (C) 2015-2020, Alwin Esch (Team KODI) 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "Definitions.h" 12 | #include "DownConvert.h" 13 | #include "FirFilter.h" 14 | #include "FreqShift.h" 15 | #include "IirFilter.h" 16 | #include "RDSGroupDecoder.h" 17 | 18 | #define MAX_RDS_ARRAY_SIZE (16 * 32 * 512) 19 | 20 | #define RDS_FREQUENCY 57000.0 21 | #define RDS_BITRATE (RDS_FREQUENCY / 48.0) //!< 1187.5 bps bitrate 22 | #define RDSPLL_RANGE 12.0 //!< maximum deviation limit of PLL 23 | #define RDSPLL_BW 1.00 //!< natural frequency ~loop bandwidth 24 | #define RDSPLL_ZETA 0.707 //!< PLL Loop damping factor 25 | 26 | #define USE_FEC 1 //!< set to zero to disable FEC correction 27 | 28 | #define NUMBITS_CRC 10 29 | #define NUMBITS_MSG 16 30 | #define NUMBITS_BLOCK (NUMBITS_CRC + NUMBITS_MSG) 31 | #define BLOCK_ERROR_LIMIT 0 //!< number of bad blocks before trying to resync at the bit level 32 | 33 | #define CRC_POLY 0x5B9 //!< RDS crc polynomial x^10+x^8+x^7+x^5+x^4+x^3+1 34 | 35 | #define GROUPB_BIT 0x0800 //!< bit position in BlockB for determining group A or B msgs 36 | 37 | //! RDS decoder states 38 | #define STATE_BITSYNC 0 //!< looking for initial bit position in Block 1 39 | #define STATE_BLOCKSYNC 1 //!< looking for initial correct block order 40 | #define STATE_GROUPDECODE 2 //!< decode groups after achieving bit and block sync 41 | #define STATE_GROUPRESYNC 3 //!< waiting for beginning of new group after getting a block error 42 | 43 | /*! 44 | * Block offset syndromes for chkword checking 45 | * created multiplying Block Offsets by parity check matrix 46 | * Takes into account that the (26,16)code is a shortened cyclic block code 47 | * from the original (341,331) length cyclic code. 48 | */ 49 | #define OFFSET_SYNDROME_BLOCK_A 0x3D8 50 | #define OFFSET_SYNDROME_BLOCK_B 0x3D4 51 | #define OFFSET_SYNDROME_BLOCK_C 0x25C 52 | #define OFFSET_SYNDROME_BLOCK_CP 0x3CC 53 | #define OFFSET_SYNDROME_BLOCK_D 0x258 54 | 55 | class cRadioReceiver; 56 | 57 | class ATTRIBUTE_HIDDEN cRDSRxSignalProcessor 58 | { 59 | public: 60 | cRDSRxSignalProcessor(cRadioReceiver* proc, RealType SampleRate); 61 | virtual ~cRDSRxSignalProcessor(); 62 | void Reset(); 63 | 64 | void Process(const RealType* inputStream, unsigned int items); 65 | 66 | private: 67 | void ProcessRdsPll(ComplexType* pInData, RealType* pOutData, unsigned int InLength); 68 | void ProcessNewRdsBit(int bit); 69 | uint32_t CheckBlock(uint32_t SyndromeOffset, int UseFec); 70 | 71 | RealType m_SampleRate; 72 | cRDSGroupDecoder m_Decoder; 73 | RealType m_ProcessRate; 74 | 75 | ComplexType* m_ProcessArrayIn; 76 | ComplexType* m_RdsRaw; //variables for RDS processing 77 | RealType* m_RdsMag; 78 | RealType* m_RdsData; 79 | RealType* m_RdsMatchCoef; 80 | RealType m_RdsLastSync; 81 | RealType m_RdsLastSyncSlope; 82 | RealType m_RdsLastData; 83 | unsigned int m_RdsMatchCoefLength; 84 | 85 | RealType m_RdsNcoPhase = 0.0; //variables for RDS PLL 86 | RealType m_RdsNcoFreq = 0.0; 87 | RealType m_RdsNcoAcc; 88 | RealType m_RdsNcoLLimit; 89 | RealType m_RdsNcoHLimit; 90 | RealType m_RdsPllAlpha; 91 | RealType m_RdsPllBeta; 92 | 93 | cFirFilter m_RdsLPFilter; 94 | cFirFilter m_RdsMatchedFilter; 95 | cIirFilter m_RdsBitSyncFilter; 96 | CRDSDownConvert m_DownConvert; 97 | 98 | int m_RdsLastBit; 99 | uint32_t m_InBitStream; //input shift register for incoming raw data 100 | int m_CurrentBlock; 101 | int m_CurrentBitPosition; 102 | int m_DecodeState; 103 | int m_BGroupOffset; 104 | int m_BlockErrors; 105 | uint16_t m_BlockData[4]; 106 | 107 | RealType m_ClockLastValue; 108 | bool m_ClockInput; 109 | unsigned int m_DataInputPrev; 110 | }; 111 | -------------------------------------------------------------------------------- /src/RTL_SDR_Source.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013, Joris van Rantwijk. 3 | * Copyright (C) 2015-2020, Alwin Esch (Team KODI) 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #include "RTL_SDR_Source.h" 10 | 11 | #include "RadioReceiver.h" 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | cRtlSdrSource::cRtlSdrSource(cRadioReceiver* proc) 19 | : m_DevicePtr(nullptr), m_Proc(proc), m_BlockLength(default_block_length) 20 | { 21 | } 22 | 23 | cRtlSdrSource::~cRtlSdrSource() 24 | { 25 | Close(); 26 | if (m_DevicePtr) 27 | rtlsdr_close(m_DevicePtr); 28 | m_DevicePtr = nullptr; 29 | } 30 | 31 | bool cRtlSdrSource::OpenDevice(int dev_index) 32 | { 33 | if (m_DevicePtr) 34 | rtlsdr_close(m_DevicePtr); 35 | m_DevicePtr = nullptr; 36 | 37 | const char* devname = rtlsdr_get_device_name(dev_index); 38 | if (devname != nullptr) 39 | m_DeviceName = devname; 40 | 41 | int ret = rtlsdr_open(&m_DevicePtr, dev_index); 42 | if (ret < 0) 43 | { 44 | m_error = "Failed to open RTL-SDR device ("; 45 | m_error += strerror(-ret); 46 | m_error += ")"; 47 | return false; 48 | } 49 | 50 | m_DeviceIndex = dev_index; 51 | return true; 52 | } 53 | 54 | void cRtlSdrSource::Close() 55 | { 56 | m_Cancelled = true; 57 | 58 | if (m_DevicePtr) 59 | rtlsdr_cancel_async(m_DevicePtr); 60 | 61 | m_running = false; 62 | if (m_thread.joinable()) 63 | m_thread.join(); 64 | } 65 | 66 | // Configure RTL-SDR tuner and prepare for streaming. 67 | bool cRtlSdrSource::Configure( 68 | uint32_t sample_rate, uint32_t frequency, int tuner_gain, int block_length, bool agcmode) 69 | { 70 | int r; 71 | 72 | if (!m_DevicePtr) 73 | return false; 74 | 75 | r = rtlsdr_set_sample_rate(m_DevicePtr, sample_rate); 76 | if (r < 0) 77 | { 78 | m_error = "rtlsdr_set_sample_rate failed"; 79 | return false; 80 | } 81 | 82 | r = rtlsdr_set_center_freq(m_DevicePtr, frequency); 83 | if (r < 0) 84 | { 85 | m_error = "rtlsdr_set_center_freq failed"; 86 | return false; 87 | } 88 | 89 | if (tuner_gain == INT_MIN) 90 | { 91 | r = rtlsdr_set_tuner_gain_mode(m_DevicePtr, 0); 92 | if (r < 0) 93 | { 94 | m_error = "rtlsdr_set_tuner_gain_mode could not set automatic gain"; 95 | return false; 96 | } 97 | } 98 | else 99 | { 100 | r = rtlsdr_set_tuner_gain_mode(m_DevicePtr, 1); 101 | if (r < 0) 102 | { 103 | m_error = "rtlsdr_set_tuner_gain_mode could not set manual gain"; 104 | return false; 105 | } 106 | 107 | r = rtlsdr_set_tuner_gain(m_DevicePtr, tuner_gain); 108 | if (r < 0) 109 | { 110 | m_error = "rtlsdr_set_tuner_gain failed"; 111 | return false; 112 | } 113 | } 114 | 115 | //! set RTL AGC mode 116 | r = rtlsdr_set_agc_mode(m_DevicePtr, int(agcmode)); 117 | if (r < 0) 118 | { 119 | m_error = "rtlsdr_set_agc_mode failed"; 120 | return false; 121 | } 122 | 123 | //! set block length 124 | m_BlockLength = 125 | (block_length < 4096) ? 4096 : (block_length > 1024 * 1024) ? 1024 * 1024 : block_length; 126 | m_BlockLength -= m_BlockLength % 4096; 127 | m_SampleRate = sample_rate; 128 | m_Frequency = frequency; 129 | m_TunerGain = tuner_gain; 130 | m_agcMode = agcmode; 131 | 132 | //! reset buffer to start streaming 133 | if (rtlsdr_reset_buffer(m_DevicePtr) < 0) 134 | { 135 | m_error = "rtlsdr_reset_buffer failed"; 136 | return false; 137 | } 138 | 139 | m_Cancelled = false; 140 | m_running = true; 141 | m_thread = std::thread([&] { Process(); }); 142 | 143 | return true; 144 | } 145 | 146 | uint32_t cRtlSdrSource::GetSampleRate() 147 | { 148 | return rtlsdr_get_sample_rate(m_DevicePtr); 149 | } 150 | 151 | uint32_t cRtlSdrSource::GetFrequency() 152 | { 153 | if (m_DevicePtr) 154 | return rtlsdr_get_center_freq(m_DevicePtr); 155 | else 156 | return 0; 157 | } 158 | 159 | void cRtlSdrSource::SetFrequency(uint32_t freq) 160 | { 161 | if (m_DevicePtr) 162 | rtlsdr_set_center_freq(m_DevicePtr, freq); 163 | } 164 | 165 | int cRtlSdrSource::GetTunerGain() 166 | { 167 | return rtlsdr_get_tuner_gain(m_DevicePtr); 168 | } 169 | 170 | std::vector cRtlSdrSource::GetTunerGains() 171 | { 172 | int num_gains = rtlsdr_get_tuner_gains(m_DevicePtr, nullptr); 173 | if (num_gains <= 0) 174 | return std::vector(); 175 | 176 | std::vector gains(num_gains); 177 | if (rtlsdr_get_tuner_gains(m_DevicePtr, gains.data()) != num_gains) 178 | return std::vector(); 179 | 180 | return gains; 181 | } 182 | 183 | bool cRtlSdrSource::GetDeviceNames(std::vector& result) 184 | { 185 | int device_count = rtlsdr_get_device_count(); 186 | if (device_count <= 0) 187 | return false; 188 | 189 | result.reserve(device_count); 190 | for (int i = 0; i < device_count; ++i) 191 | result.push_back(std::string(rtlsdr_get_device_name(i))); 192 | 193 | return true; 194 | } 195 | 196 | void cRtlSdrSource::ReadAsyncCB(unsigned char* buf, uint32_t len, void* ctx) 197 | { 198 | cRtlSdrSource* thisClass = (cRtlSdrSource*)ctx; 199 | 200 | if (len != 2 * thisClass->m_BlockLength) 201 | { 202 | kodi::Log(ADDON_LOG_ERROR, "RtlSdr: short read, samples lost"); 203 | return; 204 | } 205 | 206 | thisClass->m_Samples.resize(thisClass->m_BlockLength); 207 | for (unsigned int i = 0; i < thisClass->m_BlockLength; ++i) 208 | { 209 | thisClass->m_Samples[i] = 210 | ComplexType((buf[2 * i] / (255.0 / 2.0) - 1.0), (buf[2 * i + 1] / (255.0 / 2.0) - 1.0)); 211 | } 212 | thisClass->m_Proc->WriteDataBuffer(thisClass->m_Samples); 213 | } 214 | 215 | void cRtlSdrSource::Process() 216 | { 217 | unsigned int restartTry = 0; 218 | bool isOK = true; 219 | 220 | while (m_running && !m_Cancelled) 221 | { 222 | int readFail = rtlsdr_read_async(m_DevicePtr, ReadAsyncCB, this, 15, 2 * m_BlockLength); 223 | if (m_Cancelled) 224 | break; 225 | if (!isOK || readFail) 226 | { 227 | kodi::Log(ADDON_LOG_ERROR, "RtlSdr: %s", error().c_str()); 228 | if (restartTry >= DEVICE_RESTART_TRIES) 229 | { 230 | kodi::Log(ADDON_LOG_ERROR, "RtlSdr: All restarts failed, exiting!"); 231 | break; 232 | } 233 | 234 | std::this_thread::sleep_for(std::chrono::milliseconds(1000)); 235 | 236 | restartTry++; 237 | kodi::Log(ADDON_LOG_INFO, "RtlSdr: %s trying restart %i", restartTry); 238 | isOK = Configure(m_SampleRate, m_Frequency, m_TunerGain, m_BlockLength, m_agcMode); 239 | continue; 240 | } 241 | } 242 | 243 | m_Proc->EndDataBuffer(); 244 | return; 245 | } 246 | -------------------------------------------------------------------------------- /src/RTL_SDR_Source.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013, Joris van Rantwijk. 3 | * Copyright (C) 2015-2020, Alwin Esch (Team KODI) 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "Definitions.h" 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | class cRadioReceiver; 20 | 21 | class ATTRIBUTE_HIDDEN cRtlSdrSource 22 | { 23 | public: 24 | //static const int default_block_length = 16 * 32 * 512; 25 | static const int default_block_length = 65536; 26 | 27 | cRtlSdrSource(cRadioReceiver* proc); 28 | virtual ~cRtlSdrSource(); 29 | 30 | /*!> 31 | * Open the given rtl sdr device index number 32 | * @param dev_index Index to open 33 | * @return true if successed 34 | */ 35 | bool OpenDevice(int dev_index); 36 | 37 | /*!> 38 | * Close the rtl sdr device 39 | */ 40 | void Close(); 41 | 42 | /*!> 43 | * Configure RTL-SDR tuner and prepare for streaming. 44 | * 45 | * sample_rate :: desired sample rate in Hz. 46 | * frequency :: desired center frequency in Hz. 47 | * tuner_gain :: desired tuner gain in 0.1 dB, or INT_MIN for auto-gain. 48 | * block_length :: preferred number of samples per block. 49 | * 50 | * Return true for success, false if an error occurred. 51 | */ 52 | bool Configure(uint32_t sample_rate, 53 | uint32_t frequency, 54 | int tuner_gain, 55 | int block_length = default_block_length, 56 | bool agcmode = false); 57 | 58 | /*!> Return current sample frequency in Hz. */ 59 | uint32_t GetSampleRate(); 60 | 61 | /*!> Return current center frequency in Hz. */ 62 | uint32_t GetFrequency(); 63 | 64 | void SetFrequency(uint32_t freq); 65 | 66 | /*!> Return current tuner gain in units of 0.1 dB. */ 67 | int GetTunerGain(); 68 | 69 | /*!> Return a list of supported tuner gain settings in units of 0.1 dB. */ 70 | std::vector GetTunerGains(); 71 | 72 | /*!> Return name of opened RTL-SDR device. */ 73 | std::string GetDeviceName() const { return m_DeviceName; } 74 | 75 | /*!> Return the last error, or return an empty string if there is no error. */ 76 | std::string error() 77 | { 78 | std::string ret(m_error); 79 | m_error.clear(); 80 | return ret; 81 | } 82 | 83 | /*!> Return true if the device is OK, return false if there is an error. */ 84 | operator bool() const { return m_DevicePtr && m_error.empty(); } 85 | 86 | /*!> Return a list of supported devices. */ 87 | static bool GetDeviceNames(std::vector& result); 88 | 89 | protected: 90 | void Process(); 91 | 92 | private: 93 | static void ReadAsyncCB(unsigned char* buf, uint32_t len, void* ctx); 94 | 95 | std::atomic m_running = {false}; 96 | std::thread m_thread; 97 | 98 | struct rtlsdr_dev* m_DevicePtr; 99 | cRadioReceiver* m_Proc; 100 | unsigned int m_BlockLength; 101 | int m_DeviceIndex; 102 | uint32_t m_SampleRate; 103 | uint32_t m_Frequency; 104 | int m_TunerGain; 105 | bool m_agcMode; 106 | std::vector m_Samples; 107 | bool m_Cancelled; 108 | 109 | std::string m_DeviceName; 110 | std::string m_error; 111 | }; 112 | -------------------------------------------------------------------------------- /src/RadioReceiver.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015-2020, Alwin Esch (Team KODI) 3 | * 4 | * SPDX-License-Identifier: GPL-2.0-or-later 5 | * See LICENSE.md for more information. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include "Definitions.h" 11 | #include "DownConvert.h" 12 | #include "RTL_SDR_Source.h" 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | class cFmDecoder; 24 | class cChannelSettings; 25 | 26 | struct FMRadioChannel 27 | { 28 | unsigned int iUniqueId; 29 | float fChannelFreq; 30 | std::string strChannelName; 31 | std::string strIconPath; 32 | }; 33 | 34 | class ATTRIBUTE_HIDDEN cRadioReceiver : public kodi::addon::CAddonBase, 35 | public kodi::addon::CInstancePVRClient 36 | { 37 | public: 38 | cRadioReceiver(); 39 | virtual ~cRadioReceiver(); 40 | 41 | PVR_ERROR GetCapabilities(kodi::addon::PVRCapabilities& capabilities) override; 42 | PVR_ERROR GetBackendName(std::string& name) override; 43 | PVR_ERROR GetBackendVersion(std::string& version) override; 44 | PVR_ERROR GetConnectionString(std::string& connection) override; 45 | 46 | /*! 47 | * From Kodi used functions 48 | */ 49 | PVR_ERROR GetChannelsAmount(int& amount) override; 50 | PVR_ERROR GetChannels(bool radio, kodi::addon::PVRChannelsResultSet& results) override; 51 | PVR_ERROR DeleteChannel(const kodi::addon::PVRChannel& channel) override; 52 | PVR_ERROR RenameChannel(const kodi::addon::PVRChannel& channel) override; 53 | PVR_ERROR OpenDialogChannelSettings(const kodi::addon::PVRChannel& channel) override; 54 | PVR_ERROR OpenDialogChannelAdd(const kodi::addon::PVRChannel& channel) override; 55 | PVR_ERROR GetSignalStatus(int channelUid, kodi::addon::PVRSignalStatus& signalStatus) override; 56 | 57 | bool OpenLiveStream(const kodi::addon::PVRChannel& channel) override; 58 | void CloseLiveStream() override; 59 | PVR_ERROR GetStreamProperties(std::vector& properties) override; 60 | DEMUX_PACKET* DemuxRead() override; 61 | void DemuxAbort() override; 62 | 63 | int CurrentChannel() { return m_activeChannelInfo.GetChannelNumber(); } 64 | 65 | bool GetSignalStatus(float& interfaceLevel, float& audioLevel, bool& stereo); 66 | 67 | /*! 68 | * Intern of addon used functions 69 | */ 70 | bool LoadChannelData(bool initial); 71 | bool SaveChannelData(void); 72 | bool SetChannel(const kodi::addon::PVRChannel& channel); 73 | unsigned int CreateNewUniqueId() { return m_UniqueIdNextNew++; } 74 | std::string CreateChannelName(FMRadioChannel& channel) const; 75 | std::vector* GetChannelData() { return &m_channels; } 76 | cRtlSdrSource* GetSource() { return &m_RtlSdrReceiver; } 77 | bool SetChannelName(std::string name); 78 | bool IsActive() { return m_StreamActive; } 79 | bool IsChannelActive(int index) { return m_StreamActive ? m_activeIndex == index : false; } 80 | bool IsSettingActive() { return m_SettingsDialog != nullptr; } 81 | 82 | void RegisterDialog(cChannelSettings* dialog); 83 | void SetStreamChange() { m_StreamChange = true; } 84 | 85 | private: 86 | std::string GetSettingsFile() const; 87 | 88 | inline void AdjustGain(float* samples, float gain, unsigned int n); 89 | inline void SamplesMeanRMS(const float* samples, double& mean, double& rms, unsigned int n); 90 | 91 | std::string m_strDefaultIcon; 92 | std::string m_channelName; 93 | int m_DeviceIndex = -1; 94 | std::vector m_channels; 95 | std::vector m_streams; 96 | kodi::addon::PVRChannel m_activeChannelInfo; 97 | int m_activeIndex = -1; 98 | double m_activeChannelFrequency; 99 | double m_IfRate = 1.0e6; 100 | double m_PCMRate = OUTPUT_SAMPLERATE; 101 | int m_LnaGain = INT_MIN; 102 | bool m_AgcMode = false; 103 | bool m_StreamChange = false; 104 | bool m_StreamActive = false; 105 | float m_AudioLevel = 0.0f; 106 | double m_activeTunerFreq; 107 | kodi::addon::PVRSignalStatus m_activeSignalStatus; 108 | 109 | cChannelSettings* m_SettingsDialog = nullptr; 110 | 111 | /*! 112 | * Input stream related functions and data 113 | */ 114 | public: 115 | bool AddUECPDataFrame(uint8_t* UECPDataFrame, unsigned int length); 116 | void WriteDataBuffer(std::vector& iqsamples); 117 | void EndDataBuffer(); 118 | 119 | private: 120 | size_t SourceQueuedSamples(); 121 | bool SourceGetSamples(std::vector& samples); 122 | 123 | cRtlSdrSource m_RtlSdrReceiver; 124 | cFmDecoder* m_FMDecoder = nullptr; 125 | 126 | std::mutex m_UECPMutex; 127 | std::vector m_UECPOutputBuffer; 128 | 129 | std::mutex m_AudioSignalMutex; 130 | double m_PTSNext; 131 | 132 | size_t m_AudioSourceSize; 133 | std::deque> m_AudioSourceBuffer; 134 | std::mutex m_AudioSourceMutex; 135 | std::condition_variable m_AudioSourceEvent; 136 | bool m_AudioSourceEndMarked; 137 | bool m_AudioSourceBufferWarning; 138 | 139 | unsigned int m_UniqueIdNextNew; 140 | }; 141 | -------------------------------------------------------------------------------- /src/Utils.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2005-2020 Team Kodi 3 | * https://kodi.tv 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #include "Utils.h" 10 | 11 | #include 12 | #include 13 | 14 | #define FORMAT_BLOCK_SIZE 2048 // # of bytes to increment per try 15 | 16 | std::string StringUtils::Format(const char* fmt, ...) 17 | { 18 | va_list args; 19 | va_start(args, fmt); 20 | std::string str = FormatV(fmt, args); 21 | va_end(args); 22 | 23 | return str; 24 | } 25 | 26 | std::string StringUtils::FormatV(const char* fmt, va_list args) 27 | { 28 | if (fmt == nullptr) 29 | return ""; 30 | 31 | int size = FORMAT_BLOCK_SIZE; 32 | va_list argCopy; 33 | 34 | char* cstr = reinterpret_cast(malloc(sizeof(char) * size)); 35 | if (cstr == nullptr) 36 | return ""; 37 | 38 | while (1) 39 | { 40 | va_copy(argCopy, args); 41 | 42 | int nActual = vsnprintf(cstr, size, fmt, argCopy); 43 | va_end(argCopy); 44 | 45 | if (nActual > -1 && nActual < size) // We got a valid result 46 | { 47 | std::string str(cstr, nActual); 48 | free(cstr); 49 | return str; 50 | } 51 | if (nActual > -1) // Exactly what we will need (glibc 2.1) 52 | size = nActual + 1; 53 | else // Let's try to double the size (glibc 2.0) 54 | size *= 2; 55 | 56 | char* new_cstr = reinterpret_cast(realloc(cstr, sizeof(char) * size)); 57 | if (new_cstr == nullptr) 58 | break; 59 | 60 | cstr = new_cstr; 61 | } 62 | 63 | free(cstr); 64 | return ""; 65 | } 66 | 67 | std::string& StringUtils::Trim(std::string& str) 68 | { 69 | TrimLeft(str); 70 | return TrimRight(str); 71 | } 72 | 73 | // hack to ensure that std::string::iterator will be dereferenced as _unsigned_ char 74 | // without this hack "TrimX" functions failed on Win32 with UTF-8 strings 75 | static int isspace_c(char c) 76 | { 77 | return ::isspace((unsigned char)c); 78 | } 79 | 80 | 81 | std::string& StringUtils::TrimLeft(std::string& str) 82 | { 83 | str.erase(str.begin(), std::find_if(str.begin(), str.end(), std::not1(std::ptr_fun(isspace_c)))); 84 | return str; 85 | } 86 | 87 | std::string& StringUtils::TrimRight(std::string& str) 88 | { 89 | str.erase(std::find_if(str.rbegin(), str.rend(), std::not1(std::ptr_fun(isspace_c))).base(), 90 | str.end()); 91 | return str; 92 | } 93 | -------------------------------------------------------------------------------- /src/Utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2005-2020 Team Kodi 3 | * https://kodi.tv 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | typedef std::complex IQSample; 16 | typedef std::vector IQSampleVector; 17 | 18 | typedef double Sample; 19 | typedef std::vector SampleVector; 20 | 21 | /** Compute mean and RMS over a sample vector. */ 22 | inline void samples_mean_rms(const SampleVector& samples, double& mean, double& rms) 23 | { 24 | Sample vsum = 0; 25 | Sample vsumsq = 0; 26 | 27 | unsigned int n = samples.size(); 28 | for (unsigned int i = 0; i < n; i++) 29 | { 30 | Sample v = samples[i]; 31 | vsum += v; 32 | vsumsq += v * v; 33 | } 34 | 35 | mean = vsum / n; 36 | rms = sqrt(vsumsq / n); 37 | } 38 | 39 | class StringUtils 40 | { 41 | public: 42 | static std::string Format(const char* fmt, ...); 43 | static std::string FormatV(const char* fmt, va_list args); 44 | static std::string& Trim(std::string& str); 45 | static std::string& TrimLeft(std::string& str); 46 | static std::string& TrimRight(std::string& str); 47 | }; 48 | -------------------------------------------------------------------------------- /src/XMLUtils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2005-2020 Team Kodi 3 | * https://kodi.tv 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | * See LICENSE.md for more information. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | namespace xml 19 | { 20 | 21 | //============================================================================== 22 | /// @brief Returns true if the encoding of the document is specified as as UTF-8 23 | /// 24 | /// @param[in] strXML The XML file (embedded in a string) to check. 25 | /// @return true if UTF8 26 | /// 27 | inline bool HasUTF8Declaration(const std::string& strXML) 28 | { 29 | std::string test = strXML; 30 | std::transform(test.begin(), test.end(), test.begin(), ::tolower); 31 | // test for the encoding="utf-8" string 32 | if (test.find("encoding=\"utf-8\"") != std::string::npos) 33 | return true; 34 | // TODO: test for plain UTF8 here? 35 | return false; 36 | } 37 | //------------------------------------------------------------------------------ 38 | 39 | //============================================================================== 40 | /// @brief Check given tag on XML has a child. 41 | /// 42 | /// @param[in] pRootNode TinyXML related note field 43 | /// @param[in] strTag XML identification tag to check 44 | /// @return true if child present. 45 | /// 46 | inline bool HasChild(const TiXmlNode* pRootNode, const std::string& strTag) 47 | { 48 | const TiXmlElement* pElement = pRootNode->FirstChildElement(strTag); 49 | if (!pElement) 50 | return false; 51 | const TiXmlNode* pNode = pElement->FirstChild(); 52 | return (pNode != nullptr); 53 | } 54 | //------------------------------------------------------------------------------ 55 | 56 | //============================================================================== 57 | /// @brief Returns true if the encoding of the document is other then UTF-8. 58 | /// 59 | /// @param[in] pDoc TinyXML related document field 60 | /// @param[in] strEncoding Returns the encoding of the document. Empty if UTF-8 61 | /// @return String of encoding if present and not UTF-8 62 | /// 63 | inline bool GetEncoding(const TiXmlDocument* pDoc, std::string& strEncoding) 64 | { 65 | const TiXmlNode* pNode = nullptr; 66 | while ((pNode = pDoc->IterateChildren(pNode)) && pNode->Type() != TiXmlNode::TINYXML_DECLARATION) 67 | { 68 | } 69 | if (!pNode) 70 | return false; 71 | const TiXmlDeclaration* pDecl = pNode->ToDeclaration(); 72 | if (!pDecl) 73 | return false; 74 | strEncoding = pDecl->Encoding(); 75 | std::transform(strEncoding.begin(), strEncoding.end(), strEncoding.begin(), ::toupper); 76 | 77 | if (strEncoding == "UTF-8" || strEncoding == "UTF8") 78 | strEncoding.clear(); 79 | 80 | return !strEncoding.empty(); // Other encoding then UTF8? 81 | } 82 | //------------------------------------------------------------------------------ 83 | 84 | //============================================================================== 85 | /// @brief To get a text string value stored inside XML. 86 | /// 87 | /// @param[in] pRootNode TinyXML related note field 88 | /// @param[in] strTag XML identification tag 89 | /// @param[out] value The readed value from XML 90 | /// @return true if available and successfully done 91 | /// 92 | inline bool GetString(const TiXmlNode* pRootNode, const std::string& strTag, std::string& value) 93 | { 94 | const TiXmlElement* pElement = pRootNode->FirstChildElement(strTag); 95 | if (!pElement) 96 | return false; 97 | const TiXmlNode* pNode = pElement->FirstChild(); 98 | if (pNode != nullptr) 99 | { 100 | value = pNode->Value(); 101 | return true; 102 | } 103 | value.clear(); 104 | return false; 105 | } 106 | //------------------------------------------------------------------------------ 107 | 108 | //============================================================================== 109 | /// @brief To get a HEX formated integer string inside XML. 110 | /// 111 | /// @param[in] pRootNode TinyXML related note field 112 | /// @param[in] strTag XML identification tag 113 | /// @param[out] value The readed value from XML 114 | /// @return true if available and successfully done 115 | /// 116 | inline bool GetHex(const TiXmlNode* pRootNode, const std::string& strTag, uint32_t& value) 117 | { 118 | const TiXmlNode* pNode = pRootNode->FirstChild(strTag); 119 | if (!pNode || !pNode->FirstChild()) 120 | return false; 121 | value = strtoul(pNode->FirstChild()->Value(), nullptr, 16); 122 | return true; 123 | } 124 | //------------------------------------------------------------------------------ 125 | 126 | //============================================================================== 127 | /// @brief To get a 32 bit unsigned integer value stored inside XML. 128 | /// 129 | /// @param[in] pRootNode TinyXML related note field 130 | /// @param[in] strTag XML identification tag 131 | /// @param[out] value The readed value from XML 132 | /// @return true if available and successfully done 133 | /// 134 | inline bool GetUInt(const TiXmlNode* pRootNode, const std::string& strTag, uint32_t& value) 135 | { 136 | const TiXmlNode* pNode = pRootNode->FirstChild(strTag); 137 | if (!pNode || !pNode->FirstChild()) 138 | return false; 139 | value = atol(pNode->FirstChild()->Value()); 140 | return true; 141 | } 142 | //------------------------------------------------------------------------------ 143 | 144 | //============================================================================== 145 | /// @brief To get a 32 bit integer value stored inside XML. 146 | /// 147 | /// @param[in] pRootNode TinyXML related note field 148 | /// @param[in] strTag XML identification tag 149 | /// @param[out] value The readed value from XML 150 | /// @return true if available and successfully done 151 | /// 152 | inline bool GetInt(const TiXmlNode* pRootNode, const std::string& strTag, int32_t& value) 153 | { 154 | const TiXmlNode* pNode = pRootNode->FirstChild(strTag); 155 | if (!pNode || !pNode->FirstChild()) 156 | return false; 157 | value = atoi(pNode->FirstChild()->Value()); 158 | return true; 159 | } 160 | //------------------------------------------------------------------------------ 161 | 162 | //============================================================================== 163 | /// @brief To get a 32 bit integer value stored inside XML. 164 | /// 165 | /// This can define min and max values if content out of wanted range. 166 | /// 167 | /// @param[in] pRootNode TinyXML related note field 168 | /// @param[in] strTag XML identification tag 169 | /// @param[out] value The readed value from XML 170 | /// @param[in] min Minimal return value 171 | /// @param[in] max Maximal return value 172 | /// @return true if available and successfully done 173 | /// 174 | inline bool GetInt(const TiXmlNode* pRootNode, 175 | const std::string& strTag, 176 | int32_t& value, 177 | const int32_t min, 178 | const int32_t max) 179 | { 180 | if (GetInt(pRootNode, strTag, value)) 181 | { 182 | if (value < min) 183 | value = min; 184 | if (value > max) 185 | value = max; 186 | return true; 187 | } 188 | return false; 189 | } 190 | //------------------------------------------------------------------------------ 191 | 192 | //============================================================================== 193 | /// @brief To get a 64 bit integer value stored inside XML. 194 | /// 195 | /// @param[in] pRootNode TinyXML related note field 196 | /// @param[in] strTag XML identification tag 197 | /// @param[out] value The readed value from XML 198 | /// @return true if available and successfully done 199 | /// 200 | inline bool GetLong(const TiXmlNode* pRootNode, const std::string& strTag, int64_t& value) 201 | { 202 | const TiXmlNode* pNode = pRootNode->FirstChild(strTag); 203 | if (!pNode || !pNode->FirstChild()) 204 | return false; 205 | value = atoll(pNode->FirstChild()->Value()); 206 | return true; 207 | } 208 | //------------------------------------------------------------------------------ 209 | 210 | //============================================================================== 211 | /// @brief To get a floating point value stored inside XML. 212 | /// 213 | /// @param[in] pRootNode TinyXML related note field 214 | /// @param[in] strTag XML identification tag 215 | /// @param[out] value The readed value from XML 216 | /// @return true if available and successfully done 217 | /// 218 | inline bool GetFloat(const TiXmlNode* pRootNode, const std::string& strTag, float& value) 219 | { 220 | const TiXmlNode* pNode = pRootNode->FirstChild(strTag); 221 | if (!pNode || !pNode->FirstChild()) 222 | return false; 223 | value = atof(pNode->FirstChild()->Value()); 224 | return true; 225 | } 226 | //------------------------------------------------------------------------------ 227 | 228 | //============================================================================== 229 | /// @brief To get a floating point value stored inside XML. 230 | /// 231 | /// This can define min and max values if content out of wanted range. 232 | /// 233 | /// @param[in] pRootNode TinyXML related note field 234 | /// @param[in] strTag XML identification tag 235 | /// @param[out] value The readed value from XML 236 | /// @param[in] min Minimal return value 237 | /// @param[in] max Maximal return value 238 | /// @return true if available and successfully done 239 | /// 240 | inline bool GetFloat(const TiXmlNode* pRootNode, 241 | const std::string& strTag, 242 | float& value, 243 | const float min, 244 | const float max) 245 | { 246 | if (GetFloat(pRootNode, strTag, value)) 247 | { // check range 248 | if (value < min) 249 | value = min; 250 | if (value > max) 251 | value = max; 252 | return true; 253 | } 254 | return false; 255 | } 256 | //------------------------------------------------------------------------------ 257 | 258 | //============================================================================== 259 | /// @brief To get a double floating point value stored inside XML. 260 | /// 261 | /// @param[in] pRootNode TinyXML related note field 262 | /// @param[in] strTag XML identification tag 263 | /// @param[out] value The readed value from XML 264 | /// @return true if available and successfully done 265 | /// 266 | inline bool GetDouble(const TiXmlNode* pRootNode, const std::string& strTag, double& value) 267 | { 268 | const TiXmlNode* node = pRootNode->FirstChild(strTag); 269 | if (!node || !node->FirstChild()) 270 | return false; 271 | value = atof(node->FirstChild()->Value()); 272 | return true; 273 | } 274 | //------------------------------------------------------------------------------ 275 | 276 | //============================================================================== 277 | /// @brief To get a boolean value stored inside XML. 278 | /// 279 | /// @param[in] pRootNode TinyXML related note field 280 | /// @param[in] strTag XML identification tag 281 | /// @param[out] value The readed value from XML 282 | /// @return true if available and successfully done 283 | /// 284 | inline bool GetBoolean(const TiXmlNode* pRootNode, const std::string& strTag, bool& value) 285 | { 286 | const TiXmlNode* pNode = pRootNode->FirstChild(strTag); 287 | if (!pNode || !pNode->FirstChild()) 288 | return false; 289 | std::string strEnabled = pNode->FirstChild()->Value(); 290 | std::transform(strEnabled.begin(), strEnabled.end(), strEnabled.begin(), ::tolower); 291 | if (strEnabled == "off" || strEnabled == "no" || strEnabled == "disabled" || 292 | strEnabled == "false" || strEnabled == "0") 293 | value = false; 294 | else 295 | { 296 | value = true; 297 | if (strEnabled != "on" && strEnabled != "yes" && strEnabled != "enabled" && 298 | strEnabled != "true") 299 | return false; // invalid bool switch - it's probably some other string. 300 | } 301 | return true; 302 | } 303 | //------------------------------------------------------------------------------ 304 | 305 | //============================================================================== 306 | /// @brief To get a path value stored inside XML. 307 | /// 308 | /// @param[in] pRootNode TinyXML related note field 309 | /// @param[in] strTag XML identification tag 310 | /// @param[out] value The readed path value from XML 311 | /// @return true if available and successfully done 312 | /// 313 | inline bool GetPath(const TiXmlNode* pRootNode, const std::string& strTag, std::string& value) 314 | { 315 | const TiXmlElement* pElement = pRootNode->FirstChildElement(strTag); 316 | if (!pElement) 317 | return false; 318 | 319 | const TiXmlNode* pNode = pElement->FirstChild(); 320 | if (pNode != nullptr) 321 | { 322 | value = pNode->Value(); 323 | return true; 324 | } 325 | value.clear(); 326 | return false; 327 | } 328 | //------------------------------------------------------------------------------ 329 | 330 | //============================================================================== 331 | /// @brief To store a text string inside XML. 332 | /// 333 | /// @param[in] pRootNode TinyXML related note field 334 | /// @param[in] strTag XML identification tag 335 | /// @param[in] value Value to store 336 | /// 337 | inline void SetString(TiXmlNode* pRootNode, const std::string& strTag, const std::string& value) 338 | { 339 | TiXmlElement newElement(strTag); 340 | TiXmlNode* pNewNode = pRootNode->InsertEndChild(newElement); 341 | if (pNewNode) 342 | { 343 | TiXmlText xmlValue(value); 344 | pNewNode->InsertEndChild(xmlValue); 345 | } 346 | } 347 | //------------------------------------------------------------------------------ 348 | 349 | //============================================================================== 350 | /// @brief To store a list of strings inside XML. 351 | /// 352 | /// @param[in] pRootNode TinyXML related note field 353 | /// @param[in] strTag XML identification tag 354 | /// @param[in] arrayValue List with string values to store 355 | /// 356 | inline void SetStringArray(TiXmlNode* pRootNode, 357 | const std::string& strTag, 358 | const std::vector& arrayValue) 359 | { 360 | for (const auto& value : arrayValue) 361 | SetString(pRootNode, strTag, value); 362 | } 363 | //------------------------------------------------------------------------------ 364 | 365 | //============================================================================== 366 | /// @brief To store a HEX formated integer string inside XML. 367 | /// 368 | /// @param[in] pRootNode TinyXML related note field 369 | /// @param[in] strTag XML identification tag 370 | /// @param[in] value Value to store 371 | /// 372 | inline void SetHex(TiXmlNode* pRootNode, const std::string& strTag, uint32_t value) 373 | { 374 | size_t size = snprintf(nullptr, 0, "%x", value) + 1; 375 | std::unique_ptr strValue(new char[size]); 376 | snprintf(strValue.get(), size, "%x", value); 377 | SetString(pRootNode, strTag, std::string(strValue.get(), strValue.get() + size - 1)); 378 | } 379 | //------------------------------------------------------------------------------ 380 | 381 | //============================================================================== 382 | /// @brief To store a 32 bit unsigned integer value inside XML. 383 | /// 384 | /// @param[in] pRootNode TinyXML related note field 385 | /// @param[in] strTag XML identification tag 386 | /// @param[in] value Value to store 387 | /// 388 | inline void SetUInt(TiXmlNode* pRootNode, const std::string& strTag, uint32_t value) 389 | { 390 | SetString(pRootNode, strTag, std::to_string(value)); 391 | } 392 | //------------------------------------------------------------------------------ 393 | 394 | //============================================================================== 395 | /// @brief To store a 32 bit integer value inside XML. 396 | /// 397 | /// @param[in] pRootNode TinyXML related note field 398 | /// @param[in] strTag XML identification tag 399 | /// @param[in] value Value to store 400 | /// 401 | inline void SetInt(TiXmlNode* pRootNode, const std::string& strTag, int32_t value) 402 | { 403 | SetString(pRootNode, strTag, std::to_string(value)); 404 | } 405 | //------------------------------------------------------------------------------ 406 | 407 | //============================================================================== 408 | /// @brief To store a 64 bit integer value inside XML. 409 | /// 410 | /// @param[in] pRootNode TinyXML related note field 411 | /// @param[in] strTag XML identification tag 412 | /// @param[in] value Value to store 413 | /// 414 | inline void SetLong(TiXmlNode* pRootNode, const std::string& strTag, int64_t value) 415 | { 416 | SetString(pRootNode, strTag, std::to_string(value)); 417 | } 418 | //------------------------------------------------------------------------------ 419 | 420 | //============================================================================== 421 | /// @brief To store a floating point value inside XML. 422 | /// 423 | /// @param[in] pRootNode TinyXML related note field 424 | /// @param[in] strTag XML identification tag 425 | /// @param[in] value Value to store 426 | /// 427 | inline void SetFloat(TiXmlNode* pRootNode, const std::string& strTag, float value) 428 | { 429 | SetString(pRootNode, strTag, std::to_string(value)); 430 | } 431 | //------------------------------------------------------------------------------ 432 | 433 | //============================================================================== 434 | /// @brief To store a double floating point value inside XML. 435 | /// 436 | /// @param[in] pRootNode TinyXML related note field 437 | /// @param[in] strTag XML identification tag 438 | /// @param[in] value Value to store 439 | /// 440 | inline void SetDouble(TiXmlNode* pRootNode, const std::string& strTag, double value) 441 | { 442 | SetString(pRootNode, strTag, std::to_string(value)); 443 | } 444 | //------------------------------------------------------------------------------ 445 | 446 | //============================================================================== 447 | /// @brief To store a boolean value inside XML. 448 | /// 449 | /// By use of "true" or "false" as string in XML. 450 | /// 451 | /// @param[in] pRootNode TinyXML related note field 452 | /// @param[in] strTag XML identification tag 453 | /// @param[in] value Boolean value to store 454 | /// 455 | inline void SetBoolean(TiXmlNode* pRootNode, const std::string& strTag, bool value) 456 | { 457 | SetString(pRootNode, strTag, value ? "true" : "false"); 458 | } 459 | //------------------------------------------------------------------------------ 460 | 461 | //============================================================================== 462 | /// @brief To store a path value inside XML. 463 | /// 464 | /// @param[in] pRootNode TinyXML related note field 465 | /// @param[in] strTag XML identification tag 466 | /// @param[in] value Path to store 467 | /// 468 | inline void SetPath(TiXmlNode* pRootNode, const std::string& strTag, const std::string& value) 469 | { 470 | const int path_version = 1; 471 | TiXmlElement newElement(strTag); 472 | newElement.SetAttribute("pathversion", path_version); 473 | TiXmlNode* pNewNode = pRootNode->InsertEndChild(newElement); 474 | if (pNewNode) 475 | { 476 | TiXmlText xmlValue(value); 477 | pNewNode->InsertEndChild(xmlValue); 478 | } 479 | } 480 | //------------------------------------------------------------------------------ 481 | 482 | } /* namespace xml */ 483 | -------------------------------------------------------------------------------- /src/filtercoef.h: -------------------------------------------------------------------------------- 1 | // filtercoef.h: filte coefficients for various halfband filters 2 | // 3 | // History: 4 | // 2010-09-15 Initial creation MSW 5 | // 2011-03-27 Initial release 6 | ////////////////////////////////////////////////////////////////////// 7 | //========================================================================================== 8 | // + + + This Software is released under the "Simplified BSD License" + + + 9 | //Copyright 2010 Moe Wheatley. All rights reserved. 10 | // 11 | //Redistribution and use in source and binary forms, with or without modification, are 12 | //permitted provided that the following conditions are met: 13 | // 14 | // 1. Redistributions of source code must retain the above copyright notice, this list of 15 | // conditions and the following disclaimer. 16 | // 17 | // 2. Redistributions in binary form must reproduce the above copyright notice, this list 18 | // of conditions and the following disclaimer in the documentation and/or other materials 19 | // provided with the distribution. 20 | // 21 | //THIS SOFTWARE IS PROVIDED BY Moe Wheatley ``AS IS'' AND ANY EXPRESS OR IMPLIED 22 | //WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 23 | //FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Moe Wheatley OR 24 | //CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | //CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | //SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 27 | //ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28 | //NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 29 | //ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | // 31 | //The views and conclusions contained in the software and documentation are those of the 32 | //authors and should not be interpreted as representing official policies, either expressed 33 | //or implied, of Moe Wheatley. 34 | //============================================================================= 35 | #ifndef FILTERCOEF_H 36 | #define FILTERCOEF_H 37 | 38 | ////////////////////////////////////////////////////////////////////// 39 | // Filter -140dB Alias free normalized bandwidth constants for each type of filter 40 | // These values are used to determine what filter to use in the decimation 41 | // chains as a function of desired maximum output bandwidth 42 | ////////////////////////////////////////////////////////////////////// 43 | 44 | //normalized alias free bandwidths for the filters 45 | #define CIC3_MAX (.5 - .4985) 46 | #define HB11TAP_MAX (.5 - .475) 47 | #define HB15TAP_MAX (.5 - .451) 48 | #define HB19TAP_MAX (.5 - .428) 49 | #define HB23TAP_MAX (.5 - .409) 50 | #define HB27TAP_MAX (.5 - .392) 51 | #define HB31TAP_MAX (.5 - .378) 52 | #define HB35TAP_MAX (.5 - .366) 53 | #define HB39TAP_MAX (.5 - .356) 54 | #define HB43TAP_MAX (.5 - .347) 55 | #define HB47TAP_MAX (.5 - .340) 56 | #define HB51TAP_MAX (.5 - .333) 57 | 58 | //////////////////////////////////////////////////////////////////// 59 | // The following coefficients were designed with 60 | // MatLab for best alias rejection at -140dB 61 | //////////////////////////////////////////////////////////////////// 62 | #define HB11TAP_LENGTH 11 63 | const RealType HB11TAP_H[HB11TAP_LENGTH] = { 64 | 0.0060431029837374152, 0.0, -0.049372515458761493, 0.0, 0.29332944952052842, 0.5, 65 | 0.29332944952052842, 0.0, -0.049372515458761493, 0.0, 0.0060431029837374152, 66 | }; 67 | 68 | #define HB15TAP_LENGTH 15 69 | const RealType HB15TAP_H[HB15TAP_LENGTH] = {-0.001442203300285281, 0.0, 0.013017512802724852, 0.0, 70 | -0.061653278604903369, 0.0, 0.30007792316024057, 0.5, 71 | 0.30007792316024057, 0.0, -0.061653278604903369, 0.0, 72 | 0.013017512802724852, 0.0, -0.001442203300285281}; 73 | 74 | #define HB19TAP_LENGTH 19 75 | const RealType HB19TAP_H[HB19TAP_LENGTH] = { 76 | 0.00042366527106480427, 0.0, -0.0040717333369021894, 0.0, 0.019895653881950692, 0.0, 77 | -0.070740034412329067, 0.0, 0.30449249772844139, 0.5, 0.30449249772844139, 0.0, 78 | -0.070740034412329067, 0.0, 0.019895653881950692, 0.0, -0.0040717333369021894, 0.0, 79 | 0.00042366527106480427}; 80 | 81 | #define HB23TAP_LENGTH 23 82 | const RealType HB23TAP_H[HB23TAP_LENGTH] = { 83 | -0.00014987651418332164, 0.0, 0.0014748633283609852, 0.0, -0.0074416944990005314, 0.0, 84 | 0.026163522731980929, 0.0, -0.077593699116544707, 0.0, 0.30754683719791986, 0.5, 85 | 0.30754683719791986, 0.0, -0.077593699116544707, 0.0, 0.026163522731980929, 0.0, 86 | -0.0074416944990005314, 0.0, 0.0014748633283609852, 0.0, -0.00014987651418332164}; 87 | #define HB27TAP_LENGTH 27 88 | const RealType HB27TAP_H[HB27TAP_LENGTH] = { 89 | 0.000063730426952664685, 0.0, -0.00061985193978569082, 0.0, 0.0031512504783365756, 0.0, 90 | -0.011173151342856621, 0.0, 0.03171888754393197, 0.0, -0.082917863582770729, 0.0, 91 | 0.3097770473566307, 0.5, 0.3097770473566307, 0.0, -0.082917863582770729, 0.0, 92 | 0.03171888754393197, 0.0, -0.011173151342856621, 0.0, 0.0031512504783365756, 0.0, 93 | -0.00061985193978569082, 0.0, 0.000063730426952664685}; 94 | 95 | #define HB31TAP_LENGTH 31 96 | const RealType HB31TAP_H[HB31TAP_LENGTH] = { 97 | -0.000030957335326552226, 0.0, 0.00029271992847303054, 0.0, -0.0014770381124258423, 0.0, 98 | 0.0052539088990950535, 0.0, -0.014856378748476874, 0.0, 0.036406651919555999, 0.0, 99 | -0.08699862567952929, 0.0, 0.31140967076042625, 0.5, 0.31140967076042625, 0.0, 100 | -0.08699862567952929, 0.0, 0.036406651919555999, 0.0, -0.014856378748476874, 0.0, 101 | 0.0052539088990950535, 0.0, -0.0014770381124258423, 0.0, 0.00029271992847303054, 0.0, 102 | -0.000030957335326552226}; 103 | #define HB35TAP_LENGTH 35 104 | const RealType HB35TAP_H[HB35TAP_LENGTH] = { 105 | 0.000017017718072971716, 0.0, -0.00015425042851962818, 0.0, 0.00076219685751140838, 0.0, 106 | -0.002691614694785393, 0.0, 0.0075927497927344764, 0.0, -0.018325727896057686, 0.0, 107 | 0.040351004914363969, 0.0, -0.090198224668969554, 0.0, 0.31264689763504327, 0.5, 108 | 0.31264689763504327, 0.0, -0.090198224668969554, 0.0, 0.040351004914363969, 0.0, 109 | -0.018325727896057686, 0.0, 0.0075927497927344764, 0.0, -0.002691614694785393, 0.0, 110 | 0.00076219685751140838, 0.0, -0.00015425042851962818, 0.0, 0.000017017718072971716}; 111 | #define HB39TAP_LENGTH 39 112 | const RealType HB39TAP_H[HB39TAP_LENGTH] = { 113 | -0.000010175082832074367, 0.0, 0.000088036416015024345, 0.0, -0.00042370835558387595, 0.0, 114 | 0.0014772557414459019, 0.0, -0.0041468438954260153, 0.0, 0.0099579126901608011, 0.0, 115 | -0.021433527104289002, 0.0, 0.043598963493432855, 0.0, -0.092695953625928404, 0.0, 116 | 0.31358799113382152, 0.5, 0.31358799113382152, 0, -0.092695953625928404, 0.0, 117 | 0.043598963493432855, 0.0, -0.021433527104289002, 0.0, 0.0099579126901608011, 0.0, 118 | -0.0041468438954260153, 0.0, 0.0014772557414459019, 0.0, -0.00042370835558387595, 0.0, 119 | 0.000088036416015024345, 0.0, -0.000010175082832074367}; 120 | #define HB43TAP_LENGTH 43 121 | const RealType HB43TAP_H[HB43TAP_LENGTH] = { 122 | 0.0000067666739082756387, 0.0, -0.000055275221547958285, 0.0, 0.00025654074579418561, 0.0, 123 | -0.0008748125689163153, 0.0, 0.0024249876017061502, 0.0, -0.0057775190656021748, 0.0, 124 | 0.012299834239523121, 0.0, -0.024244050662087069, 0.0, 0.046354303503099069, 0.0, 125 | -0.094729903598633314, 0.0, 0.31433918020123208, 0.5, 0.31433918020123208, 0.0, 126 | -0.094729903598633314, 0.0, 0.046354303503099069, 0.0, -0.024244050662087069, 0.0, 127 | 0.012299834239523121, 0.0, -0.0057775190656021748, 0.0, 0.0024249876017061502, 0.0, 128 | -0.0008748125689163153, 0.0, 0.00025654074579418561, 0.0, -0.000055275221547958285, 0.0, 129 | 0.0000067666739082756387}; 130 | #define HB47TAP_LENGTH 47 131 | const RealType HB47TAP_H[HB47TAP_LENGTH] = { 132 | -0.0000045298314172004251, 0.0, 0.000035333704512843228, 0.0, -0.00015934776420643447, 0.0, 133 | 0.0005340788063118928, 0.0, -0.0014667949695500761, 0.0, 0.0034792089350833247, 0.0, 134 | -0.0073794356720317733, 0.0, 0.014393786384683398, 0.0, -0.026586603160193314, 0.0, 135 | 0.048538673667907428, 0.0, -0.09629115286535718, 0.0, 0.31490673428547367, 0.5, 136 | 0.31490673428547367, 0.0, -0.09629115286535718, 0.0, 0.048538673667907428, 0.0, 137 | -0.026586603160193314, 0.0, 0.014393786384683398, 0.0, -0.0073794356720317733, 0.0, 138 | 0.0034792089350833247, 0.0, -0.0014667949695500761, 0.0, 0.0005340788063118928, 0.0, 139 | -0.00015934776420643447, 0.0, 0.000035333704512843228, 0.0, -0.0000045298314172004251}; 140 | #define HB51TAP_LENGTH 51 141 | const RealType HB51TAP_H[HB51TAP_LENGTH] = { 142 | 0.0000033359253688981639, 0.0, -0.000024584155158361803, 0.0, 0.00010677777483317733, 0.0, 143 | -0.00034890723143173914, 0.0, 0.00094239127078189603, 0.0, -0.0022118302078923137, 0.0, 144 | 0.0046575030752162277, 0.0, -0.0090130973415220566, 0.0, 0.016383673864361164, 0.0, 145 | -0.028697281101743237, 0.0, 0.05043292242400841, 0.0, -0.097611898315791965, 0.0, 146 | 0.31538104435015801, 0.5, 0.31538104435015801, 0.0, -0.097611898315791965, 0.0, 147 | 0.05043292242400841, 0.0, -0.028697281101743237, 0.0, 0.016383673864361164, 0.0, 148 | -0.0090130973415220566, 0.0, 0.0046575030752162277, 0.0, -0.0022118302078923137, 0.0, 149 | 0.00094239127078189603, 0.0, -0.00034890723143173914, 0.0, 0.00010677777483317733, 0.0, 150 | -0.000024584155158361803, 0.0, 0.0000033359253688981639}; 151 | 152 | #endif // FILTERCOEF_H 153 | --------------------------------------------------------------------------------