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
;
81 /* Create a new backing store, then render into it. */
82 waterfall
->resized
= gdk_pixbuf_new(GDK_COLORSPACE_RGB
, FALSE
, 8, waterfall
->size
.width
, waterfall
->size
.height
);
83 if (waterfall
->subpixbufs
.part2
) {
84 /* Render the oldest slices first. */
85 gdk_pixbuf_scale(waterfall
->subpixbufs
.part2
, waterfall
->resized
,
86 0, 0, (int) part2_size
, waterfall
->size
.height
,
87 0, offset_y
, scale_x
, scale_y
,
90 if (waterfall
->subpixbufs
.part1
) {
91 /* Then render the more recent slices. */
92 gdk_pixbuf_scale(waterfall
->subpixbufs
.part1
, waterfall
->resized
,
93 (int) part2_size
, 0, (int) part1_size
, waterfall
->size
.height
,
94 part2_size
, offset_y
, scale_x
, scale_y
,
97 pixels
= gdk_pixbuf_get_pixels(waterfall
->resized
);
98 rowstride
= gdk_pixbuf_get_rowstride(waterfall
->resized
);
99 channels
= gdk_pixbuf_get_n_channels(waterfall
->resized
);
100 /* Draw a narrow marker to represent the border between part1 and part2. */
101 for (row
= 0; row
< waterfall
->size
.height
; row
++) {
102 pixels
[row
*rowstride
+ ((int) part2_size
% waterfall
->size
.width
) * channels
+ 1] = 192;
106 cr
= gdk_cairo_create(spectrogram_drawingarea
->widget
.window
);
108 gdk_cairo_set_source_pixbuf(cr
, waterfall
->resized
, 0, 0);
114 void waterfall_configure_spectrogram(GtkWidget
*widget
,
115 GdkEventConfigure
*event
,
118 struct waterfall_context
*waterfall
= user_data
;
120 printf("configure %d %d,%d+%d,%d send_event=%d on window %p\n",
121 event
->type
, event
->x
, event
->y
, event
->width
, event
->height
, event
->send_event
, event
->window
);
123 waterfall
->size
.width
= event
->width
;
124 waterfall
->size
.height
= event
->height
;
126 invalidate_spectrogram(waterfall
);
129 gboolean
waterfall_key_press(GtkWidget
*widget
,
133 struct waterfall_context
*waterfall
= user_data
;
134 int resolution
= waterfall
->zoom
.f_high
- waterfall
->zoom
.f_low
+ 1;
135 int headroom
= waterfall
->integration_samples
/2 - waterfall
->zoom
.f_high
;
136 gboolean zoom_changed
= FALSE
;
138 printf("key press\n");
139 switch (event
->keyval
) {
142 waterfall
->zoom
.f_low
-= MIN(waterfall
->zoom
.f_low
, resolution
/ 2);
143 waterfall
->zoom
.f_high
-= MIN(waterfall
->zoom
.f_low
, resolution
/ 2);
148 waterfall
->zoom
.f_low
+= MIN(headroom
, resolution
/ 2);
149 waterfall
->zoom
.f_high
+= MIN(headroom
, resolution
/ 2);
154 if (resolution
/ 4 > 1) {
155 waterfall
->zoom
.f_low
+= resolution
/ 4;
156 waterfall
->zoom
.f_high
-= resolution
/ 4;
162 waterfall
->zoom
.f_low
-= MIN(waterfall
->zoom
.f_low
, resolution
/ 4);
163 waterfall
->zoom
.f_high
+= MIN(headroom
, resolution
/ 4);
166 case GDK_KEY_bracketleft
:
168 waterfall
->zoom
.noise_floor
--;
170 case GDK_KEY_bracketright
:
172 waterfall
->zoom
.noise_floor
++;
174 case GDK_KEY_braceleft
:
176 waterfall
->zoom
.sensitivity
/= 1.5;
178 case GDK_KEY_braceright
:
180 waterfall
->zoom
.sensitivity
*= 1.5;
185 invalidate_spectrogram(waterfall
);
191 void connect_signal(GtkBuilder
*builder
,
193 const gchar
*signal_name
,
194 const gchar
*handler_name
,
195 GObject
*connect_object
,
203 { "gtk_main_quit", G_CALLBACK(>k_main_quit
) },
204 { "gtk_widget_hide_on_delete", G_CALLBACK(>k_widget_hide_on_delete
) },
205 { "gtk_widget_show", G_CALLBACK(>k_widget_show
) },
206 { "waterfall_open_output", G_CALLBACK(&waterfall_open_output
) },
211 for (i
= 0; handlers
[i
].name
; i
++) {
212 if (!strcmp(handlers
[i
].name
, handler_name
)) {
213 GCallback handler
= handlers
[i
].fn
;
214 g_signal_connect_object(object
, signal_name
, handler
,
215 connect_object
, flags
);
220 /* TODO: Connect to some error-spewing function? */
223 void invalidate_spectrogram(struct waterfall_context
*waterfall
)
225 GdkRegion
*visible_region
;
227 /* Just invalidate everything. */
228 if (waterfall
->resized
) {
229 g_object_unref(G_OBJECT(waterfall
->resized
));
230 waterfall
->resized
= NULL
;
232 visible_region
= gdk_drawable_get_visible_region(waterfall
->drawingarea
->widget
.window
);
233 gdk_window_invalidate_region(waterfall
->drawingarea
->widget
.window
,
236 gdk_region_destroy(visible_region
);
239 void split_pixbuf(struct waterfall_context
*waterfall
)
241 int max_resolution
= waterfall
->integration_samples
/2 + 1;
243 if (waterfall
->subpixbufs
.part1
) {
244 g_object_unref(G_OBJECT(waterfall
->subpixbufs
.part1
));
246 if (waterfall
->subpixbufs
.part2
) {
247 g_object_unref(G_OBJECT(waterfall
->subpixbufs
.part2
));
250 waterfall
->subpixbufs
.part1_size
= waterfall
->slice
% waterfall
->n_slices
;
251 waterfall
->subpixbufs
.part2_size
= waterfall
->n_slices
- waterfall
->subpixbufs
.part1_size
;
252 if (waterfall
->subpixbufs
.part1_size
) {
253 waterfall
->subpixbufs
.part1
= gdk_pixbuf_new_subpixbuf(waterfall
->original
,
255 waterfall
->subpixbufs
.part1_size
, max_resolution
);
257 waterfall
->subpixbufs
.part1
= NULL
;
259 if (waterfall
->subpixbufs
.part2_size
) {
260 waterfall
->subpixbufs
.part2
= gdk_pixbuf_new_subpixbuf(waterfall
->original
,
261 waterfall
->subpixbufs
.part1_size
, 0,
262 waterfall
->subpixbufs
.part2_size
, max_resolution
);
264 waterfall
->subpixbufs
.part2
= NULL
;
268 gboolean
waterfall_input(GIOChannel
*source
,
269 GIOCondition condition
,
272 struct waterfall_context
*waterfall
= userdata
;
273 double normalization
= log10(waterfall
->integration_samples
* 32768);
278 switch (g_io_channel_read_chars(source
,
280 waterfall
->buf_size
- waterfall
->buf_index
,
283 case G_IO_STATUS_NORMAL
:
284 waterfall
->buf_index
+= n
;
286 case G_IO_STATUS_AGAIN
:
287 printf("Try again later\n");
290 case G_IO_STATUS_EOF
:
291 printf("End of input\n");
294 case G_IO_STATUS_ERROR
:
295 printf("Error - %s\n", err
->message
);
303 while (waterfall
->buf_index
>= waterfall
->integration_samples
*SAMPLE_SIZE
) {
304 int i
, row
, rowstride
, channels
, modslice
;
305 int resolution
= waterfall
->integration_samples
/2 + 1;
308 /* Analyze the spectrum. */
309 printf("Integrate %d %zu %d (%d)\n", batch_size
, waterfall
->buf_index
,
310 waterfall
->slice
, waterfall
->slice
% waterfall
->n_slices
);
311 for (i
= 0; i
< waterfall
->integration_samples
; i
++) {
313 memcpy(&x
, waterfall
->buf
+ i
*SAMPLE_SIZE
, sizeof (x
));
314 waterfall
->fft
.samples
[i
] = x
;
316 fftw_execute(waterfall
->fft
.p
);
318 /* Draw a new slice. */
319 modslice
= waterfall
->slice
% waterfall
->n_slices
;
320 pixels
= gdk_pixbuf_get_pixels(waterfall
->original
);
321 rowstride
= gdk_pixbuf_get_rowstride(waterfall
->original
);
322 channels
= gdk_pixbuf_get_n_channels(waterfall
->original
);
323 for (row
= 0; row
< resolution
; row
++) {
324 double signal_i
= waterfall
->fft
.spectrum
[row
][0], signal_q
= waterfall
->fft
.spectrum
[row
][1];
325 /* XXX Give invsqrt a chance to happen. */
326 double power
= -log10(1.0 / hypot(signal_i
, signal_q
)) - normalization
;
327 double z
= (power
+ waterfall
->zoom
.noise_floor
) * waterfall
->zoom
.sensitivity
;
328 if (row
> 50 && row
< 60) {
329 printf("Power = %g (%g)\n", power
, z
);
331 /* Drawing to the original-size pixbuf, resampling happens in the expose event. */
332 pixels
[row
*rowstride
+ modslice
* channels
+ 0] = z
>= 256 ? MIN(z
- 256, 255) : 0;
333 pixels
[row
*rowstride
+ modslice
* channels
+ 1] = z
>= 256 ? MAX(511 - z
, 0) : MAX(z
, 0);
334 pixels
[row
*rowstride
+ modslice
* channels
+ 2] = z
< 256 ? MIN(255 - z
, 255) : 0;
339 memmove(waterfall
->buf
,
340 waterfall
->buf
+ waterfall
->integration_samples
*SAMPLE_SIZE
,
341 waterfall
->buf_size
- waterfall
->integration_samples
*SAMPLE_SIZE
);
342 waterfall
->buf_index
-= waterfall
->integration_samples
*SAMPLE_SIZE
;
346 /* Scroll the waterfall. */
347 split_pixbuf(waterfall
);
348 invalidate_spectrogram(waterfall
);
350 if (batch_size
> 2) {
351 printf("I can't keep up (%d)\n", batch_size
);
361 int main(int argc
, char *argv
[])
363 GtkBuilder
*gtk_builder
;
364 GtkWindow
*waterfall_window
;
365 GIOChannel
*stdin_channel
;
368 struct waterfall_context waterfall
= {
378 waterfall
.integration_samples
= INTEGRATION_SAMPLES
;
379 waterfall
.n_slices
= N_SLICES
;
380 waterfall
.zoom
.f_high
= waterfall
.integration_samples
/2;
382 gtk_init(&argc
, &argv
);
384 gtk_builder
= gtk_builder_new();
385 gtk_builder_add_from_file(gtk_builder
, "waterfall.glade", NULL
);
386 gtk_builder_connect_signals_full(gtk_builder
, &connect_signal
, NULL
);
388 /* Give signal handlers a means of accessing gtk_builder. */
389 g_object_set(gtk_builder_get_object(gtk_builder
,
391 "user-data", gtk_builder
,
394 waterfall_window
= GTK_WINDOW(gtk_builder_get_object(gtk_builder
, "waterfall_window"));
395 waterfall
.drawingarea
= GTK_DRAWING_AREA(gtk_builder_get_object(gtk_builder
, "image_detail_drawingarea"));
397 g_signal_connect(G_OBJECT(waterfall
.drawingarea
), "expose-event",
398 G_CALLBACK(&waterfall_expose_spectrogram
), &waterfall
);
399 g_signal_connect(G_OBJECT(waterfall
.drawingarea
), "configure-event",
400 G_CALLBACK(&waterfall_configure_spectrogram
), &waterfall
);
401 g_signal_connect(G_OBJECT(waterfall_window
), "key-press-event",
402 G_CALLBACK(&waterfall_key_press
), &waterfall
);
404 /* Don't block on stdin. */
405 stdin_flags
= fcntl(0, F_GETFL
, 0);
406 fcntl(0, F_SETFL
, stdin_flags
| O_NONBLOCK
);
408 stdin_channel
= g_io_channel_unix_new(0);
409 g_io_channel_set_encoding(stdin_channel
, NULL
, &err
);
410 g_io_channel_set_buffer_size(stdin_channel
, BUF_SAMPLES
*SAMPLE_SIZE
);
411 waterfall
.buf
= malloc(BUF_SAMPLES
*SAMPLE_SIZE
);
412 waterfall
.buf_size
= waterfall
.integration_samples
* SAMPLE_SIZE
;
413 g_io_add_watch(stdin_channel
, G_IO_IN
, &waterfall_input
, &waterfall
);
415 waterfall
.original
= gdk_pixbuf_new(GDK_COLORSPACE_RGB
, FALSE
, 8,
416 waterfall
.n_slices
, waterfall
.integration_samples
/2 + 1);
417 split_pixbuf(&waterfall
);
419 waterfall
.fft
.samples
= fftw_malloc(sizeof (*waterfall
.fft
.samples
) * waterfall
.integration_samples
);
420 waterfall
.fft
.spectrum
= fftw_malloc(sizeof (*waterfall
.fft
.spectrum
) * (waterfall
.integration_samples
/2 + 1));
421 waterfall
.fft
.p
= fftw_plan_dft_r2c_1d(waterfall
.integration_samples
,
422 waterfall
.fft
.samples
, waterfall
.fft
.spectrum
,
425 gtk_widget_show(GTK_WIDGET(waterfall_window
));
427 invalidate_spectrogram(&waterfall
);
431 fftw_destroy_plan(waterfall
.fft
.p
);
432 fftw_free(waterfall
.fft
.spectrum
);
433 fftw_free(waterfall
.fft
.samples
);