├── .github └── workflows │ └── build.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake ├── Modules │ ├── FindFFTW.cmake │ └── FindLiquid.cmake └── cmake_uninstall.cmake.in ├── screenshot.jpg └── src ├── CMakeLists.txt ├── abstractsamplesource.cpp ├── abstractsamplesource.h ├── amplitudedemod.cpp ├── amplitudedemod.h ├── cursor.cpp ├── cursor.h ├── cursors.cpp ├── cursors.h ├── fft.cpp ├── fft.h ├── frequencydemod.cpp ├── frequencydemod.h ├── inputsource.cpp ├── inputsource.h ├── main.cpp ├── mainwindow.cpp ├── mainwindow.h ├── phasedemod.cpp ├── phasedemod.h ├── plot.cpp ├── plot.h ├── plots.cpp ├── plots.h ├── plotview.cpp ├── plotview.h ├── samplebuffer.cpp ├── samplebuffer.h ├── samplesource.cpp ├── samplesource.h ├── spectrogramcontrols.cpp ├── spectrogramcontrols.h ├── spectrogramplot.cpp ├── spectrogramplot.h ├── subscriber.h ├── threshold.cpp ├── threshold.h ├── traceplot.cpp ├── traceplot.h ├── tuner.cpp ├── tuner.h ├── tunertransform.cpp ├── tunertransform.h ├── util.cpp └── util.h /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | # Run every friday 8 | schedule: 9 | - cron: 1 12 * * 5 10 | 11 | env: 12 | BUILD_TYPE: Release 13 | # For macOS qt keg-only package 14 | CMAKE_PREFIX_PATH: '/opt/homebrew/opt/qt@5' 15 | 16 | jobs: 17 | build: 18 | strategy: 19 | matrix: 20 | os: ['macos-latest', 'ubuntu-22.04', 'ubuntu-20.04'] 21 | runs-on: ${{ matrix.os }} 22 | 23 | steps: 24 | - uses: actions/checkout@v2 25 | 26 | - name: Install dependencies (macOS) 27 | run: brew install fftw liquid-dsp qt@5 28 | if: matrix.os == 'macos-latest' 29 | 30 | - name: Install dependencies (Ubuntu) 31 | run: | 32 | sudo apt update 33 | sudo apt install libfftw3-dev libliquid-dev qtbase5-dev 34 | if: startsWith(matrix.os, 'ubuntu-') 35 | 36 | - name: Create Build Environment 37 | run: cmake -E make_directory ${{runner.workspace}}/build 38 | 39 | - name: Configure CMake 40 | shell: bash 41 | working-directory: ${{runner.workspace}}/build 42 | run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE 43 | 44 | - name: Build 45 | working-directory: ${{runner.workspace}}/build 46 | shell: bash 47 | run: cmake --build . --config $BUILD_TYPE 48 | 49 | - name: Test 50 | working-directory: ${{runner.workspace}}/build 51 | shell: bash 52 | run: ctest -C $BUILD_TYPE 53 | 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build outputs 2 | build/ 3 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1) 2 | project(inspectrum CXX) 3 | enable_testing() 4 | 5 | add_subdirectory(src) 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | {one line to give the program's name and a brief idea of what it does.} 635 | Copyright (C) {year} {name of author} 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | {project} Copyright (C) {year} {fullname} 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # inspectrum 2 | inspectrum is a tool for analysing captured signals, primarily from software-defined radio receivers. 3 | 4 | ![inspectrum screenshot](/screenshot.jpg) 5 | 6 | ## Features 7 | * Large (100GB+) file support 8 | * Spectrogram with zoom/pan 9 | * Plots of amplitude, frequency, phase and IQ samples 10 | * Cursors for measuring period, symbol rate and extracting symbols 11 | * Export of selected time period, filtered samples and demodulated data 12 | 13 | ## Install 14 | ### Linux 15 | Install inspectrum with your package manager, it should be present in most distros. 16 | 17 | ### macOS 18 | * [Homebrew](https://formulae.brew.sh/formula/inspectrum) 19 | * [MacPorts](https://ports.macports.org/port/inspectrum/) 20 | 21 | ## Windows 22 | * [radioconda](https://github.com/ryanvolz/radioconda) 23 | * [conda](https://anaconda.org/conda-forge/inspectrum) 24 | 25 | ## Build from source 26 | ### Prerequisites 27 | 28 | * cmake >= 3.1 29 | * fftw 3.x 30 | * [liquid-dsp](https://github.com/jgaeddert/liquid-dsp) >= v1.3.0 31 | * pkg-config 32 | * qt5 33 | 34 | ### Build instructions 35 | 36 | Build instructions can be found here: https://github.com/miek/inspectrum/wiki/Build 37 | 38 | ### Run 39 | 40 | ./inspectrum [filename] 41 | 42 | ## Input 43 | inspectrum supports the following file types: 44 | * `*.sigmf-meta, *.sigmf-data` - SigMF recordings 45 | * `*.cf32`, `*.fc32`, `*.cfile` - Complex 32-bit floating point samples (GNU Radio, osmocom_fft) 46 | * `*.cf64`, `*.fc64` - Complex 64-bit floating point samples 47 | * `*.cs32`, `*.sc32`, `*.c32` - Complex 32-bit signed integer samples (SDRAngel) 48 | * `*.cs16`, `*.sc16`, `*.c16` - Complex 16-bit signed integer samples (BladeRF) 49 | * `*.cs8`, `*.sc8`, `*.c8` - Complex 8-bit signed integer samples (HackRF) 50 | * `*.cu8`, `*.uc8` - Complex 8-bit unsigned integer samples (RTL-SDR) 51 | * `*.f32` - Real 32-bit floating point samples 52 | * `*.f64` - Real 64-bit floating point samples (MATLAB) 53 | * `*.s16` - Real 16-bit signed integer samples 54 | * `*.s8` - Real 8-bit signed integer samples 55 | * `*.u8` - Real 8-bit unsigned integer samples 56 | 57 | If an unknown file extension is loaded, inspectrum will default to `*.cf32`. 58 | 59 | Note: 64-bit samples will be truncated to 32-bit before processing, as inspectrum only supports 32-bit internally. 60 | -------------------------------------------------------------------------------- /cmake/Modules/FindFFTW.cmake: -------------------------------------------------------------------------------- 1 | # - Find FFTW 2 | # Find the native FFTW includes and library 3 | # 4 | # FFTW_INCLUDES - where to find fftw3.h 5 | # FFTW_LIBRARIES - List of libraries when using FFTW. 6 | # FFTW_FOUND - True if FFTW found. 7 | 8 | if (FFTW_INCLUDES) 9 | # Already in cache, be silent 10 | set (FFTW_FIND_QUIETLY TRUE) 11 | endif (FFTW_INCLUDES) 12 | 13 | find_package(PkgConfig) 14 | pkg_check_modules(PC_FFTW QUIET fftw3f) 15 | 16 | find_path (FFTW_INCLUDES fftw3.h 17 | HINTS ${PC_FFTW_INCLUDEDIR} ${PC_FFTW_INCLUDE_DIRS}) 18 | 19 | find_library (FFTW_LIBRARIES NAMES fftw3f 20 | HINTS ${PC_FFTW_LIBDIR} ${PC_FFTW_LIBRARY_DIRS}) 21 | 22 | # handle the QUIETLY and REQUIRED arguments and set FFTW_FOUND to TRUE if 23 | # all listed variables are TRUE 24 | include (FindPackageHandleStandardArgs) 25 | find_package_handle_standard_args (FFTW DEFAULT_MSG FFTW_LIBRARIES FFTW_INCLUDES) 26 | 27 | mark_as_advanced (FFTW_LIBRARIES FFTW_INCLUDES) 28 | -------------------------------------------------------------------------------- /cmake/Modules/FindLiquid.cmake: -------------------------------------------------------------------------------- 1 | # - Find LIQUID 2 | # Find the native LIQUID includes and library 3 | # 4 | # LIQUID_INCLUDES - where to find LIQUID.h 5 | # LIQUID_LIBRARIES - List of libraries when using LIQUID. 6 | # LIQUID_FOUND - True if LIQUID found. 7 | 8 | if (LIQUID_INCLUDES) 9 | # Already in cache, be silent 10 | set (LIQUID_FIND_QUIETLY TRUE) 11 | endif (LIQUID_INCLUDES) 12 | 13 | find_path (LIQUID_INCLUDES liquid/liquid.h) 14 | 15 | find_library (LIQUID_LIBRARIES NAMES liquid) 16 | 17 | # handle the QUIETLY and REQUIRED arguments and set LIQUID_FOUND to TRUE if 18 | # all listed variables are TRUE 19 | include (FindPackageHandleStandardArgs) 20 | find_package_handle_standard_args (LIQUID DEFAULT_MSG LIQUID_LIBRARIES LIQUID_INCLUDES) 21 | 22 | #mark_as_advanced (LIQUID_LIBRARIES LIQUID_INCLUDES) 23 | -------------------------------------------------------------------------------- /cmake/cmake_uninstall.cmake.in: -------------------------------------------------------------------------------- 1 | # http://www.vtk.org/Wiki/CMake_FAQ#Can_I_do_.22make_uninstall.22_with_CMake.3F 2 | 3 | IF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") 4 | MESSAGE(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"") 5 | ENDIF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") 6 | 7 | FILE(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) 8 | STRING(REGEX REPLACE "\n" ";" files "${files}") 9 | FOREACH(file ${files}) 10 | MESSAGE(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"") 11 | IF(EXISTS "$ENV{DESTDIR}${file}") 12 | EXEC_PROGRAM( 13 | "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" 14 | OUTPUT_VARIABLE rm_out 15 | RETURN_VALUE rm_retval 16 | ) 17 | IF(NOT "${rm_retval}" STREQUAL 0) 18 | MESSAGE(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"") 19 | ENDIF(NOT "${rm_retval}" STREQUAL 0) 20 | ELSEIF(IS_SYMLINK "$ENV{DESTDIR}${file}") 21 | EXEC_PROGRAM( 22 | "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" 23 | OUTPUT_VARIABLE rm_out 24 | RETURN_VALUE rm_retval 25 | ) 26 | IF(NOT "${rm_retval}" STREQUAL 0) 27 | MESSAGE(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"") 28 | ENDIF(NOT "${rm_retval}" STREQUAL 0) 29 | ELSE(EXISTS "$ENV{DESTDIR}${file}") 30 | MESSAGE(STATUS "File \"$ENV{DESTDIR}${file}\" does not exist.") 31 | ENDIF(EXISTS "$ENV{DESTDIR}${file}") 32 | ENDFOREACH(file) 33 | -------------------------------------------------------------------------------- /screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miek/inspectrum/c6f4ecf6f74ea4dd33742feadff6e63f492b6e45/screenshot.jpg -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_AUTOMOC ON) 2 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 3 | list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/Modules) 4 | 5 | # For OSX - don't clear RPATH on install 6 | set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) 7 | 8 | if (MSVC) 9 | #force std::complex<> typedefs in liquiddsp 10 | add_definitions(-D_LIBCPP_COMPLEX) 11 | 12 | #enable math definitions in math.h 13 | add_definitions(-D_USE_MATH_DEFINES) 14 | 15 | #build a graphical application without the console 16 | option(BUILD_WIN32 "Build win32 app, false for console" TRUE) 17 | if (BUILD_WIN32) 18 | set(EXE_ARGS WIN32) 19 | set(CMAKE_EXE_LINKER_FLAGS "/entry:mainCRTStartup ${CMAKE_EXE_LINKER_FLAGS}") 20 | endif (BUILD_WIN32) 21 | endif (MSVC) 22 | 23 | if (NOT CMAKE_CXX_FLAGS) 24 | set(CMAKE_CXX_FLAGS "-O2") 25 | endif (NOT CMAKE_CXX_FLAGS) 26 | 27 | # This only works in cmake >3.1 28 | set(CMAKE_CXX_STANDARD 14) 29 | 30 | list(APPEND inspectrum_sources 31 | abstractsamplesource.cpp 32 | amplitudedemod.cpp 33 | cursor.cpp 34 | cursors.cpp 35 | main.cpp 36 | fft.cpp 37 | frequencydemod.cpp 38 | mainwindow.cpp 39 | inputsource.cpp 40 | phasedemod.cpp 41 | plot.cpp 42 | plots.cpp 43 | plotview.cpp 44 | samplebuffer.cpp 45 | samplesource.cpp 46 | spectrogramcontrols.cpp 47 | spectrogramplot.cpp 48 | threshold.cpp 49 | traceplot.cpp 50 | tuner.cpp 51 | tunertransform.cpp 52 | util.cpp 53 | ) 54 | 55 | find_package(Qt5Widgets REQUIRED) 56 | find_package(Qt5Concurrent REQUIRED) 57 | find_package(FFTW REQUIRED) 58 | find_package(Liquid REQUIRED) 59 | 60 | include_directories( 61 | ${FFTW_INCLUDES} 62 | ${LIQUID_INCLUDES} 63 | ) 64 | 65 | add_executable(inspectrum ${EXE_ARGS} ${inspectrum_sources}) 66 | 67 | target_link_libraries(inspectrum 68 | Qt5::Core Qt5::Widgets Qt5::Concurrent 69 | ${FFTW_LIBRARIES} 70 | ${LIQUID_LIBRARIES} 71 | ) 72 | 73 | set(INSTALL_DEFAULT_BINDIR "bin" CACHE STRING "Appended to CMAKE_INSTALL_PREFIX") 74 | 75 | install(TARGETS inspectrum RUNTIME DESTINATION ${INSTALL_DEFAULT_BINDIR}) 76 | 77 | # Create uninstall target 78 | configure_file( 79 | ${PROJECT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in 80 | ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake 81 | @ONLY) 82 | 83 | add_custom_target(uninstall 84 | ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake 85 | ) 86 | -------------------------------------------------------------------------------- /src/abstractsamplesource.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "abstractsamplesource.h" 21 | 22 | void AbstractSampleSource::subscribe(Subscriber *subscriber) 23 | { 24 | subscribers.insert(subscriber); 25 | } 26 | 27 | void AbstractSampleSource::invalidate() 28 | { 29 | for (auto subscriber : subscribers) { 30 | subscriber->invalidateEvent(); 31 | } 32 | } 33 | 34 | int AbstractSampleSource::subscriberCount() 35 | { 36 | return subscribers.size(); 37 | } 38 | 39 | void AbstractSampleSource::unsubscribe(Subscriber *subscriber) 40 | { 41 | subscribers.erase(subscriber); 42 | } -------------------------------------------------------------------------------- /src/abstractsamplesource.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include "subscriber.h" 27 | 28 | class AbstractSampleSource 29 | { 30 | 31 | public: 32 | virtual ~AbstractSampleSource() {}; 33 | virtual std::type_index sampleType() = 0; 34 | void subscribe(Subscriber *subscriber); 35 | int subscriberCount(); 36 | void unsubscribe(Subscriber *subscriber); 37 | 38 | protected: 39 | virtual void invalidate(); 40 | 41 | private: 42 | std::set subscribers; 43 | }; 44 | -------------------------------------------------------------------------------- /src/amplitudedemod.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "amplitudedemod.h" 21 | 22 | AmplitudeDemod::AmplitudeDemod(std::shared_ptr>> src) : SampleBuffer(src) 23 | { 24 | 25 | } 26 | 27 | void AmplitudeDemod::work(void *input, void *output, int count, size_t sampleid) 28 | { 29 | auto in = static_cast*>(input); 30 | auto out = static_cast(output); 31 | std::transform(in, in + count, out, 32 | [](std::complex s) { return std::norm(s) * 2.0f - 1.0f; }); 33 | } 34 | -------------------------------------------------------------------------------- /src/amplitudedemod.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include "samplebuffer.h" 23 | 24 | class AmplitudeDemod : public SampleBuffer, float> 25 | { 26 | public: 27 | AmplitudeDemod(std::shared_ptr>> src); 28 | void work(void *input, void *output, int count, size_t sampleid) override; 29 | }; 30 | -------------------------------------------------------------------------------- /src/cursor.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include 21 | #include "cursor.h" 22 | 23 | Cursor::Cursor(Qt::Orientation orientation, Qt::CursorShape mouseCursorShape, QObject * parent) : QObject::QObject(parent), orientation(orientation), cursorShape(mouseCursorShape) 24 | { 25 | 26 | } 27 | 28 | int Cursor::fromPoint(QPoint point) 29 | { 30 | return (orientation == Qt::Vertical) ? point.x() : point.y(); 31 | } 32 | 33 | bool Cursor::pointOverCursor(QPoint point) 34 | { 35 | const int margin = 5; 36 | range_t range = {cursorPosition - margin, cursorPosition + margin}; 37 | return range.contains(fromPoint(point)); 38 | } 39 | 40 | bool Cursor::mouseEvent(QEvent::Type type, QMouseEvent event) 41 | { 42 | // If the mouse pointer moves over a cursor, display a resize pointer 43 | if (pointOverCursor(event.pos()) && type != QEvent::Leave) { 44 | if (!cursorOverrided) { 45 | cursorOverrided = true; 46 | QApplication::setOverrideCursor(QCursor(cursorShape)); 47 | } 48 | // Restore pointer if it moves off the cursor, or leaves the widget 49 | } else if (cursorOverrided) { 50 | cursorOverrided = false; 51 | QApplication::restoreOverrideCursor(); 52 | } 53 | 54 | // Start dragging on left mouse button press, if over a cursor 55 | if (type == QEvent::MouseButtonPress) { 56 | if (event.button() == Qt::LeftButton) { 57 | if (pointOverCursor(event.pos())) { 58 | dragging = true; 59 | return true; 60 | } 61 | } 62 | 63 | // Update current cursor position if we're dragging 64 | } else if (type == QEvent::MouseMove) { 65 | if (dragging) { 66 | cursorPosition = fromPoint(event.pos()); 67 | emit posChanged(); 68 | } 69 | 70 | // Stop dragging on left mouse button release 71 | } else if (type == QEvent::MouseButtonRelease) { 72 | if (event.button() == Qt::LeftButton && dragging) { 73 | dragging = false; 74 | return true; 75 | } 76 | } 77 | return false; 78 | } 79 | 80 | int Cursor::pos() 81 | { 82 | return cursorPosition; 83 | } 84 | 85 | void Cursor::setPos(int newPos) 86 | { 87 | cursorPosition = newPos; 88 | } 89 | -------------------------------------------------------------------------------- /src/cursor.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | #include "util.h" 26 | 27 | class Cursor : public QObject 28 | { 29 | Q_OBJECT 30 | 31 | public: 32 | Cursor(Qt::Orientation orientation, Qt::CursorShape mouseCursorShape, QObject * parent); 33 | int pos(); 34 | void setPos(int newPos); 35 | bool mouseEvent(QEvent::Type type, QMouseEvent event); 36 | 37 | signals: 38 | void posChanged(); 39 | 40 | private: 41 | int fromPoint(QPoint point); 42 | bool pointOverCursor(QPoint point); 43 | 44 | Qt::Orientation orientation; 45 | Qt::CursorShape cursorShape; 46 | bool dragging = false; 47 | bool cursorOverrided = false; 48 | int cursorPosition = 0; 49 | }; 50 | -------------------------------------------------------------------------------- /src/cursors.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include 21 | #include 22 | #include "cursors.h" 23 | 24 | Cursors::Cursors(QObject * parent) : QObject::QObject(parent) 25 | { 26 | minCursor = new Cursor(Qt::Vertical, Qt::SizeHorCursor, this); 27 | maxCursor = new Cursor(Qt::Vertical, Qt::SizeHorCursor, this); 28 | connect(minCursor, &Cursor::posChanged, this, &Cursors::cursorMoved); 29 | connect(maxCursor, &Cursor::posChanged, this, &Cursors::cursorMoved); 30 | } 31 | 32 | void Cursors::cursorMoved() 33 | { 34 | // Swap cursors if one has been dragged past the other 35 | if (minCursor->pos() > maxCursor->pos()) { 36 | std::swap(minCursor, maxCursor); 37 | } 38 | emit cursorsMoved(); 39 | } 40 | 41 | bool Cursors::pointWithinDragRegion(QPoint point) { 42 | int margin = 10; 43 | range_t range = {minCursor->pos()+margin, maxCursor->pos()-margin}; 44 | return range.contains(point.x()); 45 | } 46 | 47 | bool Cursors::mouseEvent(QEvent::Type type, QMouseEvent event) 48 | { 49 | if (minCursor->mouseEvent(type, event)) 50 | return true; 51 | if (maxCursor->mouseEvent(type, event)) 52 | return true; 53 | 54 | // If the mouse pointer is between the cursors, display a resize pointer 55 | if (pointWithinDragRegion(event.pos()) && type != QEvent::Leave) { 56 | if (!cursorOverride) { 57 | cursorOverride = true; 58 | QApplication::setOverrideCursor(QCursor(Qt::SizeAllCursor)); 59 | } 60 | // Restore pointer otherwise 61 | } else { 62 | if (cursorOverride) { 63 | cursorOverride = false; 64 | QApplication::restoreOverrideCursor(); 65 | } 66 | } 67 | // Start dragging on left mouse button press, if between the cursors 68 | if (type == QEvent::MouseButtonPress) { 69 | if (event.button() == Qt::LeftButton) { 70 | if (pointWithinDragRegion(event.pos())) { 71 | dragging = true; 72 | dragPos = event.pos(); 73 | return true; 74 | } 75 | } 76 | // Update both cursor positons if we're dragging 77 | } else if (type == QEvent::MouseMove) { 78 | if (dragging) { 79 | int dx = event.pos().x() - dragPos.x(); 80 | minCursor->setPos(minCursor->pos() + dx); 81 | maxCursor->setPos(maxCursor->pos() + dx); 82 | dragPos = event.pos(); 83 | emit cursorsMoved(); 84 | } 85 | // Stop dragging on left mouse button release 86 | } else if (type == QEvent::MouseButtonRelease) { 87 | if (event.button() == Qt::LeftButton && dragging) { 88 | dragging = false; 89 | return true; 90 | } 91 | } 92 | return false; 93 | } 94 | 95 | void Cursors::paintFront(QPainter &painter, QRect &rect, range_t sampleRange) 96 | { 97 | painter.save(); 98 | 99 | QRect cursorRect(minCursor->pos(), rect.top(), maxCursor->pos() - minCursor->pos(), rect.height()); 100 | 101 | // Draw translucent white fill for highlight 102 | painter.fillRect( 103 | cursorRect, 104 | QBrush(QColor(255, 255, 255, 50)) 105 | ); 106 | 107 | // Draw vertical edges for individual segments 108 | painter.setPen(QPen(Qt::gray, 1, Qt::DashLine)); 109 | for (long i = 1; i < segmentCount; i++) { 110 | int pos = minCursor->pos() + (i * cursorRect.width() / segmentCount); 111 | painter.drawLine(pos, rect.top(), pos, rect.bottom()); 112 | } 113 | 114 | // Draw vertical edges 115 | painter.setPen(QPen(Qt::white, 1, Qt::SolidLine)); 116 | painter.drawLine(minCursor->pos(), rect.top(), minCursor->pos(), rect.bottom()); 117 | painter.drawLine(maxCursor->pos(), rect.top(), maxCursor->pos(), rect.bottom()); 118 | 119 | painter.restore(); 120 | } 121 | 122 | int Cursors::segments() 123 | { 124 | return segmentCount; 125 | } 126 | 127 | range_t Cursors::selection() 128 | { 129 | return {minCursor->pos(), maxCursor->pos()}; 130 | } 131 | 132 | void Cursors::setSegments(int segments) 133 | { 134 | segmentCount = std::max(segments, 1); 135 | } 136 | 137 | void Cursors::setSelection(range_t selection) 138 | { 139 | minCursor->setPos(selection.minimum); 140 | maxCursor->setPos(selection.maximum); 141 | } 142 | -------------------------------------------------------------------------------- /src/cursors.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include "cursor.h" 27 | #include "util.h" 28 | 29 | class Cursors : public QObject 30 | { 31 | Q_OBJECT 32 | 33 | public: 34 | Cursors(QObject * parent); 35 | int segments(); 36 | bool mouseEvent(QEvent::Type type, QMouseEvent event); 37 | void paintFront(QPainter &painter, QRect &rect, range_t sampleRange); 38 | range_t selection(); 39 | void setSegments(int segments); 40 | void setSelection(range_t selection); 41 | 42 | public slots: 43 | void cursorMoved(); 44 | 45 | signals: 46 | void cursorsMoved(); 47 | 48 | private: 49 | bool pointWithinDragRegion(QPoint point); 50 | 51 | Cursor *minCursor; 52 | Cursor *maxCursor; 53 | int segmentCount = 1; 54 | 55 | QPoint dragPos; // keep track of dragging distance 56 | bool cursorOverride = false; // used to record if cursor is overridden 57 | bool dragging = false; // record if mouse is dragging 58 | }; 59 | -------------------------------------------------------------------------------- /src/fft.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "fft.h" 21 | #include "string.h" 22 | 23 | FFT::FFT(int size) 24 | { 25 | fftSize = size; 26 | 27 | fftwIn = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex) * fftSize); 28 | fftwOut = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex) * fftSize); 29 | fftwPlan = fftwf_plan_dft_1d(fftSize, fftwIn, fftwOut, FFTW_FORWARD, FFTW_MEASURE); 30 | } 31 | 32 | FFT::~FFT() 33 | { 34 | if (fftwPlan) fftwf_destroy_plan(fftwPlan); 35 | if (fftwIn) fftwf_free(fftwIn); 36 | if (fftwOut) fftwf_free(fftwOut); 37 | } 38 | 39 | void FFT::process(void *dest, void *source) 40 | { 41 | memcpy(fftwIn, source, fftSize * sizeof(fftwf_complex)); 42 | fftwf_execute(fftwPlan); 43 | memcpy(dest, fftwOut, fftSize * sizeof(fftwf_complex)); 44 | } 45 | -------------------------------------------------------------------------------- /src/fft.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | 24 | class FFT 25 | { 26 | public: 27 | FFT(int size); 28 | ~FFT(); 29 | void process(void *dest, void *source); 30 | int getSize() { 31 | return fftSize; 32 | } 33 | 34 | private: 35 | int fftSize; 36 | fftwf_complex *fftwIn = nullptr; 37 | fftwf_complex *fftwOut = nullptr; 38 | fftwf_plan fftwPlan = nullptr; 39 | }; 40 | -------------------------------------------------------------------------------- /src/frequencydemod.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "frequencydemod.h" 21 | #include 22 | #include "util.h" 23 | 24 | FrequencyDemod::FrequencyDemod(std::shared_ptr>> src) : SampleBuffer(src) 25 | { 26 | 27 | } 28 | 29 | void FrequencyDemod::work(void *input, void *output, int count, size_t sampleid) 30 | { 31 | auto in = static_cast*>(input); 32 | auto out = static_cast(output); 33 | freqdem fdem = freqdem_create(relativeBandwidth() / 2.0); 34 | for (int i = 0; i < count; i++) { 35 | freqdem_demodulate(fdem, in[i], &out[i]); 36 | } 37 | freqdem_destroy(fdem); 38 | } 39 | -------------------------------------------------------------------------------- /src/frequencydemod.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include "samplebuffer.h" 23 | 24 | class FrequencyDemod : public SampleBuffer, float> 25 | { 26 | public: 27 | FrequencyDemod(std::shared_ptr>> src); 28 | void work(void *input, void *output, int count, size_t sampleid) override; 29 | }; 30 | -------------------------------------------------------------------------------- /src/inputsource.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, Mike Walters 3 | * Copyright (C) 2015, Jared Boone 4 | * 5 | * This file is part of inspectrum. 6 | * 7 | * This program 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 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program 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 | 21 | #include "inputsource.h" 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | 30 | #include 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | 43 | class ComplexF32SampleAdapter : public SampleAdapter { 44 | public: 45 | size_t sampleSize() override { 46 | return sizeof(std::complex); 47 | } 48 | 49 | void copyRange(const void* const src, size_t start, size_t length, std::complex* const dest) override { 50 | auto s = reinterpret_cast*>(src); 51 | std::copy(&s[start], &s[start + length], dest); 52 | } 53 | }; 54 | 55 | class ComplexF64SampleAdapter : public SampleAdapter { 56 | public: 57 | size_t sampleSize() override { 58 | return sizeof(std::complex); 59 | } 60 | 61 | void copyRange(const void* const src, size_t start, size_t length, std::complex* const dest) override { 62 | auto s = reinterpret_cast*>(src); 63 | std::transform(&s[start], &s[start + length], dest, 64 | [](const std::complex& v) -> std::complex { 65 | return { static_cast(v.real()) , static_cast(v.imag()) }; 66 | } 67 | ); 68 | } 69 | }; 70 | 71 | class ComplexS32SampleAdapter : public SampleAdapter { 72 | public: 73 | size_t sampleSize() override { 74 | return sizeof(std::complex); 75 | } 76 | 77 | void copyRange(const void* const src, size_t start, size_t length, std::complex* const dest) override { 78 | auto s = reinterpret_cast*>(src); 79 | std::transform(&s[start], &s[start + length], dest, 80 | [](const std::complex& v) -> std::complex { 81 | const float k = 1.0f / 2147483648.0f; 82 | return { v.real() * k, v.imag() * k }; 83 | } 84 | ); 85 | } 86 | }; 87 | 88 | class ComplexS16SampleAdapter : public SampleAdapter { 89 | public: 90 | size_t sampleSize() override { 91 | return sizeof(std::complex); 92 | } 93 | 94 | void copyRange(const void* const src, size_t start, size_t length, std::complex* const dest) override { 95 | auto s = reinterpret_cast*>(src); 96 | std::transform(&s[start], &s[start + length], dest, 97 | [](const std::complex& v) -> std::complex { 98 | const float k = 1.0f / 32768.0f; 99 | return { v.real() * k, v.imag() * k }; 100 | } 101 | ); 102 | } 103 | }; 104 | 105 | class ComplexS8SampleAdapter : public SampleAdapter { 106 | public: 107 | size_t sampleSize() override { 108 | return sizeof(std::complex); 109 | } 110 | 111 | void copyRange(const void* const src, size_t start, size_t length, std::complex* const dest) override { 112 | auto s = reinterpret_cast*>(src); 113 | std::transform(&s[start], &s[start + length], dest, 114 | [](const std::complex& v) -> std::complex { 115 | const float k = 1.0f / 128.0f; 116 | return { v.real() * k, v.imag() * k }; 117 | } 118 | ); 119 | } 120 | }; 121 | 122 | class ComplexU8SampleAdapter : public SampleAdapter { 123 | public: 124 | size_t sampleSize() override { 125 | return sizeof(std::complex); 126 | } 127 | 128 | void copyRange(const void* const src, size_t start, size_t length, std::complex* const dest) override { 129 | auto s = reinterpret_cast*>(src); 130 | std::transform(&s[start], &s[start + length], dest, 131 | [](const std::complex& v) -> std::complex { 132 | const float k = 1.0f / 128.0f; 133 | return { (v.real() - 127.4f) * k, (v.imag() - 127.4f) * k }; 134 | } 135 | ); 136 | } 137 | }; 138 | 139 | class RealF32SampleAdapter : public SampleAdapter { 140 | public: 141 | size_t sampleSize() override { 142 | return sizeof(float); 143 | } 144 | 145 | void copyRange(const void* const src, size_t start, size_t length, std::complex* const dest) override { 146 | auto s = reinterpret_cast(src); 147 | std::transform(&s[start], &s[start + length], dest, 148 | [](const float& v) -> std::complex { 149 | return {v, 0.0f}; 150 | } 151 | ); 152 | } 153 | }; 154 | 155 | class RealF64SampleAdapter : public SampleAdapter { 156 | public: 157 | size_t sampleSize() override { 158 | return sizeof(double); 159 | } 160 | 161 | void copyRange(const void* const src, size_t start, size_t length, std::complex* const dest) override { 162 | auto s = reinterpret_cast(src); 163 | std::transform(&s[start], &s[start + length], dest, 164 | [](const double& v) -> std::complex { 165 | return {static_cast(v), 0.0f}; 166 | } 167 | ); 168 | } 169 | }; 170 | 171 | class RealS16SampleAdapter : public SampleAdapter { 172 | public: 173 | size_t sampleSize() override { 174 | return sizeof(int16_t); 175 | } 176 | 177 | void copyRange(const void* const src, size_t start, size_t length, std::complex* const dest) override { 178 | auto s = reinterpret_cast(src); 179 | std::transform(&s[start], &s[start + length], dest, 180 | [](const int16_t& v) -> std::complex { 181 | const float k = 1.0f / 32768.0f; 182 | return { v * k, 0.0f }; 183 | } 184 | ); 185 | } 186 | }; 187 | 188 | class RealS8SampleAdapter : public SampleAdapter { 189 | public: 190 | size_t sampleSize() override { 191 | return sizeof(int8_t); 192 | } 193 | 194 | void copyRange(const void* const src, size_t start, size_t length, std::complex* const dest) override { 195 | auto s = reinterpret_cast(src); 196 | std::transform(&s[start], &s[start + length], dest, 197 | [](const int8_t& v) -> std::complex { 198 | const float k = 1.0f / 128.0f; 199 | return { v * k, 0.0f }; 200 | } 201 | ); 202 | } 203 | }; 204 | 205 | class RealU8SampleAdapter : public SampleAdapter { 206 | public: 207 | size_t sampleSize() override { 208 | return sizeof(uint8_t); 209 | } 210 | 211 | void copyRange(const void* const src, size_t start, size_t length, std::complex* const dest) override { 212 | auto s = reinterpret_cast(src); 213 | std::transform(&s[start], &s[start + length], dest, 214 | [](const uint8_t& v) -> std::complex { 215 | const float k = 1.0f / 128.0f; 216 | return { (v - 127.4f) * k, 0 }; 217 | } 218 | ); 219 | } 220 | }; 221 | 222 | InputSource::InputSource() 223 | { 224 | } 225 | 226 | InputSource::~InputSource() 227 | { 228 | cleanup(); 229 | } 230 | 231 | void InputSource::cleanup() 232 | { 233 | if (mmapData != nullptr) { 234 | inputFile->unmap(mmapData); 235 | mmapData = nullptr; 236 | } 237 | 238 | if (inputFile != nullptr) { 239 | delete inputFile; 240 | inputFile = nullptr; 241 | } 242 | } 243 | 244 | QJsonObject InputSource::readMetaData(const QString &filename) 245 | { 246 | QFile datafile(filename); 247 | if (!datafile.open(QFile::ReadOnly | QIODevice::Text)) { 248 | throw std::runtime_error("Error while opening meta data file: " + datafile.errorString().toStdString()); 249 | } 250 | 251 | QJsonDocument d = QJsonDocument::fromJson(datafile.readAll()); 252 | datafile.close(); 253 | auto root = d.object(); 254 | 255 | if (!root.contains("global") || !root["global"].isObject()) { 256 | throw std::runtime_error("SigMF meta data is invalid (no global object found)"); 257 | } 258 | 259 | auto global = root["global"].toObject(); 260 | 261 | if (!global.contains("core:datatype") || !global["core:datatype"].isString()) { 262 | throw std::runtime_error("SigMF meta data does not specify a valid datatype"); 263 | } 264 | 265 | 266 | auto datatype = global["core:datatype"].toString(); 267 | if (datatype.compare("cf32_le") == 0) { 268 | sampleAdapter = std::make_unique(); 269 | } else if (datatype.compare("ci32_le") == 0) { 270 | sampleAdapter = std::make_unique(); 271 | } else if (datatype.compare("ci16_le") == 0) { 272 | sampleAdapter = std::make_unique(); 273 | } else if (datatype.compare("ci8") == 0) { 274 | sampleAdapter = std::make_unique(); 275 | } else if (datatype.compare("cu8") == 0) { 276 | sampleAdapter = std::make_unique(); 277 | } else if (datatype.compare("rf32_le") == 0) { 278 | sampleAdapter = std::make_unique(); 279 | _realSignal = true; 280 | } else if (datatype.compare("ri16_le") == 0) { 281 | sampleAdapter = std::make_unique(); 282 | _realSignal = true; 283 | } else if (datatype.compare("ri8") == 0) { 284 | sampleAdapter = std::make_unique(); 285 | _realSignal = true; 286 | } else if (datatype.compare("ru8") == 0) { 287 | sampleAdapter = std::make_unique(); 288 | _realSignal = true; 289 | } else { 290 | throw std::runtime_error("SigMF meta data specifies unsupported datatype"); 291 | } 292 | 293 | if (global.contains("core:sample_rate") && global["core:sample_rate"].isDouble()) { 294 | setSampleRate(global["core:sample_rate"].toDouble()); 295 | } 296 | 297 | 298 | if (root.contains("captures") && root["captures"].isArray()) { 299 | auto captures = root["captures"].toArray(); 300 | 301 | for (auto capture_ref : captures) { 302 | if (capture_ref.isObject()) { 303 | auto capture = capture_ref.toObject(); 304 | if (capture.contains("core:frequency") && capture["core:frequency"].isDouble()) { 305 | frequency = capture["core:frequency"].toDouble(); 306 | } 307 | } else { 308 | throw std::runtime_error("SigMF meta data is invalid (invalid capture object)"); 309 | } 310 | } 311 | } 312 | 313 | if(root.contains("annotations") && root["annotations"].isArray()) { 314 | 315 | size_t offset = 0; 316 | 317 | if (global.contains("core:offset")) { 318 | offset = global["offset"].toDouble(); 319 | } 320 | 321 | auto annotations = root["annotations"].toArray(); 322 | 323 | for (auto annotation_ref : annotations) { 324 | if (annotation_ref.isObject()) { 325 | auto sigmf_annotation = annotation_ref.toObject(); 326 | 327 | const size_t sample_start = sigmf_annotation["core:sample_start"].toDouble(); 328 | 329 | if (sample_start < offset) 330 | continue; 331 | 332 | const size_t rel_sample_start = sample_start - offset; 333 | 334 | const size_t sample_count = sigmf_annotation["core:sample_count"].toDouble(); 335 | auto sampleRange = range_t{rel_sample_start, rel_sample_start + sample_count - 1}; 336 | 337 | const double freq_lower_edge = sigmf_annotation["core:freq_lower_edge"].toDouble(); 338 | const double freq_upper_edge = sigmf_annotation["core:freq_upper_edge"].toDouble(); 339 | auto frequencyRange = range_t{freq_lower_edge, freq_upper_edge}; 340 | 341 | auto label = sigmf_annotation["core:label"].toString(); 342 | if (label.isEmpty()) { 343 | label = sigmf_annotation["core:description"].toString(); 344 | } 345 | 346 | auto comment = sigmf_annotation["core:comment"].toString(); 347 | 348 | annotationList.emplace_back(sampleRange, frequencyRange, label, comment); 349 | } 350 | } 351 | } 352 | 353 | return root; 354 | } 355 | 356 | void InputSource::openFile(const char *filename) 357 | { 358 | QFileInfo fileInfo(filename); 359 | std::string suffix = std::string(fileInfo.suffix().toLower().toUtf8().constData()); 360 | if (_fmt != "") { suffix = _fmt; } // allow fmt override 361 | if ((suffix == "cfile") || (suffix == "cf32") || (suffix == "fc32")) { 362 | sampleAdapter = std::make_unique(); 363 | } 364 | else if ((suffix == "cf64") || (suffix == "fc64")) { 365 | sampleAdapter = std::make_unique(); 366 | } 367 | else if ((suffix == "cs32") || (suffix == "sc32") || (suffix == "c32")) { 368 | sampleAdapter = std::make_unique(); 369 | } 370 | else if ((suffix == "cs16") || (suffix == "sc16") || (suffix == "c16")) { 371 | sampleAdapter = std::make_unique(); 372 | } 373 | else if ((suffix == "cs8") || (suffix == "sc8") || (suffix == "c8")) { 374 | sampleAdapter = std::make_unique(); 375 | } 376 | else if ((suffix == "cu8") || (suffix == "uc8")) { 377 | sampleAdapter = std::make_unique(); 378 | } 379 | else if (suffix == "f32") { 380 | sampleAdapter = std::make_unique(); 381 | _realSignal = true; 382 | } 383 | else if (suffix == "f64") { 384 | sampleAdapter = std::make_unique(); 385 | _realSignal = true; 386 | } 387 | else if (suffix == "s16") { 388 | sampleAdapter = std::make_unique(); 389 | _realSignal = true; 390 | } 391 | else if (suffix == "s8") { 392 | sampleAdapter = std::make_unique(); 393 | _realSignal = true; 394 | } 395 | else if (suffix == "u8") { 396 | sampleAdapter = std::make_unique(); 397 | _realSignal = true; 398 | } 399 | else { 400 | sampleAdapter = std::make_unique(); 401 | } 402 | 403 | QString dataFilename; 404 | 405 | annotationList.clear(); 406 | QString metaFilename; 407 | 408 | if (suffix == "sigmf-meta" || suffix == "sigmf-data" || suffix == "sigmf-") { 409 | dataFilename = fileInfo.path() + "/" + fileInfo.completeBaseName() + ".sigmf-data"; 410 | metaFilename = fileInfo.path() + "/" + fileInfo.completeBaseName() + ".sigmf-meta"; 411 | auto metaData = readMetaData(metaFilename); 412 | QFile datafile(dataFilename); 413 | if (!datafile.open(QFile::ReadOnly | QIODevice::Text)) { 414 | auto global = metaData["global"].toObject(); 415 | if (global.contains("core:dataset")) { 416 | auto datasetfilename = global["core:dataset"].toString(); 417 | if(QFileInfo(datasetfilename).isAbsolute()){ 418 | dataFilename = datasetfilename; 419 | } 420 | else{ 421 | dataFilename = fileInfo.path() + "/" + datasetfilename; 422 | } 423 | } 424 | } 425 | } 426 | else if (suffix == "sigmf") { 427 | throw std::runtime_error("SigMF archives are not supported. Consider extracting a recording."); 428 | } 429 | else { 430 | dataFilename = filename; 431 | } 432 | 433 | auto file = std::make_unique(dataFilename); 434 | if (!file->open(QFile::ReadOnly)) { 435 | throw std::runtime_error(file->errorString().toStdString()); 436 | } 437 | 438 | auto size = file->size(); 439 | sampleCount = size / sampleAdapter->sampleSize(); 440 | 441 | auto data = file->map(0, size); 442 | if (data == nullptr) 443 | throw std::runtime_error("Error mmapping file"); 444 | 445 | cleanup(); 446 | 447 | inputFile = file.release(); 448 | mmapData = data; 449 | 450 | invalidate(); 451 | } 452 | 453 | void InputSource::setSampleRate(double rate) 454 | { 455 | sampleRate = rate; 456 | invalidate(); 457 | } 458 | 459 | double InputSource::rate() 460 | { 461 | return sampleRate; 462 | } 463 | 464 | std::unique_ptr[]> InputSource::getSamples(size_t start, size_t length) 465 | { 466 | if (inputFile == nullptr) 467 | return nullptr; 468 | 469 | if (mmapData == nullptr) 470 | return nullptr; 471 | 472 | if(start < 0 || length < 0) 473 | return nullptr; 474 | 475 | if (start + length > sampleCount) 476 | return nullptr; 477 | 478 | auto dest = std::make_unique[]>(length); 479 | sampleAdapter->copyRange(mmapData, start, length, dest.get()); 480 | 481 | return dest; 482 | } 483 | 484 | void InputSource::setFormat(std::string fmt){ 485 | _fmt = fmt; 486 | } 487 | -------------------------------------------------------------------------------- /src/inputsource.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, Mike Walters 3 | * Copyright (C) 2015, Jared Boone 4 | * 5 | * This file is part of inspectrum. 6 | * 7 | * This program 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 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program 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 | 21 | #pragma once 22 | 23 | #include 24 | #include 25 | #include "samplesource.h" 26 | 27 | class SampleAdapter { 28 | public: 29 | virtual size_t sampleSize() = 0; 30 | virtual void copyRange(const void* const src, size_t start, size_t length, std::complex* const dest) = 0; 31 | virtual ~SampleAdapter() { }; 32 | }; 33 | 34 | class InputSource : public SampleSource> 35 | { 36 | private: 37 | QFile *inputFile = nullptr; 38 | size_t sampleCount = 0; 39 | double sampleRate = 0.0; 40 | uchar *mmapData = nullptr; 41 | std::unique_ptr sampleAdapter; 42 | std::string _fmt; 43 | bool _realSignal = false; 44 | 45 | QJsonObject readMetaData(const QString &filename); 46 | 47 | public: 48 | InputSource(); 49 | ~InputSource(); 50 | void cleanup(); 51 | void openFile(const char *filename); 52 | std::unique_ptr[]> getSamples(size_t start, size_t length); 53 | size_t count() { 54 | return sampleCount; 55 | }; 56 | void setSampleRate(double rate); 57 | void setFormat(std::string fmt); 58 | double rate(); 59 | bool realSignal() { 60 | return _realSignal; 61 | }; 62 | float relativeBandwidth() { 63 | return 1; 64 | } 65 | }; 66 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include 21 | #include 22 | 23 | #include "mainwindow.h" 24 | 25 | int main(int argc, char *argv[]) 26 | { 27 | QApplication a(argc, argv); 28 | a.setApplicationName("inspectrum"); 29 | a.setOrganizationName("inspectrum"); 30 | 31 | MainWindow mainWin; 32 | 33 | QCommandLineParser parser; 34 | parser.setApplicationDescription("spectrum viewer"); 35 | parser.addHelpOption(); 36 | parser.addPositionalArgument("file", QCoreApplication::translate("main", "File to view.")); 37 | 38 | // Add options 39 | QCommandLineOption rateOption(QStringList() << "r" << "rate", 40 | QCoreApplication::translate("main", "Set sample rate."), 41 | QCoreApplication::translate("main", "Hz")); 42 | parser.addOption(rateOption); 43 | QCommandLineOption formatOption(QStringList() << "f" << "format", 44 | QCoreApplication::translate("main", "Set file format, options: cfile/cf32/fc32, cf64/fc64, cs32/sc32/c32, cs16/sc16/c16, cs8/sc8/c8, cu8/uc8, f32, f64, s16, s8, u8, sigmf-meta/sigmf-data."), 45 | QCoreApplication::translate("main", "fmt")); 46 | parser.addOption(formatOption); 47 | 48 | // Process the actual command line 49 | parser.process(a); 50 | 51 | // Check for file format override 52 | if(parser.isSet(formatOption)){ 53 | mainWin.setFormat(parser.value(formatOption)); 54 | } 55 | 56 | const QStringList args = parser.positionalArguments(); 57 | if (args.size()>=1) 58 | mainWin.openFile(args.at(0)); 59 | 60 | if (parser.isSet(rateOption)) { 61 | bool ok; 62 | auto rate = parser.value(rateOption).toDouble(&ok); 63 | if(!ok) { 64 | fputs("ERROR: could not parse rate\n", stderr); 65 | return 1; 66 | } 67 | mainWin.setSampleRate(rate); 68 | } 69 | 70 | mainWin.show(); 71 | return a.exec(); 72 | } 73 | -------------------------------------------------------------------------------- /src/mainwindow.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "mainwindow.h" 27 | #include "util.h" 28 | 29 | MainWindow::MainWindow() 30 | { 31 | setWindowTitle(tr("inspectrum")); 32 | 33 | QPixmapCache::setCacheLimit(40960); 34 | 35 | dock = new SpectrogramControls(tr("Controls"), this); 36 | dock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); 37 | addDockWidget(Qt::LeftDockWidgetArea, dock); 38 | 39 | input = new InputSource(); 40 | input->subscribe(this); 41 | 42 | plots = new PlotView(input); 43 | setCentralWidget(plots); 44 | 45 | // Connect dock inputs 46 | connect(dock, &SpectrogramControls::openFile, this, &MainWindow::openFile); 47 | connect(dock->sampleRate, static_cast(&QLineEdit::textChanged), this, static_cast(&MainWindow::setSampleRate)); 48 | connect(dock, static_cast(&SpectrogramControls::fftOrZoomChanged), plots, &PlotView::setFFTAndZoom); 49 | connect(dock->powerMaxSlider, &QSlider::valueChanged, plots, &PlotView::setPowerMax); 50 | connect(dock->powerMinSlider, &QSlider::valueChanged, plots, &PlotView::setPowerMin); 51 | connect(dock->cursorsCheckBox, &QCheckBox::stateChanged, plots, &PlotView::enableCursors); 52 | connect(dock->scalesCheckBox, &QCheckBox::stateChanged, plots, &PlotView::enableScales); 53 | connect(dock->annosCheckBox, &QCheckBox::stateChanged, plots, &PlotView::enableAnnotations); 54 | connect(dock->annosCheckBox, &QCheckBox::stateChanged, dock, &SpectrogramControls::enableAnnotations); 55 | connect(dock->commentsCheckBox, &QCheckBox::stateChanged, plots, &PlotView::enableAnnotationCommentsTooltips); 56 | connect(dock->cursorSymbolsSpinBox, static_cast(&QSpinBox::valueChanged), plots, &PlotView::setCursorSegments); 57 | 58 | // Connect dock outputs 59 | connect(plots, &PlotView::timeSelectionChanged, dock, &SpectrogramControls::timeSelectionChanged); 60 | connect(plots, &PlotView::zoomIn, dock, &SpectrogramControls::zoomIn); 61 | connect(plots, &PlotView::zoomOut, dock, &SpectrogramControls::zoomOut); 62 | 63 | // Set defaults after making connections so everything is in sync 64 | dock->setDefaults(); 65 | 66 | } 67 | 68 | void MainWindow::openFile(QString fileName) 69 | { 70 | QString title="%1: %2"; 71 | this->setWindowTitle(title.arg(QApplication::applicationName(),fileName.section('/',-1,-1))); 72 | 73 | // Try to parse osmocom_fft filenames and extract the sample rate and center frequency. 74 | // Example file name: "name-f2.411200e+09-s5.000000e+06-t20160807180210.cfile" 75 | QRegExp rx("(.*)-f(.*)-s(.*)-.*\\.cfile"); 76 | QString basename = fileName.section('/',-1,-1); 77 | 78 | if (rx.exactMatch(basename)) { 79 | QString centerfreq = rx.cap(2); 80 | QString samplerate = rx.cap(3); 81 | 82 | std::stringstream ss(samplerate.toUtf8().constData()); 83 | 84 | // Needs to be a double as the number is in scientific format 85 | double rate; 86 | ss >> rate; 87 | if (!ss.fail()) { 88 | setSampleRate(rate); 89 | } 90 | } 91 | 92 | try 93 | { 94 | input->openFile(fileName.toUtf8().constData()); 95 | } 96 | catch (const std::exception &ex) 97 | { 98 | QMessageBox msgBox(QMessageBox::Critical, "Inspectrum openFile error", QString("%1: %2").arg(fileName).arg(ex.what())); 99 | msgBox.exec(); 100 | } 101 | } 102 | 103 | void MainWindow::invalidateEvent() 104 | { 105 | plots->setSampleRate(input->rate()); 106 | 107 | // Only update the text box if it is not already representing 108 | // the current value. Otherwise the cursor might jump or the 109 | // representation might change (e.g. to scientific). 110 | double currentValue = dock->sampleRate->text().toDouble(); 111 | if(QString::number(input->rate()) != QString::number(currentValue)) { 112 | setSampleRate(input->rate()); 113 | } 114 | } 115 | 116 | void MainWindow::setSampleRate(QString rate) 117 | { 118 | auto sampleRate = rate.toDouble(); 119 | input->setSampleRate(sampleRate); 120 | plots->setSampleRate(sampleRate); 121 | 122 | // Save the sample rate in settings as we're likely to be opening the same file across multiple runs 123 | QSettings settings; 124 | settings.setValue("SampleRate", sampleRate); 125 | } 126 | 127 | void MainWindow::setSampleRate(double rate) 128 | { 129 | dock->sampleRate->setText(QString::number(rate)); 130 | } 131 | 132 | void MainWindow::setFormat(QString fmt) 133 | { 134 | input->setFormat(fmt.toUtf8().constData()); 135 | } 136 | -------------------------------------------------------------------------------- /src/mainwindow.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include "spectrogramcontrols.h" 25 | #include "plotview.h" 26 | 27 | class MainWindow : public QMainWindow, Subscriber 28 | { 29 | Q_OBJECT 30 | 31 | public: 32 | MainWindow(); 33 | void changeSampleRate(double rate); 34 | 35 | public slots: 36 | void openFile(QString fileName); 37 | void setSampleRate(QString rate); 38 | void setSampleRate(double rate); 39 | void setFormat(QString fmt); 40 | void invalidateEvent() override; 41 | 42 | private: 43 | SpectrogramControls *dock; 44 | PlotView *plots; 45 | InputSource *input; 46 | }; 47 | -------------------------------------------------------------------------------- /src/phasedemod.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "phasedemod.h" 21 | 22 | PhaseDemod::PhaseDemod(std::shared_ptr>> src) : SampleBuffer(src) 23 | { 24 | 25 | } 26 | 27 | void PhaseDemod::work(void *input, void *output, int count, size_t sampleid) 28 | { 29 | auto in = static_cast*>(input); 30 | auto out = static_cast(output); 31 | for (int i = 0; i < count; i++) { 32 | out[i] = std::arg(in[i]) * (1 / M_PI); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/phasedemod.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include "samplebuffer.h" 23 | 24 | class PhaseDemod : public SampleBuffer, float> 25 | { 26 | public: 27 | PhaseDemod(std::shared_ptr>> src); 28 | void work(void *input, void *output, int count, size_t sampleid) override; 29 | }; 30 | -------------------------------------------------------------------------------- /src/plot.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "plot.h" 21 | 22 | Plot::Plot(std::shared_ptr src) : sampleSource(src) 23 | { 24 | sampleSource->subscribe(this); 25 | } 26 | 27 | Plot::~Plot() 28 | { 29 | sampleSource->unsubscribe(this); 30 | } 31 | 32 | void Plot::invalidateEvent() 33 | { 34 | 35 | } 36 | 37 | bool Plot::mouseEvent(QEvent::Type type, QMouseEvent event) 38 | { 39 | return false; 40 | } 41 | 42 | std::shared_ptr Plot::output() 43 | { 44 | return sampleSource; 45 | } 46 | 47 | void Plot::paintBack(QPainter &painter, QRect &rect, range_t sampleRange) 48 | { 49 | painter.save(); 50 | QPen pen(Qt::white, 1, Qt::DashLine); 51 | painter.setPen(pen); 52 | painter.drawLine(rect.left(), rect.center().y(), rect.right(), rect.center().y()); 53 | painter.restore(); 54 | } 55 | 56 | void Plot::paintMid(QPainter &painter, QRect &rect, range_t sampleRange) 57 | { 58 | 59 | } 60 | 61 | void Plot::paintFront(QPainter &painter, QRect &rect, range_t sampleRange) 62 | { 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/plot.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | #include "abstractsamplesource.h" 26 | #include "util.h" 27 | 28 | class Plot : public QObject, public Subscriber 29 | { 30 | Q_OBJECT 31 | 32 | public: 33 | Plot(std::shared_ptr src); 34 | ~Plot(); 35 | void invalidateEvent() override; 36 | virtual bool mouseEvent(QEvent::Type type, QMouseEvent event); 37 | virtual std::shared_ptr output(); 38 | virtual void paintBack(QPainter &painter, QRect &rect, range_t sampleRange); 39 | virtual void paintMid(QPainter &painter, QRect &rect, range_t sampleRange); 40 | virtual void paintFront(QPainter &painter, QRect &rect, range_t sampleRange); 41 | int height() const { return _height; }; 42 | 43 | signals: 44 | void repaint(); 45 | 46 | protected: 47 | void setHeight(int height) { _height = height; }; 48 | 49 | std::shared_ptr sampleSource; 50 | 51 | private: 52 | // TODO: don't hardcode this 53 | int _height = 200; 54 | }; 55 | -------------------------------------------------------------------------------- /src/plots.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2017, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "amplitudedemod.h" 21 | #include "frequencydemod.h" 22 | #include "phasedemod.h" 23 | #include "threshold.h" 24 | #include "traceplot.h" 25 | 26 | #include "plots.h" 27 | 28 | std::multimap Plots::plots; 29 | Plots::_init Plots::_initializer; 30 | 31 | Plot* Plots::samplePlot(std::shared_ptr source) 32 | { 33 | return new TracePlot(source); 34 | } 35 | 36 | Plot* Plots::amplitudePlot(std::shared_ptr source) 37 | { 38 | typedef SampleSource> Source; 39 | std::shared_ptr concrete = std::dynamic_pointer_cast(source); 40 | return new TracePlot( std::make_shared(concrete) ); 41 | } 42 | 43 | Plot* Plots::frequencyPlot(std::shared_ptr source) 44 | { 45 | typedef SampleSource> Source; 46 | std::shared_ptr concrete = std::dynamic_pointer_cast(source); 47 | return new TracePlot( std::make_shared( concrete ) ); 48 | } 49 | 50 | Plot* Plots::phasePlot(std::shared_ptr source) 51 | { 52 | typedef SampleSource> Source; 53 | std::shared_ptr concrete = std::dynamic_pointer_cast(source); 54 | return new TracePlot(std::make_shared(concrete)); 55 | } 56 | 57 | Plot* Plots::thresholdPlot(std::shared_ptr source) 58 | { 59 | typedef SampleSource Source; 60 | std::shared_ptr concrete= std::dynamic_pointer_cast(source); 61 | return new TracePlot( std::make_shared( concrete ) ); 62 | } 63 | -------------------------------------------------------------------------------- /src/plots.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include "plot.h" 25 | #include "samplesource.h" 26 | 27 | struct PlotInfo 28 | { 29 | const char *name; 30 | std::function)> creator; 31 | }; 32 | 33 | class Plots 34 | { 35 | 36 | public: 37 | static std::multimap plots; 38 | 39 | static Plot* samplePlot(std::shared_ptr source); 40 | static Plot* amplitudePlot(std::shared_ptr source); 41 | static Plot* frequencyPlot(std::shared_ptr source); 42 | static Plot* phasePlot(std::shared_ptr source); 43 | static Plot* thresholdPlot(std::shared_ptr source); 44 | 45 | static class _init 46 | { 47 | public: 48 | _init() { 49 | plots.emplace(typeid(std::complex), PlotInfo{"sample plot", samplePlot}); 50 | plots.emplace(typeid(std::complex), PlotInfo{"amplitude plot", amplitudePlot}); 51 | plots.emplace(typeid(std::complex), PlotInfo{"frequency plot", frequencyPlot}); 52 | plots.emplace(typeid(std::complex), PlotInfo{"phase plot", phasePlot}); 53 | plots.emplace(typeid(float), PlotInfo{"threshold plot", thresholdPlot}); 54 | }; 55 | } _initializer; 56 | }; -------------------------------------------------------------------------------- /src/plotview.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015-2016, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "plotview.h" 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include "plots.h" 39 | 40 | PlotView::PlotView(InputSource *input) : cursors(this), viewRange({0, 0}) 41 | { 42 | mainSampleSource = input; 43 | setDragMode(QGraphicsView::ScrollHandDrag); 44 | setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); 45 | setMouseTracking(true); 46 | enableCursors(false); 47 | connect(&cursors, &Cursors::cursorsMoved, this, &PlotView::cursorsMoved); 48 | 49 | spectrogramPlot = new SpectrogramPlot(std::shared_ptr>>(mainSampleSource)); 50 | auto tunerOutput = std::dynamic_pointer_cast>>(spectrogramPlot->output()); 51 | 52 | enableScales(true); 53 | 54 | enableAnnotations(true); 55 | enableAnnotationCommentsTooltips(true); 56 | 57 | addPlot(spectrogramPlot); 58 | 59 | mainSampleSource->subscribe(this); 60 | } 61 | 62 | void PlotView::addPlot(Plot *plot) 63 | { 64 | plots.emplace_back(plot); 65 | connect(plot, &Plot::repaint, this, &PlotView::repaint); 66 | } 67 | 68 | void PlotView::mouseMoveEvent(QMouseEvent *event) 69 | { 70 | updateAnnotationTooltip(event); 71 | QGraphicsView::mouseMoveEvent(event); 72 | } 73 | 74 | void PlotView::mouseReleaseEvent(QMouseEvent *event) 75 | { 76 | // This is used to show the tooltip again on drag release if the mouse is 77 | // hovering over an annotation. 78 | updateAnnotationTooltip(event); 79 | QGraphicsView::mouseReleaseEvent(event); 80 | } 81 | 82 | void PlotView::updateAnnotationTooltip(QMouseEvent *event) 83 | { 84 | // If there are any mouse buttons pressed, we assume 85 | // that the plot is being dragged and hide the tooltip. 86 | bool isDrag = event->buttons() != Qt::NoButton; 87 | if (!annotationCommentsEnabled 88 | || !spectrogramPlot->isAnnotationsEnabled() 89 | || isDrag) { 90 | QToolTip::hideText(); 91 | } else { 92 | QString* comment = spectrogramPlot->mouseAnnotationComment(event); 93 | if (comment != nullptr) { 94 | QToolTip::showText(event->globalPos(), *comment); 95 | } else { 96 | QToolTip::hideText(); 97 | } 98 | } 99 | } 100 | 101 | void PlotView::contextMenuEvent(QContextMenuEvent * event) 102 | { 103 | QMenu menu; 104 | 105 | // Get selected plot 106 | Plot *selectedPlot = nullptr; 107 | auto it = plots.begin(); 108 | int y = -verticalScrollBar()->value(); 109 | for (; it != plots.end(); it++) { 110 | auto&& plot = *it; 111 | if (range_t{y, y + plot->height()}.contains(event->pos().y())) { 112 | selectedPlot = plot.get(); 113 | break; 114 | } 115 | y += plot->height(); 116 | } 117 | if (selectedPlot == nullptr) 118 | return; 119 | 120 | // Add actions to add derived plots 121 | // that are compatible with selectedPlot's output 122 | QMenu *plotsMenu = menu.addMenu("Add derived plot"); 123 | auto src = selectedPlot->output(); 124 | auto compatiblePlots = as_range(Plots::plots.equal_range(src->sampleType())); 125 | for (auto p : compatiblePlots) { 126 | auto plotInfo = p.second; 127 | auto action = new QAction(QString("Add %1").arg(plotInfo.name), plotsMenu); 128 | auto plotCreator = plotInfo.creator; 129 | connect( 130 | action, &QAction::triggered, 131 | this, [=]() { 132 | addPlot(plotCreator(src)); 133 | } 134 | ); 135 | plotsMenu->addAction(action); 136 | } 137 | 138 | // Add submenu for extracting symbols 139 | QMenu *extractMenu = menu.addMenu("Extract symbols"); 140 | // Add action to extract symbols from selected plot to stdout 141 | auto extract = new QAction("To stdout", extractMenu); 142 | connect( 143 | extract, &QAction::triggered, 144 | this, [=]() { 145 | extractSymbols(src, false); 146 | } 147 | ); 148 | extract->setEnabled(cursorsEnabled && (src->sampleType() == typeid(float))); 149 | extractMenu->addAction(extract); 150 | 151 | // Add action to extract symbols from selected plot to clipboard 152 | auto extractClipboard = new QAction("Copy to clipboard", extractMenu); 153 | connect( 154 | extractClipboard, &QAction::triggered, 155 | this, [=]() { 156 | extractSymbols(src, true); 157 | } 158 | ); 159 | extractClipboard->setEnabled(cursorsEnabled && (src->sampleType() == typeid(float))); 160 | extractMenu->addAction(extractClipboard); 161 | 162 | // Add action to export the selected samples into a file 163 | auto save = new QAction("Export samples to file...", &menu); 164 | connect( 165 | save, &QAction::triggered, 166 | this, [=]() { 167 | if (selectedPlot == spectrogramPlot) { 168 | exportSamples(spectrogramPlot->tunerEnabled() ? spectrogramPlot->output() : spectrogramPlot->input()); 169 | } else { 170 | exportSamples(src); 171 | } 172 | } 173 | ); 174 | menu.addAction(save); 175 | 176 | // Add action to remove the selected plot 177 | auto rem = new QAction("Remove plot", &menu); 178 | connect( 179 | rem, &QAction::triggered, 180 | this, [=]() { 181 | plots.erase(it); 182 | } 183 | ); 184 | // Don't allow remove the first plot (the spectrogram) 185 | rem->setEnabled(it != plots.begin()); 186 | menu.addAction(rem); 187 | 188 | updateViewRange(false); 189 | if(menu.exec(event->globalPos())) 190 | updateView(false); 191 | } 192 | 193 | void PlotView::cursorsMoved() 194 | { 195 | selectedSamples = { 196 | columnToSample(horizontalScrollBar()->value() + cursors.selection().minimum), 197 | columnToSample(horizontalScrollBar()->value() + cursors.selection().maximum) 198 | }; 199 | 200 | emitTimeSelection(); 201 | viewport()->update(); 202 | } 203 | 204 | void PlotView::emitTimeSelection() 205 | { 206 | size_t sampleCount = selectedSamples.length(); 207 | float selectionTime = sampleCount / (float)mainSampleSource->rate(); 208 | emit timeSelectionChanged(selectionTime); 209 | } 210 | 211 | void PlotView::enableCursors(bool enabled) 212 | { 213 | cursorsEnabled = enabled; 214 | if (enabled) { 215 | int margin = viewport()->rect().width() / 3; 216 | cursors.setSelection({viewport()->rect().left() + margin, viewport()->rect().right() - margin}); 217 | cursorsMoved(); 218 | } 219 | viewport()->update(); 220 | } 221 | 222 | bool PlotView::viewportEvent(QEvent *event) { 223 | // Handle wheel events for zooming (before the parent's handler to stop normal scrolling) 224 | if (event->type() == QEvent::Wheel) { 225 | QWheelEvent *wheelEvent = (QWheelEvent*)event; 226 | if (QApplication::keyboardModifiers() & Qt::ControlModifier) { 227 | bool canZoomIn = zoomLevel < fftSize; 228 | bool canZoomOut = zoomLevel > 1; 229 | int delta = wheelEvent->angleDelta().y(); 230 | if ((delta > 0 && canZoomIn) || (delta < 0 && canZoomOut)) { 231 | scrollZoomStepsAccumulated += delta; 232 | 233 | // `updateViewRange()` keeps the center sample in the same place after zoom. Apply 234 | // a scroll adjustment to keep the sample under the mouse cursor in the same place instead. 235 | #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) 236 | zoomPos = wheelEvent->position().x(); 237 | #else 238 | zoomPos = wheelEvent->pos().x(); 239 | #endif 240 | zoomSample = columnToSample(horizontalScrollBar()->value() + zoomPos); 241 | if (scrollZoomStepsAccumulated >= 120) { 242 | scrollZoomStepsAccumulated -= 120; 243 | emit zoomIn(); 244 | } else if (scrollZoomStepsAccumulated <= -120) { 245 | scrollZoomStepsAccumulated += 120; 246 | emit zoomOut(); 247 | } 248 | } 249 | return true; 250 | } 251 | } 252 | 253 | // Pass mouse events to individual plot objects 254 | if (event->type() == QEvent::MouseButtonPress || 255 | event->type() == QEvent::MouseMove || 256 | event->type() == QEvent::MouseButtonRelease || 257 | event->type() == QEvent::Leave) { 258 | 259 | QMouseEvent *mouseEvent = static_cast(event); 260 | 261 | int plotY = -verticalScrollBar()->value(); 262 | for (auto&& plot : plots) { 263 | bool result = plot->mouseEvent( 264 | event->type(), 265 | QMouseEvent( 266 | event->type(), 267 | QPoint(mouseEvent->pos().x(), mouseEvent->pos().y() - plotY), 268 | mouseEvent->button(), 269 | mouseEvent->buttons(), 270 | QApplication::keyboardModifiers() 271 | ) 272 | ); 273 | if (result) 274 | return true; 275 | plotY += plot->height(); 276 | } 277 | 278 | if (cursorsEnabled) 279 | if (cursors.mouseEvent(event->type(), *mouseEvent)) 280 | return true; 281 | } 282 | 283 | // Handle parent eveents 284 | return QGraphicsView::viewportEvent(event); 285 | } 286 | 287 | void PlotView::extractSymbols(std::shared_ptr src, 288 | bool toClipboard) 289 | { 290 | if (!cursorsEnabled) 291 | return; 292 | auto floatSrc = std::dynamic_pointer_cast>(src); 293 | if (!floatSrc) 294 | return; 295 | auto samples = floatSrc->getSamples(selectedSamples.minimum, selectedSamples.length()); 296 | auto step = (float)selectedSamples.length() / cursors.segments(); 297 | auto symbols = std::vector(); 298 | for (auto i = step / 2; i < selectedSamples.length(); i += step) 299 | { 300 | symbols.push_back(samples[i]); 301 | } 302 | if (!toClipboard) { 303 | for (auto f : symbols) 304 | std::cout << f << ", "; 305 | std::cout << std::endl << std::flush; 306 | } else { 307 | QClipboard *clipboard = QGuiApplication::clipboard(); 308 | QString symbolText; 309 | QTextStream symbolStream(&symbolText); 310 | for (auto f : symbols) 311 | symbolStream << f << ", "; 312 | clipboard->setText(symbolText); 313 | } 314 | } 315 | 316 | void PlotView::exportSamples(std::shared_ptr src) 317 | { 318 | if (src->sampleType() == typeid(std::complex)) { 319 | exportSamples>(src); 320 | } else { 321 | exportSamples(src); 322 | } 323 | } 324 | 325 | template 326 | void PlotView::exportSamples(std::shared_ptr src) 327 | { 328 | auto sampleSrc = std::dynamic_pointer_cast>(src); 329 | if (!sampleSrc) { 330 | return; 331 | } 332 | 333 | QFileDialog dialog(this); 334 | dialog.setAcceptMode(QFileDialog::AcceptSave); 335 | dialog.setFileMode(QFileDialog::AnyFile); 336 | dialog.setNameFilter(getFileNameFilter()); 337 | dialog.setOption(QFileDialog::DontUseNativeDialog, true); 338 | 339 | QGroupBox groupBox("Selection To Export", &dialog); 340 | QVBoxLayout vbox(&groupBox); 341 | 342 | QRadioButton cursorSelection("Cursor Selection", &groupBox); 343 | QRadioButton currentView("Current View", &groupBox); 344 | QRadioButton completeFile("Complete File (Experimental)", &groupBox); 345 | 346 | if (cursorsEnabled) { 347 | cursorSelection.setChecked(true); 348 | } else { 349 | currentView.setChecked(true); 350 | cursorSelection.setEnabled(false); 351 | } 352 | 353 | vbox.addWidget(&cursorSelection); 354 | vbox.addWidget(¤tView); 355 | vbox.addWidget(&completeFile); 356 | vbox.addStretch(1); 357 | 358 | groupBox.setLayout(&vbox); 359 | 360 | QGridLayout *l = dialog.findChild(); 361 | l->addWidget(&groupBox, 4, 1); 362 | 363 | QGroupBox groupBox2("Decimation"); 364 | QSpinBox decimation(&groupBox2); 365 | decimation.setMinimum(1); 366 | decimation.setValue(1 / sampleSrc->relativeBandwidth()); 367 | 368 | QVBoxLayout vbox2; 369 | vbox2.addWidget(&decimation); 370 | 371 | groupBox2.setLayout(&vbox2); 372 | l->addWidget(&groupBox2, 4, 2); 373 | 374 | if (dialog.exec()) { 375 | QStringList fileNames = dialog.selectedFiles(); 376 | 377 | size_t start, end; 378 | if (cursorSelection.isChecked()) { 379 | start = selectedSamples.minimum; 380 | end = start + selectedSamples.length(); 381 | } else if(currentView.isChecked()) { 382 | start = viewRange.minimum; 383 | end = start + viewRange.length(); 384 | } else { 385 | start = 0; 386 | end = sampleSrc->count(); 387 | } 388 | 389 | std::ofstream os (fileNames[0].toStdString(), std::ios::binary); 390 | 391 | size_t index; 392 | // viewRange.length() is used as some less arbitrary step value 393 | size_t step = viewRange.length(); 394 | 395 | QProgressDialog progress("Exporting samples...", "Cancel", start, end, this); 396 | progress.setWindowModality(Qt::WindowModal); 397 | for (index = start; index < end; index += step) { 398 | progress.setValue(index); 399 | if (progress.wasCanceled()) 400 | break; 401 | 402 | size_t length = std::min(step, end - index); 403 | auto samples = sampleSrc->getSamples(index, length); 404 | if (samples != nullptr) { 405 | for (auto i = 0; i < length; i += decimation.value()) { 406 | os.write((const char*)&samples[i], sizeof(SOURCETYPE)); 407 | } 408 | } 409 | } 410 | } 411 | } 412 | 413 | void PlotView::invalidateEvent() 414 | { 415 | horizontalScrollBar()->setMinimum(0); 416 | horizontalScrollBar()->setMaximum(sampleToColumn(mainSampleSource->count())); 417 | } 418 | 419 | void PlotView::repaint() 420 | { 421 | viewport()->update(); 422 | } 423 | 424 | void PlotView::setCursorSegments(int segments) 425 | { 426 | // Calculate number of samples per segment 427 | float sampPerSeg = (float)selectedSamples.length() / cursors.segments(); 428 | 429 | // Alter selection to keep samples per segment the same 430 | selectedSamples.maximum = selectedSamples.minimum + (segments * sampPerSeg + 0.5f); 431 | 432 | cursors.setSegments(segments); 433 | updateView(); 434 | emitTimeSelection(); 435 | } 436 | 437 | void PlotView::setFFTAndZoom(int size, int zoom) 438 | { 439 | auto oldSamplesPerColumn = samplesPerColumn(); 440 | 441 | // Set new FFT size 442 | fftSize = size; 443 | if (spectrogramPlot != nullptr) 444 | spectrogramPlot->setFFTSize(size); 445 | 446 | // Set new zoom level 447 | zoomLevel = zoom; 448 | if (spectrogramPlot != nullptr) 449 | spectrogramPlot->setZoomLevel(zoom); 450 | 451 | // Update horizontal (time) scrollbar 452 | horizontalScrollBar()->setSingleStep(10); 453 | horizontalScrollBar()->setPageStep(100); 454 | 455 | updateView(true, samplesPerColumn() < oldSamplesPerColumn); 456 | } 457 | 458 | void PlotView::setPowerMin(int power) 459 | { 460 | powerMin = power; 461 | if (spectrogramPlot != nullptr) 462 | spectrogramPlot->setPowerMin(power); 463 | updateView(); 464 | } 465 | 466 | void PlotView::setPowerMax(int power) 467 | { 468 | powerMax = power; 469 | if (spectrogramPlot != nullptr) 470 | spectrogramPlot->setPowerMax(power); 471 | updateView(); 472 | } 473 | 474 | void PlotView::paintEvent(QPaintEvent *event) 475 | { 476 | if (mainSampleSource == nullptr) return; 477 | 478 | QRect rect = QRect(0, 0, width(), height()); 479 | QPainter painter(viewport()); 480 | painter.fillRect(rect, Qt::black); 481 | 482 | 483 | #define PLOT_LAYER(paintFunc) \ 484 | { \ 485 | int y = -verticalScrollBar()->value(); \ 486 | for (auto&& plot : plots) { \ 487 | QRect rect = QRect(0, y, width(), plot->height()); \ 488 | plot->paintFunc(painter, rect, viewRange); \ 489 | y += plot->height(); \ 490 | } \ 491 | } 492 | 493 | PLOT_LAYER(paintBack); 494 | PLOT_LAYER(paintMid); 495 | PLOT_LAYER(paintFront); 496 | if (cursorsEnabled) 497 | cursors.paintFront(painter, rect, viewRange); 498 | 499 | if (timeScaleEnabled) { 500 | paintTimeScale(painter, rect, viewRange); 501 | } 502 | 503 | 504 | #undef PLOT_LAYER 505 | } 506 | 507 | void PlotView::paintTimeScale(QPainter &painter, QRect &rect, range_t sampleRange) 508 | { 509 | float startTime = (float)sampleRange.minimum / sampleRate; 510 | float stopTime = (float)sampleRange.maximum / sampleRate; 511 | float duration = stopTime - startTime; 512 | 513 | if (duration <= 0) 514 | return; 515 | 516 | painter.save(); 517 | 518 | QPen pen(Qt::white, 1, Qt::SolidLine); 519 | painter.setPen(pen); 520 | QFontMetrics fm(painter.font()); 521 | 522 | int tickWidth = 80; 523 | int maxTicks = rect.width() / tickWidth; 524 | 525 | double durationPerTick = 10 * pow(10, floor(log(duration / maxTicks) / log(10))); 526 | 527 | double firstTick = int(startTime / durationPerTick) * durationPerTick; 528 | 529 | double tick = firstTick; 530 | 531 | while (tick <= stopTime) { 532 | 533 | size_t tickSample = tick * sampleRate; 534 | int tickLine = sampleToColumn(tickSample - sampleRange.minimum); 535 | 536 | char buf[128]; 537 | snprintf(buf, sizeof(buf), "%.06f", tick); 538 | painter.drawLine(tickLine, 0, tickLine, 30); 539 | painter.drawText(tickLine + 2, 25, buf); 540 | 541 | tick += durationPerTick; 542 | } 543 | 544 | // Draw small ticks 545 | durationPerTick /= 10; 546 | firstTick = int(startTime / durationPerTick) * durationPerTick; 547 | tick = firstTick; 548 | while (tick <= stopTime) { 549 | 550 | size_t tickSample = tick * sampleRate; 551 | int tickLine = sampleToColumn(tickSample - sampleRange.minimum); 552 | 553 | painter.drawLine(tickLine, 0, tickLine, 10); 554 | tick += durationPerTick; 555 | } 556 | 557 | painter.restore(); 558 | } 559 | 560 | int PlotView::plotsHeight() 561 | { 562 | int height = 0; 563 | for (auto&& plot : plots) { 564 | height += plot->height(); 565 | } 566 | return height; 567 | } 568 | 569 | void PlotView::resizeEvent(QResizeEvent * event) 570 | { 571 | updateView(); 572 | } 573 | 574 | size_t PlotView::samplesPerColumn() 575 | { 576 | return fftSize / zoomLevel; 577 | } 578 | 579 | void PlotView::scrollContentsBy(int dx, int dy) 580 | { 581 | updateView(); 582 | } 583 | 584 | void PlotView::showEvent(QShowEvent *event) 585 | { 586 | // Intentionally left blank. See #171 587 | } 588 | 589 | void PlotView::updateViewRange(bool reCenter) 590 | { 591 | // Update current view 592 | auto start = columnToSample(horizontalScrollBar()->value()); 593 | viewRange = {start, std::min(start + columnToSample(width()), mainSampleSource->count())}; 594 | 595 | // Adjust time offset to zoom around central sample 596 | if (reCenter) { 597 | horizontalScrollBar()->setValue( 598 | sampleToColumn(zoomSample) - zoomPos 599 | ); 600 | } 601 | zoomSample = viewRange.minimum + viewRange.length() / 2; 602 | zoomPos = width() / 2; 603 | } 604 | 605 | void PlotView::updateView(bool reCenter, bool expanding) 606 | { 607 | if (!expanding) { 608 | updateViewRange(reCenter); 609 | } 610 | horizontalScrollBar()->setMaximum(std::max(0, sampleToColumn(mainSampleSource->count()) - width())); 611 | verticalScrollBar()->setMaximum(std::max(0, plotsHeight() - viewport()->height())); 612 | if (expanding) { 613 | updateViewRange(reCenter); 614 | } 615 | 616 | // Update cursors 617 | range_t newSelection = { 618 | sampleToColumn(selectedSamples.minimum) - horizontalScrollBar()->value(), 619 | sampleToColumn(selectedSamples.maximum) - horizontalScrollBar()->value() 620 | }; 621 | cursors.setSelection(newSelection); 622 | 623 | // Re-paint 624 | viewport()->update(); 625 | } 626 | 627 | void PlotView::setSampleRate(double rate) 628 | { 629 | sampleRate = rate; 630 | 631 | if (spectrogramPlot != nullptr) 632 | spectrogramPlot->setSampleRate(rate); 633 | 634 | emitTimeSelection(); 635 | } 636 | 637 | void PlotView::enableScales(bool enabled) 638 | { 639 | timeScaleEnabled = enabled; 640 | 641 | if (spectrogramPlot != nullptr) 642 | spectrogramPlot->enableScales(enabled); 643 | 644 | viewport()->update(); 645 | } 646 | 647 | void PlotView::enableAnnotations(bool enabled) 648 | { 649 | if (spectrogramPlot != nullptr) 650 | spectrogramPlot->enableAnnotations(enabled); 651 | 652 | viewport()->update(); 653 | } 654 | 655 | void PlotView::enableAnnotationCommentsTooltips(bool enabled) 656 | { 657 | annotationCommentsEnabled = enabled; 658 | 659 | viewport()->update(); 660 | } 661 | 662 | int PlotView::sampleToColumn(size_t sample) 663 | { 664 | return sample / samplesPerColumn(); 665 | } 666 | 667 | size_t PlotView::columnToSample(int col) 668 | { 669 | return col * samplesPerColumn(); 670 | } 671 | -------------------------------------------------------------------------------- /src/plotview.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015-2016, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | 25 | #include "cursors.h" 26 | #include "inputsource.h" 27 | #include "plot.h" 28 | #include "samplesource.h" 29 | #include "spectrogramplot.h" 30 | #include "traceplot.h" 31 | 32 | class PlotView : public QGraphicsView, Subscriber 33 | { 34 | Q_OBJECT 35 | 36 | public: 37 | PlotView(InputSource *input); 38 | void setSampleRate(double rate); 39 | 40 | signals: 41 | void timeSelectionChanged(float time); 42 | void zoomIn(); 43 | void zoomOut(); 44 | 45 | public slots: 46 | void cursorsMoved(); 47 | void enableCursors(bool enabled); 48 | void enableScales(bool enabled); 49 | void enableAnnotations(bool enabled); 50 | void enableAnnotationCommentsTooltips(bool enabled); 51 | void invalidateEvent() override; 52 | void repaint(); 53 | void setCursorSegments(int segments); 54 | void setFFTAndZoom(int fftSize, int zoomLevel); 55 | void setPowerMin(int power); 56 | void setPowerMax(int power); 57 | 58 | protected: 59 | void mouseMoveEvent(QMouseEvent *event) override; 60 | void mouseReleaseEvent(QMouseEvent *event) override; 61 | void contextMenuEvent(QContextMenuEvent * event) override; 62 | void paintEvent(QPaintEvent *event) override; 63 | void resizeEvent(QResizeEvent * event) override; 64 | void scrollContentsBy(int dx, int dy) override; 65 | bool viewportEvent(QEvent *event) override; 66 | void showEvent(QShowEvent *event) override; 67 | 68 | private: 69 | Cursors cursors; 70 | SampleSource> *mainSampleSource = nullptr; 71 | SpectrogramPlot *spectrogramPlot = nullptr; 72 | std::vector> plots; 73 | range_t viewRange; 74 | range_t selectedSamples; 75 | int zoomPos; 76 | size_t zoomSample; 77 | 78 | int fftSize = 1024; 79 | int zoomLevel = 1; 80 | int powerMin; 81 | int powerMax; 82 | bool cursorsEnabled; 83 | double sampleRate = 0.0; 84 | bool timeScaleEnabled; 85 | int scrollZoomStepsAccumulated = 0; 86 | bool annotationCommentsEnabled; 87 | 88 | void addPlot(Plot *plot); 89 | void emitTimeSelection(); 90 | void extractSymbols(std::shared_ptr src, bool toClipboard); 91 | void exportSamples(std::shared_ptr src); 92 | template void exportSamples(std::shared_ptr src); 93 | int plotsHeight(); 94 | size_t samplesPerColumn(); 95 | void updateViewRange(bool reCenter); 96 | void updateView(bool reCenter = false, bool expanding = false); 97 | void paintTimeScale(QPainter &painter, QRect &rect, range_t sampleRange); 98 | void updateAnnotationTooltip(QMouseEvent *event); 99 | 100 | int sampleToColumn(size_t sample); 101 | size_t columnToSample(int col); 102 | }; 103 | -------------------------------------------------------------------------------- /src/samplebuffer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include 21 | #include 22 | #include "samplebuffer.h" 23 | 24 | template 25 | SampleBuffer::SampleBuffer(std::shared_ptr> src) : src(src) 26 | { 27 | src->subscribe(this); 28 | } 29 | 30 | template 31 | SampleBuffer::~SampleBuffer() 32 | { 33 | src->unsubscribe(this); 34 | } 35 | 36 | template 37 | std::unique_ptr SampleBuffer::getSamples(size_t start, size_t length) 38 | { 39 | // TODO: base this on the actual history required 40 | auto history = std::min(start, (size_t)256); 41 | auto samples = src->getSamples(start - history, length + history); 42 | if (samples == nullptr) 43 | return nullptr; 44 | 45 | auto temp = std::make_unique(history + length); 46 | auto dest = std::make_unique(length); 47 | QMutexLocker ml(&mutex); 48 | work(samples.get(), temp.get(), history + length, start); 49 | memcpy(dest.get(), temp.get() + history, length * sizeof(Tout)); 50 | return dest; 51 | } 52 | 53 | template 54 | void SampleBuffer::invalidateEvent() 55 | { 56 | SampleSource::invalidate(); 57 | } 58 | 59 | template class SampleBuffer, std::complex>; 60 | template class SampleBuffer, float>; 61 | template class SampleBuffer; 62 | -------------------------------------------------------------------------------- /src/samplebuffer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | #include "samplesource.h" 26 | 27 | template 28 | class SampleBuffer : public SampleSource, public Subscriber 29 | { 30 | private: 31 | std::shared_ptr> src; 32 | QMutex mutex; 33 | 34 | public: 35 | SampleBuffer(std::shared_ptr> src); 36 | ~SampleBuffer(); 37 | void invalidateEvent(); 38 | virtual std::unique_ptr getSamples(size_t start, size_t length); 39 | virtual void work(void *input, void *output, int count, size_t sampleid) = 0; 40 | virtual size_t count() { 41 | return src->count(); 42 | }; 43 | double rate() { 44 | return src->rate(); 45 | }; 46 | 47 | float relativeBandwidth() { 48 | return src->relativeBandwidth(); 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /src/samplesource.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "samplesource.h" 21 | 22 | template 23 | std::type_index SampleSource::sampleType() 24 | { 25 | return typeid(T); 26 | } 27 | 28 | template 29 | double SampleSource::getFrequency() 30 | { 31 | return frequency; 32 | } 33 | 34 | template class SampleSource>; 35 | template class SampleSource; 36 | -------------------------------------------------------------------------------- /src/samplesource.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include "abstractsamplesource.h" 25 | 26 | #include "util.h" 27 | #include 28 | #include 29 | 30 | class Annotation 31 | { 32 | public: 33 | range_t sampleRange; 34 | range_t frequencyRange; 35 | QString label; 36 | QString comment; 37 | 38 | Annotation(range_t sampleRange, range_tfrequencyRange, QString label, 39 | QString comment) 40 | : sampleRange(sampleRange), frequencyRange(frequencyRange), label(label), 41 | comment(comment) {} 42 | }; 43 | 44 | template 45 | class SampleSource : public AbstractSampleSource 46 | { 47 | protected: 48 | double frequency; 49 | 50 | public: 51 | virtual ~SampleSource() {}; 52 | 53 | virtual std::unique_ptr getSamples(size_t start, size_t length) = 0; 54 | virtual void invalidateEvent() { }; 55 | virtual size_t count() = 0; 56 | virtual double rate() = 0; 57 | virtual float relativeBandwidth() = 0; 58 | std::vector annotationList; 59 | std::type_index sampleType() override; 60 | virtual bool realSignal() { return false; }; 61 | double getFrequency(); 62 | }; 63 | -------------------------------------------------------------------------------- /src/spectrogramcontrols.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, Mike Walters 3 | * Copyright (C) 2015, Jared Boone 4 | * 5 | * This file is part of inspectrum. 6 | * 7 | * This program 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 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program 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 | 21 | #include "spectrogramcontrols.h" 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include "util.h" 28 | 29 | SpectrogramControls::SpectrogramControls(const QString & title, QWidget * parent) 30 | : QDockWidget::QDockWidget(title, parent) 31 | { 32 | widget = new QWidget(this); 33 | layout = new QFormLayout(widget); 34 | 35 | fileOpenButton = new QPushButton("Open file...", widget); 36 | layout->addRow(fileOpenButton); 37 | 38 | sampleRate = new QLineEdit(); 39 | auto double_validator = new QDoubleValidator(this); 40 | double_validator->setBottom(0.0); 41 | sampleRate->setValidator(double_validator); 42 | layout->addRow(new QLabel(tr("Sample rate:")), sampleRate); 43 | 44 | // Spectrogram settings 45 | layout->addRow(new QLabel()); // TODO: find a better way to add an empty row? 46 | layout->addRow(new QLabel(tr("Spectrogram"))); 47 | 48 | fftSizeSlider = new QSlider(Qt::Horizontal, widget); 49 | fftSizeSlider->setRange(4, 13); 50 | fftSizeSlider->setPageStep(1); 51 | 52 | layout->addRow(new QLabel(tr("FFT size:")), fftSizeSlider); 53 | 54 | zoomLevelSlider = new QSlider(Qt::Horizontal, widget); 55 | zoomLevelSlider->setRange(0, 10); 56 | zoomLevelSlider->setPageStep(1); 57 | 58 | layout->addRow(new QLabel(tr("Zoom:")), zoomLevelSlider); 59 | 60 | powerMaxSlider = new QSlider(Qt::Horizontal, widget); 61 | powerMaxSlider->setRange(-140, 10); 62 | layout->addRow(new QLabel(tr("Power max:")), powerMaxSlider); 63 | 64 | powerMinSlider = new QSlider(Qt::Horizontal, widget); 65 | powerMinSlider->setRange(-140, 10); 66 | layout->addRow(new QLabel(tr("Power min:")), powerMinSlider); 67 | 68 | scalesCheckBox = new QCheckBox(widget); 69 | scalesCheckBox->setCheckState(Qt::Checked); 70 | layout->addRow(new QLabel(tr("Scales:")), scalesCheckBox); 71 | 72 | // Time selection settings 73 | layout->addRow(new QLabel()); // TODO: find a better way to add an empty row? 74 | layout->addRow(new QLabel(tr("Time selection"))); 75 | 76 | cursorsCheckBox = new QCheckBox(widget); 77 | layout->addRow(new QLabel(tr("Enable cursors:")), cursorsCheckBox); 78 | 79 | cursorSymbolsSpinBox = new QSpinBox(); 80 | cursorSymbolsSpinBox->setMinimum(1); 81 | cursorSymbolsSpinBox->setMaximum(99999); 82 | layout->addRow(new QLabel(tr("Symbols:")), cursorSymbolsSpinBox); 83 | 84 | rateLabel = new QLabel(); 85 | layout->addRow(new QLabel(tr("Rate:")), rateLabel); 86 | 87 | periodLabel = new QLabel(); 88 | layout->addRow(new QLabel(tr("Period:")), periodLabel); 89 | 90 | symbolRateLabel = new QLabel(); 91 | layout->addRow(new QLabel(tr("Symbol rate:")), symbolRateLabel); 92 | 93 | symbolPeriodLabel = new QLabel(); 94 | layout->addRow(new QLabel(tr("Symbol period:")), symbolPeriodLabel); 95 | 96 | // SigMF selection settings 97 | layout->addRow(new QLabel()); // TODO: find a better way to add an empty row? 98 | layout->addRow(new QLabel(tr("SigMF Control"))); 99 | 100 | annosCheckBox = new QCheckBox(widget); 101 | layout->addRow(new QLabel(tr("Display Annotations:")), annosCheckBox); 102 | commentsCheckBox = new QCheckBox(widget); 103 | layout->addRow(new QLabel(tr("Display annotation comments tooltips:")), commentsCheckBox); 104 | 105 | widget->setLayout(layout); 106 | setWidget(widget); 107 | 108 | connect(fftSizeSlider, &QSlider::valueChanged, this, &SpectrogramControls::fftSizeChanged); 109 | connect(zoomLevelSlider, &QSlider::valueChanged, this, &SpectrogramControls::zoomLevelChanged); 110 | connect(fileOpenButton, &QPushButton::clicked, this, &SpectrogramControls::fileOpenButtonClicked); 111 | connect(cursorsCheckBox, &QCheckBox::stateChanged, this, &SpectrogramControls::cursorsStateChanged); 112 | connect(powerMinSlider, &QSlider::valueChanged, this, &SpectrogramControls::powerMinChanged); 113 | connect(powerMaxSlider, &QSlider::valueChanged, this, &SpectrogramControls::powerMaxChanged); 114 | } 115 | 116 | void SpectrogramControls::clearCursorLabels() 117 | { 118 | periodLabel->setText(""); 119 | rateLabel->setText(""); 120 | symbolPeriodLabel->setText(""); 121 | symbolRateLabel->setText(""); 122 | } 123 | 124 | void SpectrogramControls::cursorsStateChanged(int state) 125 | { 126 | if (state == Qt::Unchecked) { 127 | clearCursorLabels(); 128 | } 129 | } 130 | 131 | void SpectrogramControls::setDefaults() 132 | { 133 | fftOrZoomChanged(); 134 | 135 | cursorsCheckBox->setCheckState(Qt::Unchecked); 136 | cursorSymbolsSpinBox->setValue(1); 137 | 138 | annosCheckBox->setCheckState(Qt::Checked); 139 | commentsCheckBox->setCheckState(Qt::Checked); 140 | 141 | // Try to set the sample rate from the last-used value 142 | QSettings settings; 143 | int savedSampleRate = settings.value("SampleRate", 8000000).toInt(); 144 | sampleRate->setText(QString::number(savedSampleRate)); 145 | fftSizeSlider->setValue(settings.value("FFTSize", 9).toInt()); 146 | powerMaxSlider->setValue(settings.value("PowerMax", 0).toInt()); 147 | powerMinSlider->setValue(settings.value("PowerMin", -100).toInt()); 148 | zoomLevelSlider->setValue(settings.value("ZoomLevel", 0).toInt()); 149 | } 150 | 151 | void SpectrogramControls::fftOrZoomChanged(void) 152 | { 153 | int fftSize = pow(2, fftSizeSlider->value()); 154 | int zoomLevel = std::min(fftSize, (int)pow(2, zoomLevelSlider->value())); 155 | emit fftOrZoomChanged(fftSize, zoomLevel); 156 | } 157 | 158 | void SpectrogramControls::fftSizeChanged(int value) 159 | { 160 | QSettings settings; 161 | settings.setValue("FFTSize", value); 162 | fftOrZoomChanged(); 163 | } 164 | 165 | void SpectrogramControls::zoomLevelChanged(int value) 166 | { 167 | QSettings settings; 168 | settings.setValue("ZoomLevel", value); 169 | fftOrZoomChanged(); 170 | } 171 | 172 | void SpectrogramControls::powerMinChanged(int value) 173 | { 174 | QSettings settings; 175 | settings.setValue("PowerMin", value); 176 | } 177 | 178 | void SpectrogramControls::powerMaxChanged(int value) 179 | { 180 | QSettings settings; 181 | settings.setValue("PowerMax", value); 182 | } 183 | 184 | void SpectrogramControls::fileOpenButtonClicked() 185 | { 186 | QSettings settings; 187 | QString fileName; 188 | QFileDialog fileSelect(this); 189 | fileSelect.setNameFilter(tr("All files (*);;" 190 | "complex file (*.cfile *.cf32 *.fc32);;" 191 | "complex HackRF file (*.cs8 *.sc8 *.c8);;" 192 | "complex Fancy file (*.cs16 *.sc16 *.c16);;" 193 | "complex RTL-SDR file (*.cu8 *.uc8)")); 194 | 195 | // Try and load a saved state 196 | { 197 | QByteArray savedState = settings.value("OpenFileState").toByteArray(); 198 | fileSelect.restoreState(savedState); 199 | 200 | // Filter doesn't seem to be considered part of the saved state 201 | QString lastUsedFilter = settings.value("OpenFileFilter").toString(); 202 | if(lastUsedFilter.size()) 203 | fileSelect.selectNameFilter(lastUsedFilter); 204 | } 205 | 206 | if(fileSelect.exec()) 207 | { 208 | fileName = fileSelect.selectedFiles()[0]; 209 | 210 | // Remember the state of the dialog for the next time 211 | QByteArray dialogState = fileSelect.saveState(); 212 | settings.setValue("OpenFileState", dialogState); 213 | settings.setValue("OpenFileFilter", fileSelect.selectedNameFilter()); 214 | } 215 | 216 | if (!fileName.isEmpty()) 217 | emit openFile(fileName); 218 | } 219 | 220 | void SpectrogramControls::timeSelectionChanged(float time) 221 | { 222 | if (cursorsCheckBox->checkState() == Qt::Checked) { 223 | periodLabel->setText(QString::fromStdString(formatSIValue(time)) + "s"); 224 | rateLabel->setText(QString::fromStdString(formatSIValue(1 / time)) + "Hz"); 225 | 226 | int symbols = cursorSymbolsSpinBox->value(); 227 | symbolPeriodLabel->setText(QString::fromStdString(formatSIValue(time / symbols)) + "s"); 228 | symbolRateLabel->setText(QString::fromStdString(formatSIValue(symbols / time)) + "Bd"); 229 | } 230 | } 231 | 232 | void SpectrogramControls::zoomIn() 233 | { 234 | zoomLevelSlider->setValue(zoomLevelSlider->value() + 1); 235 | } 236 | 237 | void SpectrogramControls::zoomOut() 238 | { 239 | zoomLevelSlider->setValue(zoomLevelSlider->value() - 1); 240 | } 241 | 242 | void SpectrogramControls::enableAnnotations(bool enabled) { 243 | // disable annotation comments checkbox when annotations are disabled 244 | commentsCheckBox->setEnabled(enabled); 245 | } 246 | -------------------------------------------------------------------------------- /src/spectrogramcontrols.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | class SpectrogramControls : public QDockWidget 32 | { 33 | Q_OBJECT 34 | 35 | public: 36 | SpectrogramControls(const QString & title, QWidget * parent); 37 | void setDefaults(); 38 | 39 | signals: 40 | void fftOrZoomChanged(int fftSize, int zoomLevel); 41 | void openFile(QString fileName); 42 | 43 | public slots: 44 | void timeSelectionChanged(float time); 45 | void zoomIn(); 46 | void zoomOut(); 47 | void enableAnnotations(bool enabled); 48 | 49 | private slots: 50 | void fftSizeChanged(int value); 51 | void zoomLevelChanged(int value); 52 | void powerMinChanged(int value); 53 | void powerMaxChanged(int value); 54 | void fileOpenButtonClicked(); 55 | void cursorsStateChanged(int state); 56 | 57 | private: 58 | QWidget *widget; 59 | QFormLayout *layout; 60 | void clearCursorLabels(); 61 | void fftOrZoomChanged(void); 62 | 63 | public: 64 | QPushButton *fileOpenButton; 65 | QLineEdit *sampleRate; 66 | QSlider *fftSizeSlider; 67 | QSlider *zoomLevelSlider; 68 | QSlider *powerMaxSlider; 69 | QSlider *powerMinSlider; 70 | QCheckBox *cursorsCheckBox; 71 | QSpinBox *cursorSymbolsSpinBox; 72 | QLabel *rateLabel; 73 | QLabel *periodLabel; 74 | QLabel *symbolRateLabel; 75 | QLabel *symbolPeriodLabel; 76 | QCheckBox *scalesCheckBox; 77 | QCheckBox *annosCheckBox; 78 | QCheckBox *commentsCheckBox; 79 | }; 80 | -------------------------------------------------------------------------------- /src/spectrogramplot.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "spectrogramplot.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include "util.h" 34 | 35 | 36 | SpectrogramPlot::SpectrogramPlot(std::shared_ptr>> src) : Plot(src), inputSource(src), fftSize(512), tuner(fftSize, this) 37 | { 38 | setFFTSize(fftSize); 39 | zoomLevel = 1; 40 | powerMax = 0.0f; 41 | powerMin = -50.0f; 42 | sampleRate = 0; 43 | frequencyScaleEnabled = false; 44 | sigmfAnnotationsEnabled = true; 45 | 46 | for (int i = 0; i < 256; i++) { 47 | float p = (float)i / 256; 48 | colormap[i] = QColor::fromHsvF(p * 0.83f, 1.0, 1.0 - p).rgba(); 49 | } 50 | 51 | tunerTransform = std::make_shared(src); 52 | connect(&tuner, &Tuner::tunerMoved, this, &SpectrogramPlot::tunerMoved); 53 | } 54 | 55 | void SpectrogramPlot::invalidateEvent() 56 | { 57 | // HACK: this makes sure we update the height for real signals (as InputSource is passed here before the file is opened) 58 | setFFTSize(fftSize); 59 | 60 | pixmapCache.clear(); 61 | fftCache.clear(); 62 | emit repaint(); 63 | } 64 | 65 | void SpectrogramPlot::paintFront(QPainter &painter, QRect &rect, range_t sampleRange) 66 | { 67 | if (tunerEnabled()) 68 | tuner.paintFront(painter, rect, sampleRange); 69 | 70 | if (frequencyScaleEnabled) 71 | paintFrequencyScale(painter, rect); 72 | 73 | if (sigmfAnnotationsEnabled) 74 | paintAnnotations(painter, rect, sampleRange); 75 | } 76 | 77 | void SpectrogramPlot::paintFrequencyScale(QPainter &painter, QRect &rect) 78 | { 79 | if (sampleRate == 0) { 80 | return; 81 | } 82 | 83 | if (sampleRate / 2 > UINT64_MAX) { 84 | return; 85 | } 86 | 87 | // At which pixel is F_+sampleRate/2 88 | int y = rect.y(); 89 | 90 | int plotHeight = rect.height(); 91 | if (inputSource->realSignal()) 92 | plotHeight *= 2; 93 | 94 | double bwPerPixel = (double)sampleRate / plotHeight; 95 | int tickHeight = 50; 96 | 97 | uint64_t bwPerTick = 10 * pow(10, floor(log(bwPerPixel * tickHeight) / log(10))); 98 | 99 | if (bwPerTick < 1) { 100 | return; 101 | } 102 | 103 | painter.save(); 104 | 105 | QPen pen(Qt::white, 1, Qt::SolidLine); 106 | painter.setPen(pen); 107 | QFontMetrics fm(painter.font()); 108 | 109 | 110 | uint64_t tick = 0; 111 | 112 | while (tick <= sampleRate / 2) { 113 | 114 | int tickpy = plotHeight / 2 - tick / bwPerPixel + y; 115 | int tickny = plotHeight / 2 + tick / bwPerPixel + y; 116 | 117 | if (!inputSource->realSignal()) 118 | painter.drawLine(0, tickny, 30, tickny); 119 | painter.drawLine(0, tickpy, 30, tickpy); 120 | 121 | if (tick != 0) { 122 | char buf[128]; 123 | 124 | if (bwPerTick % 1000000000 == 0) { 125 | snprintf(buf, sizeof(buf), "-%lu GHz", tick / 1000000000); 126 | } else if (bwPerTick % 1000000 == 0) { 127 | snprintf(buf, sizeof(buf), "-%lu MHz", tick / 1000000); 128 | } else if(bwPerTick % 1000 == 0) { 129 | snprintf(buf, sizeof(buf), "-%lu kHz", tick / 1000); 130 | } else { 131 | snprintf(buf, sizeof(buf), "-%lu Hz", tick); 132 | } 133 | 134 | if (!inputSource->realSignal()) 135 | painter.drawText(5, tickny - 5, buf); 136 | 137 | buf[0] = ' '; 138 | painter.drawText(5, tickpy + 15, buf); 139 | } 140 | 141 | tick += bwPerTick; 142 | } 143 | 144 | // Draw small ticks 145 | bwPerTick /= 10; 146 | 147 | if (bwPerTick >= 1 ) { 148 | tick = 0; 149 | while (tick <= sampleRate / 2) { 150 | 151 | int tickpy = plotHeight / 2 - tick / bwPerPixel + y; 152 | int tickny = plotHeight / 2 + tick / bwPerPixel + y; 153 | 154 | if (!inputSource->realSignal()) 155 | painter.drawLine(0, tickny, 3, tickny); 156 | painter.drawLine(0, tickpy, 3, tickpy); 157 | 158 | tick += bwPerTick; 159 | } 160 | } 161 | painter.restore(); 162 | } 163 | 164 | void SpectrogramPlot::paintAnnotations(QPainter &painter, QRect &rect, range_t sampleRange) 165 | { 166 | // Pixel (from the top) at which 0 Hz sits 167 | int zero = rect.y() + rect.height() / 2; 168 | 169 | painter.save(); 170 | QPen pen(Qt::white, 1, Qt::SolidLine); 171 | painter.setPen(pen); 172 | QFontMetrics fm(painter.font()); 173 | 174 | visibleAnnotationLocations.clear(); 175 | 176 | for (int i = 0; i < inputSource->annotationList.size(); i++) { 177 | Annotation a = inputSource->annotationList.at(i); 178 | 179 | size_t labelLength = fm.boundingRect(a.label).width() * getStride(); 180 | 181 | // Check if: 182 | // (1) End of annotation (might be maximum, or end of label text) is still visible in time 183 | // (2) Part of the annotation is already visible in time 184 | // 185 | // Currently there is no check if the annotation is visible in frequency. This is a 186 | // possible performance improvement 187 | // 188 | size_t start = a.sampleRange.minimum; 189 | size_t end = std::max(a.sampleRange.minimum + labelLength, a.sampleRange.maximum); 190 | 191 | if(start <= sampleRange.maximum && end >= sampleRange.minimum) { 192 | 193 | double frequency = a.frequencyRange.maximum - inputSource->getFrequency(); 194 | int x = (a.sampleRange.minimum - sampleRange.minimum) / getStride(); 195 | int y = zero - frequency / sampleRate * rect.height(); 196 | int height = (a.frequencyRange.maximum - a.frequencyRange.minimum) / sampleRate * rect.height(); 197 | int width = (a.sampleRange.maximum - a.sampleRange.minimum) / getStride(); 198 | 199 | // Draw the label 2 pixels above the box 200 | painter.drawText(x, y - 2, a.label); 201 | painter.drawRect(x, y, width, height); 202 | 203 | visibleAnnotationLocations.emplace_back(a, x, y, width, height); 204 | } 205 | } 206 | 207 | painter.restore(); 208 | } 209 | 210 | QString *SpectrogramPlot::mouseAnnotationComment(const QMouseEvent *event) { 211 | auto pos = event->pos(); 212 | int mouse_x = pos.x(); 213 | int mouse_y = pos.y(); 214 | 215 | for (auto& a : visibleAnnotationLocations) { 216 | if (!a.annotation.comment.isEmpty() && a.isInside(mouse_x, mouse_y)) { 217 | return &a.annotation.comment; 218 | } 219 | } 220 | return nullptr; 221 | } 222 | 223 | void SpectrogramPlot::paintMid(QPainter &painter, QRect &rect, range_t sampleRange) 224 | { 225 | if (!inputSource || inputSource->count() == 0) 226 | return; 227 | 228 | size_t sampleOffset = sampleRange.minimum % (getStride() * linesPerTile()); 229 | size_t tileID = sampleRange.minimum - sampleOffset; 230 | int xoffset = sampleOffset / getStride(); 231 | 232 | // Paint first (possibly partial) tile 233 | painter.drawPixmap(QRect(rect.left(), rect.y(), linesPerTile() - xoffset, height()), *getPixmapTile(tileID), QRect(xoffset, 0, linesPerTile() - xoffset, height())); 234 | tileID += getStride() * linesPerTile(); 235 | 236 | // Paint remaining tiles 237 | for (int x = linesPerTile() - xoffset; x < rect.right(); x += linesPerTile()) { 238 | // TODO: don't draw past rect.right() 239 | // TODO: handle partial final tile 240 | painter.drawPixmap(QRect(x, rect.y(), linesPerTile(), height()), *getPixmapTile(tileID), QRect(0, 0, linesPerTile(), height())); 241 | tileID += getStride() * linesPerTile(); 242 | } 243 | } 244 | 245 | QPixmap* SpectrogramPlot::getPixmapTile(size_t tile) 246 | { 247 | QPixmap *obj = pixmapCache.object(TileCacheKey(fftSize, zoomLevel, tile)); 248 | if (obj != 0) 249 | return obj; 250 | 251 | float *fftTile = getFFTTile(tile); 252 | obj = new QPixmap(linesPerTile(), fftSize); 253 | QImage image(linesPerTile(), fftSize, QImage::Format_RGB32); 254 | float powerRange = -1.0f / std::abs(int(powerMin - powerMax)); 255 | for (int y = 0; y < fftSize; y++) { 256 | auto scanLine = (QRgb*)image.scanLine(fftSize - y - 1); 257 | for (int x = 0; x < linesPerTile(); x++) { 258 | float *fftLine = &fftTile[x * fftSize]; 259 | float normPower = (fftLine[y] - powerMax) * powerRange; 260 | normPower = clamp(normPower, 0.0f, 1.0f); 261 | 262 | scanLine[x] = colormap[(uint8_t)(normPower * (256 - 1))]; 263 | } 264 | } 265 | obj->convertFromImage(image); 266 | pixmapCache.insert(TileCacheKey(fftSize, zoomLevel, tile), obj); 267 | return obj; 268 | } 269 | 270 | float* SpectrogramPlot::getFFTTile(size_t tile) 271 | { 272 | std::array* obj = fftCache.object(TileCacheKey(fftSize, zoomLevel, tile)); 273 | if (obj != nullptr) 274 | return obj->data(); 275 | 276 | std::array* destStorage = new std::array; 277 | float *ptr = destStorage->data(); 278 | size_t sample = tile; 279 | while ((ptr - destStorage->data()) < tileSize) { 280 | getLine(ptr, sample); 281 | sample += getStride(); 282 | ptr += fftSize; 283 | } 284 | fftCache.insert(TileCacheKey(fftSize, zoomLevel, tile), destStorage); 285 | return destStorage->data(); 286 | } 287 | 288 | void SpectrogramPlot::getLine(float *dest, size_t sample) 289 | { 290 | if (inputSource && fft) { 291 | // Make sample be the midpoint of the FFT, unless this takes us 292 | // past the beginning of the inputSource (if we remove the 293 | // std::max(·, 0), then an ugly red bar appears at the beginning 294 | // of the spectrogram with large zooms and FFT sizes). 295 | const auto first_sample = std::max(static_cast(sample) - fftSize / 2, 296 | static_cast(0)); 297 | auto buffer = inputSource->getSamples(first_sample, fftSize); 298 | if (buffer == nullptr) { 299 | auto neg_infinity = -1 * std::numeric_limits::infinity(); 300 | for (int i = 0; i < fftSize; i++, dest++) 301 | *dest = neg_infinity; 302 | return; 303 | } 304 | 305 | for (int i = 0; i < fftSize; i++) { 306 | buffer[i] *= window[i]; 307 | } 308 | 309 | fft->process(buffer.get(), buffer.get()); 310 | const float invFFTSize = 1.0f / fftSize; 311 | const float logMultiplier = 10.0f / log2f(10.0f); 312 | for (int i = 0; i < fftSize; i++) { 313 | // Start from the middle of the FFTW array and wrap 314 | // to rearrange the data 315 | int k = i ^ (fftSize >> 1); 316 | auto s = buffer[k] * invFFTSize; 317 | float power = s.real() * s.real() + s.imag() * s.imag(); 318 | float logPower = log2f(power) * logMultiplier; 319 | *dest = logPower; 320 | dest++; 321 | } 322 | } 323 | } 324 | 325 | int SpectrogramPlot::getStride() 326 | { 327 | return fftSize / zoomLevel; 328 | } 329 | 330 | float SpectrogramPlot::getTunerPhaseInc() 331 | { 332 | auto freq = 0.5f - tuner.centre() / (float)fftSize; 333 | return freq * Tau; 334 | } 335 | 336 | std::vector SpectrogramPlot::getTunerTaps() 337 | { 338 | float cutoff = tuner.deviation() / (float)fftSize; 339 | float gain = pow(10.0f, powerMax / -10.0f); 340 | auto atten = 60.0f; 341 | auto len = estimate_req_filter_len(std::min(cutoff, 0.05f), atten); 342 | auto taps = std::vector(len); 343 | liquid_firdes_kaiser(len, cutoff, atten, 0.0f, taps.data()); 344 | std::transform(taps.begin(), taps.end(), taps.begin(), 345 | std::bind(std::multiplies(), std::placeholders::_1, gain)); 346 | return taps; 347 | } 348 | 349 | int SpectrogramPlot::linesPerTile() 350 | { 351 | return tileSize / fftSize; 352 | } 353 | 354 | bool SpectrogramPlot::mouseEvent(QEvent::Type type, QMouseEvent event) 355 | { 356 | if (tunerEnabled()) 357 | return tuner.mouseEvent(type, event); 358 | 359 | return false; 360 | } 361 | 362 | std::shared_ptr SpectrogramPlot::output() 363 | { 364 | return tunerTransform; 365 | } 366 | 367 | void SpectrogramPlot::setFFTSize(int size) 368 | { 369 | float sizeScale = float(size) / float(fftSize); 370 | fftSize = size; 371 | fft.reset(new FFT(fftSize)); 372 | 373 | window.reset(new float[fftSize]); 374 | for (int i = 0; i < fftSize; i++) { 375 | window[i] = 0.5f * (1.0f - cos(Tau * i / (fftSize - 1))); 376 | } 377 | 378 | if (inputSource->realSignal()) { 379 | setHeight(fftSize/2); 380 | } else { 381 | setHeight(fftSize); 382 | } 383 | auto dev = tuner.deviation(); 384 | auto centre = tuner.centre(); 385 | tuner.setHeight(height()); 386 | tuner.setDeviation( dev * sizeScale ); 387 | tuner.setCentre( centre * sizeScale ); 388 | } 389 | 390 | void SpectrogramPlot::setPowerMax(int power) 391 | { 392 | powerMax = power; 393 | pixmapCache.clear(); 394 | tunerMoved(); 395 | } 396 | 397 | void SpectrogramPlot::setPowerMin(int power) 398 | { 399 | powerMin = power; 400 | pixmapCache.clear(); 401 | } 402 | 403 | void SpectrogramPlot::setZoomLevel(int zoom) 404 | { 405 | zoomLevel = zoom; 406 | } 407 | 408 | void SpectrogramPlot::setSampleRate(double rate) 409 | { 410 | sampleRate = rate; 411 | } 412 | 413 | void SpectrogramPlot::enableScales(bool enabled) 414 | { 415 | frequencyScaleEnabled = enabled; 416 | } 417 | 418 | void SpectrogramPlot::enableAnnotations(bool enabled) 419 | { 420 | sigmfAnnotationsEnabled = enabled; 421 | } 422 | 423 | bool SpectrogramPlot::isAnnotationsEnabled(void) 424 | { 425 | return sigmfAnnotationsEnabled; 426 | } 427 | 428 | bool SpectrogramPlot::tunerEnabled() 429 | { 430 | return (tunerTransform->subscriberCount() > 0); 431 | } 432 | 433 | void SpectrogramPlot::tunerMoved() 434 | { 435 | tunerTransform->setFrequency(getTunerPhaseInc()); 436 | tunerTransform->setTaps(getTunerTaps()); 437 | tunerTransform->setRelativeBandwith(tuner.deviation() * 2.0 / height()); 438 | 439 | // TODO: for invalidating traceplot cache, this shouldn't really go here 440 | QPixmapCache::clear(); 441 | 442 | emit repaint(); 443 | } 444 | 445 | uint qHash(const TileCacheKey &key, uint seed) 446 | { 447 | return key.fftSize ^ key.zoomLevel ^ key.sample ^ seed; 448 | } 449 | -------------------------------------------------------------------------------- /src/spectrogramplot.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | #include "fft.h" 26 | #include "inputsource.h" 27 | #include "plot.h" 28 | #include "tuner.h" 29 | #include "tunertransform.h" 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | class TileCacheKey; 37 | class AnnotationLocation; 38 | 39 | class SpectrogramPlot : public Plot 40 | { 41 | Q_OBJECT 42 | 43 | public: 44 | SpectrogramPlot(std::shared_ptr>> src); 45 | void invalidateEvent() override; 46 | std::shared_ptr output() override; 47 | void paintFront(QPainter &painter, QRect &rect, range_t sampleRange) override; 48 | void paintMid(QPainter &painter, QRect &rect, range_t sampleRange) override; 49 | bool mouseEvent(QEvent::Type type, QMouseEvent event) override; 50 | std::shared_ptr>> input() { return inputSource; }; 51 | void setSampleRate(double sampleRate); 52 | bool tunerEnabled(); 53 | void enableScales(bool enabled); 54 | void enableAnnotations(bool enabled); 55 | bool isAnnotationsEnabled(); 56 | QString *mouseAnnotationComment(const QMouseEvent *event); 57 | 58 | public slots: 59 | void setFFTSize(int size); 60 | void setPowerMax(int power); 61 | void setPowerMin(int power); 62 | void setZoomLevel(int zoom); 63 | void tunerMoved(); 64 | 65 | private: 66 | const int linesPerGraduation = 50; 67 | static const int tileSize = 65536; // This must be a multiple of the maximum FFT size 68 | 69 | std::shared_ptr>> inputSource; 70 | std::vector visibleAnnotationLocations; 71 | std::unique_ptr fft; 72 | std::unique_ptr window; 73 | QCache pixmapCache; 74 | QCache> fftCache; 75 | uint colormap[256]; 76 | 77 | int fftSize; 78 | int zoomLevel; 79 | float powerMax; 80 | float powerMin; 81 | double sampleRate; 82 | bool frequencyScaleEnabled; 83 | bool sigmfAnnotationsEnabled; 84 | 85 | Tuner tuner; 86 | std::shared_ptr tunerTransform; 87 | 88 | QPixmap* getPixmapTile(size_t tile); 89 | float* getFFTTile(size_t tile); 90 | void getLine(float *dest, size_t sample); 91 | int getStride(); 92 | float getTunerPhaseInc(); 93 | std::vector getTunerTaps(); 94 | int linesPerTile(); 95 | void paintFrequencyScale(QPainter &painter, QRect &rect); 96 | void paintAnnotations(QPainter &painter, QRect &rect, range_t sampleRange); 97 | }; 98 | 99 | class TileCacheKey 100 | { 101 | 102 | public: 103 | TileCacheKey(int fftSize, int zoomLevel, size_t sample) { 104 | this->fftSize = fftSize; 105 | this->zoomLevel = zoomLevel; 106 | this->sample = sample; 107 | } 108 | 109 | bool operator==(const TileCacheKey &k2) const { 110 | return (this->fftSize == k2.fftSize) && 111 | (this->zoomLevel == k2.zoomLevel) && 112 | (this->sample == k2.sample); 113 | } 114 | 115 | int fftSize; 116 | int zoomLevel; 117 | size_t sample; 118 | }; 119 | 120 | class AnnotationLocation 121 | { 122 | public: 123 | Annotation annotation; 124 | 125 | AnnotationLocation(Annotation annotation, int x, int y, int width, int height) 126 | : annotation(annotation), x(x), y(y), width(width), height(height) {} 127 | 128 | bool isInside(int pos_x, int pos_y) { 129 | return (x <= pos_x) && (pos_x <= x + width) 130 | && (y <= pos_y) && (pos_y <= y + height); 131 | } 132 | 133 | private: 134 | int x; 135 | int y; 136 | int width; 137 | int height; 138 | }; 139 | -------------------------------------------------------------------------------- /src/subscriber.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | 23 | class Subscriber 24 | { 25 | public: 26 | virtual void invalidateEvent() = 0; 27 | }; 28 | -------------------------------------------------------------------------------- /src/threshold.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "threshold.h" 21 | 22 | Threshold::Threshold(std::shared_ptr> src) : SampleBuffer(src) 23 | { 24 | 25 | } 26 | 27 | void Threshold::work(void *input, void *output, int count, size_t sampleid) 28 | { 29 | auto in = static_cast(input); 30 | auto out = static_cast(output); 31 | std::transform(in, in + count, out, 32 | [](float s) { return (s > 0) ? 1.0f : 0.0f; }); 33 | } 34 | -------------------------------------------------------------------------------- /src/threshold.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include "samplebuffer.h" 23 | 24 | class Threshold : public SampleBuffer 25 | { 26 | public: 27 | Threshold(std::shared_ptr> src); 28 | void work(void *input, void *output, int count, size_t sampleid) override; 29 | }; 30 | -------------------------------------------------------------------------------- /src/traceplot.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include "samplesource.h" 25 | #include "traceplot.h" 26 | 27 | TracePlot::TracePlot(std::shared_ptr source) : Plot(source) { 28 | connect(this, &TracePlot::imageReady, this, &TracePlot::handleImage); 29 | } 30 | 31 | void TracePlot::paintMid(QPainter &painter, QRect &rect, range_t sampleRange) 32 | { 33 | if (sampleRange.length() == 0) return; 34 | 35 | int samplesPerColumn = std::max(1UL, sampleRange.length() / rect.width()); 36 | int samplesPerTile = tileWidth * samplesPerColumn; 37 | size_t tileID = sampleRange.minimum / samplesPerTile; 38 | size_t tileOffset = sampleRange.minimum % samplesPerTile; // Number of samples to skip from first image tile 39 | int xOffset = tileOffset / samplesPerColumn; // Number of columns to skip from first image tile 40 | 41 | // Paint first (possibly partial) tile 42 | painter.drawPixmap( 43 | QRect(rect.x(), rect.y(), tileWidth - xOffset, height()), 44 | getTile(tileID++, samplesPerTile), 45 | QRect(xOffset, 0, tileWidth - xOffset, height()) 46 | ); 47 | 48 | // Paint remaining tiles 49 | for (int x = tileWidth - xOffset; x < rect.right(); x += tileWidth) { 50 | painter.drawPixmap( 51 | QRect(x, rect.y(), tileWidth, height()), 52 | getTile(tileID++, samplesPerTile) 53 | ); 54 | } 55 | } 56 | 57 | QPixmap TracePlot::getTile(size_t tileID, size_t sampleCount) 58 | { 59 | QPixmap pixmap(tileWidth, height()); 60 | QString key; 61 | QTextStream(&key) << "traceplot_" << this << "_" << tileID << "_" << sampleCount; 62 | if (QPixmapCache::find(key, &pixmap)) 63 | return pixmap; 64 | 65 | if (!tasks.contains(key)) { 66 | range_t sampleRange{tileID * sampleCount, (tileID + 1) * sampleCount}; 67 | QtConcurrent::run(this, &TracePlot::drawTile, key, QRect(0, 0, tileWidth, height()), sampleRange); 68 | tasks.insert(key); 69 | } 70 | pixmap.fill(Qt::transparent); 71 | return pixmap; 72 | } 73 | 74 | void TracePlot::drawTile(QString key, const QRect &rect, range_t sampleRange) 75 | { 76 | QImage image(rect.size(), QImage::Format_ARGB32); 77 | image.fill(Qt::transparent); 78 | 79 | QPainter painter(&image); 80 | painter.setRenderHint(QPainter::Antialiasing, true); 81 | 82 | auto firstSample = sampleRange.minimum; 83 | auto length = sampleRange.length(); 84 | 85 | // Is it a 2-channel (complex) trace? 86 | if (auto src = dynamic_cast>*>(sampleSource.get())) { 87 | auto samples = src->getSamples(firstSample, length); 88 | if (samples == nullptr) 89 | return; 90 | 91 | painter.setPen(Qt::red); 92 | plotTrace(painter, rect, reinterpret_cast(samples.get()), length, 2); 93 | painter.setPen(Qt::blue); 94 | plotTrace(painter, rect, reinterpret_cast(samples.get())+1, length, 2); 95 | 96 | // Otherwise is it single channel? 97 | } else if (auto src = dynamic_cast*>(sampleSource.get())) { 98 | auto samples = src->getSamples(firstSample, length); 99 | if (samples == nullptr) 100 | return; 101 | 102 | painter.setPen(Qt::green); 103 | plotTrace(painter, rect, samples.get(), length, 1); 104 | } else { 105 | throw std::runtime_error("TracePlot::paintMid: Unsupported source type"); 106 | } 107 | 108 | emit imageReady(key, image); 109 | } 110 | 111 | void TracePlot::handleImage(QString key, QImage image) 112 | { 113 | auto pixmap = QPixmap::fromImage(image); 114 | QPixmapCache::insert(key, pixmap); 115 | tasks.remove(key); 116 | emit repaint(); 117 | } 118 | 119 | void TracePlot::plotTrace(QPainter &painter, const QRect &rect, float *samples, size_t count, int step = 1) 120 | { 121 | QPainterPath path; 122 | range_t xRange{0, rect.width() - 2.f}; 123 | range_t yRange{0, rect.height() - 2.f}; 124 | const float xStep = 1.0 / count * rect.width(); 125 | for (size_t i = 0; i < count; i++) { 126 | float sample = samples[i*step]; 127 | float x = i * xStep; 128 | float y = (1 - sample) * (rect.height() / 2); 129 | 130 | x = xRange.clip(x) + rect.x(); 131 | y = yRange.clip(y) + rect.y(); 132 | 133 | if (i == 0) 134 | path.moveTo(x, y); 135 | else 136 | path.lineTo(x, y); 137 | } 138 | painter.drawPath(path); 139 | } 140 | -------------------------------------------------------------------------------- /src/traceplot.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #pragma once 21 | #include 22 | #include "abstractsamplesource.h" 23 | #include "plot.h" 24 | #include "util.h" 25 | 26 | class TracePlot : public Plot 27 | { 28 | Q_OBJECT 29 | 30 | public: 31 | TracePlot(std::shared_ptr source); 32 | 33 | void paintMid(QPainter &painter, QRect &rect, range_t sampleRange); 34 | std::shared_ptr source() { return sampleSource; }; 35 | 36 | signals: 37 | void imageReady(QString key, QImage image); 38 | 39 | public slots: 40 | void handleImage(QString key, QImage image); 41 | 42 | private: 43 | QSet tasks; 44 | const int tileWidth = 1000; 45 | 46 | QPixmap getTile(size_t tileID, size_t sampleCount); 47 | void drawTile(QString key, const QRect &rect, range_t sampleRange); 48 | void plotTrace(QPainter &painter, const QRect &rect, float *samples, size_t count, int step); 49 | }; 50 | -------------------------------------------------------------------------------- /src/tuner.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include 21 | #include "tuner.h" 22 | 23 | Tuner::Tuner(int height, QObject * parent) : height(height), QObject::QObject(parent) 24 | { 25 | minCursor = new Cursor(Qt::Horizontal, Qt::SizeVerCursor, this); 26 | cfCursor = new Cursor(Qt::Horizontal, Qt::SizeAllCursor, this); 27 | maxCursor = new Cursor(Qt::Horizontal, Qt::SizeVerCursor, this); 28 | connect(minCursor, &Cursor::posChanged, this, &Tuner::cursorMoved); 29 | connect(cfCursor, &Cursor::posChanged, this, &Tuner::cursorMoved); 30 | connect(maxCursor, &Cursor::posChanged, this, &Tuner::cursorMoved); 31 | 32 | cfCursor->setPos(100); 33 | _deviation = 10; 34 | updateCursors(); 35 | } 36 | 37 | int Tuner::centre() 38 | { 39 | return cfCursor->pos(); 40 | } 41 | 42 | void Tuner::cursorMoved() 43 | { 44 | Cursor *sender = static_cast(QObject::sender()); 45 | if (sender != cfCursor) { 46 | // Limit cursor positions to within plot 47 | auto posRange = range_t{0, height}; 48 | sender->setPos(posRange.clip(sender->pos())); 49 | 50 | // Limit deviation range to half of total BW (either side of centre) 51 | auto deviationRange = range_t{2, height / 2}; 52 | _deviation = deviationRange.clip(std::abs(sender->pos() - cfCursor->pos())); 53 | } else { 54 | auto cfRange = range_t{_deviation, height - _deviation}; 55 | sender->setPos(cfRange.clip(sender->pos())); 56 | } 57 | 58 | updateCursors(); 59 | } 60 | 61 | int Tuner::deviation() 62 | { 63 | return _deviation; 64 | } 65 | 66 | bool Tuner::mouseEvent(QEvent::Type type, QMouseEvent event) 67 | { 68 | if (cfCursor->mouseEvent(type, event)) 69 | return true; 70 | if (minCursor->mouseEvent(type, event)) 71 | return true; 72 | if (maxCursor->mouseEvent(type, event)) 73 | return true; 74 | 75 | return false; 76 | } 77 | 78 | void Tuner::paintFront(QPainter &painter, QRect &rect, range_t sampleRange) 79 | { 80 | painter.save(); 81 | 82 | QRect cursorRect(rect.left(), rect.top() + minCursor->pos(), rect.right(), maxCursor->pos() - minCursor->pos()); 83 | 84 | // Draw translucent white fill for highlight 85 | painter.fillRect( 86 | cursorRect, 87 | QBrush(QColor(255, 255, 255, 50)) 88 | ); 89 | 90 | // Draw tuner edges 91 | painter.setPen(QPen(Qt::white, 1, Qt::SolidLine)); 92 | painter.drawLine(rect.left(), rect.top() + minCursor->pos(), rect.right(), rect.top() + minCursor->pos()); 93 | painter.drawLine(rect.left(), rect.top() + maxCursor->pos(), rect.right(), rect.top() + maxCursor->pos()); 94 | 95 | // Draw centre freq 96 | painter.setPen(QPen(Qt::red, 1, Qt::SolidLine)); 97 | painter.drawLine(rect.left(), rect.top() + cfCursor->pos(), rect.right(), rect.top() + cfCursor->pos()); 98 | 99 | painter.restore(); 100 | } 101 | 102 | void Tuner::setCentre(int centre) 103 | { 104 | cfCursor->setPos(centre); 105 | updateCursors(); 106 | } 107 | 108 | void Tuner::setDeviation(int dev) 109 | { 110 | _deviation = std::max(1, dev); 111 | updateCursors(); 112 | } 113 | 114 | void Tuner::setHeight(int height) 115 | { 116 | this->height = height; 117 | } 118 | 119 | void Tuner::updateCursors() 120 | { 121 | minCursor->setPos(cfCursor->pos() - _deviation); 122 | maxCursor->setPos(cfCursor->pos() + _deviation); 123 | emit tunerMoved(); 124 | } 125 | -------------------------------------------------------------------------------- /src/tuner.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include "cursor.h" 27 | #include "util.h" 28 | 29 | class Tuner : public QObject 30 | { 31 | Q_OBJECT 32 | 33 | public: 34 | Tuner(int height, QObject * parent); 35 | int centre(); 36 | int deviation(); 37 | bool mouseEvent(QEvent::Type, QMouseEvent event); 38 | void paintFront(QPainter &painter, QRect &rect, range_t sampleRange); 39 | void setCentre(int centre); 40 | void setDeviation(int dev); 41 | void setHeight(int height); 42 | 43 | public slots: 44 | void cursorMoved(); 45 | 46 | signals: 47 | void tunerMoved(); 48 | 49 | private: 50 | void updateCursors(); 51 | 52 | Cursor *minCursor; 53 | Cursor *cfCursor; 54 | Cursor *maxCursor; 55 | int _deviation; 56 | int height; 57 | }; 58 | -------------------------------------------------------------------------------- /src/tunertransform.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "tunertransform.h" 21 | #include 22 | #include "util.h" 23 | 24 | TunerTransform::TunerTransform(std::shared_ptr>> src) : SampleBuffer(src), frequency(0), bandwidth(1.), taps{1.0f} 25 | { 26 | 27 | } 28 | 29 | void TunerTransform::work(void *input, void *output, int count, size_t sampleid) 30 | { 31 | auto out = static_cast*>(output); 32 | auto temp = std::make_unique[]>(count); 33 | 34 | // Mix down 35 | nco_crcf mix = nco_crcf_create(LIQUID_NCO); 36 | nco_crcf_set_phase(mix, fmodf(frequency * sampleid, Tau)); 37 | nco_crcf_set_frequency(mix, frequency); 38 | nco_crcf_mix_block_down(mix, 39 | static_cast*>(input), 40 | temp.get(), 41 | count); 42 | nco_crcf_destroy(mix); 43 | 44 | // Filter 45 | firfilt_crcf filter = firfilt_crcf_create(taps.data(), taps.size()); 46 | for (int i = 0; i < count; i++) 47 | { 48 | firfilt_crcf_push(filter, temp[i]); 49 | firfilt_crcf_execute(filter, &out[i]); 50 | } 51 | firfilt_crcf_destroy(filter); 52 | } 53 | 54 | void TunerTransform::setFrequency(float frequency) 55 | { 56 | this->frequency = frequency; 57 | } 58 | 59 | void TunerTransform::setTaps(std::vector taps) 60 | { 61 | this->taps = taps; 62 | } 63 | 64 | float TunerTransform::relativeBandwidth() { 65 | return bandwidth; 66 | } 67 | 68 | void TunerTransform::setRelativeBandwith(float bandwidth) 69 | { 70 | this->bandwidth = bandwidth; 71 | } 72 | 73 | -------------------------------------------------------------------------------- /src/tunertransform.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include "samplebuffer.h" 23 | #include 24 | 25 | class TunerTransform : public SampleBuffer, std::complex> 26 | { 27 | private: 28 | float frequency; 29 | float bandwidth; 30 | std::vector taps; 31 | 32 | public: 33 | TunerTransform(std::shared_ptr>> src); 34 | void work(void *input, void *output, int count, size_t sampleid) override; 35 | void setFrequency(float frequency); 36 | void setTaps(std::vector taps); 37 | void setRelativeBandwith(float bandwidth); 38 | float relativeBandwidth() override; 39 | }; 40 | -------------------------------------------------------------------------------- /src/util.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016, Mike Walters 3 | * 4 | * This file is part of inspectrum. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "util.h" 21 | 22 | std::string formatSIValue(float value) 23 | { 24 | std::map prefixes = { 25 | { 9, "G" }, 26 | { 6, "M" }, 27 | { 3, "k" }, 28 | { 0, "" }, 29 | { -3, "m" }, 30 | { -6, "µ" }, 31 | { -9, "n" }, 32 | }; 33 | 34 | int power = 0; 35 | while (value < 1.0f && power > -9) { 36 | value *= 1e3; 37 | power -= 3; 38 | } 39 | while (value >= 1e3 && power < 9) { 40 | value *= 1e-3; 41 | power += 3; 42 | } 43 | std::stringstream ss; 44 | ss << value << prefixes[power]; 45 | return ss.str(); 46 | } 47 | 48 | template<> const char* getFileNameFilter>() { return "complex file (*.fc32)"; }; 49 | template<> const char* getFileNameFilter() { return "float file (*.f32)"; }; 50 | -------------------------------------------------------------------------------- /src/util.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016, Mike Walters 3 | * Copyright (C) 2016, Jared Boone, ShareBrained Technology, Inc. 4 | * 5 | * This file is part of inspectrum. 6 | * 7 | * This program 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 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program 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 | 21 | #pragma once 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | static const double Tau = M_PI * 2.0; 29 | 30 | template const T& clamp (const T& value, const T& min, const T& max) 31 | { 32 | return std::min(max, std::max(min, value)); 33 | } 34 | 35 | template 36 | struct iter_pair_range : std::pair { 37 | iter_pair_range(std::pair const& x) : std::pair(x) { } 38 | Iter begin() const { 39 | return this->first; 40 | } 41 | Iter end() const { 42 | return this->second; 43 | } 44 | }; 45 | 46 | template 47 | inline iter_pair_range as_range(std::pair const& x) 48 | { 49 | return iter_pair_range(x); 50 | } 51 | 52 | template 53 | struct range_t { 54 | T minimum; 55 | T maximum; 56 | 57 | range_t& operator=(const range_t &other) { 58 | minimum = other.minimum; 59 | maximum = other.maximum; 60 | return *this; 61 | } 62 | 63 | range_t& operator=(const std::initializer_list &other) { 64 | if (other.size() == 2) { 65 | minimum = *other.begin(); 66 | maximum = *(other.begin() + 1); 67 | } 68 | return *this; 69 | } 70 | 71 | const T length() { 72 | return maximum - minimum; 73 | } 74 | 75 | const T& clip(const T& value) const { 76 | return clamp(value, minimum, maximum); 77 | } 78 | 79 | void reset_if_outside(T& value, const T& reset_value) const { 80 | if( (value < minimum ) || 81 | (value > maximum ) ) { 82 | value = reset_value; 83 | } 84 | } 85 | 86 | bool below_range(const T& value) const { 87 | return value < minimum; 88 | } 89 | 90 | bool contains(const T& value) const { 91 | // TODO: Subtle gotcha here! Range test doesn't include maximum! 92 | return (value >= minimum) && (value < maximum); 93 | } 94 | 95 | bool out_of_range(const T& value) const { 96 | // TODO: Subtle gotcha here! Range test in contains() doesn't include maximum! 97 | return !contains(value); 98 | } 99 | }; 100 | 101 | std::string formatSIValue(float value); 102 | 103 | template const char* getFileNameFilter(); 104 | --------------------------------------------------------------------------------