5 #include <gdk/gdkkeysyms.h>
11 #define BUF_SAMPLES (INTEGRATION_SAMPLES * 3)
12 #define INTEGRATION_SAMPLES 44100
16 struct waterfall_context
{
17 GtkDrawingArea
*drawingarea
;
18 GdkPixbuf
*original
; /* A 1-to-1 map from spectral data to pixels. */
19 GdkPixbuf
*resized
; /* A backing store for the scaled spectrogram. */
21 GdkPixbuf
*part1
; /* Slices from zero to the slice cursor. */
22 GdkPixbuf
*part2
; /* Slices from the slice cursor to the end. */
27 /* The size of the spectrogram drawing area. */
40 size_t integration_samples
;
45 fftw_complex
*spectrum
;
50 void invalidate_spectrogram(struct waterfall_context
*waterfall
);
52 void waterfall_open_output(GtkFileChooser
*chooser
, gpointer user_data
)
54 char *filename
= gtk_file_chooser_get_filename(chooser
);
55 printf("Open %s\n", filename
);
58 gtk_widget_hide(GTK_WIDGET(chooser
));
61 void waterfall_expose_spectrogram(GtkWidget
*widget
,
62 GdkEventExpose
*event
,
65 GtkDrawingArea
*spectrogram_drawingarea
= GTK_DRAWING_AREA(widget
);
66 struct waterfall_context
*waterfall
= user_data
;
69 if (!waterfall
->resized
) {
70 double part1_size
, part2_size
;
71 int resolution
= waterfall
->zoom
.f_high
- waterfall
->zoom
.f_low
+ 1;
72 double scale_x
= (double) waterfall
->size
.width
/ waterfall
->n_slices
;
73 double scale_y
= (double) waterfall
->size
.height
/ resolution
;
74 double offset_y
= -waterfall
->zoom
.f_low
* scale_y
;
75 int row
, rowstride
, channels
;
78 part1_size
= (double) waterfall
->subpixbufs
.part1_size
/ waterfall
->n_slices
* waterfall
->size
.width
;
79 part2_size
= waterfall
->size
.width
- (int) part1_size
;
80 printf("Expose parts %g (%d),%g (%d)\n",
81 part1_size
, waterfall
->subpixbufs
.part1_size
,
82 part2_size
, waterfall
->subpixbufs
.part2_size
);
84 /* Create a new backing store, then render into it. */
85 waterfall
->resized
= gdk_pixbuf_new(GDK_COLORSPACE_RGB
, FALSE
, 8, waterfall
->size
.width
, waterfall
->size
.height
);
86 if (waterfall
->subpixbufs
.part2
) {
87 /* Render the oldest slices first. */
88 printf("Scale (%g) part2 to width %d at 0\n",
89 scale_x
, (int) part2_size
);
90 gdk_pixbuf_scale(waterfall
->subpixbufs
.part2
, waterfall
->resized
,
91 0, 0, (int) part2_size
, waterfall
->size
.height
,
92 0, offset_y
, scale_x
, scale_y
,
95 if (waterfall
->subpixbufs
.part1
) {
96 /* Then render the more recent slices. */
97 printf("Scale (%g) part1 to width %d at %d\n",
98 scale_x
, (int) part1_size
, (int) part2_size
);
99 gdk_pixbuf_scale(waterfall
->subpixbufs
.part1
, waterfall
->resized
,
100 (int) part2_size
, 0, (int) part1_size
, waterfall
->size
.height
,
101 part2_size
, offset_y
, scale_x
, scale_y
,
104 pixels
= gdk_pixbuf_get_pixels(waterfall
->resized
);
105 rowstride
= gdk_pixbuf_get_rowstride(waterfall
->resized
);
106 channels
= gdk_pixbuf_get_n_channels(waterfall
->resized
);
107 /* Draw a narrow marker to represent the border between part1 and part2. */
108 for (row
= 0; row
< waterfall
->size
.height
; row
++) {
109 pixels
[row
*rowstride
+ ((int) part2_size
% waterfall
->size
.width
) * channels
+ 1] = 192;
113 cr
= gdk_cairo_create(spectrogram_drawingarea
->widget
.window
);
115 gdk_cairo_set_source_pixbuf(cr
, waterfall
->resized
, 0, 0);
121 void waterfall_configure_spectrogram(GtkWidget
*widget
,
122 GdkEventConfigure
*event
,
125 struct waterfall_context
*waterfall
= user_data
;
127 printf("configure %d %d,%d+%d,%d send_event=%d on window %p\n",
128 event
->type
, event
->x
, event
->y
, event
->width
, event
->height
, event
->send_event
, event
->window
);
130 waterfall
->size
.width
= event
->width
;
131 waterfall
->size
.height
= event
->height
;
133 invalidate_spectrogram(waterfall
);
136 gboolean
waterfall_key_press(GtkWidget
*widget
,
140 struct waterfall_context
*waterfall
= user_data
;
141 int resolution
= waterfall
->zoom
.f_high
- waterfall
->zoom
.f_low
+ 1;
142 int headroom
= waterfall
->integration_samples
/2 - waterfall
->zoom
.f_high
;
143 gboolean zoom_changed
= FALSE
;
145 printf("key press\n");
146 switch (event
->keyval
) {
149 waterfall
->zoom
.f_low
-= MIN(waterfall
->zoom
.f_low
, resolution
/ 2);
150 waterfall
->zoom
.f_high
-= MIN(waterfall
->zoom
.f_low
, resolution
/ 2);
155 waterfall
->zoom
.f_low
+= MIN(headroom
, resolution
/ 2);
156 waterfall
->zoom
.f_high
+= MIN(headroom
, resolution
/ 2);
161 if (resolution
/ 4 > 1) {
162 waterfall
->zoom
.f_low
+= resolution
/ 4;
163 waterfall
->zoom
.f_high
-= resolution
/ 4;
169 waterfall
->zoom
.f_low
-= MIN(waterfall
->zoom
.f_low
, resolution
/ 4);
170 waterfall
->zoom
.f_high
+= MIN(headroom
, resolution
/ 4);
173 case GDK_KEY_bracketleft
:
175 waterfall
->zoom
.noise_floor
--;
177 case GDK_KEY_bracketright
:
179 waterfall
->zoom
.noise_floor
++;
181 case GDK_KEY_braceleft
:
183 waterfall
->zoom
.sensitivity
/= 1.5;
185 case GDK_KEY_braceright
:
187 waterfall
->zoom
.sensitivity
*= 1.5;
192 invalidate_spectrogram(waterfall
);
198 void connect_signal(GtkBuilder
*builder
,
200 const gchar
*signal_name
,
201 const gchar
*handler_name
,
202 GObject
*connect_object
,
210 { "gtk_main_quit", G_CALLBACK(>k_main_quit
) },
211 { "gtk_widget_hide_on_delete", G_CALLBACK(>k_widget_hide_on_delete
) },
212 { "gtk_widget_show", G_CALLBACK(>k_widget_show
) },
213 { "waterfall_open_output", G_CALLBACK(&waterfall_open_output
) },
218 for (i
= 0; handlers
[i
].name
; i
++) {
219 if (!strcmp(handlers
[i
].name
, handler_name
)) {
220 GCallback handler
= handlers
[i
].fn
;
221 g_signal_connect_object(object
, signal_name
, handler
,
222 connect_object
, flags
);
227 /* TODO: Connect to some error-spewing function? */
230 void invalidate_spectrogram(struct waterfall_context
*waterfall
)
232 GdkRegion
*visible_region
;
234 /* Just invalidate everything. */
235 if (waterfall
->resized
) {
236 g_object_unref(G_OBJECT(waterfall
->resized
));
237 waterfall
->resized
= NULL
;
239 visible_region
= gdk_drawable_get_visible_region(waterfall
->drawingarea
->widget
.window
);
240 gdk_window_invalidate_region(waterfall
->drawingarea
->widget
.window
,
243 gdk_region_destroy(visible_region
);
246 void split_pixbuf(struct waterfall_context
*waterfall
)
248 int max_resolution
= waterfall
->integration_samples
/2 + 1;
250 if (waterfall
->subpixbufs
.part1
) {
251 g_object_unref(G_OBJECT(waterfall
->subpixbufs
.part1
));
253 if (waterfall
->subpixbufs
.part2
) {
254 g_object_unref(G_OBJECT(waterfall
->subpixbufs
.part2
));
257 waterfall
->subpixbufs
.part1_size
= waterfall
->slice
% waterfall
->n_slices
;
258 waterfall
->subpixbufs
.part2_size
= waterfall
->n_slices
- waterfall
->subpixbufs
.part1_size
;
259 printf("Split %d,%d\n", waterfall
->subpixbufs
.part1_size
, waterfall
->subpixbufs
.part2_size
);
260 if (waterfall
->subpixbufs
.part1_size
) {
261 waterfall
->subpixbufs
.part1
= gdk_pixbuf_new_subpixbuf(waterfall
->original
,
263 waterfall
->subpixbufs
.part1_size
, max_resolution
);
265 waterfall
->subpixbufs
.part1
= NULL
;
267 if (waterfall
->subpixbufs
.part2_size
) {
268 waterfall
->subpixbufs
.part2
= gdk_pixbuf_new_subpixbuf(waterfall
->original
,
269 waterfall
->subpixbufs
.part1_size
, 0,
270 waterfall
->subpixbufs
.part2_size
, max_resolution
);
272 waterfall
->subpixbufs
.part2
= NULL
;
276 gboolean
waterfall_input(GIOChannel
*source
,
277 GIOCondition condition
,
280 struct waterfall_context
*waterfall
= userdata
;
281 double normalization
= log10(waterfall
->integration_samples
* 32768);
286 switch (g_io_channel_read_chars(source
,
288 waterfall
->buf_size
- waterfall
->buf_index
,
291 case G_IO_STATUS_NORMAL
:
292 waterfall
->buf_index
+= n
;
294 case G_IO_STATUS_AGAIN
:
295 printf("Try again later\n");
298 case G_IO_STATUS_EOF
:
299 printf("End of input\n");
302 case G_IO_STATUS_ERROR
:
303 printf("Error - %s\n", err
->message
);
311 while (waterfall
->buf_index
>= waterfall
->integration_samples
*SAMPLE_SIZE
) {
312 int i
, row
, rowstride
, channels
, modslice
;
313 int resolution
= waterfall
->integration_samples
/2 + 1;
316 /* Analyze the spectrum. */
317 printf("Integrate %d %zu %d (%d)\n", batch_size
, waterfall
->buf_index
,
318 waterfall
->slice
, waterfall
->slice
% waterfall
->n_slices
);
319 for (i
= 0; i
< waterfall
->integration_samples
; i
++) {
321 memcpy(&x
, waterfall
->buf
+ i
*SAMPLE_SIZE
, sizeof (x
));
322 waterfall
->fft
.samples
[i
] = x
;
324 fftw_execute(waterfall
->fft
.p
);
326 /* Draw a new slice. */
327 modslice
= waterfall
->slice
% waterfall
->n_slices
;
328 pixels
= gdk_pixbuf_get_pixels(waterfall
->original
);
329 rowstride
= gdk_pixbuf_get_rowstride(waterfall
->original
);
330 channels
= gdk_pixbuf_get_n_channels(waterfall
->original
);
331 for (row
= 0; row
< resolution
; row
++) {
332 double signal_i
= waterfall
->fft
.spectrum
[row
][0], signal_q
= waterfall
->fft
.spectrum
[row
][1];
333 /* XXX Give invsqrt a chance to happen. */
334 double power
= -log10(1.0 / hypot(signal_i
, signal_q
)) - normalization
;
335 double z
= (power
+ waterfall
->zoom
.noise_floor
) * waterfall
->zoom
.sensitivity
;
336 if (row
> 50 && row
< 60) {
337 printf("Power = %g (%g)\n", power
, z
);
339 /* Drawing to the original-size pixbuf, resampling happens in the expose event. */
340 pixels
[row
*rowstride
+ modslice
* channels
+ 0] = z
>= 256 ? MIN(z
- 256, 255) : 0;
341 pixels
[row
*rowstride
+ modslice
* channels
+ 1] = z
>= 256 ? MAX(512 - z
, 0) : MAX(z
, 0);
342 pixels
[row
*rowstride
+ modslice
* channels
+ 2] = z
< 256 ? MIN(255 - z
, 255) : 0;
347 memmove(waterfall
->buf
,
348 waterfall
->buf
+ waterfall
->integration_samples
*SAMPLE_SIZE
,
349 waterfall
->buf_size
- waterfall
->integration_samples
*SAMPLE_SIZE
);
350 waterfall
->buf_index
-= waterfall
->integration_samples
*SAMPLE_SIZE
;
354 /* Scroll the waterfall. */
355 split_pixbuf(waterfall
);
356 invalidate_spectrogram(waterfall
);
358 if (batch_size
> 2) {
359 printf("I can't keep up (%d)\n", batch_size
);
369 int main(int argc
, char *argv
[])
371 GtkBuilder
*gtk_builder
;
372 GtkWindow
*waterfall_window
;
373 GIOChannel
*stdin_channel
;
376 struct waterfall_context waterfall
= {
378 .integration_samples
= INTEGRATION_SAMPLES
,
380 .n_slices
= N_SLICES
,
383 .f_high
= INTEGRATION_SAMPLES
/2,
389 gtk_init(&argc
, &argv
);
391 gtk_builder
= gtk_builder_new();
392 gtk_builder_add_from_file(gtk_builder
, "waterfall.glade", NULL
);
393 gtk_builder_connect_signals_full(gtk_builder
, &connect_signal
, NULL
);
395 /* Give signal handlers a means of accessing gtk_builder. */
396 g_object_set(gtk_builder_get_object(gtk_builder
,
398 "user-data", gtk_builder
,
401 waterfall_window
= GTK_WINDOW(gtk_builder_get_object(gtk_builder
, "waterfall_window"));
402 waterfall
.drawingarea
= GTK_DRAWING_AREA(gtk_builder_get_object(gtk_builder
, "image_detail_drawingarea"));
404 g_signal_connect(G_OBJECT(waterfall
.drawingarea
), "expose-event",
405 G_CALLBACK(&waterfall_expose_spectrogram
), &waterfall
);
406 g_signal_connect(G_OBJECT(waterfall
.drawingarea
), "configure-event",
407 G_CALLBACK(&waterfall_configure_spectrogram
), &waterfall
);
408 g_signal_connect(G_OBJECT(waterfall_window
), "key-press-event",
409 G_CALLBACK(&waterfall_key_press
), &waterfall
);
411 /* Don't block on stdin. */
412 stdin_flags
= fcntl(0, F_GETFL
, 0);
413 fcntl(0, F_SETFL
, stdin_flags
| O_NONBLOCK
);
415 stdin_channel
= g_io_channel_unix_new(0);
416 g_io_channel_set_encoding(stdin_channel
, NULL
, &err
);
417 g_io_channel_set_buffer_size(stdin_channel
, BUF_SAMPLES
*SAMPLE_SIZE
);
418 waterfall
.buf
= malloc(BUF_SAMPLES
*SAMPLE_SIZE
);
419 waterfall
.buf_size
= INTEGRATION_SAMPLES
*SAMPLE_SIZE
;
420 g_io_add_watch(stdin_channel
, G_IO_IN
, &waterfall_input
, &waterfall
);
422 waterfall
.original
= gdk_pixbuf_new(GDK_COLORSPACE_RGB
, FALSE
, 8, waterfall
.n_slices
, INTEGRATION_SAMPLES
/2 + 1);
423 split_pixbuf(&waterfall
);
425 waterfall
.fft
.samples
= fftw_malloc(sizeof (*waterfall
.fft
.samples
) * INTEGRATION_SAMPLES
);
426 waterfall
.fft
.spectrum
= fftw_malloc(sizeof (*waterfall
.fft
.spectrum
) * (INTEGRATION_SAMPLES
/2 + 1));
427 waterfall
.fft
.p
= fftw_plan_dft_r2c_1d(INTEGRATION_SAMPLES
,
428 waterfall
.fft
.samples
, waterfall
.fft
.spectrum
,
431 gtk_widget_show(GTK_WIDGET(waterfall_window
));
433 invalidate_spectrogram(&waterfall
);
437 fftw_destroy_plan(waterfall
.fft
.p
);
438 fftw_free(waterfall
.fft
.spectrum
);
439 fftw_free(waterfall
.fft
.samples
);