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