5 #include <gdk/gdkkeysyms.h>
11 #define BUF_SAMPLES (INTEGRATION_SAMPLES * 3)
12 #define INTEGRATION_SAMPLES 44100
15 struct waterfall_context
{
16 GtkDrawingArea
*drawingarea
;
17 GdkPixbuf
*original
; /* A 1-to-1 map from spectral data to pixels. */
18 GdkPixbuf
*resized
; /* A backing store for the scaled spectrogram. */
20 GdkPixbuf
*part1
; /* Slices from zero to the slice cursor. */
21 GdkPixbuf
*part2
; /* Slices from the slice cursor to the end. */
26 /* The size of the spectrogram drawing area. */
37 size_t integration_samples
;
42 fftw_complex
*spectrum
;
47 void invalidate_spectrogram(struct waterfall_context
*waterfall
);
49 void waterfall_open_output(GtkFileChooser
*chooser
, gpointer user_data
)
51 char *filename
= gtk_file_chooser_get_filename(chooser
);
52 printf("Open %s\n", filename
);
55 gtk_widget_hide(GTK_WIDGET(chooser
));
58 void waterfall_expose_spectrogram(GtkWidget
*widget
,
59 GdkEventExpose
*event
,
62 GtkDrawingArea
*spectrogram_drawingarea
= GTK_DRAWING_AREA(widget
);
63 struct waterfall_context
*waterfall
= user_data
;
66 if (!waterfall
->resized
) {
67 double part1_size
, part2_size
;
68 int resolution
= waterfall
->zoom
.f_high
- waterfall
->zoom
.f_low
+ 1;
69 double scale_x
= (double) waterfall
->size
.width
/ waterfall
->n_slices
;
70 double scale_y
= (double) waterfall
->size
.height
/ resolution
;
71 double offset_y
= -waterfall
->zoom
.f_low
* scale_y
;
72 int row
, rowstride
, channels
;
75 part1_size
= (double) waterfall
->subpixbufs
.part1_size
/ waterfall
->n_slices
* waterfall
->size
.width
;
76 part2_size
= waterfall
->size
.width
- (int) part1_size
;
77 printf("Expose parts %g (%d),%g (%d)\n",
78 part1_size
, waterfall
->subpixbufs
.part1_size
,
79 part2_size
, waterfall
->subpixbufs
.part2_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 printf("Scale (%g) part2 to width %d at 0\n",
86 scale_x
, (int) part2_size
);
87 gdk_pixbuf_scale(waterfall
->subpixbufs
.part2
, waterfall
->resized
,
88 0, 0, (int) part2_size
, waterfall
->size
.height
,
89 0, offset_y
, scale_x
, scale_y
,
92 if (waterfall
->subpixbufs
.part1
) {
93 /* Then render the more recent slices. */
94 printf("Scale (%g) part1 to width %d at %d\n",
95 scale_x
, (int) part1_size
, (int) part2_size
);
96 gdk_pixbuf_scale(waterfall
->subpixbufs
.part1
, waterfall
->resized
,
97 (int) part2_size
, 0, (int) part1_size
, waterfall
->size
.height
,
98 part2_size
, offset_y
, scale_x
, scale_y
,
101 pixels
= gdk_pixbuf_get_pixels(waterfall
->resized
);
102 rowstride
= gdk_pixbuf_get_rowstride(waterfall
->resized
);
103 channels
= gdk_pixbuf_get_n_channels(waterfall
->resized
);
104 /* Draw a narrow marker to represent the border between part1 and part2. */
105 for (row
= 0; row
< waterfall
->size
.height
; row
++) {
106 pixels
[row
*rowstride
+ (int) part2_size
* channels
+ 1] = 192;
110 cr
= gdk_cairo_create(spectrogram_drawingarea
->widget
.window
);
112 gdk_cairo_set_source_pixbuf(cr
, waterfall
->resized
, 0, 0);
118 void waterfall_configure_spectrogram(GtkWidget
*widget
,
119 GdkEventConfigure
*event
,
122 struct waterfall_context
*waterfall
= user_data
;
124 printf("configure %d %d,%d+%d,%d send_event=%d on window %p\n",
125 event
->type
, event
->x
, event
->y
, event
->width
, event
->height
, event
->send_event
, event
->window
);
127 waterfall
->size
.width
= event
->width
;
128 waterfall
->size
.height
= event
->height
;
130 invalidate_spectrogram(waterfall
);
133 gboolean
waterfall_key_press(GtkWidget
*widget
,
137 struct waterfall_context
*waterfall
= user_data
;
138 int resolution
= waterfall
->zoom
.f_high
- waterfall
->zoom
.f_low
+ 1;
139 int headroom
= waterfall
->integration_samples
/2 - waterfall
->zoom
.f_high
;
140 gboolean zoom_changed
= FALSE
;
142 printf("key press\n");
143 switch (event
->keyval
) {
146 waterfall
->zoom
.f_low
-= MIN(waterfall
->zoom
.f_low
, resolution
/ 2);
147 waterfall
->zoom
.f_high
-= MIN(waterfall
->zoom
.f_low
, resolution
/ 2);
152 waterfall
->zoom
.f_low
+= MIN(headroom
, resolution
/ 2);
153 waterfall
->zoom
.f_high
+= MIN(headroom
, resolution
/ 2);
158 waterfall
->zoom
.f_low
+= resolution
/ 4;
159 waterfall
->zoom
.f_high
-= resolution
/ 4;
164 waterfall
->zoom
.f_low
-= MIN(waterfall
->zoom
.f_low
, resolution
/ 4);
165 waterfall
->zoom
.f_high
+= MIN(headroom
, resolution
/ 4);
171 invalidate_spectrogram(waterfall
);
177 void connect_signal(GtkBuilder
*builder
,
179 const gchar
*signal_name
,
180 const gchar
*handler_name
,
181 GObject
*connect_object
,
189 { "gtk_main_quit", G_CALLBACK(>k_main_quit
) },
190 { "gtk_widget_hide_on_delete", G_CALLBACK(>k_widget_hide_on_delete
) },
191 { "gtk_widget_show", G_CALLBACK(>k_widget_show
) },
192 { "waterfall_open_output", G_CALLBACK(&waterfall_open_output
) },
197 for (i
= 0; handlers
[i
].name
; i
++) {
198 if (!strcmp(handlers
[i
].name
, handler_name
)) {
199 GCallback handler
= handlers
[i
].fn
;
200 g_signal_connect_object(object
, signal_name
, handler
,
201 connect_object
, flags
);
206 /* TODO: Connect to some error-spewing function? */
209 void invalidate_spectrogram(struct waterfall_context
*waterfall
)
211 GdkRegion
*visible_region
;
213 /* Just invalidate everything. */
214 if (waterfall
->resized
) {
215 g_object_unref(G_OBJECT(waterfall
->resized
));
216 waterfall
->resized
= NULL
;
218 visible_region
= gdk_drawable_get_visible_region(waterfall
->drawingarea
->widget
.window
);
219 gdk_window_invalidate_region(waterfall
->drawingarea
->widget
.window
,
222 gdk_region_destroy(visible_region
);
225 void split_pixbuf(struct waterfall_context
*waterfall
)
227 int max_resolution
= waterfall
->integration_samples
/2 + 1;
229 if (waterfall
->subpixbufs
.part1
) {
230 g_object_unref(G_OBJECT(waterfall
->subpixbufs
.part1
));
232 if (waterfall
->subpixbufs
.part2
) {
233 g_object_unref(G_OBJECT(waterfall
->subpixbufs
.part2
));
236 waterfall
->subpixbufs
.part1_size
= waterfall
->slice
% waterfall
->n_slices
;
237 waterfall
->subpixbufs
.part2_size
= waterfall
->n_slices
- waterfall
->subpixbufs
.part1_size
;
238 printf("Split %d,%d\n", waterfall
->subpixbufs
.part1_size
, waterfall
->subpixbufs
.part2_size
);
239 if (waterfall
->subpixbufs
.part1_size
) {
240 waterfall
->subpixbufs
.part1
= gdk_pixbuf_new_subpixbuf(waterfall
->original
,
242 waterfall
->subpixbufs
.part1_size
, max_resolution
);
244 waterfall
->subpixbufs
.part1
= NULL
;
246 if (waterfall
->subpixbufs
.part2_size
) {
247 waterfall
->subpixbufs
.part2
= gdk_pixbuf_new_subpixbuf(waterfall
->original
,
248 waterfall
->subpixbufs
.part1_size
, 0,
249 waterfall
->subpixbufs
.part2_size
, max_resolution
);
251 waterfall
->subpixbufs
.part2
= NULL
;
255 gboolean
waterfall_input(GIOChannel
*source
,
256 GIOCondition condition
,
259 struct waterfall_context
*waterfall
= userdata
;
260 double normalization
= log10(waterfall
->integration_samples
* 32768);
265 switch (g_io_channel_read_chars(source
,
267 waterfall
->buf_size
- waterfall
->buf_index
,
270 case G_IO_STATUS_NORMAL
:
271 waterfall
->buf_index
+= n
;
273 case G_IO_STATUS_AGAIN
:
274 printf("Try again later\n");
277 case G_IO_STATUS_EOF
:
278 printf("End of input\n");
281 case G_IO_STATUS_ERROR
:
282 printf("Error - %s\n", err
->message
);
290 while (waterfall
->buf_index
>= waterfall
->integration_samples
*SAMPLE_SIZE
) {
291 int i
, row
, rowstride
, channels
, modslice
;
292 int resolution
= waterfall
->integration_samples
/2 + 1;
295 /* Analyze the spectrum. */
296 printf("Integrate %d %zu %d (%d)\n", batch_size
, waterfall
->buf_index
,
297 waterfall
->slice
, waterfall
->slice
% waterfall
->n_slices
);
298 for (i
= 0; i
< waterfall
->integration_samples
; i
++) {
300 memcpy(&x
, waterfall
->buf
+ i
*SAMPLE_SIZE
, sizeof (x
));
301 waterfall
->fft
.samples
[i
] = x
;
303 fftw_execute(waterfall
->fft
.p
);
305 /* Draw a new slice. */
306 modslice
= waterfall
->slice
% waterfall
->n_slices
;
307 pixels
= gdk_pixbuf_get_pixels(waterfall
->original
);
308 rowstride
= gdk_pixbuf_get_rowstride(waterfall
->original
);
309 channels
= gdk_pixbuf_get_n_channels(waterfall
->original
);
310 for (row
= 0; row
< resolution
; row
++) {
311 double signal_i
= waterfall
->fft
.spectrum
[row
][0], signal_q
= waterfall
->fft
.spectrum
[row
][1];
312 /* XXX Give invsqrt a chance to happen. */
313 double power
= -log10(1.0 / hypot(signal_i
, signal_q
)) - normalization
;
314 double z
= (power
+ 5) * 200;
315 if (row
> 50 && row
< 60) {
316 printf("Power = %g (%g)\n", power
, z
);
318 /* Drawing to the original-size pixbuf, resampling happens in the expose event. */
319 pixels
[row
*rowstride
+ modslice
* channels
+ 0] = z
>= 256 ? z
- 256 : 0;
320 pixels
[row
*rowstride
+ modslice
* channels
+ 1] = z
>= 256 ? 512 - z
: z
;
321 pixels
[row
*rowstride
+ modslice
* channels
+ 2] = z
< 256 ? 255 - z
: 0;
326 memmove(waterfall
->buf
,
327 waterfall
->buf
+ waterfall
->integration_samples
*SAMPLE_SIZE
,
328 waterfall
->buf_size
- waterfall
->integration_samples
*SAMPLE_SIZE
);
329 waterfall
->buf_index
-= waterfall
->integration_samples
*SAMPLE_SIZE
;
333 /* Scroll the waterfall. */
334 split_pixbuf(waterfall
);
335 invalidate_spectrogram(waterfall
);
337 if (batch_size
> 2) {
338 printf("I can't keep up (%d)\n", batch_size
);
348 int main(int argc
, char *argv
[])
350 GtkBuilder
*gtk_builder
;
351 GtkWindow
*waterfall_window
;
352 GIOChannel
*stdin_channel
;
355 struct waterfall_context waterfall
= {
357 .integration_samples
= INTEGRATION_SAMPLES
,
362 .f_high
= INTEGRATION_SAMPLES
/2,
366 gtk_init(&argc
, &argv
);
368 gtk_builder
= gtk_builder_new();
369 gtk_builder_add_from_file(gtk_builder
, "waterfall.glade", NULL
);
370 gtk_builder_connect_signals_full(gtk_builder
, &connect_signal
, NULL
);
372 /* Give signal handlers a means of accessing gtk_builder. */
373 g_object_set(gtk_builder_get_object(gtk_builder
,
375 "user-data", gtk_builder
,
378 waterfall_window
= GTK_WINDOW(gtk_builder_get_object(gtk_builder
, "waterfall_window"));
379 waterfall
.drawingarea
= GTK_DRAWING_AREA(gtk_builder_get_object(gtk_builder
, "image_detail_drawingarea"));
381 g_signal_connect(G_OBJECT(waterfall
.drawingarea
), "expose-event",
382 G_CALLBACK(&waterfall_expose_spectrogram
), &waterfall
);
383 g_signal_connect(G_OBJECT(waterfall
.drawingarea
), "configure-event",
384 G_CALLBACK(&waterfall_configure_spectrogram
), &waterfall
);
385 g_signal_connect(G_OBJECT(waterfall_window
), "key-press-event",
386 G_CALLBACK(&waterfall_key_press
), &waterfall
);
388 /* Don't block on stdin. */
389 stdin_flags
= fcntl(0, F_GETFL
, 0);
390 fcntl(0, F_SETFL
, stdin_flags
| O_NONBLOCK
);
392 stdin_channel
= g_io_channel_unix_new(0);
393 g_io_channel_set_encoding(stdin_channel
, NULL
, &err
);
394 g_io_channel_set_buffer_size(stdin_channel
, BUF_SAMPLES
*SAMPLE_SIZE
);
395 waterfall
.buf
= malloc(BUF_SAMPLES
*SAMPLE_SIZE
);
396 waterfall
.buf_size
= INTEGRATION_SAMPLES
*SAMPLE_SIZE
;
397 g_io_add_watch(stdin_channel
, G_IO_IN
, &waterfall_input
, &waterfall
);
399 waterfall
.original
= gdk_pixbuf_new(GDK_COLORSPACE_RGB
, FALSE
, 8, waterfall
.n_slices
, INTEGRATION_SAMPLES
/2 + 1);
400 split_pixbuf(&waterfall
);
402 waterfall
.fft
.samples
= fftw_malloc(sizeof (*waterfall
.fft
.samples
) * INTEGRATION_SAMPLES
);
403 waterfall
.fft
.spectrum
= fftw_malloc(sizeof (*waterfall
.fft
.spectrum
) * (INTEGRATION_SAMPLES
/2 + 1));
404 waterfall
.fft
.p
= fftw_plan_dft_r2c_1d(INTEGRATION_SAMPLES
,
405 waterfall
.fft
.samples
, waterfall
.fft
.spectrum
,
408 gtk_widget_show(GTK_WIDGET(waterfall_window
));
410 invalidate_spectrogram(&waterfall
);
414 fftw_destroy_plan(waterfall
.fft
.p
);
415 fftw_free(waterfall
.fft
.spectrum
);
416 fftw_free(waterfall
.fft
.samples
);