├── .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 | 
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