├── .dir-locals.el ├── .gitignore ├── LICENSE ├── Makefile ├── Makefile.dep ├── README.md ├── color.c ├── color.h ├── common.h ├── main.c ├── stream.c ├── stream.h ├── stream_raw.c ├── stream_raw.h ├── stream_wav.c ├── stream_wav.h ├── wedge.c ├── wedge.h ├── window.c └── window.h /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((c-mode . ((eval . (let ((gtk-args '("-I/usr/include/gtk-3.0" 2 | "-I/usr/include/at-spi2-atk/2.0" 3 | "-I/usr/include/at-spi-2.0" 4 | "-I/usr/include/dbus-1.0" 5 | "-I/usr/lib/x86_64-linux-gnu/dbus-1.0/include" 6 | "-I/usr/include/gtk-3.0" 7 | "-I/usr/include/gio-unix-2.0/" 8 | "-I/usr/include/mirclient" 9 | "-I/usr/include/mircommon" 10 | "-I/usr/include/cairo" 11 | "-I/usr/include/pango-1.0" 12 | "-I/usr/include/harfbuzz" 13 | "-I/usr/include/pango-1.0" 14 | "-I/usr/include/atk-1.0" 15 | "-I/usr/include/cairo" 16 | "-I/usr/include/pixman-1" 17 | "-I/usr/include/freetype2" 18 | "-I/usr/include/libpng12" 19 | "-I/usr/include/gdk-pixbuf-2.0" 20 | "-I/usr/include/libpng12" 21 | "-I/usr/include/glib-2.0" 22 | "-I/usr/lib/x86_64-linux-gnu/glib-2.0/include"))) 23 | (setq company-clang-arguments (append company-clang-arguments gtk-args) 24 | flycheck-clang-args (append flycheck-clang-args gtk-args))))))) 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | waterfall 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Pieter Noordhuis 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DEFS=-D_POSIX_C_SOURCE=200809 2 | override CFLAGS+=-std=c99 -Wall -Wextra -Wpedantic -g -ggdb $(shell pkg-config --cflags gtk+-3.0) $(DEFS) 3 | override LDFLAGS+=-lm -lpthread -lfftw3 $(shell pkg-config --libs gtk+-3.0) 4 | MAIN=waterfall 5 | MAIN_OBJS=main.o stream.o window.o wedge.o color.o 6 | STREAM_OBJS=$(patsubst %.c,%.o,$(wildcard stream_*.c)) 7 | OBJS=$(MAIN_OBJS) $(STREAM_OBJS) 8 | 9 | .PHONY: all 10 | all: $(MAIN) 11 | 12 | .PHONY: clean 13 | clean: 14 | rm -f $(MAIN) $(OBJS) 15 | 16 | .PHONY: dep 17 | dep: 18 | $(CC) -MM $(OBJS:.o=.c) > Makefile.dep 19 | 20 | -include Makefile.dep 21 | 22 | $(MAIN): $(OBJS) 23 | $(CC) -o $@ $^ $(LDFLAGS) 24 | 25 | %.o: %.c %.h 26 | $(CC) $(CFLAGS) -c -o $@ $< 27 | -------------------------------------------------------------------------------- /Makefile.dep: -------------------------------------------------------------------------------- 1 | main.o: main.c stream.h window.h wedge.h color.h stream_raw.h \ 2 | stream_wav.h 3 | stream.o: stream.c stream.h 4 | window.o: window.c window.h stream.h wedge.h common.h 5 | wedge.o: wedge.c wedge.h stream.h color.h 6 | color.o: color.c color.h 7 | stream_raw.o: stream_raw.c stream_raw.h stream.h 8 | stream_wav.o: stream_wav.c stream_raw.h stream.h 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # waterfall 2 | 3 | Interactive spectrum analysis. 4 | 5 | ## Usage 6 | 7 | Dependencies: 8 | 9 | * GTK+ 3 10 | * FFTW 3 11 | 12 | To build, run `make` in the project root. 13 | 14 | To execute, run `./waterfall` and specify a signal file to analyze. 15 | 16 | ### Supported formats 17 | 18 | * Wave (WAV) 19 | * Raw 8-bit signed integer 20 | * Raw 16-bit signed integer 21 | * Raw 32-bit signed integer 22 | * Raw 32-bit float 23 | 24 | Wave files are expected to have 2 channels (in-phase and quadrature). 25 | 26 | Raw files are expected to have interleaved in-phase and quadrature samples. 27 | 28 | ## License 29 | 30 | Simplified BSD. See `LICENSE` file. 31 | -------------------------------------------------------------------------------- /color.c: -------------------------------------------------------------------------------- 1 | #include "color.h" 2 | 3 | color_t *color = &colormap_rainbow[0]; 4 | 5 | // Rainbow color map from http://www.cs.uml.edu/~haim/ColorCenter/RainbowCM.htm 6 | colormap_t colormap_rainbow = { 7 | { 0, 0, 0 }, 8 | { 45, 0, 36 }, 9 | { 56, 0, 46 }, 10 | { 60, 0, 49 }, 11 | { 67, 0, 54 }, 12 | { 70, 0, 59 }, 13 | { 71, 0, 61 }, 14 | { 75, 0, 68 }, 15 | { 74, 0, 73 }, 16 | { 74, 0, 77 }, 17 | { 73, 0, 81 }, 18 | { 71, 0, 87 }, 19 | { 69, 1, 90 }, 20 | { 68, 2, 94 }, 21 | { 66, 3, 97 }, 22 | { 63, 6, 102 }, 23 | { 61, 7, 106 }, 24 | { 58, 10, 109 }, 25 | { 56, 12, 113 }, 26 | { 53, 15, 116 }, 27 | { 48, 18, 119 }, 28 | { 47, 20, 121 }, 29 | { 44, 23, 124 }, 30 | { 41, 27, 128 }, 31 | { 40, 28, 129 }, 32 | { 37, 32, 132 }, 33 | { 34, 36, 134 }, 34 | { 29, 43, 137 }, 35 | { 25, 52, 138 }, 36 | { 24, 57, 139 }, 37 | { 24, 62, 141 }, 38 | { 24, 64, 142 }, 39 | { 23, 65, 142 }, 40 | { 23, 69, 143 }, 41 | { 23, 71, 142 }, 42 | { 23, 71, 142 }, 43 | { 23, 73, 142 }, 44 | { 23, 75, 142 }, 45 | { 23, 75, 142 }, 46 | { 23, 78, 142 }, 47 | { 23, 80, 142 }, 48 | { 23, 80, 142 }, 49 | { 23, 82, 141 }, 50 | { 23, 85, 141 }, 51 | { 23, 85, 141 }, 52 | { 23, 87, 140 }, 53 | { 23, 87, 140 }, 54 | { 24, 90, 140 }, 55 | { 24, 90, 140 }, 56 | { 24, 93, 139 }, 57 | { 24, 93, 139 }, 58 | { 24, 93, 139 }, 59 | { 24, 93, 139 }, 60 | { 24, 97, 139 }, 61 | { 24, 97, 139 }, 62 | { 25, 101, 138 }, 63 | { 25, 101, 138 }, 64 | { 25, 104, 137 }, 65 | { 25, 104, 137 }, 66 | { 25, 104, 137 }, 67 | { 26, 108, 137 }, 68 | { 26, 108, 137 }, 69 | { 27, 111, 136 }, 70 | { 27, 111, 136 }, 71 | { 27, 111, 136 }, 72 | { 27, 115, 135 }, 73 | { 27, 115, 135 }, 74 | { 28, 118, 134 }, 75 | { 28, 118, 134 }, 76 | { 29, 122, 133 }, 77 | { 29, 122, 133 }, 78 | { 29, 122, 133 }, 79 | { 29, 122, 133 }, 80 | { 29, 125, 132 }, 81 | { 29, 125, 132 }, 82 | { 30, 128, 131 }, 83 | { 30, 128, 131 }, 84 | { 31, 131, 130 }, 85 | { 31, 131, 130 }, 86 | { 31, 131, 130 }, 87 | { 32, 134, 128 }, 88 | { 32, 134, 128 }, 89 | { 33, 137, 127 }, 90 | { 33, 137, 127 }, 91 | { 33, 137, 127 }, 92 | { 34, 140, 125 }, 93 | { 34, 140, 125 }, 94 | { 35, 142, 123 }, 95 | { 35, 142, 123 }, 96 | { 36, 145, 121 }, 97 | { 36, 145, 121 }, 98 | { 36, 145, 121 }, 99 | { 37, 147, 118 }, 100 | { 37, 147, 118 }, 101 | { 38, 150, 116 }, 102 | { 38, 150, 116 }, 103 | { 40, 152, 113 }, 104 | { 40, 152, 113 }, 105 | { 41, 154, 111 }, 106 | { 41, 154, 111 }, 107 | { 42, 156, 108 }, 108 | { 42, 156, 108 }, 109 | { 43, 158, 106 }, 110 | { 43, 158, 106 }, 111 | { 43, 158, 106 }, 112 | { 45, 160, 104 }, 113 | { 45, 160, 104 }, 114 | { 46, 162, 101 }, 115 | { 46, 162, 101 }, 116 | { 48, 164, 99 }, 117 | { 48, 164, 99 }, 118 | { 50, 166, 97 }, 119 | { 50, 166, 97 }, 120 | { 51, 168, 95 }, 121 | { 53, 170, 93 }, 122 | { 53, 170, 93 }, 123 | { 53, 170, 93 }, 124 | { 55, 172, 91 }, 125 | { 55, 172, 91 }, 126 | { 57, 174, 88 }, 127 | { 57, 174, 88 }, 128 | { 59, 175, 86 }, 129 | { 62, 177, 84 }, 130 | { 64, 178, 82 }, 131 | { 64, 178, 82 }, 132 | { 67, 180, 80 }, 133 | { 67, 180, 80 }, 134 | { 69, 181, 79 }, 135 | { 72, 183, 77 }, 136 | { 72, 183, 77 }, 137 | { 72, 183, 77 }, 138 | { 75, 184, 76 }, 139 | { 77, 186, 74 }, 140 | { 80, 187, 73 }, 141 | { 83, 189, 72 }, 142 | { 87, 190, 72 }, 143 | { 91, 191, 71 }, 144 | { 95, 192, 70 }, 145 | { 99, 193, 70 }, 146 | { 103, 194, 70 }, 147 | { 107, 195, 70 }, 148 | { 111, 196, 70 }, 149 | { 111, 196, 70 }, 150 | { 115, 196, 70 }, 151 | { 119, 197, 70 }, 152 | { 123, 197, 70 }, 153 | { 130, 198, 71 }, 154 | { 133, 199, 71 }, 155 | { 137, 199, 72 }, 156 | { 140, 199, 72 }, 157 | { 143, 199, 73 }, 158 | { 143, 199, 73 }, 159 | { 147, 199, 73 }, 160 | { 150, 199, 74 }, 161 | { 153, 199, 74 }, 162 | { 156, 199, 75 }, 163 | { 160, 200, 76 }, 164 | { 167, 200, 78 }, 165 | { 170, 200, 79 }, 166 | { 173, 200, 79 }, 167 | { 173, 200, 79 }, 168 | { 177, 200, 80 }, 169 | { 180, 200, 81 }, 170 | { 183, 199, 82 }, 171 | { 186, 199, 82 }, 172 | { 190, 199, 83 }, 173 | { 196, 199, 85 }, 174 | { 199, 198, 85 }, 175 | { 199, 198, 85 }, 176 | { 203, 198, 86 }, 177 | { 206, 197, 87 }, 178 | { 212, 197, 89 }, 179 | { 215, 196, 90 }, 180 | { 218, 195, 91 }, 181 | { 224, 194, 94 }, 182 | { 224, 194, 94 }, 183 | { 230, 193, 96 }, 184 | { 233, 192, 98 }, 185 | { 236, 190, 100 }, 186 | { 238, 189, 104 }, 187 | { 240, 188, 106 }, 188 | { 240, 188, 106 }, 189 | { 242, 187, 110 }, 190 | { 244, 185, 114 }, 191 | { 245, 184, 116 }, 192 | { 247, 183, 120 }, 193 | { 248, 182, 123 }, 194 | { 248, 182, 123 }, 195 | { 250, 181, 125 }, 196 | { 251, 180, 128 }, 197 | { 252, 180, 130 }, 198 | { 253, 180, 133 }, 199 | { 253, 180, 133 }, 200 | { 254, 180, 134 }, 201 | { 254, 179, 138 }, 202 | { 255, 179, 142 }, 203 | { 255, 179, 145 }, 204 | { 255, 179, 145 }, 205 | { 255, 179, 152 }, 206 | { 255, 180, 161 }, 207 | { 255, 180, 164 }, 208 | { 255, 180, 167 }, 209 | { 255, 180, 167 }, 210 | { 255, 181, 169 }, 211 | { 255, 181, 170 }, 212 | { 255, 182, 173 }, 213 | { 255, 183, 176 }, 214 | { 255, 183, 176 }, 215 | { 255, 184, 179 }, 216 | { 255, 185, 179 }, 217 | { 255, 185, 182 }, 218 | { 255, 186, 182 }, 219 | { 255, 186, 182 }, 220 | { 255, 187, 185 }, 221 | { 255, 188, 185 }, 222 | { 255, 189, 188 }, 223 | { 255, 189, 188 }, 224 | { 255, 190, 188 }, 225 | { 255, 191, 191 }, 226 | { 255, 192, 191 }, 227 | { 255, 194, 194 }, 228 | { 255, 194, 194 }, 229 | { 255, 197, 197 }, 230 | { 255, 198, 198 }, 231 | { 255, 200, 200 }, 232 | { 255, 201, 201 }, 233 | { 255, 201, 201 }, 234 | { 255, 202, 202 }, 235 | { 255, 203, 203 }, 236 | { 255, 205, 205 }, 237 | { 255, 206, 206 }, 238 | { 255, 206, 206 }, 239 | { 255, 208, 208 }, 240 | { 255, 209, 209 }, 241 | { 255, 211, 211 }, 242 | { 255, 215, 215 }, 243 | { 255, 216, 216 }, 244 | { 255, 216, 216 }, 245 | { 255, 218, 218 }, 246 | { 255, 219, 219 }, 247 | { 255, 221, 221 }, 248 | { 255, 223, 223 }, 249 | { 255, 226, 226 }, 250 | { 255, 228, 228 }, 251 | { 255, 230, 230 }, 252 | { 255, 230, 230 }, 253 | { 255, 232, 232 }, 254 | { 255, 235, 235 }, 255 | { 255, 237, 237 }, 256 | { 255, 240, 240 }, 257 | { 255, 243, 243 }, 258 | { 255, 246, 246 }, 259 | { 255, 249, 249 }, 260 | { 255, 251, 251 }, 261 | { 255, 253, 253 }, 262 | { 255, 255, 255 }, 263 | }; 264 | 265 | // Blue to yellow color map from http://www.cs.uml.edu/~haim/ColorCenter/BTYCM.htm 266 | colormap_t colormap_blue_to_yellow = { 267 | { 7, 7, 254 }, 268 | { 23, 23, 252 }, 269 | { 30, 30, 250 }, 270 | { 36, 36, 248 }, 271 | { 40, 40, 247 }, 272 | { 44, 44, 245 }, 273 | { 47, 47, 243 }, 274 | { 50, 50, 242 }, 275 | { 52, 52, 240 }, 276 | { 55, 55, 239 }, 277 | { 57, 57, 238 }, 278 | { 59, 59, 236 }, 279 | { 61, 61, 235 }, 280 | { 63, 63, 234 }, 281 | { 65, 65, 233 }, 282 | { 66, 66, 231 }, 283 | { 68, 68, 230 }, 284 | { 69, 69, 229 }, 285 | { 71, 71, 228 }, 286 | { 72, 72, 227 }, 287 | { 74, 74, 226 }, 288 | { 75, 75, 225 }, 289 | { 76, 76, 225 }, 290 | { 78, 78, 224 }, 291 | { 79, 79, 223 }, 292 | { 80, 80, 222 }, 293 | { 81, 81, 221 }, 294 | { 82, 82, 221 }, 295 | { 84, 84, 220 }, 296 | { 85, 85, 219 }, 297 | { 86, 86, 218 }, 298 | { 87, 87, 218 }, 299 | { 88, 88, 217 }, 300 | { 89, 89, 216 }, 301 | { 90, 90, 216 }, 302 | { 91, 91, 215 }, 303 | { 92, 92, 214 }, 304 | { 93, 93, 214 }, 305 | { 94, 94, 213 }, 306 | { 95, 95, 213 }, 307 | { 96, 96, 212 }, 308 | { 97, 97, 212 }, 309 | { 98, 98, 211 }, 310 | { 98, 98, 210 }, 311 | { 99, 99, 210 }, 312 | { 100, 100, 209 }, 313 | { 101, 101, 209 }, 314 | { 102, 102, 208 }, 315 | { 103, 103, 208 }, 316 | { 104, 104, 208 }, 317 | { 105, 105, 207 }, 318 | { 105, 105, 207 }, 319 | { 106, 106, 206 }, 320 | { 107, 107, 206 }, 321 | { 108, 108, 205 }, 322 | { 109, 109, 205 }, 323 | { 110, 110, 204 }, 324 | { 110, 110, 204 }, 325 | { 111, 111, 204 }, 326 | { 112, 112, 203 }, 327 | { 113, 113, 203 }, 328 | { 114, 114, 202 }, 329 | { 114, 114, 202 }, 330 | { 115, 115, 202 }, 331 | { 116, 116, 201 }, 332 | { 117, 117, 201 }, 333 | { 118, 118, 200 }, 334 | { 118, 118, 200 }, 335 | { 119, 119, 200 }, 336 | { 120, 120, 199 }, 337 | { 121, 121, 199 }, 338 | { 121, 121, 199 }, 339 | { 122, 122, 198 }, 340 | { 123, 123, 198 }, 341 | { 124, 124, 198 }, 342 | { 124, 124, 197 }, 343 | { 125, 125, 197 }, 344 | { 126, 126, 197 }, 345 | { 127, 127, 196 }, 346 | { 128, 128, 196 }, 347 | { 128, 128, 195 }, 348 | { 129, 129, 195 }, 349 | { 130, 130, 195 }, 350 | { 130, 130, 194 }, 351 | { 131, 131, 194 }, 352 | { 132, 132, 194 }, 353 | { 133, 133, 193 }, 354 | { 133, 133, 193 }, 355 | { 134, 134, 193 }, 356 | { 135, 135, 192 }, 357 | { 136, 136, 192 }, 358 | { 136, 136, 192 }, 359 | { 137, 137, 191 }, 360 | { 138, 138, 191 }, 361 | { 139, 139, 191 }, 362 | { 139, 139, 190 }, 363 | { 140, 140, 190 }, 364 | { 141, 141, 190 }, 365 | { 142, 142, 189 }, 366 | { 142, 142, 189 }, 367 | { 143, 143, 189 }, 368 | { 144, 144, 188 }, 369 | { 144, 144, 188 }, 370 | { 145, 145, 188 }, 371 | { 146, 146, 187 }, 372 | { 147, 147, 187 }, 373 | { 147, 147, 187 }, 374 | { 148, 148, 186 }, 375 | { 149, 149, 186 }, 376 | { 149, 149, 186 }, 377 | { 150, 150, 185 }, 378 | { 151, 151, 185 }, 379 | { 152, 152, 185 }, 380 | { 152, 152, 184 }, 381 | { 153, 153, 184 }, 382 | { 154, 154, 184 }, 383 | { 154, 154, 183 }, 384 | { 155, 155, 183 }, 385 | { 156, 156, 182 }, 386 | { 157, 157, 182 }, 387 | { 157, 157, 182 }, 388 | { 158, 158, 181 }, 389 | { 159, 159, 181 }, 390 | { 159, 159, 181 }, 391 | { 160, 160, 180 }, 392 | { 161, 161, 180 }, 393 | { 162, 162, 180 }, 394 | { 162, 162, 179 }, 395 | { 163, 163, 179 }, 396 | { 164, 164, 178 }, 397 | { 164, 164, 178 }, 398 | { 165, 165, 178 }, 399 | { 166, 166, 177 }, 400 | { 167, 167, 177 }, 401 | { 167, 167, 176 }, 402 | { 168, 168, 176 }, 403 | { 169, 169, 176 }, 404 | { 169, 169, 175 }, 405 | { 170, 170, 175 }, 406 | { 171, 171, 174 }, 407 | { 172, 172, 174 }, 408 | { 172, 172, 173 }, 409 | { 173, 173, 173 }, 410 | { 174, 174, 173 }, 411 | { 174, 174, 172 }, 412 | { 175, 175, 172 }, 413 | { 176, 176, 171 }, 414 | { 177, 177, 171 }, 415 | { 177, 177, 170 }, 416 | { 178, 178, 170 }, 417 | { 179, 179, 169 }, 418 | { 179, 179, 169 }, 419 | { 180, 180, 168 }, 420 | { 181, 181, 168 }, 421 | { 181, 181, 167 }, 422 | { 182, 182, 167 }, 423 | { 183, 183, 166 }, 424 | { 184, 184, 166 }, 425 | { 184, 184, 165 }, 426 | { 185, 185, 165 }, 427 | { 186, 186, 164 }, 428 | { 186, 186, 164 }, 429 | { 187, 187, 163 }, 430 | { 188, 188, 163 }, 431 | { 189, 189, 162 }, 432 | { 189, 189, 162 }, 433 | { 190, 190, 161 }, 434 | { 191, 191, 161 }, 435 | { 191, 191, 160 }, 436 | { 192, 192, 159 }, 437 | { 193, 193, 159 }, 438 | { 194, 194, 158 }, 439 | { 194, 194, 158 }, 440 | { 195, 195, 157 }, 441 | { 196, 196, 157 }, 442 | { 196, 196, 156 }, 443 | { 197, 197, 155 }, 444 | { 198, 198, 155 }, 445 | { 199, 199, 154 }, 446 | { 199, 199, 153 }, 447 | { 200, 200, 153 }, 448 | { 201, 201, 152 }, 449 | { 201, 201, 151 }, 450 | { 202, 202, 151 }, 451 | { 203, 203, 150 }, 452 | { 204, 204, 149 }, 453 | { 204, 204, 149 }, 454 | { 205, 205, 148 }, 455 | { 206, 206, 147 }, 456 | { 206, 206, 146 }, 457 | { 207, 207, 146 }, 458 | { 208, 208, 145 }, 459 | { 209, 209, 144 }, 460 | { 209, 209, 143 }, 461 | { 210, 210, 143 }, 462 | { 211, 211, 142 }, 463 | { 211, 211, 141 }, 464 | { 212, 212, 140 }, 465 | { 213, 213, 139 }, 466 | { 214, 214, 138 }, 467 | { 214, 214, 138 }, 468 | { 215, 215, 137 }, 469 | { 216, 216, 136 }, 470 | { 216, 216, 135 }, 471 | { 217, 217, 134 }, 472 | { 218, 218, 133 }, 473 | { 219, 219, 132 }, 474 | { 219, 219, 131 }, 475 | { 220, 220, 130 }, 476 | { 221, 221, 129 }, 477 | { 221, 221, 128 }, 478 | { 222, 222, 127 }, 479 | { 223, 223, 126 }, 480 | { 224, 224, 125 }, 481 | { 224, 224, 124 }, 482 | { 225, 225, 123 }, 483 | { 226, 226, 122 }, 484 | { 226, 226, 121 }, 485 | { 227, 227, 119 }, 486 | { 228, 228, 118 }, 487 | { 229, 229, 117 }, 488 | { 229, 229, 116 }, 489 | { 230, 230, 114 }, 490 | { 231, 231, 113 }, 491 | { 232, 232, 112 }, 492 | { 232, 232, 110 }, 493 | { 233, 233, 109 }, 494 | { 234, 234, 107 }, 495 | { 234, 234, 106 }, 496 | { 235, 235, 104 }, 497 | { 236, 236, 103 }, 498 | { 237, 237, 101 }, 499 | { 237, 237, 100 }, 500 | { 238, 238, 98 }, 501 | { 239, 239, 96 }, 502 | { 239, 239, 94 }, 503 | { 240, 240, 92 }, 504 | { 241, 241, 91 }, 505 | { 242, 242, 89 }, 506 | { 242, 242, 86 }, 507 | { 243, 243, 84 }, 508 | { 244, 244, 82 }, 509 | { 245, 245, 80 }, 510 | { 245, 245, 77 }, 511 | { 246, 246, 74 }, 512 | { 247, 247, 72 }, 513 | { 247, 247, 69 }, 514 | { 248, 248, 65 }, 515 | { 249, 249, 62 }, 516 | { 250, 250, 58 }, 517 | { 250, 250, 54 }, 518 | { 251, 251, 49 }, 519 | { 252, 252, 44 }, 520 | { 253, 253, 37 }, 521 | { 253, 253, 28 }, 522 | { 254, 254, 13 }, 523 | }; 524 | -------------------------------------------------------------------------------- /color.h: -------------------------------------------------------------------------------- 1 | #ifndef _COLOR_H 2 | #define _COLOR_H 3 | 4 | #include 5 | 6 | typedef uint8_t color_t[3]; 7 | 8 | typedef color_t colormap_t[256]; 9 | 10 | // Pointer to first color in chosen colormap 11 | extern color_t *color; 12 | 13 | // Available colormaps 14 | extern colormap_t colormap_rainbow; 15 | extern colormap_t colormap_blue_to_yellow; 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /common.h: -------------------------------------------------------------------------------- 1 | #ifndef _COMMON_H 2 | #define _COMMON_H 3 | 4 | #ifndef M_TAU 5 | #define M_TAU 6.28318530717958647693 6 | #endif 7 | 8 | #ifndef DEBUG 9 | #define DEBUG 0 10 | #endif 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "stream.h" 12 | #include "window.h" 13 | #include "wedge.h" 14 | #include "color.h" 15 | 16 | #include "stream_raw.h" 17 | #include "stream_wav.h" 18 | 19 | static struct { 20 | char *type; 21 | int32_t sample_rate; 22 | int32_t center_freq; 23 | } *global; 24 | 25 | void usage(__attribute__((unused)) int argc, char **argv) { 26 | fprintf(stderr, "Usage: %s [OPTION]... FILE\n", argv[0]); 27 | fprintf(stderr, "Analyze frequency spectrum of signal in FILE\n"); 28 | fprintf(stderr, "\n"); 29 | fprintf(stderr, " -t TYPE File type of FILE [wav,u8,s8,s16,s32,f32]\n"); 30 | fprintf(stderr, " -r RATE Sample rate of signal\n"); 31 | fprintf(stderr, " -c FREQ Center frequency of signal\n"); 32 | fprintf(stderr, "\n"); 33 | exit(EXIT_FAILURE); 34 | } 35 | 36 | static void parse(int argc, char **argv) { 37 | int opt; 38 | 39 | if (argc < 2) { 40 | usage(argc, argv); 41 | } 42 | 43 | if (strcmp(argv[1], "--help") == 0) { 44 | usage(argc, argv); 45 | } 46 | 47 | global = calloc(sizeof(*global), 1); 48 | global->type = NULL; 49 | global->sample_rate = -1; 50 | global->center_freq = -1; 51 | 52 | while ((opt = getopt(argc, argv, "ht:r:c:")) != -1) { 53 | switch (opt) { 54 | case 'h': 55 | usage(argc, argv); 56 | break; 57 | case 't': 58 | global->type = strdup(optarg); 59 | break; 60 | case 'r': 61 | global->sample_rate = atoi(optarg); 62 | break; 63 | case 'c': 64 | global->center_freq = atoi(optarg); 65 | break; 66 | default: 67 | fprintf(stderr, "Try '%s --help' for more information.\n", argv[0]); 68 | exit(EXIT_FAILURE); 69 | } 70 | } 71 | } 72 | 73 | void detect_handler(const char *file) { 74 | const char *ext; 75 | 76 | ext = strrchr(file, '.'); 77 | if (ext == NULL) { 78 | return; 79 | } 80 | 81 | if (strcasecmp(ext + 1, "wav") == 0) { 82 | global->type = "wav"; 83 | } 84 | } 85 | 86 | int main(int argc, char **argv) { 87 | const char *file; 88 | stream_handler_t *handler; 89 | stream_t *stream; 90 | 91 | parse(argc, argv); 92 | 93 | if (optind < argc) { 94 | file = argv[optind]; 95 | } else { 96 | fprintf(stderr, "%s: please specify file\n", argv[0]); 97 | exit(EXIT_FAILURE); 98 | } 99 | 100 | // If type is not specified as argument, 101 | // try to derive it from the file name 102 | if (global->type == NULL) { 103 | detect_handler(file); 104 | } 105 | 106 | // Initialize handler from file type 107 | if (global->type != NULL) { 108 | if (strcasecmp(global->type, "wav") == 0) { 109 | handler = &stream_wav_handler; 110 | } else if (strcasecmp(global->type, "u8") == 0) { 111 | handler = &stream_u8_handler; 112 | } else if (strcasecmp(global->type, "s8") == 0) { 113 | handler = &stream_s8_handler; 114 | } else if (strcasecmp(global->type, "s16") == 0) { 115 | handler = &stream_s16_handler; 116 | } else if (strcasecmp(global->type, "s32") == 0) { 117 | handler = &stream_s32_handler; 118 | } else if (strcasecmp(global->type, "f32") == 0) { 119 | handler = &stream_f32_handler; 120 | } else { 121 | fprintf(stderr, "%s: invalid file type '%s'\n", argv[0], global->type); 122 | exit(EXIT_FAILURE); 123 | } 124 | } else { 125 | fprintf(stderr, "%s: please specify file type\n", argv[0]); 126 | exit(EXIT_FAILURE); 127 | } 128 | 129 | stream = stream_open(file, handler); 130 | if (stream == NULL) { 131 | fprintf(stderr, "%s: error opening stream\n", argv[0]); 132 | exit(EXIT_FAILURE); 133 | } 134 | 135 | // Initialize sample rate if not yet initialized by the handler 136 | if (stream->sample_rate <= 0) { 137 | if (global->sample_rate <= 0) { 138 | fprintf(stderr, "%s: please specify sample rate\n", argv[0]); 139 | exit(EXIT_FAILURE); 140 | } 141 | 142 | stream->sample_rate = global->sample_rate; 143 | } 144 | 145 | // Initialize center frequency 146 | if (stream->center_freq <= 0) { 147 | if (global->center_freq <= 0) { 148 | stream->center_freq = 0; 149 | } else { 150 | stream->center_freq = global->center_freq; 151 | } 152 | } 153 | 154 | stream_fftw_init(stream); 155 | 156 | wedge_processing_init(); 157 | 158 | color = &colormap_rainbow[0]; 159 | 160 | window_run(stream); 161 | 162 | stream_close(stream); 163 | 164 | exit(EXIT_SUCCESS); 165 | } 166 | -------------------------------------------------------------------------------- /stream.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "stream.h" 15 | 16 | stream_t *stream_open(const char *file, const stream_handler_t *handler) { 17 | stream_t *s; 18 | int rv; 19 | 20 | s = calloc(1, sizeof(*s)); 21 | s->fd = -1; 22 | s->file = file; 23 | s->handler = handler; 24 | s->sample_rate = -1; 25 | s->center_freq = -1; 26 | 27 | s->fd = open(file, O_RDONLY); 28 | if (s->fd < 0) { 29 | goto error; 30 | } 31 | 32 | rv = fstat(s->fd, &s->stat); 33 | if (rv < 0) { 34 | goto error; 35 | } 36 | 37 | #if _XOPEN_SOURCE >= 600 || _POSIX_C_SOURCE >= 200112L 38 | rv = posix_fadvise(s->fd, 0, s->stat.st_size, POSIX_FADV_RANDOM); 39 | if (rv < 0) { 40 | goto error; 41 | } 42 | #endif 43 | 44 | rv = s->handler->init(s->handler, s); 45 | if (rv < 0) { 46 | goto error; 47 | } 48 | 49 | return s; 50 | 51 | error: 52 | stream_close(s); 53 | return NULL; 54 | } 55 | 56 | void stream_close(stream_t *s) { 57 | if (s != NULL) { 58 | if (s->fd >= 0) { 59 | close(s->fd); 60 | } 61 | free(s); 62 | } 63 | } 64 | 65 | void stream_fftw_init(stream_t *s) { 66 | int i; 67 | 68 | for (i = 0; i < 20; i++) { 69 | s->fftw_in[i] = (fftw_complex *) fftw_malloc(sizeof(fftw_complex) * (1 << i)); 70 | s->fftw_out[i] = (fftw_complex *) fftw_malloc(sizeof(fftw_complex) * (1 << i)); 71 | s->fftw_plan[i] = fftw_plan_dft_1d(1 << i, s->fftw_in[i], s->fftw_out[i], FFTW_FORWARD, FFTW_ESTIMATE); 72 | s->fftw_n[i] = 1 << i; 73 | } 74 | } 75 | 76 | void stream_fftw_free(stream_t *s) { 77 | int i; 78 | 79 | for (i = 0; i < 20; i++) { 80 | fftw_destroy_plan(s->fftw_plan[i]); 81 | fftw_free(s->fftw_in[i]); 82 | fftw_free(s->fftw_out[i]); 83 | } 84 | } 85 | 86 | static void stream_fft_process(stream_t *s, uint8_t scale, double *dst) { 87 | uint16_t i; 88 | uint16_t h = s->fftw_n[scale] / 2; 89 | uint16_t p; 90 | double mag; 91 | 92 | for (i = 0; i < s->fftw_n[scale]; i++) { 93 | // Shift FFT 94 | if (i < h) { 95 | p = h + i; 96 | } else { 97 | p = i - h; 98 | } 99 | 100 | // Compute and normalize magnitude 101 | mag = (s->fftw_out[scale][p][0] * s->fftw_out[scale][p][0] + 102 | s->fftw_out[scale][p][1] * s->fftw_out[scale][p][1]); 103 | mag = mag / (s->fftw_n[scale] * s->fftw_n[scale]); 104 | 105 | // Convert to dB 106 | mag = 10.0f * log10f(mag); 107 | 108 | // Accumulate magnitude in destination buffer 109 | dst[i] += mag; 110 | } 111 | } 112 | 113 | // From: https://graphics.stanford.edu/~seander/bithacks.html#IntegerLog 114 | static uint8_t log2i(uint32_t v) { 115 | static const unsigned int b[] = { 116 | 0xAAAAAAAA, 117 | 0xCCCCCCCC, 118 | 0xF0F0F0F0, 119 | 0xFF00FF00, 120 | 0xFFFF0000, 121 | }; 122 | uint8_t i; 123 | uint8_t r; 124 | 125 | r = (v & b[0]) != 0; 126 | for (i = 4; i > 0; i--) // unroll for speed... 127 | { 128 | r |= ((v & b[i]) != 0) << i; 129 | } 130 | 131 | return r; 132 | } 133 | 134 | void stream_fft(stream_t *s, uint64_t t, double *d, uint32_t len) { 135 | int rv; 136 | int scale; 137 | 138 | if ((len & (len - 1)) != 0x0) { 139 | assert(0 && "not a power of 2"); 140 | } 141 | 142 | scale = log2i(len); 143 | rv = s->handler->read(s->handler, s, t, s->fftw_n[scale], s->fftw_in[scale]); 144 | if (rv < 0) { 145 | perror("read"); 146 | assert(0); 147 | } 148 | 149 | fftw_execute(s->fftw_plan[scale]); 150 | stream_fft_process(s, scale, d); 151 | } 152 | 153 | 154 | static void stream__fadvise(stream_t *s, int8_t advise, uint64_t start, uint64_t len) { 155 | uint64_t offset; 156 | uint64_t bytes; 157 | int8_t rv; 158 | 159 | offset = s->handler->to_byte_offset(s->handler, s, start); 160 | bytes = s->handler->to_byte_length(s->handler, s, len); 161 | rv = posix_fadvise(s->fd, offset, bytes, advise); 162 | if (rv < 0) { 163 | perror("posix_fadvise"); 164 | assert(0); 165 | } 166 | } 167 | 168 | void stream_fadvise_willneed(stream_t *s, uint64_t start, uint64_t len) { 169 | stream__fadvise(s, POSIX_FADV_WILLNEED, start, len); 170 | } 171 | 172 | void stream_fadvise_dontneed(stream_t *s, uint64_t start, uint64_t len) { 173 | stream__fadvise(s, POSIX_FADV_DONTNEED, start, len); 174 | } 175 | -------------------------------------------------------------------------------- /stream.h: -------------------------------------------------------------------------------- 1 | #ifndef _STREAM_H 2 | #define _STREAM_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | typedef struct stream_s stream_t; 11 | 12 | typedef struct stream_handler_s stream_handler_t; 13 | 14 | struct stream_handler_s { 15 | int8_t (*init)(const stream_handler_t *, stream_t *); 16 | uint64_t (*to_byte_offset)(const stream_handler_t *, stream_t *, uint64_t); 17 | uint64_t (*to_byte_length)(const stream_handler_t *, stream_t *, uint64_t); 18 | int8_t (*read)(const stream_handler_t *, stream_t *, uint64_t, uint64_t, fftw_complex *); 19 | 20 | // Private data for handler. 21 | // Keep in mind that a single handler can be used for multiple 22 | // streams, so any data specific to the stream should be stored 23 | // in the data field of the stream itself. 24 | void *data; 25 | }; 26 | 27 | struct stream_s { 28 | const char *file; 29 | const stream_handler_t *handler; 30 | 31 | int fd; 32 | struct stat stat; 33 | void *data; 34 | 35 | int32_t sample_rate; 36 | int32_t center_freq; 37 | int64_t n_samples; 38 | 39 | fftw_complex *fftw_in[20]; 40 | fftw_complex *fftw_out[20]; 41 | fftw_plan fftw_plan[20]; 42 | uint16_t fftw_n[20]; 43 | }; 44 | 45 | stream_t *stream_open(const char *file, const stream_handler_t *handler); 46 | 47 | void stream_close(stream_t *s); 48 | 49 | void stream_fftw_init(stream_t *s); 50 | 51 | void stream_fftw_free(stream_t *s); 52 | 53 | void stream_fft(stream_t *s, uint64_t t, double *d, uint32_t len); 54 | 55 | void stream_fadvise_willneed(stream_t *s, uint64_t start, uint64_t len); 56 | 57 | void stream_fadvise_dontneed(stream_t *s, uint64_t start, uint64_t len); 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /stream_raw.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "stream_raw.h" 7 | 8 | typedef struct _raw_s _raw_t; 9 | 10 | struct _raw_s { 11 | // Bytes per sample component (in-phase or quadrature) 12 | uint8_t bytes; 13 | 14 | // Function to process arbitrary data into complex samples 15 | void (*process)(const void *, fftw_complex *, uint64_t); 16 | }; 17 | 18 | static int8_t 19 | stream_raw__init(const stream_handler_t *h, 20 | stream_t *s) 21 | { 22 | _raw_t *r = (_raw_t *) h->data; 23 | s->n_samples = s->stat.st_size / (2 * r->bytes); 24 | return 0; 25 | } 26 | 27 | static uint64_t 28 | stream_raw__to_byte_offset(const stream_handler_t *h, 29 | __attribute__((unused)) stream_t *s, 30 | uint64_t offset) 31 | { 32 | _raw_t *r = (_raw_t *) h->data; 33 | return 2 * r->bytes * offset; 34 | } 35 | 36 | static uint64_t 37 | stream_raw__to_byte_length(const stream_handler_t *h, 38 | __attribute__((unused)) stream_t *s, 39 | uint64_t length) 40 | { 41 | const _raw_t *r = (const _raw_t *) h->data; 42 | return 2 * r->bytes * length; 43 | } 44 | 45 | static int8_t 46 | stream_raw__read(const stream_handler_t *h, 47 | stream_t *s, 48 | uint64_t offset, 49 | uint64_t length, 50 | fftw_complex *out) 51 | { 52 | const _raw_t *r = (const _raw_t *) h->data; 53 | uint64_t byte_offset = stream_raw__to_byte_offset(h, s, offset); 54 | uint64_t byte_length = stream_raw__to_byte_length(h, s, length); 55 | off_t rv; 56 | void *in; 57 | 58 | rv = lseek(s->fd, byte_offset, SEEK_SET); 59 | if (rv < 0) { 60 | return rv; 61 | } 62 | 63 | in = malloc(byte_length); 64 | rv = read(s->fd, in, byte_length); 65 | if (rv < 0) { 66 | return rv; 67 | } 68 | 69 | r->process(in, out, length); 70 | free(in); 71 | return 0; 72 | } 73 | 74 | static void 75 | _raw_u8_process(const void *data, 76 | fftw_complex *out, 77 | uint64_t length) 78 | { 79 | const uint8_t *in = (const uint8_t *) data; 80 | uint64_t i; 81 | 82 | // See http://cgit.osmocom.org/gr-osmosdr/tree/lib/rtl/rtl_source_c.cc#n176 83 | for (i = 0; i < length; i++) { 84 | out[i][0] = ((float) in[i * 2 + 0] - 127.4f) / 128.0f; 85 | out[i][1] = ((float) in[i * 2 + 1] - 127.4f) / 128.0f; 86 | } 87 | } 88 | 89 | static _raw_t raw_u8 = { 90 | .bytes = 1, 91 | .process = &_raw_u8_process, 92 | }; 93 | 94 | stream_handler_t stream_u8_handler = { 95 | .init = &stream_raw__init, 96 | .to_byte_offset = &stream_raw__to_byte_offset, 97 | .to_byte_length = &stream_raw__to_byte_length, 98 | .read = &stream_raw__read, 99 | .data = &raw_u8, 100 | }; 101 | 102 | static void 103 | _raw_s8_process(const void *data, 104 | fftw_complex *out, 105 | uint64_t length) 106 | { 107 | const int8_t *in = (const int8_t *) data; 108 | uint64_t i; 109 | 110 | for (i = 0; i < length; i++) { 111 | out[i][0] = (-(float) in[i * 2 + 0]) / INT8_MIN; 112 | out[i][1] = (-(float) in[i * 2 + 1]) / INT8_MIN; 113 | } 114 | } 115 | 116 | static _raw_t raw_s8 = { 117 | .bytes = 1, 118 | .process = &_raw_s8_process, 119 | }; 120 | 121 | stream_handler_t stream_s8_handler = { 122 | .init = &stream_raw__init, 123 | .to_byte_offset = &stream_raw__to_byte_offset, 124 | .to_byte_length = &stream_raw__to_byte_length, 125 | .read = &stream_raw__read, 126 | .data = &raw_s8, 127 | }; 128 | 129 | static void 130 | _raw_s16_process(const void *data, 131 | fftw_complex *out, 132 | uint64_t length) 133 | { 134 | const int16_t *in = (const int16_t *) data; 135 | uint64_t i; 136 | 137 | for (i = 0; i < length; i++) { 138 | out[i][0] = (-(float) in[i * 2 + 0]) / INT16_MIN; 139 | out[i][1] = (-(float) in[i * 2 + 1]) / INT16_MIN; 140 | } 141 | } 142 | 143 | static _raw_t raw_s16 = { 144 | .bytes = 2, 145 | .process = &_raw_s16_process, 146 | }; 147 | 148 | stream_handler_t stream_s16_handler = { 149 | .init = &stream_raw__init, 150 | .to_byte_offset = &stream_raw__to_byte_offset, 151 | .to_byte_length = &stream_raw__to_byte_length, 152 | .read = &stream_raw__read, 153 | .data = &raw_s16, 154 | }; 155 | 156 | static void 157 | _raw_s32_process(const void *data, 158 | fftw_complex *out, 159 | uint64_t length) 160 | { 161 | const int32_t *in = (const int32_t *) data; 162 | uint64_t i; 163 | 164 | for (i = 0; i < length; i++) { 165 | out[i][0] = (-(float) in[i * 2 + 0]) / INT32_MIN; 166 | out[i][1] = (-(float) in[i * 2 + 1]) / INT32_MIN; 167 | } 168 | } 169 | 170 | static _raw_t raw_s32 = { 171 | .bytes = 4, 172 | .process = &_raw_s32_process, 173 | }; 174 | 175 | stream_handler_t stream_s32_handler = { 176 | .init = &stream_raw__init, 177 | .to_byte_offset = &stream_raw__to_byte_offset, 178 | .to_byte_length = &stream_raw__to_byte_length, 179 | .read = &stream_raw__read, 180 | .data = &raw_s32, 181 | }; 182 | 183 | static void 184 | _raw_f32_process(const void *data, 185 | fftw_complex *out, 186 | uint64_t length) 187 | { 188 | const float *in = (const float *) data; 189 | uint64_t i; 190 | 191 | for (i = 0; i < length; i++) { 192 | out[i][0] = in[i * 2 + 0]; 193 | out[i][1] = in[i * 2 + 1]; 194 | } 195 | } 196 | 197 | static _raw_t raw_f32 = { 198 | .bytes = 4, 199 | .process = &_raw_f32_process, 200 | }; 201 | 202 | stream_handler_t stream_f32_handler = { 203 | .init = &stream_raw__init, 204 | .to_byte_offset = &stream_raw__to_byte_offset, 205 | .to_byte_length = &stream_raw__to_byte_length, 206 | .read = &stream_raw__read, 207 | .data = &raw_f32, 208 | }; 209 | -------------------------------------------------------------------------------- /stream_raw.h: -------------------------------------------------------------------------------- 1 | #ifndef _STREAM_RAW_H 2 | #define _STREAM_RAW_H 3 | 4 | #include "stream.h" 5 | 6 | extern stream_handler_t stream_u8_handler; 7 | extern stream_handler_t stream_s8_handler; 8 | extern stream_handler_t stream_s16_handler; 9 | extern stream_handler_t stream_s32_handler; 10 | extern stream_handler_t stream_f32_handler; 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /stream_wav.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "stream_raw.h" 10 | 11 | typedef struct _wav_header_s _wav_header_t; 12 | 13 | struct _wav_header_s { 14 | char riff[4]; 15 | uint32_t file_size; 16 | char wave[4]; 17 | 18 | char fmt[4]; 19 | uint32_t fmt_len; 20 | 21 | uint16_t type; 22 | uint16_t channels; 23 | uint32_t sample_rate; 24 | uint32_t _ignore_1; 25 | uint16_t _ignore_2; 26 | uint16_t bits_per_sample; 27 | 28 | char data[4]; 29 | uint32_t data_len; 30 | 31 | // Following fields not included in file header 32 | uint16_t bytes_per_sample; 33 | }; 34 | 35 | static int8_t 36 | stream_wav__init(__attribute__((unused)) const stream_handler_t *h, 37 | stream_t *s) 38 | { 39 | _wav_header_t *header; 40 | int8_t rv; 41 | 42 | header = malloc(sizeof(*header)); 43 | rv = read(s->fd, header, 44); 44 | if (rv < 0) { 45 | return rv; 46 | } 47 | 48 | if (strncmp(header->riff, "RIFF", 4) != 0) { 49 | return -1; 50 | } 51 | 52 | if (strncmp(header->wave, "WAVE", 4) != 0) { 53 | return -1; 54 | } 55 | 56 | if (header->channels != 2) { 57 | return -1; 58 | } 59 | 60 | header->bytes_per_sample = (2 * (header->bits_per_sample / 8)); 61 | 62 | s->sample_rate = header->sample_rate; 63 | s->n_samples = (s->stat.st_size - sizeof(*header)) / header->bytes_per_sample; 64 | s->data = header; 65 | 66 | return 0; 67 | } 68 | 69 | static uint64_t 70 | stream_wav__to_byte_offset(__attribute__((unused)) const stream_handler_t *h, 71 | stream_t *s, 72 | uint64_t offset) 73 | { 74 | const _wav_header_t *header; 75 | header = (const _wav_header_t *) s->data; 76 | return sizeof(_wav_header_t) + 2 * (header->bits_per_sample / 8) * offset; 77 | } 78 | 79 | static uint64_t 80 | stream_wav__to_byte_length(__attribute__((unused)) const stream_handler_t *h, 81 | stream_t *s, 82 | uint64_t length) 83 | { 84 | const _wav_header_t *header; 85 | header = (const _wav_header_t *) s->data; 86 | return 2 * (header->bits_per_sample / 8) * length; 87 | } 88 | 89 | static int8_t 90 | stream_wav__read(const stream_handler_t *h, 91 | stream_t *s, 92 | uint64_t offset, 93 | uint64_t length, 94 | fftw_complex *out) 95 | { 96 | uint64_t byte_offset = stream_wav__to_byte_offset(h, s, offset); 97 | uint64_t byte_length = stream_wav__to_byte_length(h, s, length); 98 | off_t rv; 99 | const _wav_header_t *header; 100 | uint64_t i; 101 | 102 | rv = lseek(s->fd, byte_offset, SEEK_SET); 103 | if (rv < 0) { 104 | return rv; 105 | } 106 | 107 | header = (const _wav_header_t *) s->data; 108 | if (header->bits_per_sample == 16) { 109 | int16_t *in; 110 | 111 | in = alloca(byte_length); 112 | rv = read(s->fd, in, byte_length); 113 | if (rv < 0) { 114 | return rv; 115 | } 116 | 117 | for (i = 0; i < length; i++) { 118 | out[i][0] = (-(float) in[i * 2 + 0]) / INT16_MIN; 119 | out[i][1] = (-(float) in[i * 2 + 1]) / INT16_MIN; 120 | } 121 | } else { 122 | assert(0 && "invalid sample size"); 123 | } 124 | 125 | return 0; 126 | } 127 | 128 | stream_handler_t stream_wav_handler = { 129 | .init = &stream_wav__init, 130 | .to_byte_offset = &stream_wav__to_byte_offset, 131 | .to_byte_length = &stream_wav__to_byte_length, 132 | .read = &stream_wav__read, 133 | }; 134 | -------------------------------------------------------------------------------- /stream_wav.h: -------------------------------------------------------------------------------- 1 | #ifndef _STREAM_WAV_H 2 | #define _STREAM_WAV_H 3 | 4 | #include "stream.h" 5 | 6 | extern stream_handler_t stream_wav_handler; 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /wedge.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "wedge.h" 7 | #include "color.h" 8 | 9 | // Only the address of this user data key is used. 10 | // Contents is not relevant. 11 | static cairo_user_data_key_t _data_key; 12 | 13 | wedge_t *wedge_new(int width, int height) { 14 | wedge_t *w; 15 | 16 | w = calloc(1, sizeof(wedge_t)); 17 | if (w == NULL) { 18 | assert(0); 19 | } 20 | 21 | w->width = width; 22 | w->height = height; 23 | 24 | return w; 25 | } 26 | 27 | static void wedge__willneed(wedge_t *w) { 28 | int64_t i; 29 | 30 | for (i = 0; i < w->height; i++) { 31 | stream_fadvise_willneed(w->stream, w->start + i * w->skip, w->width); 32 | } 33 | } 34 | 35 | static void wedge__dontneed(wedge_t *w) { 36 | int64_t i; 37 | 38 | for (i = 0; i < w->height; i++) { 39 | stream_fadvise_dontneed(w->stream, w->start + i * w->skip, w->width); 40 | } 41 | } 42 | 43 | void wedge_free(wedge_t *w) { 44 | wedge__dontneed(w); 45 | cairo_surface_destroy(w->surface); 46 | free(w); 47 | } 48 | 49 | static unsigned char *wedge__surface_alloc(wedge_t *w) { 50 | unsigned char *data; 51 | cairo_t *cr; 52 | 53 | w->stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, w->width); 54 | 55 | data = calloc(w->stride, w->height); 56 | if (data == NULL) { 57 | assert(0); 58 | } 59 | 60 | w->surface = cairo_image_surface_create_for_data(data, 61 | CAIRO_FORMAT_ARGB32, 62 | w->width, 63 | w->height, 64 | w->stride); 65 | 66 | // Underlying buffer should be freed when surface is destroyed 67 | cairo_surface_set_user_data(w->surface, &_data_key, data, free); 68 | 69 | // Make surface transparent 70 | cr = cairo_create(w->surface); 71 | cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.0); 72 | cairo_paint(cr); 73 | cairo_destroy(cr); 74 | 75 | return data; 76 | } 77 | 78 | static void wedge_process(wedge_t *w) { 79 | stream_t *stream; 80 | double *mag; 81 | unsigned char *data; 82 | int64_t i; 83 | int64_t j; 84 | 85 | stream = w->stream; 86 | mag = malloc(sizeof(double) * w->width); 87 | data = wedge__surface_alloc(w); 88 | 89 | for (i = 0; i < w->height; i++) { 90 | if (w->start + i * w->skip + w->width >= stream->n_samples) { 91 | break; 92 | } 93 | 94 | memset(mag, 0, sizeof(double) * w->width); 95 | stream_fft(stream, w->start + i * w->skip, mag, w->width); 96 | 97 | // Normalize and populate wedge buffer 98 | int min = -100; 99 | int max = -60; 100 | for (j = 0; j < w->width; j++) { 101 | int16_t val; 102 | 103 | val = (255 * (mag[j] - min)) / (max - min); 104 | 105 | // Clamp 106 | if (val > 255) { 107 | val = 255; 108 | } else if (val < 0){ 109 | val = 0; 110 | } 111 | 112 | data[i * w->stride + j*4 + 0] = color[val][2]; 113 | data[i * w->stride + j*4 + 1] = color[val][1]; 114 | data[i * w->stride + j*4 + 2] = color[val][0]; 115 | data[i * w->stride + j*4 + 3] = 255; 116 | } 117 | } 118 | 119 | w->status = WEDGE_DONE; 120 | w->callback_fn(w, w->callback_data); 121 | 122 | free(mag); 123 | } 124 | 125 | static GList *queue = NULL; 126 | static pthread_mutex_t lock; 127 | static pthread_cond_t cond; 128 | 129 | static void *wedge_worker(__attribute__((unused)) void *data) { 130 | GList *link; 131 | wedge_t *w; 132 | 133 | for (;;) { 134 | pthread_mutex_lock(&lock); 135 | for (;;) { 136 | link = g_list_first(queue); 137 | if (link == NULL) { 138 | pthread_cond_wait(&cond, &lock); 139 | continue; 140 | } 141 | 142 | w = (wedge_t *) link->data; 143 | queue = g_list_delete_link(queue, link); 144 | break; 145 | } 146 | 147 | pthread_mutex_unlock(&lock); 148 | 149 | w->status = WEDGE_PROCESSING; 150 | 151 | // Do actual work while not holding the lock 152 | wedge__willneed(w); 153 | wedge_process(w); 154 | wedge__dontneed(w); 155 | } 156 | 157 | return NULL; 158 | } 159 | 160 | void wedge_processing_init(void) { 161 | pthread_t t; 162 | 163 | queue = NULL; 164 | 165 | pthread_mutex_init(&lock, NULL); 166 | pthread_cond_init(&cond, NULL); 167 | pthread_create(&t, NULL, &wedge_worker, NULL); 168 | } 169 | 170 | void wedge_processing_queue(wedge_t *w) { 171 | w->status = WEDGE_QUEUED; 172 | 173 | pthread_mutex_lock(&lock); 174 | queue = g_list_append(queue, w); 175 | pthread_cond_signal(&cond); 176 | pthread_mutex_unlock(&lock); 177 | } 178 | 179 | int wedge_processing_dequeue(wedge_t *w) { 180 | GList *link; 181 | 182 | pthread_mutex_lock(&lock); 183 | link = g_list_find(queue, w); 184 | if (link == NULL) { 185 | pthread_mutex_unlock(&lock); 186 | return -1; 187 | } 188 | 189 | queue = g_list_remove_link(queue, link); 190 | pthread_mutex_unlock(&lock); 191 | return 0; 192 | } 193 | -------------------------------------------------------------------------------- /wedge.h: -------------------------------------------------------------------------------- 1 | #ifndef _WEDGE_H 2 | #define _WEDGE_H 3 | 4 | #include 5 | #include 6 | 7 | #include "stream.h" 8 | 9 | #define WEDGE_QUEUED 1 10 | #define WEDGE_PROCESSING 2 11 | #define WEDGE_DONE 3 12 | 13 | typedef struct wedge_s wedge_t; 14 | 15 | struct wedge_s { 16 | volatile int64_t status; 17 | 18 | // Callback number when this wedge was most recently used 19 | uint64_t draw_cb; 20 | 21 | stream_t *stream; 22 | 23 | int width; 24 | int height; 25 | int stride; 26 | 27 | int sx; 28 | int sy; 29 | int64_t start; 30 | int64_t end; 31 | int64_t skip; 32 | 33 | unsigned char *data; 34 | cairo_surface_t *surface; 35 | 36 | void (*callback_fn)(wedge_t *, void *); 37 | void *callback_data; 38 | }; 39 | 40 | wedge_t *wedge_new(int width, int height); 41 | 42 | void wedge_free(wedge_t *w); 43 | 44 | void wedge_processing_init(void); 45 | 46 | void wedge_processing_queue(wedge_t *w); 47 | 48 | int wedge_processing_dequeue(wedge_t *w); 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /window.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "window.h" 9 | #include "stream.h" 10 | #include "wedge.h" 11 | #include "common.h" 12 | 13 | #define SCALE_WIDTH 30 14 | #define TICK_MARGIN 3 15 | 16 | // User coordinates of viewport 17 | typedef struct { 18 | int32_t w; 19 | int32_t h; 20 | 21 | // User coordinates of (0, 0) and (w, h) 22 | double uox; 23 | double uoy; 24 | double udx; 25 | double udy; 26 | double uw; 27 | double uh; 28 | double updx; 29 | double updy; 30 | 31 | // Scaled coordinates of (0, 0) and (w, h) 32 | double sox; 33 | double soy; 34 | double sdx; 35 | double sdy; 36 | double sw; 37 | double sh; 38 | double spdx; 39 | double spdy; 40 | 41 | // Scale factor in x and y axis 42 | int8_t sx; 43 | int8_t sy; 44 | 45 | // Offset of first wedge IN view 46 | int64_t start; 47 | 48 | // Offset of first wedge OUT of view 49 | int64_t end; 50 | 51 | // Separation between wedges 52 | int64_t skip; 53 | 54 | // Offsets of stream 55 | int64_t stream_start; 56 | int64_t stream_end; 57 | } viewport_t; 58 | 59 | typedef struct { 60 | uint16_t wedge_height; 61 | 62 | // Used to track dragging motion 63 | int xstart; 64 | int ystart; 65 | 66 | cairo_matrix_t window_to_user; 67 | cairo_matrix_t window_to_user_inv; 68 | 69 | cairo_matrix_t user_to_scaled; 70 | cairo_matrix_t user_to_scaled_inv; 71 | cairo_matrix_t user_to_scaled_start; 72 | 73 | viewport_t viewport; 74 | 75 | stream_t *stream; 76 | GPtrArray *wedges; 77 | 78 | uint64_t draw_cb_calls; 79 | 80 | GtkWidget *drawing_area; 81 | GtkWidget *time_scale; 82 | GtkWidget *frequency_scale; 83 | } window_state_t; 84 | 85 | static void _cairo_matrix_init_inverse(cairo_matrix_t *dst, const cairo_matrix_t *src) { 86 | memcpy(dst, src, sizeof(cairo_matrix_t)); 87 | cairo_matrix_invert(dst); 88 | } 89 | 90 | static uint64_t timediff(struct timeval t1, struct timeval t2) { 91 | uint64_t m1 = t1.tv_sec * 1000000 + t1.tv_usec; 92 | uint64_t m2 = t2.tv_sec * 1000000 + t2.tv_usec; 93 | return m2 - m1; 94 | } 95 | 96 | static void compute_scale_factor(window_state_t *state, int8_t *sx, int8_t *sy) { 97 | double ddx = 1.0f; 98 | double ddy = 1.0f; 99 | double dfx; 100 | double dfy; 101 | 102 | // Find equivalent for 1x1 pixel block in user coordinates 103 | cairo_matrix_transform_distance(&state->window_to_user_inv, &ddx, &ddy); 104 | cairo_matrix_transform_distance(&state->user_to_scaled_inv, &ddx, &ddy); 105 | 106 | ddx = log2f(fabs(ddx)); 107 | dfx = -floor(ddx); 108 | if (dfx < 0) { 109 | *sx = 0; 110 | } else { 111 | *sx = dfx; 112 | } 113 | 114 | ddy = log2f(fabs(ddy)); 115 | dfy = -floor(ddy); 116 | if (dfy < 0) { 117 | *sy = 0; 118 | } else { 119 | *sy = dfy; 120 | } 121 | 122 | // At scale 14 the wedge width is 16384, which is the maximum cairo surface width. 123 | // (the cairo status flips to invalid value after using one of higher width) 124 | if (*sx > 14) { 125 | *sx = 14; 126 | } 127 | 128 | if (*sy > 14) { 129 | *sy = 14; 130 | } 131 | 132 | if (DEBUG) { 133 | fprintf(stderr, "sx: %d (ddx: %.3f), sy: %d (ddy: %.3f)\n", *sx, ddx, *sy, ddy); 134 | } 135 | 136 | return; 137 | } 138 | 139 | static void compute_viewport(window_state_t *state, viewport_t *v) { 140 | stream_t *stream = state->stream; 141 | double min; 142 | double max; 143 | double unit; 144 | 145 | compute_scale_factor(state, &v->sx, &v->sy); 146 | 147 | v->w = gtk_widget_get_allocated_width(state->drawing_area); 148 | v->h = gtk_widget_get_allocated_height(state->drawing_area); 149 | 150 | v->uox = 0.0f; 151 | v->uoy = 0.0f; 152 | v->udx = v->w; 153 | v->udy = v->h; 154 | v->uw = v->w; 155 | v->uh = v->h; 156 | v->updx = 1.0f; 157 | v->updy = 1.0f; 158 | 159 | // Find equivalent for the origin and window size in user coordinates 160 | cairo_matrix_transform_point(&state->window_to_user_inv, &v->uox, &v->uoy); 161 | cairo_matrix_transform_point(&state->window_to_user_inv, &v->udx, &v->udy); 162 | cairo_matrix_transform_distance(&state->window_to_user_inv, &v->uw, &v->uh); 163 | cairo_matrix_transform_distance(&state->window_to_user_inv, &v->updx, &v->updy); 164 | 165 | v->sox = v->uox; 166 | v->soy = v->uoy; 167 | v->sdx = v->udx; 168 | v->sdy = v->udy; 169 | v->sw = v->uw; 170 | v->sh = v->uh; 171 | v->spdx = v->updx; 172 | v->spdy = v->updy; 173 | 174 | // Find equivalent for the origin and window size in scaled coordinates 175 | cairo_matrix_transform_point(&state->user_to_scaled_inv, &v->sox, &v->soy); 176 | cairo_matrix_transform_point(&state->user_to_scaled_inv, &v->sdx, &v->sdy); 177 | cairo_matrix_transform_distance(&state->user_to_scaled_inv, &v->sw, &v->sh); 178 | cairo_matrix_transform_distance(&state->user_to_scaled_inv, &v->spdx, &v->spdy); 179 | 180 | if (v->soy < v->sdy) { 181 | min = v->soy; 182 | max = v->sdy; 183 | } else { 184 | min = v->sdy; 185 | max = v->soy; 186 | } 187 | 188 | // Determine offsets of first wedge in view and first wedge out of view 189 | unit = state->wedge_height * stream->sample_rate / powf(2.0f, v->sy); 190 | v->start = floor((min * stream->sample_rate) / unit) * unit; 191 | v->end = (floor((max * stream->sample_rate) / unit) + 1.0f) * unit; 192 | v->skip = unit; 193 | 194 | // Offsets of stream 195 | v->stream_start = 0; 196 | v->stream_end = stream->n_samples; 197 | } 198 | 199 | static void _wedge_row_callback(__attribute__((unused)) wedge_t *w, void *data) { 200 | GtkWidget *widget = (GtkWidget *) data; 201 | 202 | gtk_widget_queue_draw(widget); 203 | } 204 | 205 | static wedge_t *create_wedge(GtkWidget *widget, window_state_t *state, viewport_t *v, int64_t start, int64_t end) { 206 | wedge_t *w; 207 | 208 | w = wedge_new(1 << v->sx, state->wedge_height); 209 | 210 | w->stream = state->stream; 211 | 212 | w->sx = v->sx; 213 | w->sy = v->sy; 214 | w->start = start; 215 | w->end = end; 216 | w->skip = (end - start) / w->height; 217 | 218 | w->callback_fn = &_wedge_row_callback; 219 | w->callback_data = (void *) widget; 220 | 221 | wedge_processing_queue(w); 222 | 223 | if (DEBUG) { 224 | fprintf(stderr, "create wedge: sx: %d, sy: %d, start: %ld, end: %ld\n", w->sx, w->sy, w->start, w->end); 225 | } 226 | 227 | return w; 228 | } 229 | 230 | static void create_wedges_in_view(GtkWidget *widget, window_state_t *state) { 231 | viewport_t *v; 232 | stream_t *stream; 233 | int64_t i; 234 | int64_t j; 235 | wedge_t *w; 236 | 237 | compute_viewport(state, &state->viewport); 238 | 239 | v = &state->viewport; 240 | stream = state->stream; 241 | 242 | // Remove wedges that are queued for processing and are no longer 243 | // at the current scale level (e.g. when user rapidly zooms in/out). 244 | for (i = 0; i < state->wedges->len; i++) { 245 | w = (wedge_t *) g_ptr_array_index(state->wedges, i); 246 | if (w->status != WEDGE_QUEUED) { 247 | continue; 248 | } 249 | 250 | // Skip queued wedges at the right scale level 251 | if (w->sx == v->sx && w->sy == v->sy) { 252 | continue; 253 | } 254 | 255 | // Attempt to dequeue wedge for processing. 256 | // This can race with the processing thread so might fail. 257 | if (wedge_processing_dequeue(w) == 0) { 258 | g_ptr_array_remove_index(state->wedges, i--); 259 | wedge_free(w); 260 | } 261 | } 262 | 263 | // Remove wedges that were not used in the most recent draw call. 264 | for (i = 0; i < state->wedges->len; i++) { 265 | w = (wedge_t *) g_ptr_array_index(state->wedges, i); 266 | if (w->status != WEDGE_DONE) { 267 | continue; 268 | } 269 | 270 | // At the current scale level, never remove wedges that 271 | // are in view or are neighboring the view. 272 | if (w->sx == v->sx && 273 | w->sy == v->sy && 274 | w->start >= (v->start - v->skip) && 275 | w->start < (v->end + v->skip)) 276 | { 277 | continue; 278 | } 279 | 280 | // If it wasn't used in the most recent draw call, remove. 281 | if (w->draw_cb < state->draw_cb_calls) { 282 | g_ptr_array_remove_index(state->wedges, i--); 283 | wedge_free(w); 284 | } 285 | } 286 | 287 | // Iterate over wedges for this view. 288 | // Note that both start and end are extended by skip so that 289 | // wedges just outside the view port will be created before they 290 | // are visible, reducing the amount of flickering on screen. 291 | for (i = (v->start - v->skip); i < (v->end + v->skip); i += v->skip) { 292 | // Skip negative start offset 293 | if (i < 0) { 294 | continue; 295 | } 296 | 297 | // Break on out of bounds start offset 298 | if (i >= stream->n_samples) { 299 | break; 300 | } 301 | 302 | // Try to find the same wedge 303 | for (j = 0; j < state->wedges->len; j++) { 304 | w = g_ptr_array_index(state->wedges, j); 305 | if (w->sx == v->sx && w->sy == v->sy && w->start == i) { 306 | break; 307 | } 308 | } 309 | 310 | // Skip if this wedge was already computed 311 | if (j < state->wedges->len) { 312 | continue; 313 | } 314 | 315 | w = create_wedge(widget, state, v, i, i + v->skip); 316 | g_ptr_array_add(state->wedges, w); 317 | } 318 | 319 | return; 320 | } 321 | 322 | static void configure_event_cb (__attribute__((unused)) GtkWidget *widget, 323 | __attribute__((unused)) GdkEventConfigure *event, 324 | __attribute__((unused)) gpointer data) { 325 | window_state_t *state; 326 | static double sx = NAN; 327 | static double sy = NAN; 328 | double dx; 329 | double dy; 330 | int w = gtk_widget_get_allocated_width(widget); 331 | 332 | state = (window_state_t *) data; 333 | 334 | // Initialize sx and sy on the first configuration event. 335 | // Don't do this on subsequent events to avoid scaling on resize. 336 | if (isnan(sx) && isnan(sy)) { 337 | sx = w; 338 | sy = -1.0f; 339 | } 340 | 341 | dx = w / sx / 2.0f; 342 | dy = -state->stream->n_samples / state->stream->sample_rate; 343 | 344 | // Recompute window_to_user matrix 345 | cairo_matrix_init_identity(&state->window_to_user); 346 | cairo_matrix_scale(&state->window_to_user, sx, sy); 347 | cairo_matrix_translate(&state->window_to_user, dx, dy); 348 | 349 | // Recompute inverse 350 | _cairo_matrix_init_inverse(&state->window_to_user_inv, &state->window_to_user); 351 | 352 | // Ensure all wedges in view are available 353 | create_wedges_in_view(widget, state); 354 | 355 | gtk_widget_queue_draw(widget); 356 | } 357 | 358 | static gint _wedge_sort_fn(gconstpointer a, gconstpointer b) { 359 | wedge_t *wa = *((wedge_t **) a); 360 | wedge_t *wb = *((wedge_t **) b); 361 | 362 | // Check explicitly instead of returning 'wb - wa' to avoid 363 | // the gint from overflowing. 364 | if (wa->start < wb->start) { 365 | return -1; 366 | } else if (wa->start > wb->start) { 367 | return 1; 368 | } else { 369 | return 0; 370 | } 371 | } 372 | 373 | static gint _wedges_cover_viewport(GPtrArray *wedges, const viewport_t *v) { 374 | int64_t start = -1; 375 | int64_t end = -1; 376 | uint8_t i; 377 | wedge_t *w; 378 | int64_t start_max; 379 | int64_t end_min; 380 | 381 | g_ptr_array_sort(wedges, _wedge_sort_fn); 382 | 383 | // Check if wedges are consecutive 384 | for (i = 0; i < wedges->len; i++) { 385 | w = (wedge_t *) g_ptr_array_index(wedges, i); 386 | if (i == 0) { 387 | start = w->start; 388 | end = w->end; 389 | } else { 390 | if (w->start != end) { 391 | return -1; 392 | } 393 | 394 | end = w->end; 395 | } 396 | } 397 | 398 | // Check if range covers viewport 399 | start_max = v->stream_start; 400 | if (v->start > start_max) { 401 | start_max = v->start; 402 | } 403 | end_min = v->stream_end; 404 | if (v->end < end_min) { 405 | end_min = v->end; 406 | } 407 | if (start > start_max || end < end_min) { 408 | return -1; 409 | } 410 | 411 | return 0; 412 | } 413 | 414 | // Wedges are accumulated from best fit to worst fit (with respect to their scale). 415 | // To draw them as a stack starting with the worst fit and ending with the 416 | // best fit, the returned array must be traversed from the last element to the first. 417 | static GPtrArray *_window_find_wedges_to_draw(window_state_t *state, const viewport_t *v) { 418 | GPtrArray *wedges_to_draw; 419 | GPtrArray *wedges_for_scale; 420 | int8_t sx; 421 | int8_t sy; 422 | int8_t dx; 423 | int8_t dy; 424 | int8_t wi; 425 | int8_t wn; 426 | uint16_t i; 427 | wedge_t *w; 428 | int rv; 429 | 430 | wedges_to_draw = g_ptr_array_new(); 431 | 432 | // Draw wedges for scale level. If wedges at this level don't 433 | // cover the viewport, try progressively deviating scales. 434 | sx = v->sx; 435 | sy = v->sy; 436 | dx = 0; 437 | dy = 0; 438 | wi = 1; 439 | wn = 0; 440 | for (;;) { 441 | // Keep array of wedges at this scale level so we can 442 | // check later if the viewport was covered. 443 | wedges_for_scale = g_ptr_array_new(); 444 | 445 | // Find applicable wedges 446 | for (i = 0; i < state->wedges->len; i++) { 447 | w = g_ptr_array_index(state->wedges, i); 448 | 449 | // Only draw processed wedges 450 | if (w->status != WEDGE_DONE) { 451 | continue; 452 | } 453 | 454 | // Scale must match 455 | if (w->sx != sx || w->sy != sy) { 456 | continue; 457 | } 458 | 459 | // Wedge must be in view 460 | if (w->end < v->start || w->start > v->end) { 461 | continue; 462 | } 463 | 464 | g_ptr_array_add(wedges_for_scale, w); 465 | g_ptr_array_add(wedges_to_draw, w); 466 | } 467 | 468 | // Check if drawn wedges cover the viewport 469 | rv = _wedges_cover_viewport(wedges_for_scale, v); 470 | g_ptr_array_unref(wedges_for_scale); 471 | if (rv == 0) { 472 | break; 473 | } 474 | 475 | // If distance from center scale (v.sx, v.sy) is done, grow range 476 | if (--wi <= 0) { 477 | // Stop after walking at distance 3 478 | if (wn++ >= 3) { 479 | break; 480 | } 481 | 482 | sy++; 483 | dx = 1; 484 | dy = 0; 485 | wi = 8 * wn; 486 | continue; 487 | } 488 | 489 | // Walk around (v->sx, v->sy) counter clockwise 490 | for (;;) { 491 | sx += dx; 492 | sy += dy; 493 | if (sy < v->sy - wn) { 494 | dx = 1; 495 | sx = sx + dx; 496 | dy = 0; 497 | sy = v->sy - wn; 498 | } else if (sy > v->sy + wn) { 499 | dx = -1; 500 | sx = sx + dx; 501 | dy = 0; 502 | sy = v->sy + wn; 503 | } else if (sx < v->sx - wn) { 504 | dx = 0; 505 | sx = v->sx - wn; 506 | dy = -1; 507 | sy = sy + dy; 508 | } else if (sx > v->sx + wn) { 509 | dx = 0; 510 | sx = v->sx + wn; 511 | dy = 1; 512 | sy = sy + dy; 513 | } 514 | 515 | if (sx >= 0 && sy >= 0) { 516 | break; 517 | } else { 518 | // Keep going if out of bounds 519 | continue; 520 | } 521 | } 522 | } 523 | 524 | return wedges_to_draw; 525 | } 526 | 527 | static gboolean draw_cb(__attribute__((unused)) GtkWidget *widget, 528 | cairo_t *cr, 529 | gpointer data) { 530 | window_state_t *state; 531 | GPtrArray *wedges; 532 | uint16_t i; 533 | wedge_t *w; 534 | 535 | state = (window_state_t *) data; 536 | wedges = _window_find_wedges_to_draw(state, &state->viewport); 537 | 538 | // Keep counter for draw callbacks 539 | state->draw_cb_calls++; 540 | 541 | // Apply transformations in accumulators and draw wedges 542 | cairo_transform(cr, &state->window_to_user); 543 | cairo_transform(cr, &state->user_to_scaled); 544 | 545 | // Paint background 546 | cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); 547 | cairo_set_operator(cr, CAIRO_OPERATOR_OVER); 548 | cairo_paint(cr); 549 | 550 | // Paint patterns from base to current scale 551 | for (i = wedges->len; i > 0; i--) { 552 | double dx; 553 | double dy; 554 | 555 | w = g_ptr_array_index(wedges, i - 1); 556 | w->draw_cb = state->draw_cb_calls; 557 | 558 | dx = 0.0f; 559 | dy = (double) w->start / (double) state->stream->sample_rate; 560 | 561 | cairo_save(cr); 562 | 563 | cairo_translate(cr, dx, dy); 564 | cairo_scale(cr, 1.0f / w->width, 1.0f / powf(2.0f, w->sy)); 565 | cairo_set_operator(cr, CAIRO_OPERATOR_OVER); 566 | cairo_set_source_surface(cr, w->surface, -(w->width / 2), 0); 567 | 568 | if (1) { 569 | cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_PAD); 570 | cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_BILINEAR); 571 | } else { 572 | cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST); 573 | } 574 | 575 | cairo_rectangle(cr, -(w->width / 2), -1, w->width, w->height + 1); 576 | cairo_fill(cr); 577 | 578 | cairo_restore(cr); 579 | } 580 | 581 | g_ptr_array_unref(wedges); 582 | 583 | return FALSE; 584 | } 585 | 586 | static void _to_human_time(char *buf, size_t len, double t, double dt) { 587 | int32_t h; 588 | int32_t m; 589 | int32_t s; 590 | int32_t ms; 591 | char min[2]; 592 | uint8_t p = 0; 593 | 594 | // Round to nearest millisecond 595 | t = round(1000.0f * t) / 1000.0f; 596 | if (t < 0) { 597 | t *= -1.0f; 598 | min[0] = '-'; 599 | min[1] = '\0'; 600 | } else { 601 | min[0] = '\0'; 602 | } 603 | 604 | // Compute fractions 605 | h = floor(t / 3600.0f); 606 | t -= h * 3600.0f; 607 | m = floor(t / 60.0f); 608 | t -= m * 60.0f; 609 | s = floor(t); 610 | t -= s; 611 | ms = round(t * 1000.0f); 612 | 613 | p += snprintf(buf + p, len - p, "%s%02d:%02d:%02d", min, h, m, s); 614 | if (dt < 1.0f) { 615 | p += snprintf(buf + p, len - p, ".%03d", ms); 616 | } 617 | } 618 | 619 | static void _window_time_scale_draw_axis(window_state_t *state, cairo_t *cr) { 620 | const viewport_t *v = &state->viewport; 621 | double scale = SCALE_WIDTH / 36.0f; 622 | double font_size = 11.0f * scale; 623 | 624 | // Use a minimum of 120 pixels between major ticks 625 | double dy = scale * 120.0f * fabs(v->spdy); 626 | double major_tick_delta; 627 | 628 | // Different staggering if > 1s or < 1s per major tick 629 | if (dy >= 1.0f) { 630 | double mul; 631 | 632 | // Make routine work for minutes and seconds 633 | mul = 1.0f; 634 | if (dy > 60.0f) { 635 | dy /= 60.0f; 636 | mul = 60.0f; 637 | } 638 | 639 | if (dy > 30.0f) { 640 | major_tick_delta = 60.0f * mul; 641 | } else if (dy > 10.0f) { 642 | major_tick_delta = 30.0f * mul; 643 | } else if (dy > 5.0f) { 644 | major_tick_delta = 10.0f * mul; 645 | } else if (dy > 2.0f) { 646 | major_tick_delta = 5.0f * mul; 647 | } else if (dy > 1.0f) { 648 | major_tick_delta = 2.0f * mul; 649 | } else { 650 | major_tick_delta = 1.0f * mul; 651 | } 652 | } else { 653 | double base; 654 | double rem; 655 | 656 | base = pow(10.0f, floor(log10(dy))); 657 | rem = dy / base; 658 | 659 | if (rem > 5.0f) { 660 | major_tick_delta = 10.0f * base; 661 | } else if (rem > 2.0f) { 662 | major_tick_delta = 5.0f * base; 663 | } else if (rem > 1.0f) { 664 | major_tick_delta = 2.0f * base; 665 | } else { 666 | major_tick_delta = 1.0f * base; 667 | } 668 | } 669 | 670 | // 10 minor ticks per major tick 671 | double minor_tick_delta = major_tick_delta / 10.0f; 672 | 673 | // The viewport is flipped on the x axis (increases from bottom to top) 674 | double t_min = v->sdy; 675 | double t_max = v->soy; 676 | 677 | // Start further left of the visible area so that labels that start 678 | // off screen will overflow on screen. 679 | int64_t p_min = floor(t_min / minor_tick_delta) - 10; 680 | int64_t p_max = ceil(t_max / minor_tick_delta) + 1; 681 | 682 | for (int64_t p = p_min; p < p_max; p++) { 683 | double tick = p * minor_tick_delta; 684 | double x = 0.0f; 685 | double y = tick; 686 | char buf[32]; 687 | 688 | cairo_matrix_transform_point(&state->user_to_scaled, &x, &y); 689 | cairo_matrix_transform_point(&state->window_to_user, &x, &y); 690 | 691 | if (p % 10 == 0) { 692 | _to_human_time(buf, sizeof(buf), tick, major_tick_delta); 693 | 694 | cairo_new_path(cr); 695 | cairo_move_to(cr, SCALE_WIDTH - TICK_MARGIN, y); 696 | cairo_line_to(cr, TICK_MARGIN, y); 697 | cairo_set_line_width(cr, scale); 698 | cairo_stroke(cr); 699 | 700 | cairo_save(cr); 701 | cairo_translate(cr, SCALE_WIDTH / 2.0f, y - 3); 702 | cairo_rotate(cr, -0.25 * M_TAU); 703 | cairo_set_font_size(cr, font_size); 704 | cairo_show_text(cr, buf); 705 | cairo_restore(cr); 706 | } else { 707 | cairo_new_path(cr); 708 | cairo_move_to(cr, SCALE_WIDTH - TICK_MARGIN, y); 709 | cairo_line_to(cr, (2.0f * SCALE_WIDTH / 3.0f) - TICK_MARGIN, y); 710 | cairo_set_line_width(cr, scale); 711 | cairo_stroke(cr); 712 | 713 | } 714 | } 715 | } 716 | 717 | static gboolean _window_time_scale_draw(__attribute__((unused)) GtkWidget *widget, 718 | cairo_t *cr, 719 | gpointer data) { 720 | window_state_t *state; 721 | GtkStyleContext *style; 722 | GdkRGBA bg; 723 | GdkRGBA fg; 724 | 725 | state = (window_state_t *) data; 726 | style = gtk_widget_get_style_context(widget); 727 | gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &bg); 728 | gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &fg); 729 | 730 | cairo_set_source_rgba(cr, bg.red, bg.green, bg.blue, bg.alpha); 731 | cairo_set_operator(cr, CAIRO_OPERATOR_OVER); 732 | cairo_paint(cr); 733 | 734 | cairo_set_source_rgba(cr, fg.red, fg.green, fg.blue, fg.alpha); 735 | _window_time_scale_draw_axis(state, cr); 736 | 737 | return FALSE; 738 | } 739 | 740 | static void _to_human_frequency(window_state_t *state, char *buf, size_t len, double t, double dt) { 741 | const char *suffix; 742 | double div; 743 | int8_t lcf = floor(log10(state->stream->center_freq)); 744 | if (lcf >= 9) { 745 | suffix = "GHz"; 746 | div = 1000000000.0f; 747 | } else if (lcf >= 6){ 748 | suffix = "MHz"; 749 | div = 1000000.0f; 750 | } else if (lcf >= 3) { 751 | suffix = "KHz"; 752 | div = 1000.0f; 753 | } else { 754 | suffix = "Hz"; 755 | div = 1.0f; 756 | } 757 | 758 | char fmt[16] = "%.0f %s"; 759 | int8_t precision = (int8_t) (floor(log10(div)) - floor(log10(dt))); 760 | if (precision > 0) { 761 | fmt[2] = '0' + precision; 762 | } 763 | 764 | snprintf(buf, len, fmt, t / div, suffix); 765 | } 766 | 767 | static void _window_frequency_scale_draw_axis(window_state_t *state, cairo_t *cr) { 768 | const viewport_t *v = &state->viewport; 769 | double scale = SCALE_WIDTH / 36.0f; 770 | double font_size = 11.0f * scale; 771 | 772 | // Use a minimum of 120 pixels between major ticks 773 | double dx = scale * 120.0f * fabs(v->spdx); 774 | 775 | // Multiply by sample rate to normalize w.r.t. actual frequency, 776 | // not the fake scale of 1 unit per the complete spectrum. 777 | // The resulting scale is Hz between major ticks. 778 | dx *= state->stream->sample_rate; 779 | 780 | double base = pow(10.0f, floor(log10(dx))); 781 | double rem = dx / base; 782 | double major_tick_delta; 783 | 784 | if (rem > 5.0f) { 785 | major_tick_delta = 10.0f * base; 786 | } else if (rem > 2.0f) { 787 | major_tick_delta = 5.0f * base; 788 | } else if (rem > 1.0f) { 789 | major_tick_delta = 2.0f * base; 790 | } else { 791 | major_tick_delta = 1.0f * base; 792 | } 793 | 794 | // 10 minor ticks per major tick 795 | double minor_tick_delta = major_tick_delta / 10.0f; 796 | 797 | // Translate min/max to frequency in Hz. 798 | double t_min = state->stream->center_freq + state->stream->sample_rate * v->sox; 799 | double t_max = state->stream->center_freq + state->stream->sample_rate * v->sdx; 800 | 801 | // Start further left of the visible area so that labels that start 802 | // off screen will overflow on screen. 803 | int64_t p_min = floor(t_min / minor_tick_delta) - 10; 804 | int64_t p_max = ceil(t_max / minor_tick_delta) + 1; 805 | 806 | for (int64_t p = p_min; p < p_max; p++) { 807 | double tick = p * minor_tick_delta; 808 | double x = (tick - state->stream->center_freq) / state->stream->sample_rate; 809 | double y = 0.0f; 810 | char buf[32]; 811 | 812 | cairo_matrix_transform_point(&state->user_to_scaled, &x, &y); 813 | cairo_matrix_transform_point(&state->window_to_user, &x, &y); 814 | 815 | if (p % 10 == 0) { 816 | _to_human_frequency(state, buf, sizeof(buf), tick, major_tick_delta); 817 | 818 | cairo_new_path(cr); 819 | cairo_move_to(cr, x, SCALE_WIDTH - TICK_MARGIN); 820 | cairo_line_to(cr, x, TICK_MARGIN); 821 | cairo_set_line_width(cr, scale); 822 | cairo_stroke(cr); 823 | 824 | cairo_save(cr); 825 | cairo_translate(cr, x + 3, SCALE_WIDTH / 2.0f); 826 | cairo_set_font_size(cr, font_size); 827 | cairo_show_text(cr, buf); 828 | cairo_restore(cr); 829 | } else { 830 | cairo_new_path(cr); 831 | cairo_move_to(cr, x, SCALE_WIDTH - TICK_MARGIN); 832 | cairo_line_to(cr, x, (2.0f * SCALE_WIDTH / 3.0f) - TICK_MARGIN); 833 | cairo_set_line_width(cr, scale); 834 | cairo_stroke(cr); 835 | } 836 | } 837 | } 838 | 839 | static gboolean _window_frequency_scale_draw(__attribute__((unused)) GtkWidget *widget, 840 | cairo_t *cr, 841 | gpointer data) { 842 | window_state_t *state; 843 | GtkStyleContext *style; 844 | GdkRGBA bg; 845 | GdkRGBA fg; 846 | 847 | state = (window_state_t *) data; 848 | style = gtk_widget_get_style_context(widget); 849 | gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &bg); 850 | gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &fg); 851 | 852 | cairo_set_source_rgba(cr, bg.red, bg.green, bg.blue, bg.alpha); 853 | cairo_set_operator(cr, CAIRO_OPERATOR_OVER); 854 | cairo_paint(cr); 855 | 856 | cairo_set_source_rgba(cr, fg.red, fg.green, fg.blue, fg.alpha); 857 | _window_frequency_scale_draw_axis(state, cr); 858 | 859 | return FALSE; 860 | } 861 | 862 | static void close_window(void) { 863 | gtk_main_quit(); 864 | } 865 | 866 | static void window_state_restore_start(window_state_t *state) { 867 | memcpy(&state->user_to_scaled, &state->user_to_scaled_start, sizeof(cairo_matrix_t)); 868 | _cairo_matrix_init_inverse(&state->user_to_scaled_inv, &state->user_to_scaled); 869 | } 870 | 871 | static void window_state_translate(window_state_t *state, 872 | double dx, 873 | double dy) 874 | { 875 | // Convert position delta to user coordinates to scaled coordinates 876 | cairo_matrix_transform_distance(&state->window_to_user_inv, &dx, &dy); 877 | cairo_matrix_transform_distance(&state->user_to_scaled_inv, &dx, &dy); 878 | 879 | // Apply transformation 880 | cairo_matrix_translate(&state->user_to_scaled, dx, dy); 881 | _cairo_matrix_init_inverse(&state->user_to_scaled_inv, &state->user_to_scaled); 882 | } 883 | 884 | static void window_state_scale(window_state_t *state, 885 | double offset_x, 886 | double offset_y, 887 | double scale_x, 888 | double scale_y) 889 | { 890 | // Convert offset to user coordinates to scaled coordinates 891 | cairo_matrix_transform_point(&state->window_to_user_inv, &offset_x, &offset_y); 892 | cairo_matrix_transform_point(&state->user_to_scaled_inv, &offset_x, &offset_y); 893 | 894 | // Scale around pivot point equal to mouse position when mouse drag started 895 | cairo_matrix_translate(&state->user_to_scaled, offset_x, offset_y); 896 | cairo_matrix_scale(&state->user_to_scaled, scale_x, scale_y); 897 | cairo_matrix_translate(&state->user_to_scaled, -offset_x, -offset_y); 898 | _cairo_matrix_init_inverse(&state->user_to_scaled_inv, &state->user_to_scaled); 899 | } 900 | 901 | static void window_state_create_wedges(window_state_t *state, 902 | GtkWidget *widget) 903 | { 904 | struct timeval t1; 905 | struct timeval t2; 906 | 907 | gettimeofday(&t1, NULL); 908 | create_wedges_in_view(widget, state); 909 | gettimeofday(&t2, NULL); 910 | 911 | gtk_widget_queue_draw(widget); 912 | gtk_widget_queue_draw(state->time_scale); 913 | gtk_widget_queue_draw(state->frequency_scale); 914 | 915 | if (DEBUG) { 916 | fprintf(stderr, "create_wedges: %luus\n", timediff(t1, t2)); 917 | } 918 | } 919 | 920 | static gboolean motion_notify_event_cb (GtkWidget *widget, 921 | GdkEventMotion *event, 922 | gpointer data) 923 | { 924 | window_state_t *state; 925 | 926 | state = (window_state_t *) data; 927 | 928 | if (event->state & GDK_BUTTON1_MASK) { 929 | double ddx = event->x - state->xstart; 930 | double ddy = event->y - state->ystart; 931 | 932 | window_state_restore_start(state); 933 | 934 | window_state_translate(state, ddx, ddy); 935 | 936 | window_state_create_wedges(state, widget); 937 | } 938 | 939 | if (event->state & GDK_BUTTON3_MASK) { 940 | int dx = event->x - state->xstart; 941 | int dy = event->y - state->ystart; 942 | double sdx = pow(2.0f, dx / 100.0f); 943 | double sdy = pow(2.0f, dy / 100.0f); 944 | double offset_x = state->xstart; 945 | double offset_y = state->ystart; 946 | 947 | window_state_restore_start(state); 948 | 949 | window_state_scale(state, offset_x, offset_y, sdx, sdy); 950 | 951 | window_state_create_wedges(state, widget); 952 | } 953 | 954 | return TRUE; 955 | } 956 | 957 | static gboolean button_press_event_cb (__attribute__((unused)) GtkWidget *widget, 958 | GdkEventButton *event, 959 | gpointer data) 960 | { 961 | window_state_t *state; 962 | 963 | state = (window_state_t *) data; 964 | if (event->type == GDK_BUTTON_PRESS) { 965 | state->xstart = event->x; 966 | state->ystart = event->y; 967 | memcpy(&state->user_to_scaled_start, &state->user_to_scaled, sizeof(cairo_matrix_t)); 968 | } 969 | 970 | return TRUE; 971 | } 972 | 973 | static gboolean key_press_event_cb(GtkWidget *widget, 974 | GdkEventKey *event, 975 | gpointer data) 976 | { 977 | window_state_t *state; 978 | int shift; 979 | int w; 980 | int h; 981 | float scale = 2.0f; 982 | float translate = 25.0f; 983 | 984 | state = (window_state_t *) data; 985 | shift = event->state & GDK_SHIFT_MASK; 986 | w = gtk_widget_get_allocated_width(widget); 987 | h = gtk_widget_get_allocated_height(widget); 988 | 989 | if (shift) { 990 | // Shift modifier 991 | switch (event->keyval) { 992 | case GDK_KEY_Left: 993 | window_state_scale(state, w / 2, h / 2, 1.0f / scale, 1.0f); 994 | break; 995 | case GDK_KEY_Up: 996 | window_state_scale(state, w / 2, h / 2, 1.0f, 1.0f / scale); 997 | break; 998 | case GDK_KEY_Right: 999 | window_state_scale(state, w / 2, h / 2, scale, 1.0f); 1000 | break; 1001 | case GDK_KEY_Down: 1002 | window_state_scale(state, w / 2, h / 2, 1.0f, scale); 1003 | break; 1004 | default: 1005 | return FALSE; 1006 | } 1007 | 1008 | window_state_create_wedges(state, widget); 1009 | } else { 1010 | // No modifier 1011 | switch (event->keyval) { 1012 | case GDK_KEY_Left: 1013 | window_state_translate(state, translate, 0.0f); 1014 | break; 1015 | case GDK_KEY_Up: 1016 | window_state_translate(state, 0.0f, translate); 1017 | break; 1018 | case GDK_KEY_Right: 1019 | window_state_translate(state, -translate, 0.0f); 1020 | break; 1021 | case GDK_KEY_Down: 1022 | window_state_translate(state, 0.0f, -translate); 1023 | break; 1024 | default: 1025 | return FALSE; 1026 | } 1027 | 1028 | window_state_create_wedges(state, widget); 1029 | } 1030 | 1031 | return TRUE; 1032 | } 1033 | 1034 | static GtkWidget *_create_drawing_areas(window_state_t *state) { 1035 | GtkWidget *drawing_area; 1036 | GtkWidget *time_scale; 1037 | GtkWidget *frequency_scale; 1038 | GtkWidget *grid; 1039 | 1040 | drawing_area = gtk_drawing_area_new(); 1041 | gtk_widget_set_hexpand(drawing_area, TRUE); 1042 | gtk_widget_set_vexpand(drawing_area, TRUE); 1043 | gtk_widget_set_size_request(drawing_area, 200, 200); 1044 | 1045 | // Signals used to handle the backing surface 1046 | g_signal_connect(drawing_area, "draw", 1047 | G_CALLBACK(draw_cb), state); 1048 | g_signal_connect(drawing_area, "configure-event", 1049 | G_CALLBACK(configure_event_cb), state); 1050 | g_signal_connect(drawing_area, "motion-notify-event", 1051 | G_CALLBACK(motion_notify_event_cb), state); 1052 | g_signal_connect(drawing_area, "button-press-event", 1053 | G_CALLBACK(button_press_event_cb), state); 1054 | g_signal_connect(drawing_area, "key-press-event", 1055 | G_CALLBACK(key_press_event_cb), state); 1056 | 1057 | gtk_widget_set_events(drawing_area, gtk_widget_get_events(drawing_area) 1058 | | GDK_POINTER_MOTION_MASK 1059 | | GDK_BUTTON_PRESS_MASK 1060 | | GDK_KEY_PRESS_MASK); 1061 | 1062 | gtk_widget_set_can_focus(drawing_area, TRUE); 1063 | 1064 | time_scale = gtk_drawing_area_new(); 1065 | gtk_widget_set_hexpand(time_scale, FALSE); 1066 | gtk_widget_set_vexpand(time_scale, TRUE); 1067 | gtk_widget_set_size_request(time_scale, SCALE_WIDTH, SCALE_WIDTH); 1068 | 1069 | g_signal_connect(time_scale, "draw", 1070 | G_CALLBACK(_window_time_scale_draw), state); 1071 | 1072 | frequency_scale = gtk_drawing_area_new(); 1073 | gtk_widget_set_hexpand(frequency_scale, TRUE); 1074 | gtk_widget_set_vexpand(frequency_scale, FALSE); 1075 | gtk_widget_set_size_request(frequency_scale, SCALE_WIDTH, SCALE_WIDTH); 1076 | 1077 | g_signal_connect(frequency_scale, "draw", 1078 | G_CALLBACK(_window_frequency_scale_draw), state); 1079 | 1080 | grid = gtk_grid_new(); 1081 | gtk_grid_set_row_homogeneous(GTK_GRID(grid), FALSE); 1082 | gtk_grid_set_column_homogeneous(GTK_GRID(grid), FALSE); 1083 | 1084 | gtk_grid_attach(GTK_GRID(grid), time_scale, 0, 1, 1, 1); 1085 | gtk_grid_attach(GTK_GRID(grid), frequency_scale, 1, 0, 1, 1); 1086 | gtk_grid_attach(GTK_GRID(grid), drawing_area, 1, 1, 1, 1); 1087 | 1088 | state->drawing_area = drawing_area; 1089 | state->time_scale = time_scale; 1090 | state->frequency_scale = frequency_scale; 1091 | 1092 | return grid; 1093 | } 1094 | 1095 | static void activate(GtkApplication *app, 1096 | gpointer user_data) { 1097 | GtkWidget *window; 1098 | GtkWidget *main_box; 1099 | GtkWidget *drawing_area; 1100 | stream_t *s; 1101 | window_state_t *state; 1102 | 1103 | s = (stream_t *) user_data; 1104 | 1105 | state = malloc(sizeof(*state)); 1106 | state->wedge_height = 64; 1107 | state->xstart = 0; 1108 | state->ystart = 0; 1109 | state->stream = s; 1110 | state->wedges = g_ptr_array_new(); 1111 | 1112 | cairo_matrix_init_identity(&state->user_to_scaled); 1113 | _cairo_matrix_init_inverse(&state->user_to_scaled_inv, &state->user_to_scaled); 1114 | 1115 | window = gtk_application_window_new(app); 1116 | gtk_window_set_title(GTK_WINDOW(window), "Waterfall"); 1117 | g_signal_connect(window, "destroy", G_CALLBACK(close_window), NULL); 1118 | 1119 | main_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); 1120 | gtk_container_add(GTK_CONTAINER(window), main_box); 1121 | 1122 | drawing_area = _create_drawing_areas(state); 1123 | gtk_box_pack_start(GTK_BOX(main_box), drawing_area, TRUE, TRUE, 0); 1124 | 1125 | gtk_widget_show_all(window); 1126 | } 1127 | 1128 | int window_run(stream_t *s) { 1129 | GtkApplication *app; 1130 | int status; 1131 | 1132 | app = gtk_application_new("com.github.pietern.waterfall", G_APPLICATION_FLAGS_NONE); 1133 | g_signal_connect(app, "activate", G_CALLBACK(activate), s); 1134 | status = g_application_run(G_APPLICATION(app), 0, NULL); 1135 | g_object_unref(app); 1136 | 1137 | return status; 1138 | } 1139 | -------------------------------------------------------------------------------- /window.h: -------------------------------------------------------------------------------- 1 | #ifndef _WINDOW_H 2 | #define _WINDOW_H 3 | 4 | #include 5 | 6 | #include "stream.h" 7 | 8 | int window_run(stream_t *s); 9 | 10 | #endif 11 | --------------------------------------------------------------------------------