1 /*                                                     -*- linux-c -*-
2     Copyright (C) 2004 Tom Szilagyi
3 
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8 
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13 
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 
18     $Id: volume.c 1287 2014-04-27 20:51:08Z tszilagyi $
19 */
20 
21 #include <config.h>
22 
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <math.h>
27 #include <glib.h>
28 #include <glib-object.h>
29 #include <gdk/gdk.h>
30 #include <gtk/gtk.h>
31 
32 #include "athread.h"
33 #include "utils_gui.h"
34 #include "decoder/file_decoder.h"
35 #include "options.h"
36 #include "music_browser.h"
37 #include "store_file.h"
38 #include "playlist.h"
39 #include "i18n.h"
40 #include "volume.h"
41 
42 
43 #define EPSILON 0.00000000001
44 
45 extern options_t options;
46 
47 extern GtkTreeStore * music_store;
48 extern GtkWidget * main_window;
49 
50 GtkWidget * vol_window;
51 int vol_slot_count;
52 
53 
54 void
voladj2str(float voladj,char * str)55 voladj2str(float voladj, char * str) {
56 
57 	if (fabs(voladj) < 0.05f) {
58 		sprintf(str, " %.1f dB", 0.0f);
59 	} else {
60 		sprintf(str, "% .1f dB", voladj);
61 	}
62 }
63 
64 
65 volume_t *
volume_new(GtkTreeStore * store,int type)66 volume_new(GtkTreeStore * store, int type) {
67 
68 	volume_t * vol = NULL;
69 
70 	if ((vol = (volume_t *)calloc(1, sizeof(volume_t))) == NULL) {
71 		fprintf(stderr, "volume_new(): calloc error\n");
72 		return NULL;
73 	}
74 
75 	vol->store = store;
76 	vol->type = type;
77 
78 	AQUALUNG_COND_INIT(vol->thread_wait);
79 
80 #ifndef HAVE_LIBPTHREAD
81 	vol->thread_mutex = g_mutex_new();
82 	vol->wait_mutex = g_mutex_new();
83 	vol->thread_wait = g_cond_new();
84 #endif /* !HAVE_LIBPTHREAD */
85 
86 	return vol;
87 }
88 
89 void
volume_push(volume_t * vol,char * file,GtkTreeIter iter)90 volume_push(volume_t * vol, char * file, GtkTreeIter iter) {
91 
92 	vol_item_t * item = NULL;
93 
94 	if ((item = (vol_item_t *)calloc(1, sizeof(vol_item_t))) == NULL) {
95 		fprintf(stderr, "volume_push(): calloc error\n");
96 		return;
97 	}
98 
99 	item->file = strdup(file);
100 	item->iter = iter;
101 
102 	vol->queue = g_list_append(vol->queue, item);
103 }
104 
105 void
vol_item_free(vol_item_t * item)106 vol_item_free(vol_item_t * item) {
107 
108 	free(item->file);
109 	free(item);
110 }
111 
112 void
volume_free(volume_t * vol)113 volume_free(volume_t * vol) {
114 
115 #ifndef HAVE_LIBPTHREAD
116 	g_mutex_free(vol->thread_mutex);
117 	g_mutex_free(vol->wait_mutex);
118 	g_cond_free(vol->thread_wait);
119 #endif /* !HAVE_LIBPTHREAD */
120 
121 	if (vol->volumes != NULL) {
122 		free(vol->volumes);
123 	}
124 
125 	g_list_free(vol->queue);
126 	free(vol);
127 }
128 
129 
130 inline static float
rms_env_process(rms_env_t * r,const float x)131 rms_env_process(rms_env_t * r, const float x) {
132 
133         r->sum -= r->buffer[r->pos];
134         r->sum += x;
135         r->buffer[r->pos] = x;
136 
137 	r->pos++;
138         if (r->pos == RMSSIZE) {
139 		r->pos = 0;
140 	}
141 
142         return sqrt(r->sum / (float)RMSSIZE);
143 }
144 
145 gboolean
vol_window_event(GtkWidget * widget,GdkEvent * event,gpointer * data)146 vol_window_event(GtkWidget * widget, GdkEvent * event, gpointer * data) {
147 
148 	if (event->type == GDK_DELETE) {
149 		gtk_window_iconify(GTK_WINDOW(vol_window));
150 		return TRUE;
151 	}
152 
153 	return FALSE;
154 }
155 
156 gboolean
volume_finalize(gpointer data)157 volume_finalize(gpointer data) {
158 
159 	volume_t * vol = (volume_t *)data;
160 
161 	gtk_window_resize(GTK_WINDOW(vol_window),
162 			  vol_window->allocation.width,
163 			  vol_window->allocation.height - vol->slot->allocation.height);
164 
165 	gtk_widget_destroy(vol->slot);
166 	vol->slot = NULL;
167 
168 	g_source_remove(vol->update_tag);
169 	volume_free(vol);
170 
171 	--vol_slot_count;
172 
173 	if (vol_slot_count == 0) {
174 		unregister_toplevel_window(vol_window);
175 		gtk_widget_destroy(vol_window);
176 		vol_window = NULL;
177 	}
178 
179 	return FALSE;
180 }
181 
182 void
vol_pause_event(GtkButton * button,gpointer data)183 vol_pause_event(GtkButton * button, gpointer data) {
184 
185 	volume_t * vol = (volume_t *)data;
186 
187         vol->paused = !vol->paused;
188 	if (vol->paused) {
189 		gtk_button_set_label(button, _("Resume"));
190 	} else {
191 		gtk_button_set_label(button, _("Pause"));
192 	}
193 }
194 
195 void
vol_cancel_event(GtkButton * button,gpointer data)196 vol_cancel_event(GtkButton * button, gpointer data) {
197 
198 	volume_t * vol = (volume_t *)data;
199 
200         vol->cancelled = 1;
201 }
202 
203 
204 gboolean
vol_set_filename_text(gpointer data)205 vol_set_filename_text(gpointer data) {
206 
207 	volume_t * vol = (volume_t *)data;
208 
209 	AQUALUNG_MUTEX_LOCK(vol->wait_mutex);
210 
211 	if (vol->slot) {
212 
213 		char * utf8;
214 
215 		AQUALUNG_MUTEX_LOCK(vol->thread_mutex);
216 		utf8 = g_filename_display_name(vol->item->file);
217 		AQUALUNG_MUTEX_UNLOCK(vol->thread_mutex);
218 
219 		gtk_entry_set_text(GTK_ENTRY(vol->file_entry), utf8);
220 		gtk_editable_set_position(GTK_EDITABLE(vol->file_entry), -1);
221 
222 		g_free(utf8);
223 	}
224 
225 	AQUALUNG_COND_SIGNAL(vol->thread_wait);
226 	AQUALUNG_MUTEX_UNLOCK(vol->wait_mutex);
227 
228 	return FALSE;
229 }
230 
231 gboolean
vol_update_progress(gpointer data)232 vol_update_progress(gpointer data) {
233 
234 	volume_t * vol = (volume_t *)data;
235 
236 	if (vol->slot) {
237 
238 		float fraction = 0.0f;
239 		char str_progress[10];
240 
241 		AQUALUNG_MUTEX_LOCK(vol->thread_mutex);
242 		if (vol->n_chunks != 0) {
243 			fraction = (float)vol->chunks_read / vol->n_chunks;
244 		}
245 		AQUALUNG_MUTEX_UNLOCK(vol->thread_mutex);
246 
247 		if (fraction < 0 || fraction > 1.0f) {
248 			fraction = 0.0f;
249 		}
250 
251 		gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(vol->progress), fraction);
252 		snprintf(str_progress, 10, "%.0f%%", fraction * 100.0f);
253 		gtk_progress_bar_set_text(GTK_PROGRESS_BAR(vol->progress), str_progress);
254 	}
255 
256 	return TRUE;
257 }
258 
259 void
vol_store_voladj(GtkTreeStore * store,GtkTreeIter * iter,float voladj)260 vol_store_voladj(GtkTreeStore * store, GtkTreeIter * iter, float voladj) {
261 
262 	if (!gtk_tree_store_iter_is_valid(store, iter)) {
263 		return;
264 	}
265 
266 	if (store == music_store) { /* music store */
267 		track_data_t * data;
268 		gtk_tree_model_get(GTK_TREE_MODEL(music_store), iter, MS_COL_DATA, &data, -1);
269 		data->volume = voladj;
270 		music_store_mark_changed(iter);
271 
272 	} else { /* playlist */
273 		playlist_data_t * data;
274 		char str[32];
275 		voladj2str(voladj, str);
276 		gtk_tree_model_get(GTK_TREE_MODEL(store), iter, PL_COL_DATA, &data, -1);
277 		data->voladj = voladj;
278 		gtk_tree_store_set(store, iter, PL_COL_VADJ, str, -1);
279 	}
280 }
281 
282 gboolean
vol_store_result_sep(gpointer data)283 vol_store_result_sep(gpointer data) {
284 
285 	volume_t * vol = (volume_t *)data;
286 
287 	AQUALUNG_MUTEX_LOCK(vol->wait_mutex);
288 
289 	vol_store_voladj(vol->store, &vol->item->iter,
290 			 (vol->store == music_store) ? vol->result : rva_from_volume(vol->result));
291 
292 	AQUALUNG_COND_SIGNAL(vol->thread_wait);
293 	AQUALUNG_MUTEX_UNLOCK(vol->wait_mutex);
294 
295 	return FALSE;
296 }
297 
298 gboolean
vol_store_result_avg(gpointer data)299 vol_store_result_avg(gpointer data) {
300 
301 	volume_t * vol = (volume_t *)data;
302 	GList * node = NULL;
303 	float voladj;
304 
305 	AQUALUNG_MUTEX_LOCK(vol->wait_mutex);
306 
307 	voladj = rva_from_multiple_volumes(vol->n_volumes, vol->volumes);
308 
309 	for (node = vol->queue; node; node = node->next) {
310 		vol_item_t * item = (vol_item_t *)node->data;
311 		vol_store_voladj(vol->store, &item->iter, voladj);
312 	}
313 
314 	AQUALUNG_COND_SIGNAL(vol->thread_wait);
315 	AQUALUNG_MUTEX_UNLOCK(vol->wait_mutex);
316 
317 	return FALSE;
318 }
319 
320 void
create_volume_window()321 create_volume_window() {
322 
323 	GtkWidget * vbox;
324 
325 	vol_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
326 	register_toplevel_window(vol_window, TOP_WIN_SKIN | TOP_WIN_TRAY);
327 	gtk_window_set_transient_for(GTK_WINDOW(vol_window), GTK_WINDOW(main_window));
328         gtk_window_set_title(GTK_WINDOW(vol_window), _("Calculating volume level"));
329         gtk_window_set_position(GTK_WINDOW(vol_window), GTK_WIN_POS_CENTER);
330         gtk_window_resize(GTK_WINDOW(vol_window), 480, 110);
331         g_signal_connect(G_OBJECT(vol_window), "event",
332                          G_CALLBACK(vol_window_event), NULL);
333 
334         gtk_container_set_border_width(GTK_CONTAINER(vol_window), 5);
335 
336         vbox = gtk_vbox_new(FALSE, 0);
337         gtk_container_add(GTK_CONTAINER(vol_window), vbox);
338 
339         gtk_widget_show_all(vol_window);
340 }
341 
342 void
vol_create_gui(volume_t * vol)343 vol_create_gui(volume_t * vol) {
344 
345 	GtkWidget * label;
346 	GtkWidget * vbox;
347 	GtkWidget * hbox;
348 
349 	++vol_slot_count;
350 
351 	if (vol_window == NULL) {
352 		create_volume_window();
353 	}
354 
355 	vbox = gtk_bin_get_child(GTK_BIN(vol_window));
356 
357 	vol->slot = gtk_table_new(4, 3, FALSE);
358         gtk_box_pack_start(GTK_BOX(vbox), vol->slot, FALSE, FALSE, 0);
359 
360 	gtk_table_attach(GTK_TABLE(vol->slot), gtk_hseparator_new(), 0, 3, 0, 1,
361 			 GTK_FILL, GTK_FILL, 5, 5);
362 
363         hbox = gtk_hbox_new(FALSE, 0);
364 	label = gtk_label_new(_("File:"));
365         gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
366 	gtk_table_attach(GTK_TABLE(vol->slot), hbox, 0, 1, 1, 2,
367 			 GTK_FILL, GTK_FILL, 5, 5);
368 
369         vol->file_entry = gtk_entry_new();
370         gtk_editable_set_editable(GTK_EDITABLE(vol->file_entry), FALSE);
371 	gtk_table_attach(GTK_TABLE(vol->slot), vol->file_entry, 1, 2, 1, 2,
372 			 GTK_EXPAND | GTK_FILL, GTK_FILL, 5, 5);
373 
374 	vol->pause_button = gtk_button_new_with_label(_("Pause"));
375 	g_signal_connect(vol->pause_button, "clicked", G_CALLBACK(vol_pause_event), vol);
376 	gtk_table_attach(GTK_TABLE(vol->slot), vol->pause_button, 2, 3, 1, 2,
377 			 GTK_FILL, GTK_FILL, 5, 5);
378 
379         hbox = gtk_hbox_new(FALSE, 0);
380 	label = gtk_label_new(_("Progress:"));
381         gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
382 	gtk_table_attach(GTK_TABLE(vol->slot), hbox, 0, 1, 2, 3,
383 			 GTK_FILL, GTK_FILL, 5, 5);
384 
385 	vol->progress = gtk_progress_bar_new();
386 	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(vol->progress), 0.0f);
387 	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(vol->progress), "0.0%");
388 	gtk_table_attach(GTK_TABLE(vol->slot), vol->progress, 1, 2, 2, 3,
389 			 GTK_EXPAND | GTK_FILL, GTK_FILL, 5, 5);
390 
391 	vol->cancel_button = gui_stock_label_button (_("Abort"), GTK_STOCK_CANCEL);
392 	g_signal_connect(vol->cancel_button, "clicked", G_CALLBACK(vol_cancel_event), vol);
393 	gtk_table_attach(GTK_TABLE(vol->slot), vol->cancel_button, 2, 3, 2, 3,
394 			 GTK_FILL, GTK_FILL, 5, 5);
395 
396 	gtk_table_attach(GTK_TABLE(vol->slot), gtk_hseparator_new(), 0, 3, 3, 4,
397 			 GTK_FILL, GTK_FILL, 5, 5);
398 
399 	gtk_widget_show_all(vol->slot);
400 }
401 
402 
403 /* if returns 1, file will be skipped */
404 int
process_volume_setup(volume_t * vol)405 process_volume_setup(volume_t * vol) {
406 
407         if ((vol->fdec = file_decoder_new()) == NULL) {
408                 fprintf(stderr, "calculate_volume: error: file_decoder_new() returned NULL\n");
409                 return 1;
410         }
411 
412         if (file_decoder_open(vol->fdec, vol->item->file)) {
413                 fprintf(stderr, "file_decoder_open() failed on %s\n",
414                         vol->item->file);
415                 return 1;
416         }
417 
418 	if ((vol->rms = (rms_env_t *)calloc(1, sizeof(rms_env_t))) == NULL) {
419 		fprintf(stderr, "calculate_volume(): calloc error\n");
420 		return 1;
421 	}
422 
423 	vol->chunks_read = 0;
424 	vol->chunk_size = vol->fdec->fileinfo.sample_rate / 100;
425 	vol->n_chunks = vol->fdec->fileinfo.total_samples / vol->chunk_size + 1;
426 
427 	AQUALUNG_MUTEX_LOCK(vol->wait_mutex);
428 	aqualung_idle_add(vol_set_filename_text, vol);
429 	AQUALUNG_COND_WAIT(vol->thread_wait, vol->wait_mutex);
430 	AQUALUNG_MUTEX_UNLOCK(vol->wait_mutex);
431 
432 	vol->result = 0.0f;
433 
434 	return 0;
435 }
436 
437 
438 void *
volume_thread(void * arg)439 volume_thread(void * arg) {
440 
441 	volume_t * vol = (volume_t *)arg;
442 
443 	GList * node;
444 
445 	float * samples = NULL;
446 	unsigned long numread;
447 	float chunk_power = 0.0f;
448 	float rms;
449 	unsigned long i;
450 
451 
452 	AQUALUNG_THREAD_DETACH();
453 
454 	for (node = vol->queue; node; node = node->next) {
455 
456 		vol->item = (vol_item_t *)node->data;
457 
458 		if (process_volume_setup(vol)) {
459 			continue;
460 		}
461 
462 		if ((samples = (float *)malloc(vol->chunk_size * vol->fdec->fileinfo.channels * sizeof(float))) == NULL) {
463 
464 			fprintf(stderr, "volume_thread(): malloc() error\n");
465 			file_decoder_close(vol->fdec);
466 			file_decoder_delete(vol->fdec);
467 			free(vol->rms);
468 			break;
469 		}
470 
471 		do {
472 			numread = file_decoder_read(vol->fdec, samples, vol->chunk_size);
473 			vol->chunks_read++;
474 
475 			/* calculate signal power of chunk and feed it in the rms envelope */
476 			if (numread > 0) {
477 				for (i = 0; i < numread * vol->fdec->fileinfo.channels; i++) {
478 					chunk_power += samples[i] * samples[i];
479 				}
480 				chunk_power /= numread * vol->fdec->fileinfo.channels;
481 
482 				rms = rms_env_process(vol->rms, chunk_power);
483 
484 				if (rms > vol->result) {
485 					vol->result = rms;
486 				}
487 			}
488 
489 			while (vol->paused && !vol->cancelled) {
490 				g_usleep(500000);
491 			}
492 
493 		} while (numread == vol->chunk_size && !vol->cancelled);
494 
495 		if (!vol->cancelled) {
496 
497 			vol->result = 20.0f * log10f(vol->result);
498 
499 #ifdef HAVE_MPEG
500 			/* compensate for anti-clip vol.reduction in dec_mpeg.c/mpeg_output() */
501 			if (vol->fdec->file_lib == MAD_LIB) {
502 				vol->result += 1.8f;
503 			}
504 #endif /* HAVE_MPEG */
505 
506 			if (vol->type == VOLUME_SEPARATE) {
507 
508 				AQUALUNG_MUTEX_LOCK(vol->wait_mutex);
509 				aqualung_idle_add(vol_store_result_sep, vol);
510 				AQUALUNG_COND_WAIT(vol->thread_wait, vol->wait_mutex);
511 				AQUALUNG_MUTEX_UNLOCK(vol->wait_mutex);
512 
513 			} else if (vol->type == VOLUME_AVERAGE) {
514 
515 				vol->n_volumes++;
516 				if ((vol->volumes = realloc(vol->volumes, vol->n_volumes * sizeof(float))) == NULL) {
517 					fprintf(stderr, "volume_thread(): realloc error\n");
518 					return NULL;
519 				}
520 				vol->volumes[vol->n_volumes - 1] = vol->result;
521 			}
522 		}
523 
524 		file_decoder_close(vol->fdec);
525 		file_decoder_delete(vol->fdec);
526 		free(vol->rms);
527 
528 		free(samples);
529 
530 		if (vol->cancelled) {
531 			break;
532 		}
533 	}
534 
535 	if (!vol->cancelled && vol->type == VOLUME_AVERAGE) {
536 		AQUALUNG_MUTEX_LOCK(vol->wait_mutex);
537 		aqualung_idle_add(vol_store_result_avg, vol);
538 		AQUALUNG_COND_WAIT(vol->thread_wait, vol->wait_mutex);
539 		AQUALUNG_MUTEX_UNLOCK(vol->wait_mutex);
540 	}
541 
542 	for (node = vol->queue; node; node = node->next) {
543 		vol_item_free((vol_item_t *)node->data);
544 	}
545 
546 	aqualung_idle_add(volume_finalize, vol);
547 
548 
549 	return NULL;
550 }
551 
552 
553 void
volume_start(volume_t * vol)554 volume_start(volume_t * vol) {
555 
556 	if (vol->queue == NULL) {
557 		return;
558 	}
559 
560 	vol_create_gui(vol);
561 
562 	AQUALUNG_THREAD_CREATE(vol->thread_id, NULL, volume_thread, vol);
563 
564 	vol->update_tag = aqualung_timeout_add(250, vol_update_progress, vol);
565 }
566 
567 float
rva_from_volume(float volume)568 rva_from_volume(float volume) {
569 
570 	return ((volume - options.rva_refvol) * (options.rva_steepness - 1.0f));
571 }
572 
573 /* Replaygain is almost always the "89 dB SPL" type.
574  * This means a -14 dBFS pink noise reference signal is used.
575  * A positive replaygain value means it's quiter than the reference signal.
576  * The magic values are due to different ways of calculating the levels.
577  * They're only approximations though.
578  */
579 #define RG_REFERENCE_LEVEL -14.0
580 #define RG_MAGIC_VAL1 -2.1 /* emperical */
581 #define RG_MAGIC_VAL2 1.05 /* emperical */
582 #define RG_TO_VOLUME(replaygain) ((-replaygain + RG_REFERENCE_LEVEL + RG_MAGIC_VAL1) * RG_MAGIC_VAL2)
583 
584 float
rva_from_replaygain(float rg)585 rva_from_replaygain(float rg) {
586 
587 	return rva_from_volume(RG_TO_VOLUME(rg));
588 }
589 
590 
591 float
rva_from_multiple_volumes(int nlevels,float * volumes)592 rva_from_multiple_volumes(int nlevels, float * volumes) {
593 
594 	int i, files_to_avg;
595 	char * badlevels;
596 	double sum, level, mean_level, variance, std_dev;
597 	double level_difference, threshold;
598 
599 	if ((badlevels = (char *)calloc(nlevels, sizeof(char))) == NULL) {
600 		fprintf(stderr, "rva_from_multiple_volumes() : calloc error\n");
601 		return 0.0f;
602 	}
603 
604 	sum = 0.0;
605 	for (i = 0; i < nlevels; i++) {
606 		sum += db2lin(volumes[i]);
607 	}
608 	mean_level = sum / nlevels;
609 
610 	if (!options.rva_use_linear_thresh) { /* use stddev_thresh */
611 
612 		sum = 0;
613 		for (i = 0; i < nlevels; i++) {
614 			double tmp = 20.0 * log10(db2lin(volumes[i]) / mean_level);
615 			sum += tmp * tmp;
616 		}
617 		variance = sum / nlevels;
618 
619 		/* get standard deviation */
620 		if (variance < EPSILON) {
621 			std_dev = 0.0;
622 		} else {
623 			std_dev = sqrt(variance);
624 		}
625 
626 		threshold = options.rva_avg_stddev_thresh * std_dev;
627 	} else {
628 		threshold = options.rva_avg_linear_thresh;
629 	}
630 
631 
632 	if (threshold > EPSILON && nlevels > 1) {
633 
634 		for (i = 0; i < nlevels; i++) {
635 			level_difference = fabs(20.0 * log10(mean_level / db2lin(volumes[i])));
636 			if (level_difference > threshold) {
637 				badlevels[i] = 1;
638 			}
639 		}
640 	}
641 
642 	/* throw out the levels marked as bad */
643 	files_to_avg = 0;
644 	sum = 0;
645 	for (i = 0; i < nlevels; i++) {
646 		if (!badlevels[i]) {
647 			sum += db2lin(volumes[i]);
648 			files_to_avg++;
649 		}
650 	}
651 	free(badlevels);
652 
653 	if (files_to_avg == 0) {
654 		fprintf(stderr,
655 			"rva_from_multiple_volumes: all files ignored, using mean value.\n");
656 		return rva_from_volume(20 * log10(mean_level));
657 	}
658 
659 	level = sum / files_to_avg;
660 	return rva_from_volume(20 * log10(level));
661 }
662 
663 // vim: shiftwidth=8:tabstop=8:softtabstop=8 :
664 
665