├── .gitignore
├── CMakeLists.txt
├── Doxyfile
├── LICENSE
├── README.md
├── bin
└── spectrogram.exe
├── cmake-modules
├── FindFFTW3.cmake
├── FindLibSamplerate.cmake
├── FindMad.cmake
└── FindSndfile.cmake
├── examples
├── blue_danube-hudba.mp3
└── hal_9000-rec.mp3
├── fft.cpp
├── fft.hpp
├── main.cpp
├── mainwindow.cpp
├── mainwindow.hpp
├── mainwindow.ui
├── palettes
├── fiery.png
├── temperatures.png
└── wb.png
├── scripts
└── spectrogram-video.py
├── soundfile.cpp
├── soundfile.hpp
├── spectrogram.cpp
├── spectrogram.hpp
├── types.hpp
└── viewer
├── cursorlabel.py
├── ui_viewer.py
├── viewer.py
└── viewer.ui
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled Object files
2 | *.slo
3 | *.lo
4 | *.o
5 | *.obj
6 |
7 | # Precompiled Headers
8 | *.gch
9 | *.pch
10 |
11 | # Compiled Dynamic libraries
12 | *.so
13 | *.dylib
14 | *.dll
15 |
16 | # Fortran module files
17 | *.mod
18 |
19 | # Compiled Static libraries
20 | *.lai
21 | *.la
22 | *.a
23 | *.lib
24 |
25 | # Executables
26 | *.out
27 | *.app
28 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
2 |
3 | #SET(CMAKE_VERBOSE_MAKEFILE on)
4 |
5 | PROJECT(spectrogram)
6 | INCLUDE_DIRECTORIES( ${CMAKE_BINARY_DIR} )
7 |
8 | ### extra find modules for cmake
9 |
10 | LIST(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake-modules)
11 |
12 | ### files
13 |
14 | SET(spectrogram_SOURCES
15 | main.cpp
16 | mainwindow.cpp
17 | spectrogram.cpp
18 | soundfile.cpp
19 | fft.cpp
20 | )
21 | SET(spectrogram_MOC_HEADERS
22 | mainwindow.hpp
23 | spectrogram.hpp
24 | )
25 | SET(spectrogram_UIS
26 | mainwindow.ui
27 | )
28 | SET(spectrogram_RCS
29 | # none (yet?)
30 | )
31 |
32 | ### compiler options
33 |
34 | # enable warnings
35 | IF (CMAKE_COMPILER_IS_GNUCXX)
36 | ADD_DEFINITIONS("-Wall -Wextra")
37 | ENDIF (CMAKE_COMPILER_IS_GNUCXX)
38 |
39 | ### find and use Qt4
40 |
41 | # SET(QT_USE_QTXML TRUE) # XXX moduly takhle
42 | FIND_PACKAGE(Qt4 REQUIRED)
43 | ## Qt macros
44 | INCLUDE(${QT_USE_FILE})
45 | QT4_ADD_RESOURCES( spectrogram_RC_SOURCES ${spectrogram_RCS} )
46 | # this will run uic on .ui files:
47 | QT4_WRAP_UI( spectrogram_UI_HEADERS ${spectrogram_UIS} )
48 | # this will run moc:
49 | QT4_WRAP_CPP( spectrogram_MOC_SOURCES ${spectrogram_MOC_HEADERS} )
50 | INCLUDE_DIRECTORIES(${QT_INCLUDE_DIR})
51 |
52 | ### find libsndfile
53 |
54 | FIND_PACKAGE(Sndfile REQUIRED)
55 | INCLUDE_DIRECTORIES(${SNDFILE_INCLUDE_DIR})
56 |
57 | ### find libsamplerate
58 |
59 | FIND_PACKAGE(LibSamplerate REQUIRED)
60 | INCLUDE_DIRECTORIES(${SAMPLERATE_INCLUDE_DIR})
61 |
62 | ### find fftw3
63 |
64 | SET(FFTW3_FIND_QUIETLY TRUE)
65 | FIND_PACKAGE(FFTW3 REQUIRED)
66 | INCLUDE_DIRECTORIES(${FFTW3_INCLUDES})
67 |
68 | ### find libmad
69 |
70 | FIND_PACKAGE(Mad REQUIRED)
71 | INCLUDE_DIRECTORIES(${MAD_INCLUDE_DIRS})
72 |
73 | ### included libs
74 | #ADD_SUBDIRECTORY(fftw++)
75 |
76 | #### find boost
77 | #FIND_PACKAGE(Boost COMPONENTS filesystem REQUIRED)
78 | #INCLUDE_DIRECTORIES(${Boost_INCLUDE_DIR})
79 |
80 | ### debug option
81 |
82 | OPTION(spectrogram_DEBUG "Build the project with debugging code" ON) # XXX ->OFF
83 | IF(spectrogram_DEBUG)
84 | SET(CMAKE_BUILD_TYPE Debug)
85 | SET_SOURCE_FILES_PROPERTIES(${spectrogram_SOURCES} COMPILE_FLAGS -DDEBUG)
86 | ELSE(spectrogram_DEBUG)
87 | SET(CMAKE_BUILD_TYPE Release)
88 | IF(WIN32)
89 | # prevents the console window from opening on windows
90 | SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -mwindows")
91 | ENDIF(WIN32)
92 | ENDIF(spectrogram_DEBUG)
93 |
94 | ### finish
95 |
96 | ADD_EXECUTABLE(spectrogram ${spectrogram_SOURCES} ${spectrogram_MOC_SOURCES} ${spectrogram_UI_HEADERS} ${spectrogram_RC_SOURCES})
97 |
98 | TARGET_LINK_LIBRARIES(spectrogram ${QT_LIBRARIES} ${SNDFILE_LIBRARIES} ${FFTW3_LIBRARIES} ${SAMPLERATE_LIBRARIES} ${MAD_LIBRARIES})
99 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 | {description}
294 | Copyright (C) {year} {fullname}
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, write to the Free Software Foundation, Inc.,
308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309 |
310 | Also add information on how to contact you by electronic and paper mail.
311 |
312 | If the program is interactive, make it output a short notice like this
313 | when it starts in an interactive mode:
314 |
315 | Gnomovision version 69, Copyright (C) year name of author
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 | This is free software, and you are welcome to redistribute it
318 | under certain conditions; type `show c' for details.
319 |
320 | The hypothetical commands `show w' and `show c' should show the appropriate
321 | parts of the General Public License. Of course, the commands you use may
322 | be called something other than `show w' and `show c'; they could even be
323 | mouse-clicks or menu items--whatever suits your program.
324 |
325 | You should also get your employer (if you work as a programmer) or your
326 | school, if any, to sign a "copyright disclaimer" for the program, if
327 | necessary. Here is a sample; alter the names:
328 |
329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
331 |
332 | {signature of Ty Coon}, 1 April 1989
333 | Ty Coon, President of Vice
334 |
335 | This General Public License does not permit incorporating your program into
336 | proprietary programs. If your program is a subroutine library, you may
337 | consider it more useful to permit linking proprietary applications with the
338 | library. If this is what you want to do, use the GNU Lesser General
339 | Public License instead of this License.
340 |
341 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | spectrogram
2 | ===========
3 |
4 | This GUI program can generate spectrograms from sound files and synthesize spectrograms back into sound (with large loss of fidelity).
5 |
6 | The program can be built on Linux or you can use the Windows binary (bin/spectrogram.exe). The program was a part of my 2010 bachelor's thesis and there will be no more development. It was inspired heavily by the ARSS program: http://arss.sourceforge.net/
7 |
8 | 
9 |
10 | ## Spectrogram examples
11 |
12 | Some example spectrograms of classical music made with this program can be found on my youtube channel:
13 |
14 | https://www.youtube.com/watch?v=Txp-pHU2K6w
15 |
16 | https://www.youtube.com/watch?v=XJ6XXjAOvmY
17 |
18 | https://www.youtube.com/watch?v=bGtup1qCHW0
19 |
20 | There are no demos of the synthesis functionality (sound from spectrogram), but it works a lot like this: http://arss.sourceforge.net/examples.shtml
21 |
22 | ### How to make a video spectrogram like the examples
23 |
24 | To turn the spectrogram image generated by this program into a video there is a quick & dirty script in python (scripts/spectrogram-video.py) to generate each frame as a png file (takes quite long and makes many files) and then you can use this ffmpeg command (on Linux) to put it together into a video with sound (example for 25 fps):
25 |
26 | `ffmpeg -f image2 -r 25 -i ./%06d.png -i soundfile.mp3 -b 5000k -vb 5000k -acodec copy out.mp4`
27 |
28 | ## Documentation
29 |
30 | At http://krajj7.github.io/spectrogram
31 |
--------------------------------------------------------------------------------
/bin/spectrogram.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krajj7/spectrogram/f640537617d49ac368b48ea7e3ec372bf44bae10/bin/spectrogram.exe
--------------------------------------------------------------------------------
/cmake-modules/FindFFTW3.cmake:
--------------------------------------------------------------------------------
1 | #+-----------------------------------------------------------------------------+
2 | #| Copyright (C) 2007 |
3 | #| Lars B"ahren (bahren@astron.nl) |
4 | #| |
5 | #| This program is free software; you can redistribute it and/or modify |
6 | #| it under the terms of the GNU General Public License as published by |
7 | #| the Free Software Foundation; either version 2 of the License, or |
8 | #| (at your option) any later version. |
9 | #| |
10 | #| This program is distributed in the hope that it will be useful, |
11 | #| but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | #| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | #| GNU General Public License for more details. |
14 | #| |
15 | #| You should have received a copy of the GNU General Public License |
16 | #| along with this program; if not, write to the |
17 | #| Free Software Foundation, Inc., |
18 | #| 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
19 | #+-----------------------------------------------------------------------------+
20 | # modified version.
21 |
22 | # - Check for the presence of FFTW3
23 | #
24 | # The following variables are set when FFTW3 is found:
25 | # HAVE_FFTW3 = Set to true, if all components of FFTW3 have been found.
26 | # FFTW3_INCLUDES = Include path for the header files of FFTW3
27 | # FFTW3_LIBRARIES = Link these to use FFTW3
28 |
29 | ## -----------------------------------------------------------------------------
30 | ## Search locations
31 |
32 | #include (CMakeSettings)
33 |
34 | ## -----------------------------------------------------------------------------
35 | ## Check for the header files
36 |
37 | FIND_PATH (FFTW3_INCLUDES fftw3.h
38 | PATHS
39 | ${include_locations}
40 | /opt/aips++/local/include
41 | )
42 |
43 | ## -----------------------------------------------------------------------------
44 | ## Check for the library
45 |
46 | FIND_LIBRARY (FFTW3_LIBRARIES fftw3f fftwf
47 | PATHS
48 | ${lib_locations}
49 | /opt/aips++/local/lib
50 | )
51 |
52 | ## -----------------------------------------------------------------------------
53 | ## Actions taken when all components have been found
54 |
55 | IF (FFTW3_INCLUDES AND FFTW3_LIBRARIES)
56 | SET (HAVE_FFTW3 TRUE)
57 | ELSE (FFTW3_INCLUDES AND FFTW3_LIBRARIES)
58 | SET (HAVE_FFTW3 FALSE)
59 | IF (NOT FFTW3_FIND_QUIETLY)
60 | IF (NOT FFTW3_INCLUDES)
61 | MESSAGE (STATUS "Unable to find FFTW3 header files!")
62 | ENDIF (NOT FFTW3_INCLUDES)
63 | IF (NOT FFTW3_LIBRARIES)
64 | MESSAGE (STATUS "Unable to find FFTW3 library files!")
65 | ENDIF (NOT FFTW3_LIBRARIES)
66 | ENDIF (NOT FFTW3_FIND_QUIETLY)
67 | ENDIF (FFTW3_INCLUDES AND FFTW3_LIBRARIES)
68 |
69 | IF (HAVE_FFTW3)
70 | IF (NOT FFTW3_FIND_QUIETLY)
71 | MESSAGE (STATUS "Found components for FFTW3")
72 | MESSAGE (STATUS "FFTW3_INCLUDES = ${FFTW3_INCLUDES}")
73 | MESSAGE (STATUS "FFTW3_LIBRARIES = ${FFTW3_LIBRARIES}")
74 | ENDIF (NOT FFTW3_FIND_QUIETLY)
75 | ELSE (HAVE_FFTW3)
76 | IF (FFTW3_FIND_REQUIRED)
77 | MESSAGE (FATAL_ERROR "Could not find FFTW3!")
78 | ENDIF (FFTW3_FIND_REQUIRED)
79 | ENDIF (HAVE_FFTW3)
80 |
81 | ## ------------------------------------------------------------------------------
82 | ## Mark as advanced ...
83 |
84 |
--------------------------------------------------------------------------------
/cmake-modules/FindLibSamplerate.cmake:
--------------------------------------------------------------------------------
1 | # - Find SAMPLERATE
2 | # Find the native SAMPLERATE includes and libraries
3 | #
4 | # SAMPLERATE_INCLUDE_DIR - where to find SAMPLERATE.h, etc.
5 | # SAMPLERATE_LIBRARIES - List of libraries when using libSAMPLERATE.
6 | # SAMPLERATE_FOUND - True if libSAMPLERATE found.
7 |
8 | if(SAMPLERATE_INCLUDE_DIR)
9 | # Already in cache, be silent
10 | set(SAMPLERATE_FIND_QUIETLY TRUE)
11 | endif(SAMPLERATE_INCLUDE_DIR)
12 |
13 | find_path(SAMPLERATE_INCLUDE_DIR samplerate.h)
14 |
15 | find_library(SAMPLERATE_LIBRARY NAMES samplerate)
16 |
17 | # Handle the QUIETLY and REQUIRED arguments and set SAMPLERATE_FOUND to TRUE if
18 | # all listed variables are TRUE.
19 | include(FindPackageHandleStandardArgs)
20 | find_package_handle_standard_args(SAMPLERATE DEFAULT_MSG
21 | SAMPLERATE_INCLUDE_DIR SAMPLERATE_LIBRARY)
22 |
23 | if(SAMPLERATE_FOUND)
24 | set(SAMPLERATE_LIBRARIES ${SAMPLERATE_LIBRARY})
25 | else(SAMPLERATE_FOUND)
26 | set(SAMPLERATE_LIBRARIES)
27 | endif(SAMPLERATE_FOUND)
28 |
29 | #mark_as_advanced(SAMPLERATE_INCLUDE_DIR SAMPLERATE_LIBRARY)
30 |
--------------------------------------------------------------------------------
/cmake-modules/FindMad.cmake:
--------------------------------------------------------------------------------
1 | # MAD_INCLUDE_DIRS - where to find mad/mad.h, etc.
2 | # MAD_LIBRARIES - List of libraries when using mad.
3 | # MAD_FOUND - True if mad found.
4 |
5 | # Look for the header file.
6 | FIND_PATH(MAD_INCLUDE_DIR NAMES mad.h)
7 | #MARK_AS_ADVANCED(MAD_INCLUDE_DIR)
8 |
9 | # Look for the library.
10 | FIND_LIBRARY(MAD_LIBRARY NAMES mad)
11 | #MARK_AS_ADVANCED(MAD_LIBRARY)
12 |
13 | # Copy the results to the output variables.
14 | IF(MAD_INCLUDE_DIR AND MAD_LIBRARY)
15 | SET(MAD_FOUND 1)
16 | SET(MAD_LIBRARIES ${MAD_LIBRARY})
17 | SET(MAD_INCLUDE_DIRS ${MAD_INCLUDE_DIR})
18 | ELSE(MAD_INCLUDE_DIR AND MAD_LIBRARY)
19 | SET(MAD_FOUND 0)
20 | SET(MAD_LIBRARIES)
21 | SET(MAD_INCLUDE_DIRS)
22 | ENDIF(MAD_INCLUDE_DIR AND MAD_LIBRARY)
23 |
24 | # Report the results.
25 | IF(NOT MAD_FOUND)
26 | SET(MAD_DIR_MESSAGE
27 | "libmad was not found. Make sure MAD_LIBRARY and MAD_INCLUDE_DIR are set.")
28 | IF(NOT MAD_FIND_QUIETLY)
29 | MESSAGE(STATUS "${MAD_DIR_MESSAGE}")
30 | ELSE(NOT MAD_FIND_QUIETLY)
31 | IF(MAD_FIND_REQUIRED)
32 | MESSAGE(FATAL_ERROR "${MAD_DIR_MESSAGE}")
33 | ENDIF(MAD_FIND_REQUIRED)
34 | ENDIF(NOT MAD_FIND_QUIETLY)
35 | ENDIF(NOT MAD_FOUND)
36 |
37 |
--------------------------------------------------------------------------------
/cmake-modules/FindSndfile.cmake:
--------------------------------------------------------------------------------
1 | ## from the Allegro project
2 |
3 | # - Find sndfile
4 | # Find the native sndfile includes and libraries
5 | #
6 | # SNDFILE_INCLUDE_DIR - where to find sndfile.h, etc.
7 | # SNDFILE_LIBRARIES - List of libraries when using libsndfile.
8 | # SNDFILE_FOUND - True if libsndfile found.
9 |
10 | if(SNDFILE_INCLUDE_DIR)
11 | # Already in cache, be silent
12 | set(SNDFILE_FIND_QUIETLY TRUE)
13 | endif(SNDFILE_INCLUDE_DIR)
14 |
15 | find_path(SNDFILE_INCLUDE_DIR sndfile.h)
16 |
17 | find_library(SNDFILE_LIBRARY NAMES sndfile)
18 |
19 | # Handle the QUIETLY and REQUIRED arguments and set SNDFILE_FOUND to TRUE if
20 | # all listed variables are TRUE.
21 | include(FindPackageHandleStandardArgs)
22 | find_package_handle_standard_args(SNDFILE DEFAULT_MSG
23 | SNDFILE_INCLUDE_DIR SNDFILE_LIBRARY)
24 |
25 | if(SNDFILE_FOUND)
26 | set(SNDFILE_LIBRARIES ${SNDFILE_LIBRARY})
27 | else(SNDFILE_FOUND)
28 | set(SNDFILE_LIBRARIES)
29 | endif(SNDFILE_FOUND)
30 |
31 | #mark_as_advanced(SNDFILE_INCLUDE_DIR SNDFILE_LIBRARY)
32 |
--------------------------------------------------------------------------------
/examples/blue_danube-hudba.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krajj7/spectrogram/f640537617d49ac368b48ea7e3ec372bf44bae10/examples/blue_danube-hudba.mp3
--------------------------------------------------------------------------------
/examples/hal_9000-rec.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krajj7/spectrogram/f640537617d49ac368b48ea7e3ec372bf44bae10/examples/hal_9000-rec.mp3
--------------------------------------------------------------------------------
/fft.cpp:
--------------------------------------------------------------------------------
1 | #include "fft.hpp"
2 | #include
3 |
4 | namespace
5 | {
6 | /// Returns 1 if x is only made of small primes.
7 | int smallprimes(int x)
8 | {
9 | int p[3] = {2, 3, 5};
10 | for (int i = 0; i < 3; ++i)
11 | while (x%p[i] == 0)
12 | x /= p[i];
13 | return x;
14 | }
15 |
16 | /// Returns the next integer only made of small primes.
17 | int padded_size(int x)
18 | {
19 | while (smallprimes(x)!=1)
20 | x++;
21 |
22 | return x;
23 | }
24 | }
25 |
26 | complex_vec padded_FFT(real_vec& in)
27 | {
28 | assert(in.size() > 0);
29 | const size_t n = in.size();
30 | const size_t padded = n > 256 ? padded_size(n) : n;
31 | in.resize(padded);
32 |
33 | complex_vec out(padded/2+1);
34 |
35 | fftwf_plan plan = fftwf_plan_dft_r2c_1d(padded,
36 | &in[0], (fftwf_complex*)&out[0], FFTW_ESTIMATE);
37 | fftwf_execute(plan);
38 | fftwf_destroy_plan(plan);
39 |
40 | in.resize(n);
41 | return out;
42 | }
43 |
44 | real_vec padded_IFFT(complex_vec& in)
45 | {
46 | assert(in.size() > 1);
47 | const size_t n = (in.size()-1)*2;
48 | const size_t padded = n > 256 ? padded_size(n) : n;
49 | in.resize(padded/2+1);
50 |
51 | real_vec out(padded);
52 |
53 | // note: fftw3 destroys the input array for c2r transform
54 | fftwf_plan plan = fftwf_plan_dft_c2r_1d(padded,
55 | (fftwf_complex*)&in[0], &out[0], FFTW_ESTIMATE);
56 | fftwf_execute(plan);
57 | fftwf_destroy_plan(plan);
58 |
59 | in.resize(n/2+1);
60 | return out;
61 | }
62 |
--------------------------------------------------------------------------------
/fft.hpp:
--------------------------------------------------------------------------------
1 | #ifndef FFT_HPP
2 | #define FFT_HPP
3 |
4 | /** \file fft.hpp
5 | * \brief Contains utility functions for performing the fast fourier transform and its inverse.
6 | *
7 | * It uses the FFTW3 library to perform the transforms. For better performance, the functions temporarily change the size of the input vector by padding it with zeros to a size that can be expressed as a product of small primes, that is 2^x * 3^y * 5^z.
8 | */
9 |
10 | #include
11 | #include
12 | #include
13 | #include "types.hpp"
14 |
15 | /// Performs a fast fourier transform.
16 | /** The input vector is padded with zeros for better performance and shrunk again to original size when the transform is done. */
17 | complex_vec padded_FFT(real_vec& in);
18 | /// Performs a fast inverse fourier transform.
19 | /** The input vector is destroyed in the process! */
20 | real_vec padded_IFFT(complex_vec& in);
21 |
22 | #endif
23 |
--------------------------------------------------------------------------------
/main.cpp:
--------------------------------------------------------------------------------
1 | /** \mainpage %Spectrogram -- documentation
2 | * This program can generate spectrograms from sound files and synthesize
3 | * spectrograms back into sound.
4 | *
5 | * The manual is divided into two parts:
6 | * \li \subpage user
7 | * \li \subpage prog
8 | */
9 | /** \page prog Compilation and code overview
10 | * \section compiling Compiling the program
11 | * The program can be compiled with g++ on Linux or with mingw on Windows.
12 | *
13 | * \subsection deps Dependencies
14 | * The project uses the CMake build system: http://www.cmake.org
15 | *
16 | * The development versions of the following libraries need to be usable
17 | * before compiling:
18 | * \li Qt4 (used for the GUI): http://www.qtsoftware.com/products
19 | * \li FFTW (the single-precision version, used for fast fourier transform):
20 | * http://www.fftw.org
21 | * \li SRC (aka libsamplerate, used for audio resampling):
22 | * http://www.mega-nerd.com/SRC/
23 | * \li libsndfile (for many audio formats support):
24 | * http://mega-nerd.com/libsndfile
25 | * \li MAD (for mp3 support): http://www.underbit.com/products/mad/
26 | *
27 | * In Debian for example, you can run the following command to install all the
28 | * dependencies: apt-get install cmake libqt4-dev libfftw3-dev
29 | * libsndfile-dev libsamplerate-dev libmad0-dev
30 | *
31 | * For flac and ogg format support libsndfile version 1.0.18 or higher with
32 | * libflac and libogg plugins built-in has to be used.
33 | *
34 | * \subsection Linux Linux
35 | * To prepare the makefile, go to the \c build directory in the source tree and
36 | * type cmake ..
37 | *
38 | * If there are no errors (like missing libraries), you can build the program
39 | * by typing \c make
40 | *
41 | * The executable \c spectrogram will appear in the \c build directory.
42 | *
43 | * \subsection Windows Windows
44 | * Besides the dependencies you should have MinGW and MSYS installed.
45 | *
46 | * Once you have all the dependencies configured and compiled with mingw, start
47 | * the cmake-gui program and enter the source code directory and use the \c
48 | * build directory to build the binaries in.
49 | *
50 | * Then hit "Configure" and use the "MSYS Makefiles" generator. For each
51 | * library that wasn't found automatically, enter the path manually and
52 | * continue with the configuration process.
53 | *
54 | * Once configuring is done, press "Generate"
55 | *
56 | * A Makefile now appears in the build directory. Navigate to that directory in the MSYS shell and type \c make to compile the program.
57 | *
58 | * \section code Code overview
59 | * The most interesting class of the program is Spectrogram, it holds the
60 | * parameters for a spectrogram and performs analysis (turning sounds to
61 | * images) and synthesis (turning images to sounds).
62 | *
63 | * The MainWindow class handles the GUI. The MainWindow::ui member is used to
64 | * access the widgets as designed in mainwindow.ui created with Qt Designer.
65 | * In the main window the user can specify parameters for the spectrogram and
66 | * supply audio data or a spectrogram to work with. The analysis and synthesis
67 | * itself is then performed in a separate thread to keep the GUI responsive.
68 | *
69 | * The Soundfile class provides abstraction for working with audio files. It
70 | * implements high-level operations like reading a channel of audio data from a
71 | * given file. It aggregates all implementations of SndfileData.
72 | *
73 | * SndfileData is an abstract class whose implementations perform low-level
74 | * format-dependent operations. Different libraries can be used to implement
75 | * the class and thus provide support for multiple audio formats.
76 | *
77 | * For more details, see documentation of these classes.
78 | */
79 |
80 | /** \page user User documentation
81 | * \section Introduction
82 | * All the functionality of the program is available from the main window. In
83 | * this window you can configure parameters for spectrogram generation or
84 | * synthesis and supply the data.
85 | *
86 | * Progress of long operations is indicated on the right side of the window.
87 | * The \c X button can be used to interrupt an operation that is taking too
88 | * long.
89 | *
90 | * \section analysis Spectrogram analysis
91 | * To turn a sound to a spectrogram, select the sound file in the upper right
92 | * part of the window. Many sound files are stereo, which will appear as two
93 | * channels you can choose from. The "Length" and "Samplerate" indicators are
94 | * purely informative.
95 | *
96 | * Depending on the purpose of the spectrogram and the nature of the supplied
97 | * audio data, different parameters are optimal. The meaning of the main
98 | * parameters is explained below. If you hover your mouse over a parameter
99 | * dial, an explanaition appears as a tooltip.
100 | *
101 | * \li Frequency scale: This setting determines the type of the
102 | * frequency (vertical) axis. Human hearing is logarithmic in nature. For
103 | * music and other audio that contains a high range of frequencies, logarithmic
104 | * frequency scale is a good choice. For speech or artificial sounds, linear
105 | * scale can also be used with good results.
106 | * \li Intensity scale This affects the mapping of sound intensity to
107 | * pixel brightness. The logarithmic setting is better for sounds with high
108 | * range of loundess where a linear setting would make the spectrogram too
109 | * dark.
110 | * \li Base frequency For a logarithmic frequency spectrogram, the
111 | * first band (the row of the spectrogram) will be centered at this frequency.
112 | * For a linear frequency spectrogram, the first band starts at the this
113 | * frequency.
114 | * \li Maximum frequency Sets the top frequency displayed in the
115 | * spectrogram. For speech, value of about 8000 Hz can be sufficient.
116 | * \li Pixels per second Determines the time resolution of the
117 | * spectrogram. The larger the value, the wider the spectrogram. For
118 | * synthesis, 100 or above is recommended. For viewing, 50 can be sufficient
119 | * and make the spectrogram more "compact" with long sound samples.
120 | * \li Brightness correction Some spectrograms can be very dark even
121 | * with logarithmic intensity scale. Using the square root brightness
122 | * correction will make the spectrogram easier to read, but may affect
123 | * synthesis quality.
124 | * \li Bandwidth Each horizontal band of the spectrogram will be as
125 | * wide as set here. Lower value means more detail in the frequency domain,
126 | * but less detail in the time domain.
127 | * \li Window function: Window function is applied to the frequency
128 | * intervals of the given bandwidth to lessen artifacts on their edges. The
129 | * Hann window is a good general choice.
130 | * \li Overlap: Larger overlap gives more detail in the frequency
131 | * domain and makes the spectrogram taller. If no window function is used, it
132 | * can be set to zero, otherwise setting at least 60% overlap is recommended.
133 | * \li %Palette: Shows the colors in which the spectrogram will be
134 | * drawn. You can supply your own palette from an image, in that case the
135 | * first row of pixels of the image is used. For synthesis, the colors in the
136 | * palette shouldn't repeat to make the intensity -> color mapping unambiguous.
137 | *
138 | * Once you are happy with the parameters, click the "Make spectrogram"
139 | * button. A preview will appear and you can save the resulting image.
140 | *
141 | * \section synthesis Spectrogram synthesis
142 | * To turn a spectrogram back into sound, first select the spectrogram image in
143 | * the lower right.
144 | *
145 | * The parameters and palette of the spectrogram should be set to the same
146 | * values as were used for its generation. If the spectrogram was generated by
147 | * this program, the parameters will be loaded automatically from metadata
148 | * saved in the image. You can work with spectrograms from different sources
149 | * too, in which case you need to know or guess the parameters with which they
150 | * have been created.
151 | *
152 | * Two modes of synthesis are provided:
153 | * \li Sine synthesis is fast and produces decent results.
154 | * \li Noise synthesis is slower but may give better results for "busy"
155 | * spectrograms.
156 | *
157 | * When the parameters are set, you can press "Make sound" to synthesize the
158 | * chosen spectrogram. After it's finished, you can save the resulting sound
159 | * file.
160 | *
161 | * \section formats Supported file formats
162 | * The program supports most commonly used sound file formats like mp3, wav,
163 | * flac and ogg. For the last two the build has to be linked with the
164 | * libsndfile library version 1.0.18 or higher with libflac and libogg
165 | * plugins built-in.
166 | * See http://www.mega-nerd.com/libsndfile/#Features for a full list of
167 | * sound file formats supported via libsndfile. MP3 is supported via libmad.
168 | *
169 | * Certain MP3 files with variable bitrate can display the wrong length in the
170 | * GUI. They can still be used to generate spectrograms without problems.
171 | *
172 | * Most commonly used image formats are supported, for example png, bmp, tiff,
173 | * xpm, jpg (read only), gif (read only) and others.
174 | * See http://doc.trolltech.com/4.6/qimage.html#reading-and-writing-image-files
175 | * for a full list of supported image formats.
176 | */
177 |
178 | /** \file main.cpp
179 | * \brief Code to start up the application. Also contains the main
180 | * documentation.
181 | */
182 |
183 | #include
184 | #include
185 | #include
186 |
187 | #include "soundfile.hpp"
188 | #include "mainwindow.hpp"
189 | #include "spectrogram.hpp"
190 |
191 | // testing functions
192 | namespace
193 | {
194 | void image_test()
195 | {
196 | //Soundfile file("/home/jan/ads/violin.ogg");
197 | Soundfile file("/home/jan/music/Windir/1999-Arntor/01-Byrjing.mp3");
198 | Spectrogram spec;
199 | //spec.palette = Palette("/home/jan/spectrogram/palettes/fiery.png");
200 | real_vec signal = file.read_channel(0);
201 | QImage out = spec.to_image(signal, file.data().samplerate());
202 | out.save("out.png");
203 | }
204 |
205 | void synt_test()
206 | {
207 | QImage img = QImage("/home/jan/spectrogram/out.png");
208 | //QImage img = QImage("/home/jan/or/vion-xx6.png");
209 | assert(!img.isNull());
210 | Spectrogram spec;
211 | real_vec data = spec.synthetize(img, 44100, SYNTHESIS_SINE);
212 | std::cout << "hotovo: "<
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include "mainwindow.hpp"
11 | #include
12 | #include
13 |
14 | namespace
15 | {
16 | void setCombo(QComboBox* combo, int value)
17 | {
18 | for (int index = 0; index < combo->count(); ++index)
19 | if (combo->itemData(index) == value)
20 | combo->setCurrentIndex(index);
21 | }
22 | }
23 |
24 | MainWindow::MainWindow()
25 | {
26 | ui.setupUi(this);
27 |
28 | QCompleter* completer = new QCompleter(ui.locationEdit);
29 | completer->setModel(new QDirModel(completer));
30 | ui.locationEdit->setCompleter(completer);
31 | ui.speclocEdit->setCompleter(completer);
32 |
33 | resetSoundfile();
34 | connect(ui.locationEdit, SIGNAL(editingFinished()),
35 | this, SLOT(loadSoundfile()));
36 | connect(ui.locationButton, SIGNAL(clicked()),
37 | this, SLOT(chooseSoundfile()));
38 |
39 | connect(ui.paletteButton, SIGNAL(clicked()), this, SLOT(choosePalette()));
40 |
41 | connect(ui.specSaveAsButton, SIGNAL(clicked()), this, SLOT(saveImage()));
42 | connect(ui.speclocButton, SIGNAL(clicked()), this, SLOT(chooseImage()));
43 | connect(ui.makeButton, SIGNAL(clicked()), this, SLOT(makeSpectrogram()));
44 |
45 | //connect(ui.soundSaveAsButton,SIGNAL(clicked()),this, SLOT(saveSoundfile()));
46 | connect(ui.makeSoundButton, SIGNAL(clicked()), this, SLOT(makeSound()));
47 |
48 | ui.intensityCombo->addItem("logarithmic", (int)SCALE_LOGARITHMIC);
49 | ui.intensityCombo->addItem("linear", (int)SCALE_LINEAR);
50 |
51 | ui.frequencyCombo->addItem("logarithmic", (int)SCALE_LOGARITHMIC);
52 | ui.frequencyCombo->addItem("linear", (int)SCALE_LINEAR);
53 | connect(ui.frequencyCombo, SIGNAL(currentIndexChanged(int)),
54 | this, SLOT(setFilterUnits(int)));
55 |
56 | ui.windowCombo->addItem("Hann", (int)WINDOW_HANN);
57 | ui.windowCombo->addItem("Blackman", (int)WINDOW_BLACKMAN);
58 | ui.windowCombo->addItem("Triangular", (int)WINDOW_TRIANGULAR);
59 | ui.windowCombo->addItem("Rectangular (none)", (int)WINDOW_RECTANGULAR);
60 |
61 | ui.syntCombo->addItem("sine", (int)SYNTHESIS_SINE);
62 | ui.syntCombo->addItem("noise", (int)SYNTHESIS_NOISE);
63 |
64 | ui.brightCombo->addItem("none", (int)BRIGHT_NONE);
65 | ui.brightCombo->addItem("square root", (int)BRIGHT_SQRT);
66 |
67 | spectrogram = new Spectrogram(this);
68 | connect(ui.cancelButton, SIGNAL(clicked()), spectrogram, SLOT(cancel()));
69 | connect(spectrogram, SIGNAL(progress(int)),
70 | ui.specProgress, SLOT(setValue(int)));
71 | connect(spectrogram, SIGNAL(status(const QString&)),
72 | ui.specStatus, SLOT(setText(const QString&)));
73 | setValues();
74 |
75 | image_watcher = new QFutureWatcher(this);
76 | connect(image_watcher, SIGNAL(finished()), this, SLOT(newSpectrogram()));
77 | sound_watcher = new QFutureWatcher(this);
78 | connect(sound_watcher, SIGNAL(finished()), this, SLOT(newSound()));
79 |
80 | ui.lengthEdit->setDisplayFormat("hh:mm:ss");
81 |
82 | idleState();
83 | }
84 |
85 | void MainWindow::resetSoundfile()
86 | {
87 | soundfile.reset();
88 | ui.lengthEdit->setTime(QTime(0,0,0));
89 | ui.channelsEdit->setText("0");
90 | ui.channelSpin->setMaximum(0);
91 | ui.samplerateSpin->setValue(0);
92 | }
93 |
94 | void MainWindow::loadSoundfile()
95 | {
96 | const QString& filename = ui.locationEdit->text();
97 | if (filename.isEmpty())
98 | return;
99 | soundfile.load(filename);
100 | if (!soundfileOk())
101 | {
102 | QString error;
103 | QTextStream serror(&error);
104 | serror << "The specified file is not readable or not supported.";
105 | if (!soundfile.error().isEmpty())
106 | serror << "\n\n" << soundfile.error();
107 | QMessageBox::warning(this, "Invalid file", error);
108 | return;
109 | }
110 | updateSoundfile();
111 | }
112 |
113 | void MainWindow::updateSoundfile()
114 | {
115 | if (soundfileOk())
116 | {
117 | ui.lengthEdit->setTime(QTime().addSecs((int)soundfile.data().length()));
118 | ui.channelSpin->setMinimum(1);
119 | ui.channelSpin->setMaximum(soundfile.data().channels());
120 | ui.channelsEdit->setText(QString::number(soundfile.data().channels()));
121 | ui.samplerateSpin->setValue(soundfile.data().samplerate());
122 | }
123 | else
124 | resetSoundfile();
125 | }
126 |
127 | bool MainWindow::soundfileOk()
128 | {
129 | return soundfile.valid();
130 | }
131 |
132 | void MainWindow::choosePalette()
133 | {
134 | QString filename = QFileDialog::getOpenFileName(this,
135 | "Choose the palette image", ".",
136 | "Images (*.png *.jpg *.bmp *.gif);;All files (*.*)");
137 | if (filename == NULL)
138 | return;
139 | QImage img(filename);
140 | if (img.isNull())
141 | {
142 | QMessageBox::warning(this, "Invalid image",
143 | "The picture format was not recognised.");
144 | return;
145 | }
146 | spectrogram->palette = Palette(img);
147 | updatePalette();
148 | }
149 |
150 | void MainWindow::chooseSoundfile()
151 | {
152 | QString filename = QFileDialog::getOpenFileName(this,
153 | "Choose the sound file", ".",
154 | "Sound files (*.wav *.mp3 *.ogg *.flac);;All files (*.*)");
155 | if (filename == NULL)
156 | return;
157 | ui.locationEdit->setText(filename);
158 | loadSoundfile();
159 | }
160 |
161 | void MainWindow::updatePalette()
162 | {
163 | ui.paletteLabel->setPixmap(spectrogram->palette.preview(
164 | spectrogram->palette.numColors(), ui.paletteLabel->height()));
165 | }
166 |
167 | void MainWindow::saveImage()
168 | {
169 | if (image.isNull())
170 | {
171 | QMessageBox::warning(this, "Couldn't save file",
172 | "There is nothing to save yet.");
173 | return;
174 | }
175 | QString filename =
176 | QFileDialog::getSaveFileName(this, "Save spectrogram",
177 | "spectrogram.png", "Images (*.png *.xpm)");
178 | if (filename == NULL)
179 | return;
180 | if (filename.endsWith(".jpg") || filename.endsWith(".JPG"))
181 | {
182 | QMessageBox::warning(this, "Couldn't save file",
183 | "JPG is not supported for writing. As a lossy compression format, it is a poor choice for spectrograms anyway.");
184 | return;
185 | }
186 | if (!filename.contains("."))
187 | filename.append(".png");
188 | const bool worked = image.save(filename);
189 | if (!worked)
190 | {
191 | QMessageBox::warning(this, "Couldn't save file",
192 | "The file could not be saved at the specified location, or you specified a not supported format extension.");
193 | return;
194 | }
195 | ui.speclocEdit->setText(filename);
196 | }
197 |
198 | void MainWindow::makeSpectrogram()
199 | {
200 | if (!soundfileOk())
201 | {
202 | QMessageBox::warning(this, "No sound file",
203 | "Choose a valid sound file first.");
204 | return;
205 | }
206 |
207 | loadValues();
208 |
209 | if (!checkAnalysisValues())
210 | return;
211 |
212 | workingState();
213 |
214 | const int channelidx = ui.channelSpin->value()-1;
215 | ui.specStatus->setText("Loading sound file");
216 | qApp->processEvents();
217 | real_vec signal = soundfile.read_channel(channelidx);
218 | if (!signal.size())
219 | {
220 | QMessageBox::warning(this, "Error", "Error reading sound file.");
221 | idleState();
222 | return;
223 | }
224 |
225 | QFuture future = QtConcurrent::run(spectrogram,
226 | &Spectrogram::to_image, signal, soundfile.data().samplerate());
227 | image_watcher->setFuture(future);
228 | }
229 |
230 | void MainWindow::loadValues()
231 | {
232 | spectrogram->bandwidth = ui.bandwidthSpin->value();
233 | spectrogram->basefreq = ui.basefreqSpin->value();
234 | spectrogram->maxfreq = ui.maxfreqSpin->value();
235 | spectrogram->overlap = ui.overlapSpin->value()/100;
236 | spectrogram->pixpersec = ui.ppsSpin->value();
237 | spectrogram->window = (Window)ui.windowCombo->
238 | itemData(ui.windowCombo->currentIndex()).toInt();
239 | spectrogram->frequency_axis = (AxisScale)ui.frequencyCombo->
240 | itemData(ui.frequencyCombo->currentIndex()).toInt();
241 | spectrogram->intensity_axis = (AxisScale)ui.intensityCombo->
242 | itemData(ui.intensityCombo->currentIndex()).toInt();
243 | spectrogram->correction = (BrightCorrection)ui.brightCombo->
244 | itemData(ui.brightCombo->currentIndex()).toInt();
245 | }
246 |
247 | void MainWindow::newSpectrogram()
248 | {
249 | if (!image_watcher->future().result().isNull()) // cancelled?
250 | {
251 | image = image_watcher->future().result();
252 | ui.speclocEdit->setText("unsaved");
253 | updateImage();
254 | }
255 | idleState();
256 | }
257 |
258 | void MainWindow::workingState()
259 | {
260 | ui.specProgress->setValue(0);
261 | ui.specStatus->setText("Idle");
262 | ui.cancelButton->setEnabled(true);
263 | ui.makeButton->setEnabled(false);
264 | ui.makeSoundButton->setEnabled(false);
265 | ui.speclocButton->setEnabled(false);
266 | ui.paletteButton->setEnabled(false);
267 | ui.locationButton->setEnabled(false);
268 | }
269 |
270 | void MainWindow::idleState()
271 | {
272 | ui.specProgress->setValue(0);
273 | ui.specStatus->setText("Idle");
274 | ui.cancelButton->setEnabled(false);
275 | ui.makeButton->setEnabled(true);
276 | ui.makeSoundButton->setEnabled(true);
277 | ui.speclocButton->setEnabled(true);
278 | ui.paletteButton->setEnabled(true);
279 | ui.locationButton->setEnabled(true);
280 | }
281 |
282 | void MainWindow::setFilterUnits(int index)
283 | {
284 | AxisScale scale = (AxisScale)ui.frequencyCombo->itemData(index).toInt();
285 | switch (scale)
286 | {
287 | case SCALE_LINEAR:
288 | ui.bandwidthSpin->setSuffix(" Hz");
289 | break;
290 | case SCALE_LOGARITHMIC:
291 | ui.bandwidthSpin->setSuffix(" cents");
292 | break;
293 | }
294 | }
295 |
296 | void MainWindow::chooseImage()
297 | {
298 | QString filename = QFileDialog::getOpenFileName(this,
299 | "Choose the spectrogram", ".",
300 | "Images (*.png *.jpg *.bmp *.gif);;All files (*.*)");
301 | if (filename == NULL)
302 | return;
303 | ui.speclocEdit->setText(filename);
304 | loadImage();
305 | }
306 |
307 | bool MainWindow::imageOk()
308 | {
309 | return !image.isNull();
310 | }
311 |
312 | void MainWindow::loadImage()
313 | {
314 | QString filename = ui.speclocEdit->text();
315 | if (filename.isEmpty())
316 | return;
317 | image.load(filename);
318 | if (!imageOk())
319 | {
320 | QMessageBox::warning(this, "Invalid file",
321 | "The specified file is not readable or not supported.");
322 | return;
323 | }
324 | QString params = image.text("Spectrogram");
325 | if (!params.isNull())
326 | {
327 | spectrogram->deserialize(params);
328 | setValues();
329 | }
330 |
331 | updateImage();
332 | }
333 |
334 | void MainWindow::resetImage()
335 | {
336 | ui.speclocEdit->setText("");
337 | ui.sizeEdit->setText("");
338 | ui.spectrogramLabel->setText("");
339 | }
340 |
341 | void MainWindow::updateImage()
342 | {
343 | if (imageOk())
344 | {
345 | if (image.width() > 30000)
346 | ui.spectrogramLabel->setText("Image too large to preview");
347 | else
348 | ui.spectrogramLabel->setPixmap(QPixmap::fromImage(image));
349 | QString sizetext_;
350 | QTextStream sizetext(&sizetext_);
351 | sizetext << image.width() << "x" << image.height() << " px";
352 | ui.sizeEdit->setText(sizetext_);
353 | }
354 | else
355 | resetImage();
356 | }
357 |
358 | void MainWindow::saveSoundfile(const real_vec& signal)
359 | {
360 | QString filename;
361 | while (filename.isNull())
362 | {
363 | filename = QFileDialog::getSaveFileName(this, "Save sound",
364 | "synt.wav", "Sound (*.wav *.ogg *.flac)");
365 | QMessageBox msg;
366 | msg.setText("If you don't save the sound, it will be discarded.");
367 | msg.setIcon(QMessageBox::Warning);
368 | msg.setStandardButtons(QMessageBox::Discard|QMessageBox::Save);
369 | msg.setDefaultButton(QMessageBox::Discard);
370 | msg.setEscapeButton(QMessageBox::Discard);
371 | if (filename.isNull() && msg.exec() == QMessageBox::Discard)
372 | return;
373 | }
374 | const int samplerate = 44100;
375 | //const int samplerate = ui.samplerateSpin->value();
376 | Soundfile::writeSound(filename, signal, samplerate);
377 | ui.locationEdit->setText(filename);
378 | }
379 |
380 | void MainWindow::makeSound()
381 | {
382 | if (!imageOk())
383 | {
384 | QMessageBox::warning(this, "No spectrogram",
385 | "Choose or generate a spectrogram first.");
386 | return;
387 | }
388 |
389 | loadValues();
390 | if (!checkSynthesisValues())
391 | return;
392 |
393 | workingState();
394 | SynthesisType type = (SynthesisType)ui.syntCombo->
395 | itemData(ui.syntCombo->currentIndex()).toInt();
396 | //const int samplerate = ui.samplerateSpin->value();
397 | const int samplerate = 44100;
398 | QFuture future = QtConcurrent::run(spectrogram,
399 | &Spectrogram::synthetize, image, samplerate, type);
400 | sound_watcher->setFuture(future);
401 | }
402 |
403 | void MainWindow::newSound()
404 | {
405 | if (sound_watcher->future().result().size()) // cancelled?
406 | {
407 | saveSoundfile(sound_watcher->future().result());
408 | loadSoundfile();
409 | }
410 |
411 | idleState();
412 | }
413 |
414 | void MainWindow::setValues()
415 | {
416 | ui.bandwidthSpin->setValue((int)spectrogram->bandwidth);
417 | ui.basefreqSpin->setValue(spectrogram->basefreq);
418 | ui.maxfreqSpin->setValue(spectrogram->maxfreq);
419 | ui.overlapSpin->setValue(spectrogram->overlap*100);
420 | ui.ppsSpin->setValue((int)spectrogram->pixpersec);
421 | setCombo(ui.windowCombo, spectrogram->window);
422 | setCombo(ui.intensityCombo, spectrogram->intensity_axis);
423 | setCombo(ui.frequencyCombo, spectrogram->frequency_axis);
424 | setCombo(ui.brightCombo, spectrogram->correction);
425 | updatePalette();
426 | }
427 |
428 | bool MainWindow::checkAnalysisValues()
429 | {
430 | QStringList errors;
431 | if (spectrogram->maxfreq > soundfile.data().samplerate()/2)
432 | {
433 | errors.append("Maximum frequency of the spectrogram has to be at most half the sampling frequency (aka. Nyquist frequency) of the sound file. It will be changed automatically if you continue.");
434 | spectrogram->maxfreq = soundfile.data().samplerate()/2;
435 | }
436 | if (spectrogram->frequency_axis == SCALE_LOGARITHMIC &&
437 | spectrogram->basefreq == 0 )
438 | {
439 | errors.append("Base frequency of a logarithmic spectrogram has to be larger than zero. It will be set to 27.5 hz.");
440 | spectrogram->basefreq = 27.5;
441 | }
442 | if (spectrogram->window != WINDOW_RECTANGULAR && spectrogram->overlap < 0.4)
443 | {
444 | errors.append("The specified overlap is likely insufficient for use with the selected window function.");
445 | }
446 | const size_t size = soundfile.data().length()*spectrogram->pixpersec;
447 | if (size > 30000)
448 | {
449 | errors.append(QString());
450 | QTextStream(&errors[errors.size()-1]) << "The resulting spectrogram will be very large (" << size << " px), you may have problems viewing it. Try lowering the Pixels per second value or using a shorter sound.";
451 | }
452 |
453 | return confirmWarnings(errors);
454 | }
455 |
456 | bool MainWindow::confirmWarnings(const QStringList& errors)
457 | {
458 | if (errors.size())
459 | {
460 | QMessageBox msg;
461 | msg.setWindowTitle("Please note...");
462 | msg.setIcon(QMessageBox::Warning);
463 | msg.setText(errors.join("\n\n"));
464 | msg.setStandardButtons(QMessageBox::Ok|QMessageBox::Abort);
465 | msg.setDefaultButton(QMessageBox::Ok);
466 | msg.setEscapeButton(QMessageBox::Abort);
467 | int res = msg.exec();
468 | if (res == QMessageBox::Ok)
469 | setValues();
470 | else if (res == QMessageBox::Abort)
471 | return false;
472 | }
473 | return true;
474 | }
475 |
476 | bool MainWindow::checkSynthesisValues()
477 | {
478 | QStringList errors;
479 | size_t badcolors = 0;
480 | for (int x = 0; x < image.width(); ++x)
481 | for (int y = 0; y < image.height(); ++y)
482 | if (!spectrogram->palette.has_color(image.pixel(x,y)))
483 | ++badcolors;
484 | if (badcolors)
485 | {
486 | errors.append(QString());
487 | QTextStream(&errors[errors.size()-1]) << "The spectrogram contains "<< badcolors << (badcolors > 1 ? " pixels":" pixel") <<" whose color is not in the selected palette. Unknown colors are assumed to be zero intensity. Synthesis quality will likely be affected.";
488 | }
489 |
490 | return confirmWarnings(errors);
491 | }
492 |
493 |
--------------------------------------------------------------------------------
/mainwindow.hpp:
--------------------------------------------------------------------------------
1 | #ifndef MAINWINDOW_HPP
2 | #define MAINWINDOW_HPP
3 |
4 | /** \file mainwindow.hpp
5 | * \brief Definitions for everything that has to do with GUI.*/
6 |
7 | #include
8 | #include "spectrogram.hpp"
9 | #include "ui_mainwindow.h"
10 |
11 | /// Represents the main application window.
12 | class MainWindow : public QMainWindow
13 | {
14 | Q_OBJECT
15 | public:
16 | MainWindow();
17 | private:
18 | Ui::MainWindow ui;
19 | Soundfile soundfile;
20 | bool soundfileOk();
21 | QImage image;
22 | bool imageOk();
23 | Spectrogram* spectrogram;
24 | void setValues();
25 | void loadValues();
26 | void updatePalette();
27 |
28 | void workingState();
29 | void idleState();
30 |
31 | QFutureWatcher* image_watcher;
32 | QFutureWatcher* sound_watcher;
33 | private slots:
34 | void setFilterUnits(int scale);
35 |
36 | void makeSpectrogram();
37 | bool checkAnalysisValues();
38 | void makeSound();
39 | bool checkSynthesisValues();
40 |
41 | void chooseImage();
42 | void loadImage();
43 | void updateImage();
44 | void resetImage();
45 | void saveImage();
46 |
47 | void chooseSoundfile();
48 | void loadSoundfile();
49 | void updateSoundfile();
50 | void resetSoundfile();
51 | void saveSoundfile(const real_vec& signal);
52 |
53 | void choosePalette();
54 |
55 | //void showHelp(QWidget* parent, const QString& text) const;
56 | bool confirmWarnings(const QStringList& errors);
57 |
58 | void newSpectrogram();
59 | void newSound();
60 | signals:
61 | //void makeSound(Spectrogram* spectrogram, QImage image, int samplerate);
62 | void makeSpectrogram(Spectrogram* spectrogram, real_vec signal,
63 | int samplerate);
64 | };
65 |
66 | #endif
67 |
--------------------------------------------------------------------------------
/mainwindow.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | MainWindow
4 |
5 |
6 |
7 | 0
8 | 0
9 | 652
10 | 539
11 |
12 |
13 |
14 | Spectrogram
15 |
16 |
17 |
18 | -
19 |
20 |
-
21 |
22 |
23 | Spectrogram parameters
24 |
25 |
26 |
27 | 5
28 |
29 |
-
30 |
31 |
32 | QFormLayout::ExpandingFieldsGrow
33 |
34 |
-
35 |
36 |
37 | Frequency scale
38 |
39 |
40 | frequencyCombo
41 |
42 |
43 |
44 | -
45 |
46 |
47 |
48 | 0
49 | 0
50 |
51 |
52 |
53 |
54 | -
55 |
56 |
57 | Intensity scale
58 |
59 |
60 | intensityCombo
61 |
62 |
63 |
64 | -
65 |
66 |
67 |
68 | 0
69 | 0
70 |
71 |
72 |
73 |
74 | -
75 |
76 |
77 |
78 | 0
79 | 0
80 |
81 |
82 |
83 | Base frequency
84 |
85 |
86 | basefreqSpin
87 |
88 |
89 |
90 | -
91 |
92 |
93 |
94 | 0
95 | 0
96 |
97 |
98 |
99 | <p>The first filter band will be centered at this frequency.
100 | With linear frequency scale, it may be changed to
101 | bandwidth/2 if the band would go below 0 Hz.</p>
102 |
103 | <p>For music, good values values are 27.5×2<span style=" vertical-align:super;">k</span> Hz and logarithmic frequency scale, so that filter bands align nicely with musical notes.</p>
104 |
105 |
106 | Hz
107 |
108 |
109 | 44100.000000000000000
110 |
111 |
112 | 1.000000000000000
113 |
114 |
115 | 55.000000000000000
116 |
117 |
118 |
119 | -
120 |
121 |
122 |
123 | 0
124 | 0
125 |
126 |
127 |
128 | Maximum frequency
129 |
130 |
131 | maxfreqSpin
132 |
133 |
134 |
135 | -
136 |
137 |
138 |
139 | 0
140 | 0
141 |
142 |
143 |
144 | <p>The maximum frequency that will be displayed in the spectrogram.</p>
145 |
146 |
147 | Hz
148 |
149 |
150 | 2
151 |
152 |
153 | 44100.000000000000000
154 |
155 |
156 | 100.000000000000000
157 |
158 |
159 | 22050.000000000000000
160 |
161 |
162 |
163 | -
164 |
165 |
166 | Pixels per second
167 |
168 |
169 | ppsSpin
170 |
171 |
172 |
173 | -
174 |
175 |
176 |
177 | 0
178 | 0
179 |
180 |
181 |
182 | <p>This determines the spectrogram width. A good value is double the filter bandwidth. Greater values yield a more descriptive, but longer spectrogram. For synthesis, values above 100 are recommended.</p>
183 |
184 |
185 |
186 |
187 |
188 | 1
189 |
190 |
191 | 10000
192 |
193 |
194 | 5
195 |
196 |
197 | 150
198 |
199 |
200 |
201 | -
202 |
203 |
204 | Brightness correction
205 |
206 |
207 | brightCombo
208 |
209 |
210 |
211 | -
212 |
213 |
214 |
215 | 0
216 | 0
217 |
218 |
219 |
220 |
221 |
222 |
223 | -
224 |
225 |
226 | Frequency-domain filters
227 |
228 |
229 |
230 | QFormLayout::ExpandingFieldsGrow
231 |
232 |
233 | 5
234 |
235 |
-
236 |
237 |
238 | Bandwidth
239 |
240 |
241 | bandwidthSpin
242 |
243 |
244 |
245 | -
246 |
247 |
248 | Window function
249 |
250 |
251 | windowCombo
252 |
253 |
254 |
255 | -
256 |
257 |
258 | Overlap
259 |
260 |
261 | overlapSpin
262 |
263 |
264 |
265 | -
266 |
267 |
268 |
269 | 0
270 | 0
271 |
272 |
273 |
274 | <p>Larger filter bandwidth means less horizontal bands and worse frequency resolution.</p>
275 | <p style=" margin-top:10px;">In logarithmic mode, 100 Cents = <span style=" vertical-align:super;">1</span>/<span style=" vertical-align:sub;">12</span> of an octave = one semitone per band.</p>
276 |
277 |
278 | cents
279 |
280 |
281 | 1
282 |
283 |
284 | 10000
285 |
286 |
287 | 10
288 |
289 |
290 | 100
291 |
292 |
293 |
294 | -
295 |
296 |
297 |
298 | 0
299 | 0
300 |
301 |
302 |
303 | <p>Windowing prevents artifacts from dividing the frequency domain.</p>
304 |
305 |
306 |
307 | -
308 |
309 |
310 |
311 | 0
312 | 0
313 |
314 |
315 |
316 | <p>Windowed filters overlap to give more detail in the frequency domain.</p>
317 |
318 |
319 | %
320 |
321 |
322 | 1
323 |
324 |
325 | 0.000000000000000
326 |
327 |
328 | 99.500000000000000
329 |
330 |
331 | 80.000000000000000
332 |
333 |
334 |
335 |
336 |
337 |
338 | -
339 |
340 |
341 | 0
342 |
343 |
-
344 |
345 |
346 | Palette
347 |
348 |
349 |
350 | -
351 |
352 |
353 | Qt::Horizontal
354 |
355 |
356 |
357 | 10
358 | 20
359 |
360 |
361 |
362 |
363 | -
364 |
365 |
366 |
367 | 1
368 | 0
369 |
370 |
371 |
372 |
373 | 0
374 | 25
375 |
376 |
377 |
378 |
379 | 16777215
380 | 25
381 |
382 |
383 |
384 | QFrame::StyledPanel
385 |
386 |
387 | QFrame::Sunken
388 |
389 |
390 | [palette]
391 |
392 |
393 | true
394 |
395 |
396 | paletteButton
397 |
398 |
399 |
400 | -
401 |
402 |
403 |
404 | 25
405 | 25
406 |
407 |
408 |
409 | ...
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 | -
419 |
420 |
-
421 |
422 |
423 | Sound file
424 |
425 |
426 |
427 | 5
428 |
429 |
-
430 |
431 |
432 | QFormLayout::ExpandingFieldsGrow
433 |
434 |
-
435 |
436 |
437 | Location
438 |
439 |
440 | locationEdit
441 |
442 |
443 |
444 | -
445 |
446 |
447 | 0
448 |
449 |
-
450 |
451 |
452 | -
453 |
454 |
455 |
456 | 22
457 | 22
458 |
459 |
460 |
461 | ...
462 |
463 |
464 |
465 |
466 |
467 | -
468 |
469 |
470 | Length
471 |
472 |
473 | lengthEdit
474 |
475 |
476 |
477 | -
478 |
479 |
480 |
481 | 0
482 | 0
483 |
484 |
485 |
486 | true
487 |
488 |
489 | QAbstractSpinBox::NoButtons
490 |
491 |
492 | false
493 |
494 |
495 |
496 | -
497 |
498 |
499 | Channel
500 |
501 |
502 | channelSpin
503 |
504 |
505 |
506 | -
507 |
508 |
-
509 |
510 |
511 |
512 | 1
513 | 0
514 |
515 |
516 |
517 | 1
518 |
519 |
520 | 9
521 |
522 |
523 |
524 | -
525 |
526 |
527 | of
528 |
529 |
530 | channelsEdit
531 |
532 |
533 |
534 | -
535 |
536 |
537 |
538 | 1
539 | 0
540 |
541 |
542 |
543 |
544 | 20
545 | 0
546 |
547 |
548 |
549 | true
550 |
551 |
552 |
553 |
554 |
555 | -
556 |
557 |
558 | Samplerate
559 |
560 |
561 | samplerateSpin
562 |
563 |
564 |
565 | -
566 |
567 |
568 |
569 | 0
570 | 0
571 |
572 |
573 |
574 | true
575 |
576 |
577 | QAbstractSpinBox::NoButtons
578 |
579 |
580 | Hz
581 |
582 |
583 | 100000
584 |
585 |
586 | 100
587 |
588 |
589 | 44100
590 |
591 |
592 |
593 | -
594 |
595 |
596 | Synthesis type
597 |
598 |
599 | syntCombo
600 |
601 |
602 |
603 | -
604 |
605 |
606 |
607 | 0
608 | 0
609 |
610 |
611 |
612 |
613 |
614 |
615 | -
616 |
617 |
-
618 |
619 |
620 |
621 |
622 |
623 |
624 |
625 | 170
626 | 0
627 | 0
628 |
629 |
630 |
631 |
632 |
633 |
634 |
635 |
636 | 170
637 | 0
638 | 0
639 |
640 |
641 |
642 |
643 |
644 |
645 |
646 |
647 | 170
648 | 0
649 | 0
650 |
651 |
652 |
653 |
654 |
655 |
656 |
657 | Make sound
658 |
659 |
660 |
661 |
662 |
663 |
664 |
665 |
666 | -
667 |
668 |
669 | Qt::Vertical
670 |
671 |
672 |
673 | 20
674 | 40
675 |
676 |
677 |
678 |
679 | -
680 |
681 |
682 | Progress
683 |
684 |
685 |
686 | 1
687 |
688 |
689 | 0
690 |
691 |
692 | 5
693 |
694 |
-
695 |
696 |
-
697 |
698 |
699 | Idle
700 |
701 |
702 |
703 | -
704 |
705 |
706 |
707 | 0
708 | 0
709 |
710 |
711 |
712 |
713 | 25
714 | 25
715 |
716 |
717 |
718 |
719 | 25
720 | 25
721 |
722 |
723 |
724 | Cancel
725 |
726 |
727 | X
728 |
729 |
730 |
731 |
732 |
733 | -
734 |
735 |
736 | 0
737 |
738 |
739 |
740 |
741 |
742 |
743 |
744 |
745 |
746 |
747 | -
748 |
749 |
750 | Spectrogram
751 |
752 |
753 |
754 | 5
755 |
756 |
-
757 |
758 |
759 | true
760 |
761 |
762 | 0
763 |
764 |
765 |
766 |
767 | 0
768 |
769 |
-
770 |
771 |
772 |
773 | 4
774 | 0
775 |
776 |
777 |
778 | QFrame::StyledPanel
779 |
780 |
781 | QFrame::Sunken
782 |
783 |
784 | 1
785 |
786 |
787 | 0
788 |
789 |
790 | [spectrogram preview]
791 |
792 |
793 | true
794 |
795 |
796 |
797 |
798 |
799 |
800 |
801 | -
802 |
803 |
-
804 |
805 |
806 | Qt::Vertical
807 |
808 |
809 |
810 | 20
811 | 40
812 |
813 |
814 |
815 |
816 | -
817 |
818 |
819 | QFormLayout::ExpandingFieldsGrow
820 |
821 |
-
822 |
823 |
824 | Location
825 |
826 |
827 | speclocEdit
828 |
829 |
830 |
831 | -
832 |
833 |
834 | 0
835 |
836 |
-
837 |
838 |
839 |
840 | 0
841 | 0
842 |
843 |
844 |
845 | true
846 |
847 |
848 |
849 | -
850 |
851 |
852 |
853 | 22
854 | 22
855 |
856 |
857 |
858 | ...
859 |
860 |
861 |
862 |
863 |
864 | -
865 |
866 |
867 | Size
868 |
869 |
870 | sizeEdit
871 |
872 |
873 |
874 | -
875 |
876 |
877 |
878 |
879 | -
880 |
881 |
-
882 |
883 |
884 |
885 |
886 |
887 |
888 |
889 | 170
890 | 0
891 | 0
892 |
893 |
894 |
895 |
896 |
897 |
898 |
899 |
900 | 170
901 | 0
902 | 0
903 |
904 |
905 |
906 |
907 |
908 |
909 |
910 |
911 | 170
912 | 0
913 | 0
914 |
915 |
916 |
917 |
918 |
919 |
920 |
921 | Make spectrogram
922 |
923 |
924 |
925 | -
926 |
927 |
928 | Save as
929 |
930 |
931 |
932 |
933 |
934 |
935 |
936 |
937 |
938 |
939 |
940 |
941 |
942 |
943 | frequencyCombo
944 | intensityCombo
945 | basefreqSpin
946 | maxfreqSpin
947 | ppsSpin
948 | brightCombo
949 | bandwidthSpin
950 | windowCombo
951 | overlapSpin
952 | paletteButton
953 | locationEdit
954 | locationButton
955 | lengthEdit
956 | channelSpin
957 | channelsEdit
958 | samplerateSpin
959 | syntCombo
960 | makeSoundButton
961 | cancelButton
962 | speclocEdit
963 | speclocButton
964 | sizeEdit
965 | makeButton
966 | specSaveAsButton
967 |
968 |
969 |
970 |
971 |
--------------------------------------------------------------------------------
/palettes/fiery.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krajj7/spectrogram/f640537617d49ac368b48ea7e3ec372bf44bae10/palettes/fiery.png
--------------------------------------------------------------------------------
/palettes/temperatures.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krajj7/spectrogram/f640537617d49ac368b48ea7e3ec372bf44bae10/palettes/temperatures.png
--------------------------------------------------------------------------------
/palettes/wb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krajj7/spectrogram/f640537617d49ac368b48ea7e3ec372bf44bae10/palettes/wb.png
--------------------------------------------------------------------------------
/scripts/spectrogram-video.py:
--------------------------------------------------------------------------------
1 | import sys, os
2 | import Image, ImageDraw
3 |
4 | # this script takes an RGB picture and cuts it into frames for a scrolling video
5 |
6 | # the spectrogram.png file has to be RGB mode (watch out, the spectrogram program generates grayscale by default) and resized to 720 pixels height in advance.
7 |
8 | # a video from the frames can be generated with ffmpeg:
9 | # ffmpeg -f image2 -r 25 -i ./%06d.png -i soundfile.mp3 -b 5000k -vb 5000k -acodec copy out.mp4
10 |
11 | def main():
12 | i = Image.open("spectrogram-rgb.png")
13 | res = (1280,720)
14 | # step size 2 means 25 fps for spectrogram with 50 pixels per second
15 | step = 4
16 | for center in xrange(step*0,i.size[0], step):
17 | left = center-res[0]/2
18 | left = max(left, 0)
19 | right = left+res[0]
20 | if right > i.size[0]-1:
21 | right = i.size[0]-1
22 | left = right-res[0]
23 | o = i.crop((left, 0, right, res[1]))
24 | draw = ImageDraw.Draw(o)
25 | xoff = center-left
26 | draw.line((center-left-1, 0, center-left-1, res[1]), (0,0,180))
27 | draw.line((center-left, 0, center-left, res[1]), (0,0,180))
28 | draw.line((center-left+1, 0, center-left+1, res[1]), (0,0,180))
29 | name = str(center/step).rjust(6, "0")
30 | o.save("%s.png"%name)
31 | if center%10 == 0:
32 | print "crop %s.png"%name
33 |
34 | main()
35 |
--------------------------------------------------------------------------------
/soundfile.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include "soundfile.hpp"
4 |
5 | namespace
6 | {
7 | const size_t MAX_FIXED_T_VAL=std::pow(2.0,(int)(8*sizeof(mad_fixed_t)-1)-1);
8 | }
9 |
10 | QString Soundfile::writeSound(const QString& fname, const real_vec& data,
11 | int samplerate, int format)
12 | {
13 | if (format == -1)
14 | {
15 | format = guessFormat(fname);
16 | if (!format)
17 | return "Unsupported filetype for writing.";
18 | }
19 | SF_INFO check = {0, samplerate, 1, format, 0, 0};
20 | if (!sf_format_check(&check))
21 | return "Format didn't pass sf_format_check()"; // shouldn't happen
22 |
23 | //XXX zmeni unicode nazvy
24 | SndfileHandle file(fname.toLocal8Bit(), SFM_WRITE, format, 1, samplerate);
25 | if (!file)
26 | assert(false);
27 | if (file.error())
28 | return file.strError();
29 |
30 | file.writef(&data[0], data.size());
31 | return QString();
32 | }
33 |
34 | /// Returns the format specification guessed from the specified file extension.
35 | int Soundfile::guessFormat(const QString& filename)
36 | {
37 | assert(!filename.isNull());
38 | if (filename.endsWith(".wav"))
39 | return SF_FORMAT_WAV|SF_FORMAT_PCM_16;
40 | else if (filename.endsWith(".ogg"))
41 | return SF_FORMAT_OGG|SF_FORMAT_VORBIS;
42 | else if (filename.endsWith(".flac"))
43 | return SF_FORMAT_FLAC|SF_FORMAT_PCM_16;
44 | return 0;
45 | }
46 |
47 | Soundfile::Soundfile()
48 | : data_(NULL)
49 | {
50 | }
51 |
52 | Soundfile::Soundfile(const QString& filename)
53 | {
54 | load(filename);
55 | }
56 |
57 | real_vec Soundfile::read_channel(int channel)
58 | {
59 | return data_->read_channel(channel);
60 | }
61 |
62 | bool Soundfile::valid() const
63 | {
64 | return data_ != NULL;
65 | }
66 |
67 | const QString& Soundfile::error() const
68 | {
69 | return error_;
70 | }
71 |
72 | void Soundfile::load(const QString& filename)
73 | {
74 | if (filename.endsWith(".mp3"))
75 | data_ = new MP3Data(filename);
76 | else
77 | data_ = new SndfileData(filename);
78 |
79 | if (!data_->valid())
80 | {
81 | error_ = data_->error();
82 | delete data_;
83 | data_ = NULL;
84 | }
85 | }
86 |
87 | void Soundfile::reset()
88 | {
89 | delete data_;
90 | data_ = NULL;
91 | }
92 |
93 | const SoundfileData& Soundfile::data() const
94 | {
95 | assert(data_ != NULL);
96 | return *data_;
97 | }
98 |
99 | // ----
100 |
101 | SndfileData::SndfileData(const QString& filename)
102 | {
103 | file_ = SndfileHandle(filename.toLocal8Bit());
104 | }
105 |
106 | bool SndfileData::valid() const
107 | {
108 | return (file_ && !file_.error());
109 | }
110 |
111 | QString SndfileData::error() const
112 | {
113 | return file_.strError();
114 | }
115 |
116 | size_t SndfileData::frames() const
117 | {
118 | return file_.frames();
119 | }
120 |
121 | double SndfileData::length() const
122 | {
123 | return (double)frames()/samplerate();
124 | }
125 |
126 | int SndfileData::channels() const
127 | {
128 | return file_.channels();
129 | }
130 |
131 | int SndfileData::samplerate() const
132 | {
133 | return file_.samplerate();
134 | }
135 |
136 | real_vec SndfileData::read_channel(int channel)
137 | {
138 | assert(channel < channels());
139 | real_vec buffer(frames()*channels());
140 | file_.readf(&buffer[0], frames());
141 | //size_t frames_read = file_.readf(&buffer[0], frames());
142 | //assert(frames_read == frames());
143 | file_.seek(0, SEEK_SET);
144 |
145 | if (channels() > 1)
146 | {
147 | for (size_t i = channel, j = 0; j < frames(); i += channels(), ++j)
148 | buffer[j] = buffer[i];
149 | buffer.resize(frames());
150 | }
151 | return buffer;
152 | }
153 |
154 | SndfileData::~SndfileData()
155 | {
156 | }
157 |
158 | // ---
159 |
160 | MP3Data::MP3Data(const QString& fname)
161 | : frames_(0)
162 | , length_(0)
163 | , samplerate_(0)
164 | , channels_(0)
165 | , filename_(fname)
166 | {
167 | get_mp3_stats();
168 | }
169 |
170 | void MP3Data::get_mp3_stats()
171 | {
172 | mad_stream stream;
173 | mad_header header;
174 | mad_stream_init(&stream);
175 | mad_header_init(&header);
176 |
177 | QFile file(filename_);
178 | if (!file.open(QIODevice::ReadOnly))
179 | {
180 | error_ = "Error opening file.";
181 | goto cleanup;
182 | }
183 | {
184 | uchar* buf = file.map(0, file.size());
185 | mad_stream_buffer(&stream, buf, file.size());
186 |
187 | while (true)
188 | {
189 | if (mad_header_decode(&header, &stream) == -1)
190 | {
191 | if (stream.error == MAD_ERROR_LOSTSYNC)
192 | continue;
193 | else if (stream.error == MAD_ERROR_BUFLEN)
194 | break;
195 | else
196 | {
197 | error_ = "Error decoding mp3 headers!";
198 | break;
199 | }
200 | }
201 | frames_++;
202 | length_ += header.duration.fraction;
203 | if (!samplerate_)
204 | {
205 | samplerate_ = header.samplerate;
206 | if (header.mode == MAD_MODE_SINGLE_CHANNEL)
207 | channels_ = 1;
208 | else
209 | channels_ = 2;
210 | }
211 | }
212 | length_ /= MAD_TIMER_RESOLUTION;
213 | if (!samplerate_)
214 | error_ = "Invalid mp3 file.";
215 | }
216 | cleanup:
217 | mad_stream_finish(&stream);
218 | mad_header_finish(&header);
219 | }
220 |
221 | QString MP3Data::error() const
222 | {
223 | return error_;
224 | }
225 |
226 | real_vec MP3Data::read_channel(int channel)
227 | {
228 | mad_stream stream;
229 | mad_frame frame;
230 | mad_synth synth;
231 | mad_stream_init(&stream);
232 | mad_frame_init(&frame);
233 | mad_synth_init(&synth);
234 |
235 | real_vec result;
236 |
237 | QFile file(filename_);
238 | if (!file.open(QIODevice::ReadOnly))
239 | goto cleanup;
240 | {
241 | uchar* buf = file.map(0, file.size());
242 | mad_stream_buffer(&stream, buf, file.size());
243 |
244 | while (true)
245 | {
246 | if (mad_frame_decode(&frame, &stream) == -1)
247 | {
248 | if (stream.error == MAD_ERROR_LOSTSYNC)
249 | continue;
250 | else if (stream.error == MAD_ERROR_BUFLEN)
251 | break;
252 | else
253 | {
254 | error_ = "Error decoding mp3 file.";
255 | break;
256 | }
257 | }
258 | mad_synth_frame(&synth, &frame);
259 | mad_fixed_t* samples = synth.pcm.samples[channel];
260 | for (short i = 0; i < synth.pcm.length; ++i)
261 | result.push_back((double)samples[i]/MAX_FIXED_T_VAL);
262 | }
263 | }
264 | cleanup:
265 | mad_synth_finish(&synth);
266 | mad_frame_finish(&frame);
267 | mad_stream_finish(&stream);
268 |
269 | return result;
270 | }
271 |
272 | size_t MP3Data::frames() const
273 | {
274 | return frames_;
275 | }
276 |
277 | double MP3Data::length() const//in seconds
278 | {
279 | return length_;
280 | }
281 |
282 | int MP3Data::samplerate() const
283 | {
284 | return samplerate_;
285 | }
286 |
287 | int MP3Data::channels() const
288 | {
289 | return channels_;
290 | }
291 |
292 | bool MP3Data::valid() const
293 | {
294 | return error_.isEmpty();
295 | }
296 |
--------------------------------------------------------------------------------
/soundfile.hpp:
--------------------------------------------------------------------------------
1 | #ifndef SOUNDFILE_HPP
2 | #define SOUNDFILE_HPP
3 |
4 | /** \file soundfile.hpp
5 | * \brief Definitions for classes that work with audio file formats. */
6 |
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include "types.hpp"
12 | #include "mad.h"
13 |
14 | /// An abstract interface for decoding sound files.
15 | /** It provides abstraction for all low-level functions used on sound files, implementation can be different for each format. */
16 | class SoundfileData
17 | {
18 | public:
19 | virtual ~SoundfileData() {};
20 | /// Used to get details in case of an error.
21 | virtual QString error() const = 0;
22 | /// Loads a specified channel into a real-valued vector.
23 | virtual real_vec read_channel(int channel) = 0;
24 | /// Returns the number of audio frames in each channel.
25 | virtual size_t frames() const = 0;
26 | /// Returns the length of the audio track in seconds.
27 | virtual double length() const = 0;
28 | /// Returns the samplerate of the audio file in Hz.
29 | virtual int samplerate() const = 0;
30 | /// Returns the number of channels.
31 | virtual int channels() const = 0;
32 | /// Checks if the audio file is loaded correctly and ready for use.
33 | virtual bool valid() const = 0;
34 | };
35 |
36 | /// Implements the SoundfileData interface using libsndfile.
37 | /** This provides support for multiple file formats: wav, ogg, flac and many others. */
38 | class SndfileData : public SoundfileData
39 | {
40 | public:
41 | SndfileData(const QString& fname);
42 | ~SndfileData();
43 | QString error() const;
44 | real_vec read_channel(int channel);
45 | size_t frames() const;
46 | double length() const; //in seconds
47 | int samplerate() const;
48 | int channels() const;
49 | bool valid() const;
50 | private:
51 | SndfileHandle file_;
52 | };
53 |
54 | /// Implements the SoundfileData interface using libmad.
55 | /** This provides support for MP3 files. */
56 | class MP3Data : public SoundfileData
57 | {
58 | public:
59 | MP3Data(const QString& fname);
60 | QString error() const;
61 | real_vec read_channel(int channel);
62 | size_t frames() const;
63 | double length() const;//in seconds
64 | int samplerate() const;
65 | int channels() const;
66 | bool valid() const;
67 | private:
68 | void get_mp3_stats();
69 | size_t frames_;
70 | size_t length_;
71 | int samplerate_;
72 | int channels_;
73 | QString filename_;
74 | QString error_;
75 | };
76 |
77 | /// A format-independent class that provides functionality for sound manipulation, reading and writing.
78 | /** It aggregates all implementations of SndfileData. */
79 | class Soundfile
80 | {
81 | public:
82 | /// Writes pcm data to an audio file encoded according to the extension.
83 | /** Encoding is performed by libsndfile (no mp3 writing support).
84 | * \return a string error, or null string on success. */
85 | static QString writeSound(const QString& fname, const real_vec& data,
86 | int samplerate, int format=-1);
87 | static int guessFormat(const QString& filename);
88 |
89 | Soundfile();
90 | Soundfile(const QString& fname);
91 | /// Forget the loaded file.
92 | void reset();
93 | /// Loads the specified file.
94 | void load(const QString& fname);
95 | /// If the loaded file isn't valid, this function gives the reason.
96 | const QString& error() const;
97 | /// Used to determine if a file was loaded successfully.
98 | bool valid() const;
99 | /// Read the audio data of the given channel from the loaded file.
100 | /** \return PCM data of the specified audio channel */
101 | real_vec read_channel(int channel);
102 | /// Allows access to low-level information about the file (eg. samplerate).
103 | const SoundfileData& data() const;
104 | private:
105 | SoundfileData* data_;
106 | QString error_;
107 | };
108 |
109 | #endif
110 |
--------------------------------------------------------------------------------
/spectrogram.cpp:
--------------------------------------------------------------------------------
1 | #include "spectrogram.hpp"
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include "samplerate.h"
15 |
16 | namespace
17 | {
18 | float log10scale(float val)
19 | {
20 | assert(val >= 0 && val <= 1);
21 | return std::log10(1+9*val);
22 | }
23 |
24 | float log10scale_inv(float val)
25 | {
26 | assert(val >= 0 && val <= 1);
27 | return (std::pow(10, val)-1)/9;
28 | }
29 |
30 | // cent = octave/1200
31 | double cent2freq(double cents)
32 | {
33 | return std::pow(2, cents/1200);
34 | }
35 | double freq2cent(double freq)
36 | {
37 | return std::log(freq)/std::log(2)*1200;
38 | }
39 | double cent2oct(double cents)
40 | {
41 | return cents/1200;
42 | }
43 | double oct2cent(double oct)
44 | {
45 | return oct*1200;
46 | }
47 |
48 | void shift90deg(Complex& x)
49 | {
50 | x = std::conj(Complex(x.imag(), x.real()));
51 | }
52 |
53 | /// Uses libsrc to resample the input vector to a given length.
54 | real_vec resample(const real_vec& in, size_t len)
55 | {
56 | assert(len > 0);
57 | //std::cout << "resample(data size: "<= 256)
63 | return resample(resample(in, in.size()*50), len);
64 | else if (ratio <= 1.0/256)
65 | return resample(resample(in, in.size()/50), len);
66 |
67 | real_vec out(len);
68 |
69 | SRC_DATA parms = {const_cast(&in[0]),
70 | &out[0], in.size(), out.size(), 0,0,0, ratio};
71 | src_simple(&parms, SRC_SINC_FASTEST, 1);
72 |
73 | return out;
74 | }
75 |
76 | /// Envelope detection: http://www.numerix-dsp.com/envelope.html
77 | real_vec get_envelope(complex_vec& band)
78 | {
79 | assert(band.size() > 1);
80 |
81 | // copy + phase shift
82 | complex_vec shifted(band);
83 | std::for_each(shifted.begin(), shifted.end(), shift90deg);
84 |
85 | real_vec envelope = padded_IFFT(band);
86 | real_vec shifted_signal = padded_IFFT(shifted);
87 |
88 | for (size_t i = 0; i < envelope.size(); ++i)
89 | envelope[i] = std::sqrt(envelope[i]*envelope[i] +
90 | shifted_signal[i]*shifted_signal[i]);
91 |
92 | return envelope;
93 | }
94 |
95 | double blackman_window(double x)
96 | {
97 | assert(x >= 0 && x <= 1);
98 | return std::max(0.42 - 0.5*cos(2*PI*x) + 0.08*cos(4*PI*x), 0.0);
99 | }
100 |
101 | double hann_window(double x)
102 | {
103 | assert(x >= 0 && x <= 1);
104 | return 0.5*(1-std::cos(x*2*PI));
105 | }
106 |
107 | double triangular_window(double x)
108 | {
109 | assert(x >= 0 && x <= 1);
110 | return 1-std::abs(2*(x-0.5));
111 | }
112 |
113 | double window_coef(double x, Window window)
114 | {
115 | assert(x >= 0 && x <= 1);
116 | if (window == WINDOW_RECTANGULAR)
117 | return 1.0;
118 | switch (window)
119 | {
120 | case WINDOW_HANN:
121 | return hann_window(x);
122 | case WINDOW_BLACKMAN:
123 | return blackman_window(x);
124 | case WINDOW_TRIANGULAR:
125 | return triangular_window(x);
126 | default:
127 | assert(false);
128 | }
129 | }
130 |
131 | float calc_intensity(float val, AxisScale intensity_axis)
132 | {
133 | assert(val >= 0 && val <= 1);
134 | switch (intensity_axis)
135 | {
136 | case SCALE_LOGARITHMIC:
137 | return log10scale(val);
138 | case SCALE_LINEAR:
139 | return val;
140 | default:
141 | assert(false);
142 | }
143 | }
144 |
145 | float calc_intensity_inv(float val, AxisScale intensity_axis)
146 | {
147 | assert(val >= 0 && val <= 1);
148 | switch (intensity_axis)
149 | {
150 | case SCALE_LOGARITHMIC:
151 | return log10scale_inv(val);
152 | case SCALE_LINEAR:
153 | return val;
154 | default:
155 | assert(false);
156 | }
157 | }
158 |
159 | // to <0,1> (cutoff negative)
160 | void normalize_image(std::vector& data)
161 | {
162 | float max = 0.0f;
163 | for (std::vector::iterator it=data.begin();
164 | it!=data.end(); ++it)
165 | max = std::max(*std::max_element(it->begin(), it->end()), max);
166 | if (max == 0.0f)
167 | return;
168 | for (std::vector::iterator it=data.begin();
169 | it!=data.end(); ++it)
170 | for (real_vec::iterator i = it->begin(); i != it->end(); ++i)
171 | *i = std::abs(*i)/max;
172 | }
173 |
174 | // to <-1,1>
175 | void normalize_signal(real_vec& vector)
176 | {
177 | float max = 0;
178 | for (real_vec::iterator it = vector.begin(); it != vector.end(); ++it)
179 | max = std::max(max, std::abs(*it));
180 | //std::cout <<"max: "< 0);
182 | for (real_vec::iterator it = vector.begin(); it != vector.end(); ++it)
183 | *it /= max;
184 | }
185 |
186 | // random number from <0,1>
187 | double random_double()
188 | {
189 | return ((double)rand()/(double)RAND_MAX);
190 | }
191 |
192 | float brightness_correction(float intensity, BrightCorrection correction)
193 | {
194 | switch (correction)
195 | {
196 | case BRIGHT_NONE:
197 | return intensity;
198 | case BRIGHT_SQRT:
199 | return std::sqrt(intensity);
200 | }
201 | assert(false);
202 | }
203 |
204 | /// Creates a random pink noise signal in the frequency domain
205 | /** \param size Desired number of samples in time domain (after IFFT). */
206 | complex_vec get_pink_noise(size_t size)
207 | {
208 | complex_vec res;
209 | for (size_t i = 0; i < (size+1)/2; ++i)
210 | {
211 | const float mag = std::pow((float) i, -0.5f);
212 | const double phase = (2*random_double()-1) * PI;//+-pi random phase
213 | res.push_back(Complex(mag*std::cos(phase), mag*std::sin(phase)));
214 | }
215 | return res;
216 | }
217 | }
218 |
219 | Spectrogram::Spectrogram(QObject* parent) // defaults
220 | : QObject(parent)
221 | , bandwidth(100)
222 | , basefreq(55)
223 | , maxfreq(22050)
224 | , overlap(0.8)
225 | , pixpersec(100)
226 | , window(WINDOW_HANN)
227 | , intensity_axis(SCALE_LOGARITHMIC)
228 | , frequency_axis(SCALE_LOGARITHMIC)
229 | , cancelled_(false)
230 | {
231 | }
232 |
233 | QImage Spectrogram::to_image(real_vec& signal, int samplerate) const
234 | {
235 | emit status("Transforming input");
236 | emit progress(0);
237 | const complex_vec spectrum = padded_FFT(signal);
238 |
239 | const size_t width = (spectrum.size()-1)*2*pixpersec/samplerate;
240 |
241 | // transformation of frequency in hz to index in spectrum
242 | const double filterscale = ((double)spectrum.size()*2)/samplerate;
243 | //std::cout << "filterscale: " << filterscale<<"\n";
244 |
245 | std::auto_ptr filterbank = Filterbank::get_filterbank(
246 | frequency_axis, filterscale, basefreq, bandwidth, overlap);
247 | const int bands = filterbank->num_bands_est(maxfreq);
248 | const int top_index = maxfreq*filterscale;
249 | // maxfreq has to be at most nyquist
250 | assert(top_index <= (int)spectrum.size());
251 |
252 | std::vector image_data;
253 | for (size_t bandidx = 0;; ++bandidx)
254 | {
255 | if (cancelled())
256 | return QImage();
257 | band_progress(bandidx, bands, 5, 93);
258 |
259 | // filtering
260 | intpair range = filterbank->get_band(bandidx);
261 | //std::cout << "-----\n";
262 | //std::cout << "spectrum size: " << spectrum.size() << "\n";
263 | //std::cout << "lowidx: "< top_index)
277 | break;
278 | if (range.second > top_index)
279 | std::fill(filterband.begin()+top_index-range.first,
280 | filterband.end(), Complex(0,0));
281 |
282 | // windowing
283 | apply_window(filterband, range.first, filterscale);
284 |
285 | // envelope detection + resampling
286 | const real_vec envelope = resample(get_envelope(filterband), width);
287 | image_data.push_back(envelope);
288 | }
289 |
290 | normalize_image(image_data);
291 |
292 | emit progress(99);
293 | return make_image(image_data);
294 | }
295 |
296 | /** \param data innermost values from 0 to 1, same sized vectors */
297 | QImage Spectrogram::make_image(const std::vector& data) const
298 | {
299 | emit status("Generating image");
300 | const size_t height = data.size();
301 | const size_t width = data[0].size();
302 | std::cout << "image size: " << width <<" x "< filterbank = Filterbank::get_filterbank(
360 | frequency_axis, filterscale, basefreq, bandwidth, overlap);
361 |
362 | for (int bandidx = 0; bandidx < image.height(); ++bandidx)
363 | {
364 | if (cancelled())
365 | return real_vec();
366 | band_progress(bandidx, image.height()-1);
367 |
368 | real_vec envelope = envelope_from_spectrogram(image, bandidx);
369 |
370 | // random phase between +-pi
371 | const double phase = (2*random_double()-1) * PI;
372 |
373 | real_vec bandsignal(envelope.size()*2);
374 | for (int j = 0; j < 4; ++j)
375 | {
376 | const double sine = std::cos(j*PI/2 + phase);
377 | for (size_t i = j; i < bandsignal.size(); i += 4)
378 | bandsignal[i] = envelope[i/2] * sine;
379 | }
380 | complex_vec filterband = padded_FFT(bandsignal);
381 |
382 | for (size_t i = 0; i < filterband.size(); ++i)
383 | {
384 | const double x = (double)i/(filterband.size()-1);
385 | // normalized blackman window antiderivative
386 | filterband[i] *= x - ((0.5/(2.0*PI))*sin(2.0*PI*x) +
387 | (0.08/(4.0*PI))*sin(4.0*PI*x)/0.42);
388 | }
389 |
390 | //std::cout << "spectrum size: " << spectrum.size() << "\n";
391 | //std::cout << bandidx << ". filterband size: " << filterband.size() << "; start: " << filterbank->get_band(bandidx).first <<"; end: " << filterbank->get_band(bandidx).second << "\n";
392 |
393 | const size_t center = filterbank->get_center(bandidx);
394 | const size_t offset = std::max((size_t)0, center - filterband.size()/2);
395 | //std::cout << "offset: " < 0 && offset+i < spectrum.size())
398 | spectrum[offset+i] += filterband[i];
399 | }
400 |
401 | real_vec out = padded_IFFT(spectrum);
402 | //std::cout << "samples: " << out.size() << " -> " << samples << "\n";
403 | normalize_signal(out);
404 | return out;
405 | }
406 |
407 | real_vec Spectrogram::noise_synthesis(const QImage& image, int samplerate) const
408 | {
409 | size_t samples = image.width()*samplerate/pixpersec;
410 |
411 | complex_vec noise = get_pink_noise(samplerate*10); // 10 sec loop
412 |
413 | const double filterscale = ((double)noise.size()*2)/samplerate;
414 | std::auto_ptr filterbank = Filterbank::get_filterbank(
415 | frequency_axis, filterscale, basefreq, bandwidth, overlap);
416 |
417 | const int top_index = maxfreq*filterscale;
418 |
419 | real_vec out(samples);
420 |
421 | for (int bandidx = 0; bandidx < image.height(); ++bandidx)
422 | {
423 | if (cancelled())
424 | return real_vec();
425 | band_progress(bandidx, image.height()-1);
426 |
427 | // filter noise
428 | intpair range = filterbank->get_band(bandidx);
429 | //std::cout << bandidx << "/"< from a row of pixels
476 | real_vec Spectrogram::envelope_from_spectrogram(const QImage& image, int row) const
477 | {
478 | real_vec envelope(image.width());
479 | for (int x = 0; x < image.width(); ++x)
480 | envelope[x] = calc_intensity_inv(palette.get_intensity(
481 | image.pixel(x, image.height()-row-1)), intensity_axis);
482 | return envelope;
483 | }
484 |
485 | void Spectrogram::deserialize(const QString& text)
486 | {
487 | QStringList tokens = text.split(delimiter);
488 | bandwidth = tokens[1].toDouble();
489 | basefreq = tokens[2].toDouble();
490 | maxfreq = tokens[3].toDouble();
491 | overlap = tokens[4].toDouble()/100.0;
492 | pixpersec = tokens[5].toDouble();
493 | window = (Window)tokens[6].toInt();
494 | intensity_axis = (AxisScale)tokens[7].toInt();
495 | frequency_axis = (AxisScale)tokens[8].toInt();
496 | }
497 |
498 | QString Spectrogram::serialized() const
499 | {
500 | QString out;
501 | QTextStream desc(&out);
502 | desc.setRealNumberPrecision(4);
503 | desc.setRealNumberNotation(QTextStream::FixedNotation);
504 | desc << "Spectrogram:" << delimiter
505 | << bandwidth << delimiter
506 | << basefreq << delimiter
507 | << maxfreq << delimiter
508 | << overlap*100 << delimiter
509 | << pixpersec << delimiter
510 | << (int)window << delimiter
511 | << (int)intensity_axis << delimiter
512 | << (int)frequency_axis << delimiter
513 | ;
514 | //std::cout << "serialized: " << out.toStdString() << "\n";
515 | return out;
516 | }
517 |
518 | Palette::Palette(const QImage& img)
519 | {
520 | assert(!img.isNull());
521 | for (int x = 0; x < img.width(); ++x)
522 | colors_.append(img.pixel(x, 0));
523 | }
524 |
525 | Palette::Palette()
526 | {
527 | QVector colors;
528 | for (int i = 0; i < 256; ++i)
529 | colors.append(qRgb(i, i, i));
530 | colors_ = colors;
531 | }
532 |
533 | int Palette::get_color(float val) const
534 | {
535 | assert(val >= 0 && val <= 1);
536 | if (indexable())
537 | // returns the color index
538 | return (colors_.size()-1)*val;
539 | else
540 | // returns the RGB value
541 | return colors_[(colors_.size()-1)*val];
542 | }
543 |
544 | bool Palette::has_color(QRgb color) const
545 | {
546 | return colors_.indexOf(color) != -1;
547 | }
548 |
549 | // ne moc efektivni
550 | float Palette::get_intensity(QRgb color) const
551 | {
552 | int index = colors_.indexOf(color);
553 | if (index == -1) // shouldn't happen
554 | return 0;
555 | return (float)index/(colors_.size()-1);
556 | }
557 |
558 | QImage Palette::make_canvas(int width, int height) const
559 | {
560 | if (indexable())
561 | {
562 | QImage out(width, height, QImage::Format_Indexed8);
563 | out.setColorTable(colors_);
564 | out.fill(0);
565 | return out;
566 | }
567 | else
568 | {
569 | QImage out(width, height, QImage::Format_RGB32);
570 | out.fill(colors_[0]);
571 | return out;
572 | }
573 | }
574 |
575 | bool Palette::indexable() const
576 | {
577 | return colors_.size() <= 256;
578 | }
579 |
580 | QPixmap Palette::preview(int width, int height) const
581 | {
582 | QImage out = make_canvas(width, height);
583 | for (int x = 0; x < width; ++x)
584 | out.setPixel(x, 0, get_color((double)x/(width-1)));
585 | int bytes = out.bytesPerLine();
586 | for (int y = 1; y < height; ++y)
587 | std::memcpy(out.scanLine(y), out.scanLine(0), bytes);
588 | return QPixmap::fromImage(out);
589 | }
590 |
591 | int Palette::numColors() const
592 | {
593 | return colors_.size();
594 | }
595 |
596 | Filterbank::Filterbank(double scale)
597 | : scale_(scale)
598 | {
599 | }
600 |
601 | Filterbank::~Filterbank()
602 | {
603 | }
604 |
605 | LinearFilterbank::LinearFilterbank(double scale, double base,
606 | double hzbandwidth, double overlap)
607 | : Filterbank(scale)
608 | , bandwidth_(hzbandwidth*scale)
609 | , startidx_(std::max(scale_*base-bandwidth_/2, 0.0))
610 | , step_((1-overlap)*bandwidth_)
611 | {
612 | //std::cout << "bandwidth: " << bandwidth_ << "\n";
613 | //std::cout << "step_: " << step_ << " hz\n";
614 | assert(step_ > 0);
615 | }
616 |
617 | int LinearFilterbank::num_bands_est(double maxfreq) const
618 | {
619 | return (maxfreq*scale_-startidx_)/step_;
620 | }
621 |
622 | intpair LinearFilterbank::get_band(int i) const
623 | {
624 | intpair out;
625 | out.first = startidx_ + i*step_;
626 | out.second = out.first + bandwidth_;
627 | return out;
628 | }
629 |
630 | int LinearFilterbank::get_center(int i) const
631 | {
632 | return startidx_ + i*step_ + bandwidth_/2.0;
633 | }
634 |
635 | LogFilterbank::LogFilterbank(double scale, double base,
636 | double centsperband, double overlap)
637 | : Filterbank(scale)
638 | , centsperband_(centsperband)
639 | , logstart_(freq2cent(base))
640 | , logstep_((1-overlap)*centsperband_)
641 | {
642 | assert(logstep_ > 0);
643 | //std::cout << "bandwidth: " << centsperband_ << " cpb\n";
644 | //std::cout << "logstep_: " << logstep_ << " cents\n";
645 | }
646 |
647 | int LogFilterbank::num_bands_est(double maxfreq) const
648 | {
649 | return (freq2cent(maxfreq)-logstart_)/logstep_+4;
650 | }
651 |
652 | int LogFilterbank::get_center(int i) const
653 | {
654 | const double logcenter = logstart_ + i*logstep_;
655 | return cent2freq(logcenter)*scale_;
656 | }
657 |
658 | intpair LogFilterbank::get_band(int i) const
659 | {
660 | const double logcenter = logstart_ + i*logstep_;
661 | const double loglow = logcenter - centsperband_/2.0;
662 | const double loghigh = loglow + centsperband_;
663 | intpair out;
664 | out.first = cent2freq(loglow)*scale_;
665 | out.second = cent2freq(loghigh)*scale_;
666 | //std::cout << "centerfreq: " << cent2freq(logcenter)<< "\n";
667 | //std::cout << "lowfreq: " << cent2freq(loglow) << " highfreq: "< Filterbank::get_filterbank(AxisScale type,
672 | double scale, double base, double bandwidth, double overlap)
673 | {
674 | Filterbank* filterbank;
675 | if (type == SCALE_LINEAR)
676 | filterbank=new LinearFilterbank(scale, base, bandwidth, overlap);
677 | else
678 | filterbank=new LogFilterbank(scale, base, bandwidth, overlap);
679 | return std::auto_ptr(filterbank);
680 | }
681 |
--------------------------------------------------------------------------------
/spectrogram.hpp:
--------------------------------------------------------------------------------
1 | #ifndef SPECTROGRAM_HPP
2 | #define SPECTROGRAM_HPP
3 |
4 | /** \file spectrogram.hpp
5 | * \brief Definitions for classes used for spectrogram analysis and generation.
6 | */
7 |
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include "soundfile.hpp"
16 | #include "fft.hpp"
17 |
18 | #include
19 | #include
20 |
21 | /// Represents a palette used to draw a spectrogram.
22 | /** It is basically a mapping of intensity values from the interval <0,1> to a
23 | * set of colors (the palette), where 0 represents zero intensity of the pixel
24 | * and 1 represents maximum intensity. Ideally, the mapping is 1:1
25 | * (bijection), otherwise there will be ambiguity in the synthesis process and
26 | * quality will be affected. Both the Intensity -> Color and Color ->
27 | * Intensity mappings are implemented by the Palette::get_color() and
28 | * Palette::get_intensity() functions respectively.
29 | *
30 | * For optimal image sizes, the palette will be either indexed or RGB,
31 | * depending on how many colors it contains. If there are 256 or less colors
32 | * (eg. in the default grayscale case), the palette will be 8-bit indexed,
33 | * otherwise it's RGB (24-bit, no index).
34 | *
35 | * This is transparent in usage because in both cases the make_canvas() function
36 | * will create a QImage with the appropriate color mode and QImage::setPixel()
37 | * then takes the same parameter type as the Palette::get_color() function
38 | * returns.
39 | */
40 | class Palette
41 | {
42 | public:
43 | /// Default constructor -- 8-bit grayscale palette.
44 | Palette();
45 | /// Creates a palette from an image.
46 | /**
47 | * This constructor takes an image and fills the palette with colors
48 | * from the first row of an image. \param img The image used to
49 | * generate the palette.
50 | */
51 | Palette(const QImage& img);
52 | //const QVector& colors() const;
53 | /// Mapping of intensity values from <0,1> to an index or RGB value.
54 | /**
55 | * \param val A float where 0 <= val <= 1
56 | * \return Index of the color for an indexed palette or RGB value.
57 | */
58 | int get_color(float val) const;
59 | /// Inverse mapping of color values to intensity, used for spectrogram synthesis.
60 | /** \return Corresponding intensity, a value from <0,1>. */
61 | float get_intensity(QRgb color) const;
62 | /// Returns true if the palette contains the given color, false otherwise.
63 | bool has_color(QRgb color) const;
64 | /// Creates a QImage with an appropriate color mode and dimensions.
65 | /** The resulting QImage will have the specified dimensions and color
66 | * mode depending on the number of colors in the palette. For 256 or
67 | * less colors, it will be indexed, otherwise RGB.
68 | */
69 | QImage make_canvas(int width, int height) const;
70 | /// Used to determine if the palette is indexed or RGB.
71 | /** \return \c true if the palette is indexed, otherwise \c false.*/
72 | bool indexable() const;
73 | /// Generate a preview of the palette suitable for display in a widget.
74 | QPixmap preview(int width, int height) const;
75 | /// Returns the number of colors in the palette.
76 | int numColors() const;
77 | private:
78 | QVector colors_;
79 | };
80 |
81 | // ---
82 |
83 | /// Represents the window function used for spectrogram generation.
84 | enum Window
85 | {
86 | WINDOW_HANN, /**< See http://en.wikipedia.org/wiki/Hann_window */
87 | WINDOW_BLACKMAN, /**< See http://en.wikipedia.org/wiki/Window_function#Blackman_windows */
88 | WINDOW_RECTANGULAR, /**< Doesn't do anything. */
89 | WINDOW_TRIANGULAR /**< http://en.wikipedia.org/wiki/Triangular_window#Triangular_window_.28non-zero_end-points.29 */
90 | };
91 | /// Represents the linear or logarithmic mode for frequency and intensity axes.
92 | enum AxisScale {SCALE_LINEAR, SCALE_LOGARITHMIC};
93 | /// Represents spectrogram synthesis mode.
94 | enum SynthesisType {SYNTHESIS_SINE, SYNTHESIS_NOISE};
95 | /// Represents the brightness correction used in spectrogram generation.
96 | enum BrightCorrection {BRIGHT_NONE, BRIGHT_SQRT};
97 |
98 | /// This class holds the parameters for a spectrogram and implements its synthesis and generation.
99 | class Spectrogram : public QObject
100 | {
101 | Q_OBJECT
102 | public:
103 | Spectrogram(QObject* parent = 0);
104 | /// Generates a spectrogram for the given signal.
105 | QImage to_image(real_vec& signal, int samplerate) const;
106 | /// Synthesizes the given spectrogram to sound.
107 | real_vec synthetize(const QImage& image, int samplerate,
108 | SynthesisType type) const;
109 | /// Serializes the Spectrogram object.
110 | /** The serialized string is saved in image metadata to indicate parameters with which the spectrogram has been generated. */
111 | QString serialized() const;
112 | /// Loads the serialized parameters into this object.
113 | void deserialize(const QString& serialized);
114 |
115 | /// Bandwidth of the frequency-domain filters.
116 | /** In Hz for linear spectrograms, in cents (cent = octave/1200) for logarithmic spectrograms.*/
117 | double bandwidth;
118 | /// Base frequency of the spectrogram.
119 | double basefreq;
120 | /// Maximum frequency of the spectrogram.
121 | double maxfreq;
122 | /// Overlap of the frequency-domain filters, 1 = full overlap (useless), 0 = no overlap.
123 | double overlap;
124 | /// Time resolution of the spectrogram, pixels per second.
125 | double pixpersec;
126 | /// Window function used on the frequency-domain intervals.
127 | Window window;
128 | /// Scale type of the intensity axis (linear or logarithmic)
129 | AxisScale intensity_axis;
130 | /// Scale type of the frequency axis (linear or logarithmic)
131 | AxisScale frequency_axis;
132 | /// Brightness correction used in generation of the spectrogram.
133 | BrightCorrection correction;
134 | /// Palette used for drawing the spectrogram.
135 | Palette palette;
136 | private:
137 | /// Performs sine synthesis on the given spectrogram.
138 | real_vec sine_synthesis(const QImage& image, int samplerate) const;
139 | /// Performs noise synthesis on the given spectrogram.
140 | real_vec noise_synthesis(const QImage& image, int samplerate) const;
141 | /// Draws an image from the given image data.
142 | QImage make_image(const std::vector& data) const;
143 | /// Returns intensity values (from <0,1>) from a row of pixels.
144 | real_vec envelope_from_spectrogram(const QImage& image, int row) const;
145 | /// Applies the window function to a frequency-domain interval.
146 | void apply_window(complex_vec& chunk, int lowidx,
147 | double filterscale) const;
148 | /// Delimiter of the serialized data
149 | static const char delimiter = ';';
150 | void band_progress(int x, int of, int from=0, int to=100) const;
151 | /// Indicates if the computation should be interrupted.
152 | bool cancelled() const;
153 | mutable bool cancelled_;
154 | signals:
155 | /// Signals percentual progress to the main application.
156 | void progress(int value) const;
157 | /// Signals the state of the computation to the main application.
158 | void status(const QString& text) const;
159 | public slots:
160 | /// Informs the working thread of a request to interrupt the computation.
161 | void cancel();
162 | };
163 |
164 | // --
165 |
166 | typedef std::pair intpair;
167 |
168 | /// Used to divide the frequency domain into suitable intervals.
169 | /** Each interval represents a horizontal band in a spectrogram. */
170 | class Filterbank
171 | {
172 | public:
173 | static std::auto_ptr get_filterbank(AxisScale type,
174 | double scale, double base, double hzbandwidth, double overlap);
175 |
176 | Filterbank(double scale);
177 | /// Returns start-finish indexes for a given filterband.
178 | virtual intpair get_band(int i) const = 0;
179 | /// Returns the index of the filterband's center.
180 | virtual int get_center(int i) const = 0;
181 | /// Estimated total number of intervals.
182 | virtual int num_bands_est(double maxfreq) const = 0;
183 | virtual ~Filterbank();
184 | protected:
185 | /// The proportion of frequency versus vector indices.
186 | const double scale_;
187 | };
188 |
189 | /// Divides the frequency domain to intervals of constant bandwidth.
190 | class LinearFilterbank : public Filterbank
191 | {
192 | public:
193 | LinearFilterbank(double scale, double base, double hzbandwidth,
194 | double overlap);
195 | intpair get_band(int i) const;
196 | int get_center(int i) const;
197 | int num_bands_est(double maxfreq) const;
198 | private:
199 | const double bandwidth_;
200 | const int startidx_;
201 | const double step_;
202 | };
203 |
204 | /// Divides the frequency domain to intervals with variable (logarithmic, constant-Q) bandwidth.
205 | class LogFilterbank : public Filterbank
206 | {
207 | public:
208 | LogFilterbank(double scale, double base, double centsperband,
209 | double overlap);
210 | intpair get_band(int i) const;
211 | int get_center(int i) const;
212 | int num_bands_est(double maxfreq) const;
213 | private:
214 | const double centsperband_;
215 | const double logstart_;
216 | const double logstep_;
217 | };
218 |
219 | #endif
220 |
--------------------------------------------------------------------------------
/types.hpp:
--------------------------------------------------------------------------------
1 | #ifndef TYPES_HPP
2 | #define TYPES_HPP
3 |
4 | /** \file types.hpp
5 | * \brief Contains definitions for basic types and constants.
6 | */
7 |
8 | #include
9 | #include
10 |
11 | #define PI 3.1415926535897932384626433832795
12 |
13 | typedef std::complex Complex;
14 | typedef std::vector real_vec;
15 | typedef std::vector complex_vec;
16 |
17 | #endif
18 |
--------------------------------------------------------------------------------
/viewer/cursorlabel.py:
--------------------------------------------------------------------------------
1 |
2 | from PyQt4.QtCore import *
3 | from PyQt4.QtGui import *
4 |
5 | class CursorLabel(QLabel):
6 | def __init__(self, parent=None):
7 | super(CursorLabel, self).__init__(parent)
8 | self.cursor = 0
9 |
10 | def paintEvent(self, event):
11 | QLabel.paintEvent(self, event)
12 | painter = QPainter(self)
13 | pen = QPen(QColor(255,0,0, 128))
14 | pen.setWidth(3)
15 | painter.setPen(pen)
16 | painter.drawLine(QPoint(self.cursor, 0),
17 | QPoint(self.cursor, self.height()))
18 | painter.end()
19 |
20 |
--------------------------------------------------------------------------------
/viewer/ui_viewer.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # Form implementation generated from reading ui file './viewer.ui'
4 | #
5 | # Created: Sun Aug 23 04:04:27 2009
6 | # by: PyQt4 UI code generator 4.4.2
7 | #
8 | # WARNING! All changes made in this file will be lost!
9 |
10 | from PyQt4 import QtCore, QtGui
11 |
12 | class Ui_MainWindow(object):
13 | def setupUi(self, MainWindow):
14 | MainWindow.setObjectName("MainWindow")
15 | MainWindow.resize(566,421)
16 | self.centralwidget = QtGui.QWidget(MainWindow)
17 | self.centralwidget.setObjectName("centralwidget")
18 | self.verticalLayout = QtGui.QVBoxLayout(self.centralwidget)
19 | self.verticalLayout.setObjectName("verticalLayout")
20 | self.horizontalLayout = QtGui.QHBoxLayout()
21 | self.horizontalLayout.setObjectName("horizontalLayout")
22 | self.label = QtGui.QLabel(self.centralwidget)
23 | self.label.setObjectName("label")
24 | self.horizontalLayout.addWidget(self.label)
25 | self.pathEdit = QtGui.QLineEdit(self.centralwidget)
26 | self.pathEdit.setObjectName("pathEdit")
27 | self.horizontalLayout.addWidget(self.pathEdit)
28 | self.browseButton = QtGui.QPushButton(self.centralwidget)
29 | self.browseButton.setObjectName("browseButton")
30 | self.horizontalLayout.addWidget(self.browseButton)
31 | spacerItem = QtGui.QSpacerItem(40,20,QtGui.QSizePolicy.Fixed,QtGui.QSizePolicy.Minimum)
32 | self.horizontalLayout.addItem(spacerItem)
33 | self.label_3 = QtGui.QLabel(self.centralwidget)
34 | self.label_3.setObjectName("label_3")
35 | self.horizontalLayout.addWidget(self.label_3)
36 | self.ppsSpin = QtGui.QSpinBox(self.centralwidget)
37 | self.ppsSpin.setMaximum(10000)
38 | self.ppsSpin.setSingleStep(10)
39 | self.ppsSpin.setProperty("value",QtCore.QVariant(100))
40 | self.ppsSpin.setObjectName("ppsSpin")
41 | self.horizontalLayout.addWidget(self.ppsSpin)
42 | self.verticalLayout.addLayout(self.horizontalLayout)
43 | self.scrollArea = QtGui.QScrollArea(self.centralwidget)
44 | self.scrollArea.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)
45 | self.scrollArea.setObjectName("scrollArea")
46 | self.scrollAreaWidgetContents_2 = QtGui.QWidget(self.scrollArea)
47 | self.scrollAreaWidgetContents_2.setGeometry(QtCore.QRect(0,1,513,290))
48 | self.scrollAreaWidgetContents_2.setObjectName("scrollAreaWidgetContents_2")
49 | self.horizontalLayout_3 = QtGui.QHBoxLayout(self.scrollAreaWidgetContents_2)
50 | self.horizontalLayout_3.setObjectName("horizontalLayout_3")
51 | self.imageLabel = CursorLabel(self.scrollAreaWidgetContents_2)
52 | sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding,QtGui.QSizePolicy.Expanding)
53 | sizePolicy.setHorizontalStretch(0)
54 | sizePolicy.setVerticalStretch(0)
55 | sizePolicy.setHeightForWidth(self.imageLabel.sizePolicy().hasHeightForWidth())
56 | self.imageLabel.setSizePolicy(sizePolicy)
57 | self.imageLabel.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)
58 | self.imageLabel.setObjectName("imageLabel")
59 | self.horizontalLayout_3.addWidget(self.imageLabel)
60 | self.verticalLayout.addWidget(self.scrollArea)
61 | self.progress = QtGui.QProgressBar(self.centralwidget)
62 | self.progress.setProperty("value",QtCore.QVariant(0))
63 | self.progress.setTextVisible(False)
64 | self.progress.setObjectName("progress")
65 | self.verticalLayout.addWidget(self.progress)
66 | self.horizontalLayout_2 = QtGui.QHBoxLayout()
67 | self.horizontalLayout_2.setObjectName("horizontalLayout_2")
68 | self.commandCheck = QtGui.QCheckBox(self.centralwidget)
69 | self.commandCheck.setObjectName("commandCheck")
70 | self.horizontalLayout_2.addWidget(self.commandCheck)
71 | self.commandEdit = QtGui.QLineEdit(self.centralwidget)
72 | self.commandEdit.setObjectName("commandEdit")
73 | self.horizontalLayout_2.addWidget(self.commandEdit)
74 | spacerItem1 = QtGui.QSpacerItem(20,20,QtGui.QSizePolicy.Fixed,QtGui.QSizePolicy.Minimum)
75 | self.horizontalLayout_2.addItem(spacerItem1)
76 | self.centeredCheck = QtGui.QCheckBox(self.centralwidget)
77 | self.centeredCheck.setChecked(True)
78 | self.centeredCheck.setObjectName("centeredCheck")
79 | self.horizontalLayout_2.addWidget(self.centeredCheck)
80 | spacerItem2 = QtGui.QSpacerItem(20,20,QtGui.QSizePolicy.Fixed,QtGui.QSizePolicy.Minimum)
81 | self.horizontalLayout_2.addItem(spacerItem2)
82 | self.playButton = QtGui.QPushButton(self.centralwidget)
83 | palette = QtGui.QPalette()
84 | brush = QtGui.QBrush(QtGui.QColor(170,0,0))
85 | brush.setStyle(QtCore.Qt.SolidPattern)
86 | palette.setBrush(QtGui.QPalette.Active,QtGui.QPalette.Button,brush)
87 | brush = QtGui.QBrush(QtGui.QColor(170,0,0))
88 | brush.setStyle(QtCore.Qt.SolidPattern)
89 | palette.setBrush(QtGui.QPalette.Inactive,QtGui.QPalette.Button,brush)
90 | brush = QtGui.QBrush(QtGui.QColor(170,0,0))
91 | brush.setStyle(QtCore.Qt.SolidPattern)
92 | palette.setBrush(QtGui.QPalette.Disabled,QtGui.QPalette.Button,brush)
93 | self.playButton.setPalette(palette)
94 | self.playButton.setObjectName("playButton")
95 | self.horizontalLayout_2.addWidget(self.playButton)
96 | self.stopButton = QtGui.QPushButton(self.centralwidget)
97 | self.stopButton.setObjectName("stopButton")
98 | self.horizontalLayout_2.addWidget(self.stopButton)
99 | self.verticalLayout.addLayout(self.horizontalLayout_2)
100 | MainWindow.setCentralWidget(self.centralwidget)
101 |
102 | self.retranslateUi(MainWindow)
103 | QtCore.QMetaObject.connectSlotsByName(MainWindow)
104 |
105 | def retranslateUi(self, MainWindow):
106 | MainWindow.setWindowTitle(QtGui.QApplication.translate("MainWindow", "MainWindow", None, QtGui.QApplication.UnicodeUTF8))
107 | self.label.setText(QtGui.QApplication.translate("MainWindow", "Spectrogram", None, QtGui.QApplication.UnicodeUTF8))
108 | self.browseButton.setText(QtGui.QApplication.translate("MainWindow", "Browse", None, QtGui.QApplication.UnicodeUTF8))
109 | self.label_3.setText(QtGui.QApplication.translate("MainWindow", "Pixels per second", None, QtGui.QApplication.UnicodeUTF8))
110 | self.imageLabel.setText(QtGui.QApplication.translate("MainWindow", "[image]", None, QtGui.QApplication.UnicodeUTF8))
111 | self.commandCheck.setText(QtGui.QApplication.translate("MainWindow", "Command", None, QtGui.QApplication.UnicodeUTF8))
112 | self.commandEdit.setText(QtGui.QApplication.translate("MainWindow", "xmms -p", None, QtGui.QApplication.UnicodeUTF8))
113 | self.centeredCheck.setText(QtGui.QApplication.translate("MainWindow", "Centered", None, QtGui.QApplication.UnicodeUTF8))
114 | self.playButton.setText(QtGui.QApplication.translate("MainWindow", "Play", None, QtGui.QApplication.UnicodeUTF8))
115 | self.stopButton.setText(QtGui.QApplication.translate("MainWindow", "Stop", None, QtGui.QApplication.UnicodeUTF8))
116 |
117 | from cursorlabel import CursorLabel
118 |
--------------------------------------------------------------------------------
/viewer/viewer.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import sys, os, time
4 | from PyQt4.QtCore import *
5 | from PyQt4.QtGui import *
6 | import ui_viewer
7 |
8 | class ViewerWindow(QMainWindow):
9 | def __init__(self, parent=None):
10 | super(ViewerWindow, self).__init__(parent)
11 | self.ui = ui_viewer.Ui_MainWindow()
12 | self.ui.setupUi(self)
13 | self.timer = QTimer(self)
14 | self.connect(self.timer, SIGNAL("timeout()"), self.update)
15 | self.image = QImage()
16 | self.time = 0
17 | self.lpos = 0
18 | self.paused = False
19 | self.interval = 100
20 | self.ui.scrollArea.setWidget(self.ui.imageLabel)
21 | #self.ui.scrollArea.setWidgetResizable(True)
22 | self.connect(self.ui.browseButton, SIGNAL("clicked()"), self.browse)
23 | self.connect(self.ui.playButton, SIGNAL("clicked()"), self.play)
24 | self.connect(self.ui.stopButton, SIGNAL("clicked()"), self.stop)
25 |
26 | def update(self):
27 | # center on pos
28 | self.time += self.interval
29 | pos = int(self.time/self.len*self.image.width())
30 | if pos > self.image.width():
31 | pos = 0
32 | self.stop()
33 | self.ui.imageLabel.cursor = pos
34 | #print "---"
35 | #print "cas:", self.time/1000.0, "s", "/", self.len/1000.0
36 | #print "pozice:", pos, "/", self.image.width()
37 | self.ui.progress.setValue(self.time)
38 | self.ui.imageLabel.update()
39 | if self.ui.centeredCheck.isChecked():
40 | margin = self.ui.scrollArea.width()//2-10
41 | self.ui.scrollArea.ensureVisible(pos,self.image.height()//2, margin)
42 | else:
43 | width = self.ui.scrollArea.width()
44 | margin = 3*width/4.0
45 | if pos - self.lpos > margin:
46 | self.ui.scrollArea.ensureVisible(pos+margin,
47 | self.image.height()//2, width-margin)
48 | self.lpos = pos
49 |
50 | def browse(self):
51 | filename = QFileDialog.getOpenFileName(self, "Choose spectrogram",
52 | ".", "Images (*.png *.jpg *.bmp *.xpm);;All files (*.*)")
53 | if not filename:
54 | return
55 | self.ui.pathEdit.setText(filename)
56 | self.image = QImage(filename)
57 | if self.image.isNull():
58 | QMessageBox.information(self, "error", "Couldn't read that file")
59 | return
60 | self.ui.imageLabel.setPixmap(QPixmap.fromImage(self.image))
61 | self.ui.imageLabel.adjustSize()
62 |
63 | def play(self):
64 | if self.paused:
65 | self.timer.start(self.interval)
66 | self.paused = False
67 | return
68 | if self.timer.isActive():
69 | self.timer.stop()
70 | self.paused = True
71 | return
72 |
73 | if self.image.isNull():
74 | return
75 | cmd = str(self.ui.commandEdit.text())
76 | if self.ui.commandCheck.isChecked() and str:
77 | os.system(cmd)
78 | #for i in xrange(0,5):
79 | # print i
80 | # time.sleep(1)
81 | pps = self.ui.ppsSpin.value()
82 | self.interval = 40
83 | self.len = self.image.width()*1000.0/pps
84 | self.ui.progress.setMaximum(int(self.len))
85 | if not self.timer.isActive():
86 | self.timer.start(self.interval)
87 |
88 | def stop(self):
89 | self.timer.stop()
90 | self.time = 0
91 | self.paused = False
92 |
93 | def main():
94 | app = QApplication(sys.argv)
95 | form = ViewerWindow()
96 | form.show()
97 | app.exec_()
98 |
99 | if __name__ == "__main__":
100 | main()
101 |
--------------------------------------------------------------------------------
/viewer/viewer.ui:
--------------------------------------------------------------------------------
1 |
2 | MainWindow
3 |
4 |
5 |
6 | 0
7 | 0
8 | 566
9 | 421
10 |
11 |
12 |
13 | MainWindow
14 |
15 |
16 |
17 | -
18 |
19 |
-
20 |
21 |
22 | Spectrogram
23 |
24 |
25 |
26 | -
27 |
28 |
29 | -
30 |
31 |
32 | Browse
33 |
34 |
35 |
36 | -
37 |
38 |
39 | Qt::Horizontal
40 |
41 |
42 | QSizePolicy::Fixed
43 |
44 |
45 |
46 | 40
47 | 20
48 |
49 |
50 |
51 |
52 | -
53 |
54 |
55 | Pixels per second
56 |
57 |
58 |
59 | -
60 |
61 |
62 | 10000
63 |
64 |
65 | 10
66 |
67 |
68 | 100
69 |
70 |
71 |
72 |
73 |
74 | -
75 |
76 |
77 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
78 |
79 |
80 |
81 |
82 | 0
83 | 1
84 | 513
85 | 290
86 |
87 |
88 |
89 |
-
90 |
91 |
92 |
93 | 0
94 | 0
95 |
96 |
97 |
98 | [image]
99 |
100 |
101 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | -
110 |
111 |
112 | 0
113 |
114 |
115 | false
116 |
117 |
118 |
119 | -
120 |
121 |
-
122 |
123 |
124 | Command
125 |
126 |
127 |
128 | -
129 |
130 |
131 | xmms -p
132 |
133 |
134 |
135 | -
136 |
137 |
138 | Qt::Horizontal
139 |
140 |
141 | QSizePolicy::Fixed
142 |
143 |
144 |
145 | 20
146 | 20
147 |
148 |
149 |
150 |
151 | -
152 |
153 |
154 | Centered
155 |
156 |
157 | true
158 |
159 |
160 |
161 | -
162 |
163 |
164 | Qt::Horizontal
165 |
166 |
167 | QSizePolicy::Fixed
168 |
169 |
170 |
171 | 20
172 | 20
173 |
174 |
175 |
176 |
177 | -
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 | 170
186 | 0
187 | 0
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 | 170
197 | 0
198 | 0
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 | 170
208 | 0
209 | 0
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 | Play
218 |
219 |
220 |
221 | -
222 |
223 |
224 | Stop
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 | CursorLabel
236 | QLabel
237 |
238 |
239 |
240 |
241 |
242 |
243 |
--------------------------------------------------------------------------------