1 /*
2  * lingot, a musical instrument tuner.
3  *
4  * Copyright (C) 2004-2018  Iban Cereijo.
5  * Copyright (C) 2004-2008  Jairo Chapela.
6 
7  *
8  * This file is part of lingot.
9  *
10  * lingot is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * lingot is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with lingot; if not, write to the Free Software
22  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
23  */
24 
25 #include <stdio.h>
26 #include <math.h>
27 #include <unistd.h>
28 #include <signal.h>
29 #include <string.h>
30 #include <errno.h>
31 
32 #include "lingot-defs.h"
33 
34 #include "lingot-config.h"
35 #include "lingot-gui-mainframe.h"
36 #include "lingot-gui-config-dialog.h"
37 #include "lingot-gauge.h"
38 #include "lingot-i18n.h"
39 #include "lingot-io-config.h"
40 #include "lingot-msg.h"
41 
42 static void lingot_gui_mainframe_draw_gauge_background(cairo_t *cr, const LingotMainFrame* frame);
43 static void lingot_gui_mainframe_draw_spectrum_background(cairo_t *cr, const LingotMainFrame* frame);
44 void lingot_gui_mainframe_draw_gauge(cairo_t *cr, const LingotMainFrame*);
45 void lingot_gui_mainframe_draw_spectrum(cairo_t *cr, const LingotMainFrame*);
46 void lingot_gui_mainframe_draw_labels(const LingotMainFrame*);
47 
48 // sizes
49 
50 static int gauge_size_x = 0;
51 static int gauge_size_y = 0;
52 
53 static int spectrum_size_x = 0;
54 static int spectrum_size_y = 0;
55 
56 static FLT spectrum_bottom_margin;
57 static FLT spectrum_top_margin;
58 static FLT spectrum_left_margin;
59 static FLT spectrum_right_margin;
60 
61 static FLT spectrum_inner_x;
62 static FLT spectrum_inner_y;
63 
64 static const FLT spectrum_min_db = 0; // TODO
65 static const FLT spectrum_max_db = 52;
66 static FLT spectrum_db_density = 0;
67 
68 static int labelsbox_size_x = 0;
69 static int labelsbox_size_y = 0;
70 
71 static gchar* filechooser_config_last_folder = NULL;
72 
73 static const gdouble aspect_ratio_spectrum_visible = 1.14;
74 static const gdouble aspect_ratio_spectrum_invisible = 2.07;
75 
76 // TODO: keep here?
77 static int closest_note_index = 0;
78 static FLT frequency = 0.0;
79 
lingot_gui_mainframe_callback_redraw_gauge(GtkWidget * w,cairo_t * cr,const LingotMainFrame * data)80 void lingot_gui_mainframe_callback_redraw_gauge(GtkWidget *w, cairo_t *cr, const LingotMainFrame* data) {
81 	(void)w;                //  Unused parameter.
82 	lingot_gui_mainframe_draw_gauge(cr, data);
83 }
84 
lingot_gui_mainframe_callback_redraw_spectrum(GtkWidget * w,cairo_t * cr,const LingotMainFrame * frame)85 void lingot_gui_mainframe_callback_redraw_spectrum(GtkWidget* w, cairo_t *cr, const LingotMainFrame* frame) {
86 	(void)w;                //  Unused parameter.
87 	lingot_gui_mainframe_draw_spectrum(cr, frame);
88 }
89 
lingot_gui_mainframe_callback_destroy(GtkWidget * w,LingotMainFrame * frame)90 void lingot_gui_mainframe_callback_destroy(GtkWidget* w, LingotMainFrame* frame) {
91 	(void)w;                //  Unused parameter.
92 	g_source_remove(frame->visualization_timer_uid);
93 	g_source_remove(frame->freq_computation_timer_uid);
94 	g_source_remove(frame->gauge_computation_uid);
95 	gtk_main_quit();
96 }
97 
lingot_gui_mainframe_callback_about(GtkWidget * w,LingotMainFrame * frame)98 void lingot_gui_mainframe_callback_about(GtkWidget* w, LingotMainFrame* frame) {
99 	(void)w;                //  Unused parameter.
100 	(void)frame;            //  Unused parameter.
101 	static const gchar* authors[] = {
102 			"Iban Cereijo <ibancg@gmail.com>",
103 			"Jairo Chapela <jairochapela@gmail.com>",
104 			NULL };
105 
106 	char buff[512];
107 	snprintf(buff, sizeof(buff), "Matthew Blissett (%s)", _("Logo design"));
108 	const gchar* artists[] = { buff, NULL };
109 
110 	GError *error = NULL;
111 	GtkIconTheme *icon_theme = NULL;
112 	GdkPixbuf *pixbuf = NULL;
113 
114 	// we use the property "logo" instead of "logo-icon-name", so we can specify
115 	// here at what size we want to scale the icon in this dialog
116 	icon_theme = gtk_icon_theme_get_default ();
117 	pixbuf = gtk_icon_theme_load_icon (icon_theme,
118 			"org.nongnu.lingot", // icon name
119 			80, // icon size
120 			0,  // flags
121 			&error);
122 
123 	if (error) {
124 		g_warning("Couldn’t load icon: %s", error->message);
125 		g_error_free(error);
126 	}
127 
128 	gtk_show_about_dialog(NULL,
129 			"name", "Lingot",
130 			"version", VERSION,
131 			"copyright", "\xC2\xA9 2004-2018 Iban Cereijo\n\xC2\xA9 2004-2018 Jairo Chapela",
132 			"comments", _("Accurate and easy to use musical instrument tuner"),
133 			"authors", authors,
134 			"artists", artists,
135 			"website-label", "https://www.nongnu.org/lingot/",
136 			"website", "https://www.nongnu.org/lingot/",
137 			"license-type", GTK_LICENSE_GPL_2_0,
138 			"translator-credits", _("translator-credits"),
139 			//"logo-icon-name", "org.nongnu.lingot",
140 			"logo", pixbuf,
141 			NULL);
142 
143 	if (pixbuf) {
144 		g_object_unref(pixbuf);
145 	}
146 }
147 
lingot_gui_mainframe_callback_view_spectrum(GtkWidget * w,LingotMainFrame * frame)148 void lingot_gui_mainframe_callback_view_spectrum(GtkWidget* w, LingotMainFrame* frame) {
149 	(void)w;                //  Unused parameter.
150 	gboolean visible = gtk_check_menu_item_get_active(
151 			GTK_CHECK_MENU_ITEM(frame->view_spectrum_item));
152 
153 	GtkAllocation alloc;
154 	gtk_widget_get_allocation(frame->win, &alloc);
155 	GdkGeometry hints;
156 	gdouble aspect_ratio =
157 			visible ?
158 					aspect_ratio_spectrum_visible :
159 					aspect_ratio_spectrum_invisible;
160 	hints.min_aspect = aspect_ratio;
161 	hints.max_aspect = aspect_ratio;
162 	gtk_window_set_geometry_hints(GTK_WINDOW(frame->win), frame->win, &hints,
163 			GDK_HINT_ASPECT);
164 
165 	gtk_widget_set_visible(frame->spectrum_frame, visible);
166 }
167 
lingot_gui_mainframe_callback_config_dialog(GtkWidget * w,LingotMainFrame * frame)168 void lingot_gui_mainframe_callback_config_dialog(GtkWidget* w,
169 		LingotMainFrame* frame) {
170 	(void)w;                //  Unused parameter.
171 	lingot_gui_config_dialog_show(frame, NULL);
172 }
173 
174 /* timeout for gauge and labels visualization */
lingot_gui_mainframe_callback_tout_visualization(gpointer data)175 gboolean lingot_gui_mainframe_callback_tout_visualization(gpointer data) {
176 	unsigned int period;
177 
178 	LingotMainFrame* frame = (LingotMainFrame*) data;
179 
180 	period = 1000 / frame->conf.visualization_rate;
181 	frame->visualization_timer_uid = g_timeout_add(period,
182 			lingot_gui_mainframe_callback_tout_visualization, frame);
183 
184 	gtk_widget_queue_draw(frame->gauge_area);
185 
186 	return 0;
187 }
188 
189 /* timeout for spectrum computation and display */
lingot_gui_mainframe_callback_tout_spectrum_computation_display(gpointer data)190 gboolean lingot_gui_mainframe_callback_tout_spectrum_computation_display(
191 		gpointer data) {
192 	unsigned int period;
193 
194 	LingotMainFrame* frame = (LingotMainFrame*) data;
195 
196 	period = 1000 / frame->conf.calculation_rate;
197 	frame->freq_computation_timer_uid = g_timeout_add(period,
198 			lingot_gui_mainframe_callback_tout_spectrum_computation_display,
199 			frame);
200 
201 	gtk_widget_queue_draw(frame->spectrum_area);
202 	lingot_gui_mainframe_draw_labels(frame);
203 
204 	return 0;
205 }
206 
207 /* timeout for a new gauge position computation */
lingot_gui_mainframe_callback_gauge_computation(gpointer data)208 gboolean lingot_gui_mainframe_callback_gauge_computation(gpointer data) {
209 	unsigned int period;
210 	LingotMainFrame* frame = (LingotMainFrame*) data;
211 
212 	period = 1000 / GAUGE_RATE;
213 	frame->gauge_computation_uid = g_timeout_add(period,
214 			lingot_gui_mainframe_callback_gauge_computation, frame);
215 
216 	// ignore continuous component
217 	if (!frame->core.running || isnan(frame->core.freq)
218 			|| (frame->core.freq <= frame->conf.internal_min_frequency)) {
219 		frequency = 0.0;
220 		lingot_gauge_compute(&frame->gauge, frame->conf.gauge_rest_value);
221 	} else {
222 		FLT error_cents; // do not use, unfiltered
223 		frequency = lingot_filter_filter_sample(&frame->freq_filter,
224 				frame->core.freq);
225 		closest_note_index = lingot_config_scale_get_closest_note_index(
226 				&frame->conf.scale, frame->core.freq,
227 				frame->conf.root_frequency_error, &error_cents);
228 		if (!isnan(error_cents)) {
229 			lingot_gauge_compute(&frame->gauge, error_cents);
230 		}
231 	}
232 
233 	return 0;
234 }
235 
236 /* timeout for dispatching the error queue */
lingot_gui_mainframe_callback_error_dispatcher(gpointer data)237 gboolean lingot_gui_mainframe_callback_error_dispatcher(gpointer data) {
238 	unsigned int period;
239 	GtkWidget* message_dialog;
240 	LingotMainFrame* frame = (LingotMainFrame*) data;
241 
242 	char* error_message = NULL;
243 	message_type_t message_type;
244 	int error_code;
245 	int more_messages;
246 
247 	do {
248 		more_messages = lingot_msg_get(&error_message, &message_type, &error_code);
249 
250 		if (more_messages) {
251 			GtkWindow* parent =
252 					GTK_WINDOW(
253 							(frame->config_dialog != NULL) ? frame->config_dialog->win : frame->win);
254 			GtkButtonsType buttonsType;
255 
256 			char message[2000];
257 			char* message_pointer = message;
258 
259 			message_pointer += snprintf(message_pointer,
260 					(message - message_pointer) + sizeof(message), "%s",
261 					error_message);
262 
263 			if (error_code == EBUSY) {
264 				message_pointer +=
265 						snprintf(message_pointer,
266 								(message - message_pointer) + sizeof(message),
267 								"\n\n%s",
268 								_(
269 										"Please check that there are not other processes locking the requested device. Also, consider that some audio servers can sometimes hold the resources for a few seconds since the last time they were used. In such a case, you can try again."));
270 			}
271 
272 			if ((message_type == ERROR) && !frame->core.running) {
273 				buttonsType = GTK_BUTTONS_OK;
274 				message_pointer +=
275 						snprintf(message_pointer,
276 								(message - message_pointer) + sizeof(message),
277 								"\n\n%s",
278 								_(
279 										"The core is not running, you must check your configuration."));
280 			} else {
281 				buttonsType = GTK_BUTTONS_OK;
282 			}
283 
284 			message_dialog = gtk_message_dialog_new(parent,
285 					GTK_DIALOG_DESTROY_WITH_PARENT,
286 					(message_type == ERROR) ? GTK_MESSAGE_ERROR :
287 							((message_type == WARNING) ? GTK_MESSAGE_WARNING : GTK_MESSAGE_INFO),
288 							buttonsType, "%s", message);
289 
290 			gtk_window_set_title(GTK_WINDOW(message_dialog),
291 					(message_type == ERROR) ? _("Error") :
292 							((message_type == WARNING) ? _("Warning") : _("Info")));
293 			gtk_window_set_icon(GTK_WINDOW(message_dialog),
294 					gtk_window_get_icon(GTK_WINDOW(frame->win)));
295 			gtk_dialog_run(GTK_DIALOG(message_dialog));
296 			gtk_widget_destroy(message_dialog);
297 			free(error_message);
298 
299 			//			if ((message_type == ERROR) && !frame->core.running) {
300 			//				lingot_gui_mainframe_callback_config_dialog(NULL, frame);
301 			//			}
302 
303 		}
304 	} while (more_messages);
305 
306 	period = 1000 / ERROR_DISPATCH_RATE;
307 	frame->error_dispatcher_uid = g_timeout_add(period,
308 			lingot_gui_mainframe_callback_error_dispatcher, frame);
309 
310 	return 0;
311 }
312 
lingot_gui_mainframe_callback_open_config(gpointer data,LingotMainFrame * frame)313 void lingot_gui_mainframe_callback_open_config(gpointer data,
314 		LingotMainFrame* frame) {
315 	(void)data;             //  Unused parameter.
316 	GtkWidget * dialog = gtk_file_chooser_dialog_new(
317 			_("Open Configuration File"), GTK_WINDOW(frame->win),
318 			GTK_FILE_CHOOSER_ACTION_OPEN, "_Cancel", GTK_RESPONSE_CANCEL,
319 			"_Open", GTK_RESPONSE_ACCEPT, NULL);
320 	GtkFileFilter *filefilter;
321 	char config_used = 0;
322 	LingotConfig config;
323 	filefilter = gtk_file_filter_new();
324 
325 	gtk_file_filter_set_name(filefilter,
326 			(const gchar *) _("Lingot configuration files"));
327 	gtk_file_filter_add_pattern(filefilter, "*.conf");
328 	gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filefilter);
329 	gtk_file_chooser_set_show_hidden(GTK_FILE_CHOOSER(dialog), TRUE);
330 
331 	if (filechooser_config_last_folder != NULL) {
332 		gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
333 				filechooser_config_last_folder);
334 	}
335 
336 	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
337 		char *filename;
338 		filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
339 		free(filechooser_config_last_folder);
340 		filechooser_config_last_folder = strdup(
341 				gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(dialog)));
342 		lingot_config_new(&config);
343 		lingot_io_config_load(&config, filename);
344 		config_used = 1;
345 		g_free(filename);
346 	}
347 	gtk_widget_destroy(dialog);
348 	//g_free(filefilter);
349 
350 	if (config_used) {
351 		lingot_gui_config_dialog_show(frame, &config);
352 	}
353 }
354 
lingot_gui_mainframe_callback_save_config(gpointer data,LingotMainFrame * frame)355 void lingot_gui_mainframe_callback_save_config(gpointer data, LingotMainFrame* frame) {
356 	(void)data;             //  Unused parameter.
357 	GtkWidget *dialog = gtk_file_chooser_dialog_new(
358 			_("Save Configuration File"), GTK_WINDOW(frame->win),
359 			GTK_FILE_CHOOSER_ACTION_SAVE, "_Cancel", GTK_RESPONSE_CANCEL,
360 			"_Save", GTK_RESPONSE_ACCEPT, NULL);
361 	gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog),
362 			TRUE);
363 
364 	gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), _("untitled.conf"));
365 	GtkFileFilter* filefilter = gtk_file_filter_new();
366 
367 	gtk_file_filter_set_name(filefilter,
368 			(const gchar *) _("Lingot configuration files"));
369 	gtk_file_filter_add_pattern(filefilter, "*.conf");
370 	gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filefilter);
371 	gtk_file_chooser_set_show_hidden(GTK_FILE_CHOOSER(dialog), TRUE);
372 
373 	if (filechooser_config_last_folder != NULL) {
374 		gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
375 				filechooser_config_last_folder);
376 	}
377 
378 	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
379 		char *filename;
380 		filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
381 		if (filechooser_config_last_folder != NULL)
382 			free(filechooser_config_last_folder);
383 		filechooser_config_last_folder = strdup(
384 				gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(dialog)));
385 		lingot_io_config_save(&frame->conf, filename);
386 		g_free(filename);
387 	}
388 	gtk_widget_destroy(dialog);
389 }
390 
lingot_gui_mainframe_color(GdkColor * color,int red,int green,int blue)391 void lingot_gui_mainframe_color(GdkColor* color, int red, int green, int blue) {
392 	color->red = red;
393 	color->green = green;
394 	color->blue = blue;
395 }
396 
lingot_gui_mainframe_callback_window_resize(GtkWidget * widget,GtkAllocation * allocation,void * data)397 void lingot_gui_mainframe_callback_window_resize(GtkWidget *widget,
398 		GtkAllocation *allocation, void *data) {
399 	(void)widget;           //  Unused parameter.
400 	(void)allocation;       //  Unused parameter.
401 
402 	const LingotMainFrame* frame = (LingotMainFrame*) data;
403 
404 	GtkAllocation req;
405 	gtk_widget_get_allocation(frame->gauge_area, &req);
406 
407 	if ((req.width != gauge_size_x) || (req.height != gauge_size_y)) {
408 		gauge_size_x = req.width;
409 		gauge_size_y = req.height;
410 	}
411 
412 	gtk_widget_get_allocation(frame->spectrum_area, &req);
413 
414 	if ((req.width != spectrum_size_x) || (req.height != spectrum_size_y)) {
415 		spectrum_size_x = req.width;
416 		spectrum_size_y = req.height;
417 	}
418 
419 	gtk_widget_get_allocation(frame->labelsbox, &req);
420 
421 	labelsbox_size_x = req.width;
422 	labelsbox_size_y = req.height;
423 }
424 
lingot_gui_mainframe_create(int argc,char * argv[])425 void lingot_gui_mainframe_create(int argc, char *argv[]) {
426 
427 	LingotMainFrame* frame;
428 
429 	if (filechooser_config_last_folder == NULL) {
430 		char buff[1000];
431 		snprintf(buff, sizeof(buff), "%s/%s", getenv("HOME"), CONFIG_DIR_NAME);
432 		filechooser_config_last_folder = strdup(buff);
433 	}
434 
435 	frame = malloc(sizeof(LingotMainFrame));
436 
437 	frame->config_dialog = NULL;
438 
439 	LingotConfig* const conf = &frame->conf;
440 	lingot_config_new(conf);
441 	lingot_io_config_load(conf, CONFIG_FILE_NAME);
442 
443 	lingot_gauge_new(&frame->gauge, conf->gauge_rest_value); // gauge in rest situation
444 
445 	// ----- FREQUENCY FILTER CONFIGURATION ------
446 
447 	// low pass IIR filter.
448 	FLT freq_filter_a[] = { 1.0, -0.5 };
449 	FLT freq_filter_b[] = { 0.5 };
450 
451 	lingot_filter_new(&frame->freq_filter, 1, 0, freq_filter_a, freq_filter_b);
452 
453 	// ---------------------------------------------------
454 
455 	gtk_init(&argc, &argv);
456 	//	gtk_set_locale();
457 
458 	GtkBuilder* builder = gtk_builder_new();
459 
460 	gtk_builder_add_from_resource(builder, "/org/nongnu/lingot/lingot-gui-mainframe.glade", NULL);
461 
462 	frame->win = GTK_WIDGET(gtk_builder_get_object(builder, "window1"));
463 
464 	gtk_window_set_default_icon_name("org.nongnu.lingot");
465 	gtk_window_set_icon_name(GTK_WINDOW(frame->win), "org.nongnu.lingot");
466 
467 	frame->gauge_area = GTK_WIDGET(
468 			gtk_builder_get_object(builder, "gauge_area"));
469 	frame->spectrum_area = GTK_WIDGET(
470 			gtk_builder_get_object(builder, "spectrum_area"));
471 
472 	frame->freq_label = GTK_WIDGET(
473 			gtk_builder_get_object(builder, "freq_label"));
474 	frame->tone_label = GTK_WIDGET(
475 			gtk_builder_get_object(builder, "tone_label"));
476 	frame->error_label = GTK_WIDGET(
477 			gtk_builder_get_object(builder, "error_label"));
478 
479 	frame->spectrum_frame = GTK_WIDGET(
480 			gtk_builder_get_object(builder, "spectrum_frame"));
481 	frame->view_spectrum_item = GTK_WIDGET(
482 			gtk_builder_get_object(builder, "spectrum_item"));
483 	frame->labelsbox = GTK_WIDGET(gtk_builder_get_object(builder, "labelsbox"));
484 
485 	gtk_check_menu_item_set_active(
486 			GTK_CHECK_MENU_ITEM(frame->view_spectrum_item), TRUE);
487 
488 	// show all
489 	gtk_widget_show_all(frame->win);
490 
491 	GtkAllocation alloc;
492 	gtk_widget_get_allocation(frame->win, &alloc);
493 	GdkGeometry hints;
494 	gdouble aspect_ratio = aspect_ratio_spectrum_visible;
495 	hints.min_aspect = aspect_ratio;
496 	hints.max_aspect = aspect_ratio;
497 	gtk_window_set_geometry_hints(GTK_WINDOW(frame->win), frame->win, &hints,
498 			GDK_HINT_ASPECT);
499 
500 	// GTK signals
501 	g_signal_connect(gtk_builder_get_object(builder, "preferences_item"),
502 			"activate", G_CALLBACK(lingot_gui_mainframe_callback_config_dialog),
503 			frame);
504 	g_signal_connect(gtk_builder_get_object(builder, "quit_item"), "activate",
505 			G_CALLBACK(lingot_gui_mainframe_callback_destroy), frame);
506 	g_signal_connect(gtk_builder_get_object(builder, "about_item"), "activate",
507 			G_CALLBACK(lingot_gui_mainframe_callback_about), frame);
508 	g_signal_connect(gtk_builder_get_object(builder, "spectrum_item"),
509 			"activate", G_CALLBACK(lingot_gui_mainframe_callback_view_spectrum),
510 			frame);
511 	g_signal_connect(gtk_builder_get_object(builder, "open_config_item"),
512 			"activate", G_CALLBACK(lingot_gui_mainframe_callback_open_config),
513 			frame);
514 	g_signal_connect(gtk_builder_get_object(builder, "save_config_item"),
515 			"activate", G_CALLBACK(lingot_gui_mainframe_callback_save_config),
516 			frame);
517 
518 	g_signal_connect(frame->gauge_area, "draw",
519 			G_CALLBACK(lingot_gui_mainframe_callback_redraw_gauge), frame);
520 	g_signal_connect(frame->spectrum_area, "draw",
521 			G_CALLBACK(lingot_gui_mainframe_callback_redraw_spectrum), frame);
522 	g_signal_connect(frame->win, "destroy",
523 			G_CALLBACK(lingot_gui_mainframe_callback_destroy), frame);
524 
525 	// TODO: remove
526 	g_signal_connect(frame->win, "size-allocate",
527 			G_CALLBACK(lingot_gui_mainframe_callback_window_resize), frame);
528 
529 	GtkAccelGroup* accel_group = gtk_accel_group_new();
530 	gtk_widget_add_accelerator(
531 			GTK_WIDGET(gtk_builder_get_object(builder, "preferences_item")),
532 			"activate", accel_group, 'p', GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
533 	gtk_window_add_accel_group(GTK_WINDOW(frame->win), accel_group);
534 
535 	unsigned int period;
536 	period = 1000 / conf->visualization_rate;
537 	frame->visualization_timer_uid = g_timeout_add(period,
538 			lingot_gui_mainframe_callback_tout_visualization, frame);
539 
540 	period = 1000 / conf->calculation_rate;
541 	frame->freq_computation_timer_uid = g_timeout_add(period,
542 			lingot_gui_mainframe_callback_tout_spectrum_computation_display,
543 			frame);
544 
545 	period = 1000 / GAUGE_RATE;
546 	frame->gauge_computation_uid = g_timeout_add(period,
547 			lingot_gui_mainframe_callback_gauge_computation, frame);
548 
549 	period = 1000 / ERROR_DISPATCH_RATE;
550 	frame->error_dispatcher_uid = g_timeout_add(period,
551 			lingot_gui_mainframe_callback_error_dispatcher, frame);
552 
553 	lingot_core_new(&frame->core, conf);
554 	lingot_core_start(&frame->core);
555 
556 	g_object_unref(builder);
557 
558 	gtk_main();
559 }
560 
lingot_gui_mainframe_destroy(LingotMainFrame * frame)561 void lingot_gui_mainframe_destroy(LingotMainFrame* frame) {
562 
563 	lingot_core_stop(&frame->core);
564 	lingot_core_destroy(&frame->core);
565 
566 	lingot_gauge_destroy(&frame->gauge);
567 	lingot_filter_destroy(&frame->freq_filter);
568 	lingot_config_destroy(&frame->conf);
569 	if (frame->config_dialog) {
570 		lingot_gui_config_dialog_destroy(frame->config_dialog);
571 	}
572 
573 	free(frame);
574 }
575 
576 // ---------------------------------------------------------------------------
577 
lingot_gui_mainframe_cairo_set_source_argb(cairo_t * cr,unsigned int color)578 static void lingot_gui_mainframe_cairo_set_source_argb(cairo_t *cr,
579 		unsigned int color) {
580 	cairo_set_source_rgba(cr,
581 			0.00392156862745098 * ((color >> 16) & 0xff),
582 			0.00392156862745098 * ((color >> 8) & 0xff),
583 			0.00392156862745098 * (color & 0xff),
584 			1.0 - 0.00392156862745098 * ((color >> 24) & 0xff));
585 }
586 
587 typedef struct {
588 	FLT x;
589 	FLT y;
590 } point_t;
591 
lingot_gui_mainframe_draw_gauge_tic(cairo_t * cr,const point_t * gaugeCenter,double radius1,double radius2,double angle)592 static void lingot_gui_mainframe_draw_gauge_tic(cairo_t *cr,
593 		const point_t* gaugeCenter, double radius1, double radius2,
594 		double angle) {
595 	cairo_move_to(cr, gaugeCenter->x + radius1 * sin(angle),
596 			gaugeCenter->y - radius1 * cos(angle));
597 	cairo_rel_line_to(cr, (radius2 - radius1) * sin(angle),
598 			(radius1 - radius2) * cos(angle));
599 	cairo_stroke(cr);
600 }
601 
lingot_gui_mainframe_draw_gauge_background(cairo_t * cr,const LingotMainFrame * frame)602 static void lingot_gui_mainframe_draw_gauge_background(cairo_t *cr,
603 		const LingotMainFrame* frame) {
604 
605 	// normalized dimensions
606 	static const FLT gauge_gaugeCenterY = 0.94;
607 	static const FLT gauge_centsBarStroke = 0.025;
608 	static const FLT gauge_centsBarRadius = 0.75;
609 	static const FLT gauge_centsBarMajorTicRadius = 0.04;
610 	static const FLT gauge_centsBarMinorTicRadius = 0.03;
611 	static const FLT gauge_centsBarMajorTicStroke = 0.03;
612 	static const FLT gauge_centsBarMinorTicStroke = 0.01;
613 	static const FLT gauge_centsTextSize = 0.09;
614 	static const FLT gauge_frequencyBarStroke = 0.025;
615 	static const FLT gauge_frequencyBarRadius = 0.78;
616 	static const FLT gauge_frequencyBarMajorTicRadius = 0.04;
617 	static const FLT gauge_okBarStroke = 0.07;
618 	static const FLT gauge_okBarRadius = 0.48;
619 
620 	static const FLT overtureAngle = 65.0 * M_PI / 180.0;
621 
622 	// colors
623 	static const unsigned int gauge_centsBarColor = 0x333355;
624 	static const unsigned int gauge_frequencyBarColor = 0x555533;
625 	static const unsigned int gauge_okColor = 0x99dd99;
626 	static const unsigned int gauge_koColor = 0xddaaaa;
627 
628 	const int width = gauge_size_x;
629 	int height = gauge_size_y;
630 
631 	// dimensions applied to the current size
632 	point_t gaugeCenter = { .x = width / 2, .y = height * gauge_gaugeCenterY };
633 
634 	if (width < 1.6 * height) {
635 		height = width / 1.6;
636 		gaugeCenter.y = 0.5 * (gauge_size_y - height)
637 																				+ height * gauge_gaugeCenterY;
638 	}
639 
640 	const FLT centsBarRadius = height * gauge_centsBarRadius;
641 	const FLT centsBarStroke = height * gauge_centsBarStroke;
642 	const FLT centsBarMajorTicRadius = centsBarRadius
643 			- height * gauge_centsBarMajorTicRadius;
644 	const FLT centsBarMinorTicRadius = centsBarRadius
645 			- height * gauge_centsBarMinorTicRadius;
646 	const FLT centsBarMajorTicStroke = height * gauge_centsBarMajorTicStroke;
647 	const FLT centsBarMinorTicStroke = height * gauge_centsBarMinorTicStroke;
648 	const FLT centsTextSize = height * gauge_centsTextSize;
649 	const FLT frequencyBarRadius = height * gauge_frequencyBarRadius;
650 	const FLT frequencyBarMajorTicRadius = frequencyBarRadius
651 			+ height * gauge_frequencyBarMajorTicRadius;
652 	const FLT frequencyBarStroke = height * gauge_frequencyBarStroke;
653 	const FLT okBarRadius = height * gauge_okBarRadius;
654 	const FLT okBarStroke = height * gauge_okBarStroke;
655 
656 	cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
657 	cairo_save(cr);
658 	const GdkRectangle r = { .x = 0, .y = 0, .width = gauge_size_x, .height =
659 			gauge_size_y };
660 	gdk_cairo_rectangle(cr, &r);
661 	cairo_fill_preserve(cr);
662 	cairo_restore(cr);
663 	cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
664 	cairo_stroke(cr);
665 
666 	// draw ok/ko bar
667 	cairo_set_line_width(cr, okBarStroke);
668 	cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT);
669 	lingot_gui_mainframe_cairo_set_source_argb(cr, gauge_koColor);
670 	cairo_arc(cr, gaugeCenter.x, gaugeCenter.y, okBarRadius,
671 			-0.5 * M_PI - overtureAngle, -0.5 * M_PI + overtureAngle);
672 	cairo_stroke(cr);
673 	lingot_gui_mainframe_cairo_set_source_argb(cr, gauge_okColor);
674 	cairo_arc(cr, gaugeCenter.x, gaugeCenter.y, okBarRadius,
675 			-0.5 * M_PI - 0.1 * overtureAngle,
676 			-0.5 * M_PI + 0.1 * overtureAngle);
677 	cairo_stroke(cr);
678 
679 	// draw cents bar
680 	cairo_set_line_width(cr, centsBarStroke);
681 	lingot_gui_mainframe_cairo_set_source_argb(cr, gauge_centsBarColor);
682 	cairo_arc(cr, gaugeCenter.x, gaugeCenter.y, centsBarRadius,
683 			-0.5 * M_PI - 1.05 * overtureAngle,
684 			-0.5 * M_PI + 1.05 * overtureAngle);
685 	cairo_stroke(cr);
686 
687 	// cent tics
688 	const double maxOffsetRounded = frame->conf.scale.max_offset_rounded;
689 	static const int maxMinorDivisions = 20;
690 	double centsPerMinorDivision = maxOffsetRounded / maxMinorDivisions;
691 	const double base = pow(10.0, floor(log10(centsPerMinorDivision)));
692 	double normalizedCentsPerDivision = centsPerMinorDivision / base;
693 	if (normalizedCentsPerDivision >= 6.0) {
694 		normalizedCentsPerDivision = 10.0;
695 	} else if (normalizedCentsPerDivision >= 2.5) {
696 		normalizedCentsPerDivision = 5.0;
697 	} else if (normalizedCentsPerDivision >= 1.2) {
698 		normalizedCentsPerDivision = 2.0;
699 	} else {
700 		normalizedCentsPerDivision = 1.0;
701 	}
702 	centsPerMinorDivision = normalizedCentsPerDivision * base;
703 	const double centsPerMajorDivision = 5.0 * centsPerMinorDivision;
704 
705 	// minor tics
706 	cairo_set_line_width(cr, centsBarMinorTicStroke);
707 	int maxIndex = (int) floor(0.5 * maxOffsetRounded / centsPerMinorDivision);
708 	double angleStep = 2.0 * overtureAngle * centsPerMinorDivision
709 			/ maxOffsetRounded;
710 	int index;
711 	for (index = -maxIndex; index <= maxIndex; index++) {
712 		const double angle = index * angleStep;
713 		lingot_gui_mainframe_draw_gauge_tic(cr, &gaugeCenter,
714 				centsBarMinorTicRadius, centsBarRadius, angle);
715 	}
716 
717 	// major tics
718 	maxIndex = (int) floor(0.5 * maxOffsetRounded / centsPerMajorDivision);
719 	angleStep = 2.0 * overtureAngle * centsPerMajorDivision / maxOffsetRounded;
720 	cairo_set_line_width(cr, centsBarMajorTicStroke);
721 	for (index = -maxIndex; index <= maxIndex; index++) {
722 		double angle = index * angleStep;
723 		lingot_gui_mainframe_draw_gauge_tic(cr, &gaugeCenter,
724 				centsBarMajorTicRadius, centsBarRadius, angle);
725 	}
726 
727 	// cents text
728 	cairo_set_line_width(cr, 1.0);
729 	double oldAngle = 0.0;
730 
731 	cairo_save(cr);
732 
733 	static char buff[10];
734 
735 	cairo_text_extents_t te;
736 	cairo_select_font_face(cr, "Helvetica", CAIRO_FONT_SLANT_NORMAL,
737 			CAIRO_FONT_WEIGHT_NORMAL);
738 	cairo_set_font_size(cr, centsTextSize);
739 
740 	sprintf(buff, "%s", "cent");
741 	cairo_text_extents(cr, buff, &te);
742 	cairo_move_to(cr, gaugeCenter.x - te.width / 2 - te.x_bearing,
743 			gaugeCenter.y - 0.81 * centsBarMajorTicRadius - te.height / 2
744 			- te.y_bearing);
745 	cairo_show_text(cr, buff);
746 
747 	cairo_translate(cr, gaugeCenter.x, gaugeCenter.y);
748 	for (index = -maxIndex; index <= maxIndex; index++) {
749 		double angle = index * angleStep;
750 		cairo_rotate(cr, angle - oldAngle);
751 		int cents = (int) (index * centsPerMajorDivision);
752 		sprintf(buff, "%s%i", ((cents > 0) ? "+" : ""), cents);
753 		cairo_text_extents(cr, buff, &te);
754 		cairo_move_to(cr, -te.width / 2 - te.x_bearing,
755 				-0.92 * centsBarMajorTicRadius - te.height / 2 - te.y_bearing);
756 		cairo_show_text(cr, buff);
757 		oldAngle = angle;
758 	}
759 	cairo_restore(cr);
760 	cairo_stroke(cr);
761 
762 	// draw frequency bar
763 	cairo_set_line_width(cr, frequencyBarStroke);
764 	lingot_gui_mainframe_cairo_set_source_argb(cr, gauge_frequencyBarColor);
765 	cairo_arc(cr, gaugeCenter.x, gaugeCenter.y, frequencyBarRadius,
766 			-0.5 * M_PI - 1.05 * overtureAngle,
767 			-0.5 * M_PI + 1.05 * overtureAngle);
768 	cairo_stroke(cr);
769 
770 	// frequency tics
771 	lingot_gui_mainframe_draw_gauge_tic(cr, &gaugeCenter,
772 			frequencyBarMajorTicRadius, frequencyBarRadius, 0.0);
773 }
774 
lingot_gui_mainframe_draw_gauge(cairo_t * cr,const LingotMainFrame * frame)775 void lingot_gui_mainframe_draw_gauge(cairo_t *cr, const LingotMainFrame* frame) {
776 
777 	// normalized dimensions
778 	static const FLT gauge_gaugeCenterY = 0.94;
779 	static const FLT gauge_gaugeLength = 0.85;
780 	static const FLT gauge_gaugeLengthBack = 0.08;
781 	static const FLT gauge_gaugeCenterRadius = 0.045;
782 	static const FLT gauge_gaugeStroke = 0.012;
783 	static const FLT gauge_gaugeShadowOffsetX = 0.015;
784 	static const FLT gauge_gaugeShadowOffsetY = 0.01;
785 
786 	static const FLT overtureAngle = 65.0 * M_PI / 180.0;
787 
788 	// colors
789 	static const unsigned int gauge_gaugeColor = 0xaa3333;
790 	static const unsigned int gauge_gaugeShadowColor = 0x88000000;
791 
792 	const int width = gauge_size_x;
793 	int height = gauge_size_y;
794 
795 	// dimensions applied to the current size
796 	point_t gaugeCenter = { .x = width / 2, .y = height * gauge_gaugeCenterY };
797 
798 	if (width < 1.6 * height) {
799 		height = width / 1.6;
800 		gaugeCenter.y = 0.5 * (gauge_size_y - height) + height * gauge_gaugeCenterY;
801 	}
802 
803 	const point_t gaugeShadowCenter = { .x = gaugeCenter.x
804 			+ height * gauge_gaugeShadowOffsetX, .y = gaugeCenter.y
805 			+ height * gauge_gaugeShadowOffsetY };
806 	const FLT gaugeLength = height * gauge_gaugeLength;
807 	const FLT gaugeLengthBack = height * gauge_gaugeLengthBack;
808 	const FLT gaugeCenterRadius = height * gauge_gaugeCenterRadius;
809 	const FLT gaugeStroke = height * gauge_gaugeStroke;
810 
811 	lingot_gui_mainframe_draw_gauge_background(cr, frame);
812 
813 	const double normalized_error = frame->gauge.position
814 			/ frame->conf.scale.max_offset_rounded;
815 	const double angle = 2.0 * normalized_error * overtureAngle;
816 	cairo_set_line_width(cr, gaugeStroke);
817 	cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT);
818 	lingot_gui_mainframe_cairo_set_source_argb(cr, gauge_gaugeShadowColor);
819 	lingot_gui_mainframe_draw_gauge_tic(cr, &gaugeShadowCenter,
820 			-gaugeLengthBack, -0.99 * gaugeCenterRadius, angle);
821 	lingot_gui_mainframe_draw_gauge_tic(cr, &gaugeShadowCenter,
822 			0.99 * gaugeCenterRadius, gaugeLength, angle);
823 	cairo_arc(cr, gaugeShadowCenter.x, gaugeShadowCenter.y, gaugeCenterRadius,
824 			0, 2 * M_PI);
825 	cairo_fill(cr);
826 	lingot_gui_mainframe_cairo_set_source_argb(cr, gauge_gaugeColor);
827 	lingot_gui_mainframe_draw_gauge_tic(cr, &gaugeCenter, -gaugeLengthBack,
828 			gaugeLength, angle);
829 	cairo_arc(cr, gaugeCenter.x, gaugeCenter.y, gaugeCenterRadius, 0, 2 * M_PI);
830 	cairo_fill(cr);
831 }
832 
lingot_gui_mainframe_get_signal(const LingotMainFrame * frame,int i,FLT min,FLT max)833 FLT lingot_gui_mainframe_get_signal(const LingotMainFrame* frame, int i,
834 		FLT min, FLT max) {
835 	FLT signal = frame->core.SPL[i];
836 	if (signal < min) {
837 		signal = min;
838 	} else if (signal > max) {
839 		signal = max;
840 	}
841 	return signal - min;
842 }
843 
lingot_gui_mainframe_get_noise(const LingotMainFrame * frame,FLT min,FLT max)844 FLT lingot_gui_mainframe_get_noise(const LingotMainFrame* frame, FLT min,
845 		FLT max) {
846 	FLT noise = frame->conf.min_overall_SNR;
847 	if (noise < min) {
848 		noise = min;
849 	} else if (noise > max) {
850 		noise = max;
851 	}
852 	return noise - min;
853 }
854 
lingot_gui_mainframe_format_frequency(FLT freq,char * buff)855 static char* lingot_gui_mainframe_format_frequency(FLT freq, char* buff) {
856 	if (freq == 0.0) {
857 		sprintf(buff, "0 Hz");
858 	} else if (floor(freq) == freq)
859 		sprintf(buff, "%0.0f kHz", freq);
860 	else if (floor(10 * freq) == 10 * freq) {
861 		if (freq <= 1000.0)
862 			sprintf(buff, "%0.0f Hz", 1e3 * freq);
863 		else
864 			sprintf(buff, "%0.1f kHz", freq);
865 	} else {
866 		if (freq <= 100.0)
867 			sprintf(buff, "%0.0f Hz", 1e3 * freq);
868 		else
869 			sprintf(buff, "%0.2f kHz", freq);
870 	}
871 
872 	return buff;
873 }
874 
lingot_gui_mainframe_draw_spectrum_background(cairo_t * cr,const LingotMainFrame * frame)875 void lingot_gui_mainframe_draw_spectrum_background(cairo_t *cr, const LingotMainFrame* frame) {
876 
877 	const FLT font_size = 8 + spectrum_size_y / 30;
878 
879 	static char buff[10];
880 	static char buff2[10];
881 	cairo_select_font_face(cr, "Helvetica", CAIRO_FONT_SLANT_NORMAL,
882 			CAIRO_FONT_WEIGHT_NORMAL);
883 	cairo_set_font_size(cr, font_size);
884 	sprintf(buff, "%0.0f", spectrum_min_db);
885 	sprintf(buff2, "%0.0f", spectrum_max_db);
886 	cairo_text_extents_t te;
887 	cairo_text_extents_t te2;
888 	cairo_text_extents(cr, buff, &te);
889 	cairo_text_extents(cr, buff2, &te2);
890 	if (te2.width > te.width) {
891 		te = te2;
892 	}
893 
894 	// spectrum area margins
895 	spectrum_bottom_margin = 1.6 * te.height;
896 	spectrum_top_margin = spectrum_bottom_margin;
897 	spectrum_left_margin = te.width * 1.5;
898 	spectrum_right_margin = 0.03 * spectrum_size_x;
899 	if (spectrum_right_margin > 0.8 * spectrum_left_margin) {
900 		spectrum_right_margin = 0.8 * spectrum_left_margin;
901 	}
902 	spectrum_inner_x = spectrum_size_x - spectrum_left_margin
903 			- spectrum_right_margin;
904 	spectrum_inner_y = spectrum_size_y - spectrum_bottom_margin
905 			- spectrum_top_margin;
906 
907 	sprintf(buff, "000 Hz");
908 	cairo_text_extents(cr, buff, &te);
909 	// minimum grid size in pixels
910 	const int minimum_grid_width = 1.5 * te.width;
911 	const int minimum_grid_height = 3.0 * te.height;
912 
913 	// clear all
914 	cairo_set_source_rgba(cr, 0.06, 0.2, 0.06, 1.0);
915 	GdkRectangle r = { .x = 0, .y = 0, .width = spectrum_size_x, .height =
916 			spectrum_size_y };
917 	gdk_cairo_rectangle(cr, &r);
918 	cairo_fill(cr);
919 
920 	cairo_set_source_rgba(cr, 0.56, 0.56, 0.56, 1.0);
921 	cairo_set_line_width(cr, 1.0);
922 	cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT);
923 	cairo_move_to(cr, spectrum_left_margin,
924 			spectrum_size_y - spectrum_bottom_margin);
925 	cairo_rel_line_to(cr, spectrum_inner_x, 0);
926 	cairo_stroke(cr);
927 
928 	// choose scale factor
929 	const FLT spectrum_max_frequency = 0.5 * frame->conf.sample_rate
930 			/ frame->conf.oversampling;
931 
932 	// scale factors (in KHz) to draw the grid. We will choose the smaller
933 	// factor that respects the minimum_grid_width
934 	static const double scales[] = { 0.01, 0.05, 0.1, 0.2, 0.5, 1, 2, 4, 11, 22,
935 			-1.0 };
936 
937 	int i;
938 	for (i = 0; scales[i + 1] > 0.0; i++) {
939 		if ((1e3 * scales[i] * spectrum_inner_x / spectrum_max_frequency)
940 				> minimum_grid_width)
941 			break;
942 	}
943 
944 	FLT scale = scales[i];
945 	FLT grid_width = 1e3 * scales[i] * spectrum_inner_x
946 			/ spectrum_max_frequency;
947 
948 	FLT freq = 0.0;
949 	FLT x;
950 	for (x = 0.0; x <= spectrum_inner_x; x += grid_width) {
951 		cairo_move_to(cr, spectrum_left_margin + x, spectrum_top_margin);
952 		cairo_rel_line_to(cr, 0, spectrum_inner_y + 3); // TODO: proportion
953 		cairo_stroke(cr);
954 
955 		lingot_gui_mainframe_format_frequency(freq, buff);
956 
957 		cairo_text_extents(cr, buff, &te);
958 		cairo_move_to(cr,
959 				spectrum_left_margin + x + 6 - te.width / 2 - te.x_bearing,
960 				spectrum_size_y - 0.5 * spectrum_bottom_margin - te.height / 2
961 				- te.y_bearing);
962 		cairo_show_text(cr, buff);
963 		freq += scale;
964 	}
965 
966 	spectrum_db_density = (spectrum_inner_y)
967 																			/ (spectrum_max_db - spectrum_min_db);
968 
969 	sprintf(buff, "dB");
970 
971 	cairo_text_extents(cr, buff, &te);
972 	cairo_move_to(cr, spectrum_left_margin - te.x_bearing,
973 			0.5 * spectrum_top_margin - te.height / 2 - te.y_bearing);
974 	cairo_show_text(cr, buff);
975 
976 	// scale factors (in KHz) to draw the grid. We will choose the smallest
977 	// factor that respects the minimum_grid_width
978 	static const int db_scales[] = { 5, 10, 20, 25, 50, 75, 100, -1 };
979 	for (i = 0; db_scales[i + 1] > 0; i++) {
980 		if ((db_scales[i] * spectrum_db_density) > minimum_grid_height)
981 			break;
982 	}
983 
984 	const int db_scale = db_scales[i];
985 
986 	FLT y = 0;
987 	int i0 = ceil(spectrum_min_db / db_scale);
988 	if (spectrum_min_db < 0.0) {
989 		i0--;
990 	}
991 	int i1 = ceil(spectrum_max_db / db_scale);
992 	for (i = i0; i <= i1; i++) {
993 		y = spectrum_db_density * (i * db_scale - spectrum_min_db);
994 		if ((y < 0.0) || (y > spectrum_inner_y)) {
995 			continue;
996 		}
997 		sprintf(buff, "%d", i * db_scale);
998 
999 		cairo_text_extents(cr, buff, &te);
1000 		cairo_move_to(cr,
1001 				0.45 * spectrum_left_margin - te.width / 2 - te.x_bearing,
1002 				spectrum_size_y - spectrum_bottom_margin - y - te.height / 2
1003 				- te.y_bearing);
1004 		cairo_show_text(cr, buff);
1005 
1006 		cairo_move_to(cr, spectrum_left_margin - 3,
1007 				spectrum_size_y - spectrum_bottom_margin - y);
1008 		cairo_rel_line_to(cr, spectrum_inner_x + 3, 0);
1009 		cairo_stroke(cr);
1010 	}
1011 }
1012 
lingot_gui_mainframe_draw_spectrum(cairo_t * cr,const LingotMainFrame * frame)1013 void lingot_gui_mainframe_draw_spectrum(cairo_t *cr, const LingotMainFrame* frame) {
1014 
1015 	unsigned int i;
1016 
1017 	lingot_gui_mainframe_draw_spectrum_background(cr, frame);
1018 
1019 	// TODO: change access to frame->core.X
1020 	// spectrum drawing.
1021 	if (frame->core.running) {
1022 
1023 		cairo_set_line_width(cr, 1.0);
1024 		cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT);
1025 
1026 		FLT x;
1027 		FLT y = -1;
1028 
1029 		const unsigned int min_index = 0;
1030 		const unsigned int max_index = frame->conf.fft_size / 2;
1031 
1032 		FLT index_density = spectrum_inner_x / max_index;
1033 		// TODO: step
1034 		const unsigned int index_step = 1;
1035 
1036 		static const double dashed1[] = { 5.0, 5.0 };
1037 		static int len1 = sizeof(dashed1) / sizeof(dashed1[0]);
1038 
1039 		const FLT x0 = spectrum_left_margin;
1040 		const FLT y0 = spectrum_size_y - spectrum_bottom_margin;
1041 
1042 		FLT dydxm1 = 0;
1043 
1044 		cairo_set_source_rgba(cr, 0.13, 1.0, 0.13, 1.0);
1045 
1046 		cairo_translate(cr, x0, y0);
1047 		cairo_rectangle(cr, 1.0, -1.0, spectrum_inner_x - 2,
1048 				-(spectrum_inner_y - 2));
1049 		cairo_clip(cr);
1050 		cairo_new_path(cr); // path not consumed by clip()
1051 
1052 		y = -spectrum_db_density
1053 				* lingot_gui_mainframe_get_signal(frame, min_index,
1054 						spectrum_min_db, spectrum_max_db); // dB.
1055 
1056 		cairo_move_to(cr, 0, 0);
1057 		cairo_line_to(cr, 0, y);
1058 
1059 		FLT yp1 = -spectrum_db_density
1060 				* lingot_gui_mainframe_get_signal(frame, min_index + 1,
1061 						spectrum_min_db, spectrum_max_db);
1062 		FLT ym1 = y;
1063 
1064 		for (i = index_step; i < max_index - 1; i += index_step) {
1065 
1066 			x = index_density * i;
1067 			ym1 = y;
1068 			y = yp1;
1069 			yp1 = -spectrum_db_density
1070 					* lingot_gui_mainframe_get_signal(frame, i + 1,
1071 							spectrum_min_db, spectrum_max_db);
1072 			FLT dydx = (yp1 - ym1) / (2 * index_density);
1073 			static const FLT dx = 0.4;
1074 			FLT x1 = x - (1 - dx) * index_density;
1075 			FLT x2 = x - dx * index_density;
1076 			FLT y1 = ym1 + dydxm1 * dx;
1077 			FLT y2 = y - dydx * dx;
1078 
1079 			dydxm1 = dydx;
1080 			cairo_curve_to(cr, x1, y1, x2, y2, x, y);
1081 			//			cairo_line_to(cr, x, y);
1082 		}
1083 
1084 		y = -spectrum_db_density
1085 				* lingot_gui_mainframe_get_signal(frame, max_index - 1,
1086 						spectrum_min_db, spectrum_max_db); // dB.
1087 		cairo_line_to(cr, index_density * max_index, y);
1088 		cairo_line_to(cr, index_density * max_index, 0);
1089 		cairo_close_path(cr);
1090 		cairo_fill_preserve(cr);
1091 		//		cairo_restore(cr);
1092 		cairo_stroke(cr);
1093 
1094 #ifdef DRAW_MARKERS
1095 		cairo_set_source_rgba(cr, 1.0, 1.0, 0.13, 1.0);
1096 		cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
1097 		cairo_set_line_width(cr, 10.0);
1098 
1099 		for (i = 0; i < frame->core.markers_size2; i++) {
1100 
1101 			x = index_density * frame->core.markers2[i];
1102 			y = -spectrum_db_density
1103 					* lingot_gui_mainframe_get_signal(frame,
1104 							frame->core.markers2[i], spectrum_min_db,
1105 							spectrum_max_db); // dB.
1106 			cairo_move_to(cr, x, y);
1107 			cairo_rel_line_to(cr, 0, 0);
1108 			cairo_stroke(cr);
1109 		}
1110 
1111 		cairo_set_line_width(cr, 4.0);
1112 		cairo_set_source_rgba(cr, 0.13, 0.13, 1.0, 1.0);
1113 
1114 		for (i = 0; i < frame->core.markers_size; i++) {
1115 
1116 			x = index_density * frame->core.markers[i];
1117 			y = -spectrum_db_density
1118 					* lingot_gui_mainframe_get_signal(frame,
1119 							frame->core.markers[i], spectrum_min_db,
1120 							spectrum_max_db); // dB.
1121 			cairo_move_to(cr, x, y);
1122 			cairo_rel_line_to(cr, 0, 0);
1123 			cairo_stroke(cr);
1124 		}
1125 		cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT);
1126 		cairo_set_line_width(cr, 1.0);
1127 #endif
1128 
1129 		if (frame->core.freq != 0.0) {
1130 
1131 			cairo_set_dash(cr, dashed1, len1, 0);
1132 
1133 			// fundamental frequency mark with a red vertical line.
1134 			cairo_set_source_rgba(cr, 1.0, 0.13, 0.13, 1.0);
1135 			cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
1136 
1137 			cairo_set_line_width(cr, 1.0);
1138 
1139 			// index of closest sample to fundamental frequency.
1140 			x = index_density * frame->core.freq * frame->conf.fft_size
1141 					* frame->conf.oversampling / frame->conf.sample_rate;
1142 			cairo_move_to(cr, x, 0);
1143 			cairo_rel_line_to(cr, 0.0, -spectrum_inner_y);
1144 			cairo_stroke(cr);
1145 
1146 			//			i = (int) rint(
1147 			//					frame->core.freq * frame->conf.fft_size
1148 			//							* frame->conf.oversampling
1149 			//							/ frame->conf.sample_rate);
1150 			//			y = -spectrum_db_density
1151 			//					* lingot_gui_mainframe_get_signal(frame, i, spectrum_min_db,
1152 			//							spectrum_max_db); // dB.
1153 			//			cairo_set_line_width(cr, 4.0);
1154 			//			cairo_move_to(cr, x, y);
1155 			//			cairo_rel_line_to(cr, 0, 0);
1156 			//			cairo_stroke(cr);
1157 
1158 			cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT);
1159 			cairo_set_line_width(cr, 1.0);
1160 		}
1161 
1162 		cairo_set_dash(cr, dashed1, len1, 0);
1163 		cairo_set_source_rgba(cr, 1.0, 1.0, 0.2, 1.0);
1164 
1165 		y = -spectrum_db_density
1166 				* lingot_gui_mainframe_get_noise(frame, spectrum_min_db,
1167 						spectrum_max_db); // dB.
1168 		cairo_move_to(cr, 0, y);
1169 		// noise threshold drawing.
1170 		for (i = min_index + index_step; i < max_index - 1; i += index_step) {
1171 
1172 			x = index_density * i;
1173 			y = -spectrum_db_density
1174 					* lingot_gui_mainframe_get_noise(frame, spectrum_min_db,
1175 							spectrum_max_db); // dB.
1176 			cairo_line_to(cr, x, y);
1177 		}
1178 		cairo_stroke(cr);
1179 
1180 	}
1181 
1182 }
1183 
lingot_gui_mainframe_draw_labels(const LingotMainFrame * frame)1184 void lingot_gui_mainframe_draw_labels(const LingotMainFrame* frame) {
1185 
1186 	char* note_string;
1187 	static char error_string[30];
1188 	static char freq_string[30];
1189 	static char octave_string[30];
1190 
1191 	// draw note, error and frequency labels
1192 
1193 	if (frequency == 0.0) {
1194 		note_string = "---";
1195 		strcpy(error_string, "e = ---");
1196 		strcpy(freq_string, "f = ---");
1197 		strcpy(octave_string, "");
1198 	} else {
1199 		note_string =
1200 				frame->conf.scale.note_name[lingot_config_scale_get_note_index(
1201 						&frame->conf.scale, closest_note_index)];
1202 		sprintf(error_string, "e = %+2.0f cents", frame->gauge.position);
1203 		sprintf(freq_string, "f = %6.2f Hz", frequency);
1204 		sprintf(octave_string, "%d",
1205 				lingot_config_scale_get_octave(&frame->conf.scale,
1206 						closest_note_index) + 4);
1207 	}
1208 
1209 	int font_size = 9 + labelsbox_size_x / 80;
1210 	char* markup = g_markup_printf_escaped("<span font_desc=\"%d\">%s</span>",
1211 			font_size, freq_string);
1212 	gtk_label_set_markup(GTK_LABEL(frame->freq_label), markup);
1213 	g_free(markup);
1214 	markup = g_markup_printf_escaped("<span font_desc=\"%d\">%s</span>",
1215 			font_size, error_string);
1216 	gtk_label_set_markup(GTK_LABEL(frame->error_label), markup);
1217 	g_free(markup);
1218 
1219 	font_size = 10 + labelsbox_size_x / 22;
1220 	markup =
1221 			g_markup_printf_escaped(
1222 					"<span font_desc=\"%d\" weight=\"bold\">%s</span><span font_desc=\"%d\" weight=\"bold\"><sub>%s</sub></span>",
1223 					font_size, note_string, (int) (0.75 * font_size),
1224 					octave_string);
1225 	gtk_label_set_markup(GTK_LABEL(frame->tone_label), markup);
1226 	g_free(markup);
1227 }
1228 
lingot_gui_mainframe_change_config(LingotMainFrame * frame,LingotConfig * conf)1229 void lingot_gui_mainframe_change_config(LingotMainFrame* frame,
1230 		LingotConfig* conf) {
1231 	lingot_core_stop(&frame->core);
1232 	lingot_core_destroy(&frame->core);
1233 
1234 	// dup.
1235 	lingot_config_copy(&frame->conf, conf);
1236 
1237 	lingot_core_new(&frame->core, &frame->conf);
1238 	lingot_core_start(&frame->core);
1239 
1240 	// some parameters may have changed
1241 	lingot_config_copy(conf, &frame->conf);
1242 }
1243