1 /*
2 Waveform seekbar plugin for the DeaDBeeF audio player
3
4 Copyright (C) 2014 Christian Boxdörfer <christian.boxdoerfer@posteo.de>
5
6 Based on sndfile-tools waveform by Erik de Castro Lopo.
7 waveform.c - v1.04
8 Copyright (C) 2007-2012 Erik de Castro Lopo <erikd@mega-nerd.com>
9 Copyright (C) 2012 Robin Gareus <robin@gareus.org>
10 Copyright (C) 2013 driedfruit <driedfruit@mindloop.net>
11
12 This program is free software; you can redistribute it and/or
13 modify it under the terms of the GNU General Public License
14 as published by the Free Software Foundation; either version 2
15 of the License, or (at your option) any later version.
16
17 This program is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
21
22 You should have received a copy of the GNU General Public License
23 along with this program; if not, write to the Free Software
24 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
25 */
26
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <assert.h>
32 #include <math.h>
33 #include <fcntl.h>
34 #include <gtk/gtk.h>
35
36 #include <deadbeef/deadbeef.h>
37 #include <deadbeef/gtkui_api.h>
38 #include "cache.h"
39 #include "config.h"
40 #include "config_dialog.h"
41 #include "utils.h"
42 #include "waveform.h"
43
44 #define C_COLOUR(X) (X)->r, (X)->g, (X)->b, (X)->a
45
46 //#define M_PI (3.1415926535897932384626433832795029)
47 #define LINE_WIDTH (1.0)
48 // min, max, rms
49 #define VALUES_PER_SAMPLE (3)
50 #define MAX_CHANNELS (6)
51 #define MAX_SAMPLES (4096)
52 #define DISTANCE_THRESHOLD (100)
53
54
55 /* Global variables */
56 DB_functions_t * deadbeef = NULL;
57 static DB_misc_t plugin;
58 static ddb_gtkui_t * gtkui_plugin = NULL;
59
60 static char cache_path[PATH_MAX];
61 static int cache_path_size;
62
63 enum PLAYBACK_STATUS { STOPPED = 0, PLAYING = 1, PAUSED = 2 };
64 static int playback_status = STOPPED;
65
66 typedef struct wavedata_s
67 {
68 char *fname;
69 short *data;
70 size_t data_len;
71 int channels;
72 } wavedata_t;
73
74 typedef struct
75 {
76 ddb_gtkui_widget_t base;
77 GtkWidget *popup;
78 GtkWidget *popup_item;
79 GtkWidget *drawarea;
80 GtkWidget *ruler;
81 GtkWidget *frame;
82 guint drawtimer;
83 guint resizetimer;
84 wavedata_t *wave;
85 size_t max_buffer_len;
86 int seekbar_moving;
87 float seekbar_move_x;
88 float seekbar_move_x_clicked;
89 float height;
90 float width;
91 intptr_t mutex;
92 cairo_surface_t *surf;
93 cairo_surface_t *surf_shaded;
94 } waveform_t;
95
96 typedef struct DRECT
97 {
98 double x1, y1;
99 double x2, y2;
100 } DRECT;
101
102 RENDER render;
103
104 static gboolean
105 waveform_draw_cb (void *user_data);
106
107 static gboolean
108 waveform_redraw_cb (void *user_data);
109
110 static void
111 waveform_draw (void *user_data, int shaded);
112
113 static gboolean
114 waveform_set_refresh_interval (void *user_data, int interval);
115
116 static int
on_config_changed(void * widget)117 on_config_changed (void *widget)
118 {
119 waveform_t *w = (waveform_t *) widget;
120 load_config ();
121 // enable/disable border
122 switch (CONFIG_BORDER_WIDTH) {
123 case 0:
124 gtk_frame_set_shadow_type ((GtkFrame *)w->frame, GTK_SHADOW_NONE);
125 break;
126 case 1:
127 gtk_frame_set_shadow_type ((GtkFrame *)w->frame, GTK_SHADOW_IN);
128 break;
129 }
130 switch (CONFIG_DISPLAY_RULER) {
131 case 0:
132 gtk_widget_hide (w->ruler);
133 break;
134 case 1:
135 gtk_widget_show (w->ruler);
136 break;
137 }
138
139 waveform_set_refresh_interval (w, CONFIG_REFRESH_INTERVAL);
140 g_idle_add (waveform_redraw_cb, w);
141 return 0;
142 }
143
144 static int
make_cache_dir(char * path,int size)145 make_cache_dir (char *path, int size)
146 {
147 const char *cache = getenv ("XDG_CACHE_HOME");
148 if (cache && strcmp(cache, "") == 0) {
149 cache = NULL;
150 }
151 const int sz = snprintf (path, size, cache ? "%s/deadbeef/waveform/" : "%s/.cache/deadbeef/waveform/", cache ? cache : getenv ("HOME"));
152 if (!check_dir (path, 0755)) {
153 return 0;
154 }
155 return sz;
156 }
157
158 static char *
waveform_format_uri(DB_playItem_t * it,const char * uri)159 waveform_format_uri (DB_playItem_t *it, const char *uri)
160 {
161 if (!it || !uri) {
162 return NULL;
163 }
164 const int key_len = strlen (uri) + 10;
165 char *key = malloc (key_len);
166 if (deadbeef->pl_get_item_flags (it) & DDB_IS_SUBTRACK) {
167 int subtrack = deadbeef->pl_find_meta_int (it, ":TRACKNUM", 0);
168 snprintf (key, key_len, "%d%s", subtrack, uri);
169 }
170 else {
171 snprintf (key, key_len, "%s", uri);
172 }
173 return key;
174 }
175
176 /* copied from ardour3 */
177 static inline float
_log_meter(float power,double lower_db,double upper_db,double non_linearity)178 _log_meter (float power, double lower_db, double upper_db, double non_linearity)
179 {
180 return (power < lower_db ? 0.0 : pow ((power - lower_db) / (upper_db - lower_db), non_linearity));
181 }
182
183 static inline float
alt_log_meter(float power)184 alt_log_meter (float power)
185 {
186 return _log_meter (power, -192.0, 0.0, 8.0);
187 }
188
189 static inline float
coefficient_to_dB(float coeff)190 coefficient_to_dB (float coeff)
191 {
192 return 20.0f * log10 (coeff);
193 }
194 /* end of ardour copy */
195
196 static void
color_contrast(GdkColor * color)197 color_contrast (GdkColor * color)
198 {
199 // Counting the perceptive luminance - human eye favors green color...
200 int a = 65535 - ( 2 * color->red + 3 * color->green + color->blue) / 6;
201 if (a < 32768)
202 a = 0; // bright colors - black font
203 else
204 a = 65535; // dark colors - white font
205 color->red = a;
206 color->blue = a;
207 color->green = a;
208 }
209
210 enum BORDERS
211 {
212 CORNER_NONE = 0,
213 CORNER_TOPLEFT = 1,
214 CORNER_TOPRIGHT = 2,
215 CORNER_BOTTOMLEFT = 4,
216 CORNER_BOTTOMRIGHT = 8,
217 CORNER_ALL = 15
218 };
219
220 static void
clearlooks_rounded_rectangle(cairo_t * cr,double x,double y,double w,double h,double radius,uint8_t corners)221 clearlooks_rounded_rectangle (cairo_t * cr,
222 double x, double y, double w, double h,
223 double radius, uint8_t corners)
224 {
225 if (radius < 0.01 || (corners == CORNER_NONE)) {
226 cairo_rectangle (cr, x, y, w, h);
227 return;
228 }
229
230 if (corners & CORNER_TOPLEFT)
231 cairo_move_to (cr, x + radius, y);
232 else
233 cairo_move_to (cr, x, y);
234
235 if (corners & CORNER_TOPRIGHT)
236 cairo_arc (cr, x + w - radius, y + radius, radius, M_PI * 1.5, M_PI * 2);
237 else
238 cairo_line_to (cr, x + w, y);
239
240 if (corners & CORNER_BOTTOMRIGHT)
241 cairo_arc (cr, x + w - radius, y + h - radius, radius, 0, M_PI * 0.5);
242 else
243 cairo_line_to (cr, x + w, y + h);
244
245 if (corners & CORNER_BOTTOMLEFT)
246 cairo_arc (cr, x + radius, y + h - radius, radius, M_PI * 0.5, M_PI);
247 else
248 cairo_line_to (cr, x, y + h);
249
250 if (corners & CORNER_TOPLEFT)
251 cairo_arc (cr, x + radius, y + radius, radius, M_PI, M_PI * 1.5);
252 else
253 cairo_line_to (cr, x, y);
254
255 }
256
257 static inline void
draw_cairo_line_path(cairo_t * cr,DRECT * pts,const COLOUR * c)258 draw_cairo_line_path (cairo_t* cr, DRECT *pts, const COLOUR *c)
259 {
260 cairo_set_source_rgba (cr, C_COLOUR (c));
261 cairo_line_to (cr, pts->x2, pts->y2);
262 }
263
264 static inline void
draw_cairo_line(cairo_t * cr,DRECT * pts,const COLOUR * c)265 draw_cairo_line (cairo_t* cr, DRECT *pts, const COLOUR *c)
266 {
267 cairo_set_source_rgba (cr, C_COLOUR (c));
268 cairo_move_to (cr, pts->x1, pts->y1);
269 cairo_line_to (cr, pts->x2, pts->y2);
270 cairo_stroke (cr);
271 }
272
273 static inline void
draw_cairo_rectangle(cairo_t * cr,const GdkColor * c,int alpha,float x,int y,float width,int height)274 draw_cairo_rectangle (cairo_t *cr, const GdkColor *c, int alpha, float x, int y, float width, int height)
275 {
276 cairo_set_source_rgba (cr, c->red/65535.f, c->green/65535.f, c->blue/65535.f, alpha/65535.f);
277 cairo_rectangle (cr, x, y, width, height);
278 cairo_fill (cr);
279 }
280
281 static gboolean
ruler_redraw_cb(void * user_data)282 ruler_redraw_cb (void *user_data)
283 {
284 waveform_t *w = user_data;
285 gtk_widget_queue_draw (w->ruler);
286 return FALSE;
287 }
288
289 static gboolean
waveform_draw_cb(void * user_data)290 waveform_draw_cb (void *user_data)
291 {
292 waveform_t *w = user_data;
293 gtk_widget_queue_draw (w->drawarea);
294 return TRUE;
295 }
296
297 static gboolean
waveform_redraw_cb(void * user_data)298 waveform_redraw_cb (void *user_data)
299 {
300 waveform_t *w = user_data;
301 if (w->resizetimer) {
302 g_source_remove (w->resizetimer);
303 w->resizetimer = 0;
304 }
305 waveform_draw (w, 0);
306 waveform_draw (w, 1);
307 gtk_widget_queue_draw (w->drawarea);
308 return FALSE;
309 }
310
311 //static gboolean
312 //waveform_redraw_thread (void *user_data)
313 //{
314 // w_waveform_t *w = user_data;
315 // w->resizetimer = 0;
316 // intptr_t tid = deadbeef->thread_start_low_priority (waveform_draw, w, 0);
317 // deadbeef->thread_detach (tid);
318 // gtk_widget_queue_draw (w->drawarea);
319 // return FALSE;
320 //}
321
322 static void
waveform_seekbar_draw(gpointer user_data,cairo_t * cr,int left,int top,int width,int height)323 waveform_seekbar_draw (gpointer user_data, cairo_t *cr, int left, int top, int width, int height)
324 {
325 waveform_t *w = user_data;
326 if (playback_status == STOPPED) {
327 return;
328 }
329
330 DB_playItem_t *trk = deadbeef->streamer_get_playing_track ();
331 if (trk) {
332 const float dur = deadbeef->pl_get_item_duration (trk);
333 const float pos = (deadbeef->streamer_get_playpos () * width)/ dur + left;
334 float seek_pos = 0;
335 int cursor_width = CONFIG_CURSOR_WIDTH;
336
337 if (height != w->height || width != w->width) {
338 cairo_save (cr);
339 cairo_translate (cr, 0, 0);
340 cairo_scale (cr, width/w->width, height/w->height);
341 cairo_set_source_surface (cr, w->surf_shaded, 0, 0);
342 cairo_rectangle (cr, left, top, (pos - cursor_width) / (width/w->width), height / (height/w->height));
343 cairo_fill (cr);
344 cairo_restore (cr);
345 }
346 else {
347 cairo_set_source_surface (cr, w->surf_shaded, 0, 0);
348 cairo_rectangle (cr, left, top, pos - cursor_width, height);
349 cairo_fill (cr);
350 }
351
352 draw_cairo_rectangle (cr, &CONFIG_PB_COLOR, 65535, pos - cursor_width, top, cursor_width, height);
353
354 if (w->seekbar_moving && dur > 0) {
355 if (w->seekbar_move_x < left) {
356 seek_pos = left;
357 }
358 else if (w->seekbar_move_x > width + left) {
359 seek_pos = width + left;
360 }
361 else {
362 seek_pos = w->seekbar_move_x;
363 }
364
365 if (cursor_width == 0) {
366 cursor_width = 1;
367 }
368 draw_cairo_rectangle (cr, &CONFIG_PB_COLOR, 65535, seek_pos - cursor_width, top, cursor_width, height);
369
370 if (w->seekbar_move_x != w->seekbar_move_x_clicked || w->seekbar_move_x_clicked == -1) {
371 w->seekbar_move_x_clicked = -1;
372
373 const float time = CLAMP (w->seekbar_move_x * dur / (width), 0, dur);
374 const int hr = time/3600;
375 const int mn = (time-hr*3600)/60;
376 const int sc = time-hr*3600-mn*60;
377
378 char s[1000];
379 snprintf (s, sizeof (s), "%02d:%02d:%02d", hr, mn, sc);
380
381 cairo_save (cr);
382 cairo_set_source_rgba (cr, CONFIG_PB_COLOR.red/65535.f, CONFIG_PB_COLOR.green/65535.f, CONFIG_PB_COLOR.blue/65535.f, 1);
383 cairo_set_font_size (cr, CONFIG_FONT_SIZE);
384
385 cairo_text_extents_t ex;
386 cairo_text_extents (cr, s, &ex);
387
388 const int rec_width = ex.width + 10;
389 const int rec_height = ex.height + 10;
390 int rec_pos = seek_pos - rec_width;
391 int text_pos = rec_pos + 5;
392
393 if (seek_pos < rec_width) {
394 rec_pos = 0;
395 text_pos = rec_pos + 5;
396 }
397
398 uint8_t corners = 0xff;
399
400 clearlooks_rounded_rectangle (cr, rec_pos, (height - ex.height - 10)/2, rec_width, rec_height, 3, corners);
401 cairo_fill (cr);
402 cairo_move_to (cr, text_pos, (height + ex.height)/2);
403 GdkColor color_text = CONFIG_PB_COLOR;
404 color_contrast (&color_text);
405 cairo_set_source_rgba (cr, color_text.red/65535.f, color_text.green/65535.f, color_text.blue/65535.f, 1);
406 cairo_show_text (cr, s);
407 cairo_restore (cr);
408 }
409 }
410 else if (!deadbeef->is_local_file (deadbeef->pl_find_meta_raw (trk, ":URI"))) {
411 const char *text = "Streaming...";
412 cairo_save (cr);
413 cairo_set_source_rgba (cr, CONFIG_PB_COLOR.red/65535.f, CONFIG_PB_COLOR.green/65535.f, CONFIG_PB_COLOR.blue/65535.f, 1);
414 cairo_set_font_size (cr, CONFIG_FONT_SIZE);
415 cairo_text_extents_t ex;
416 cairo_text_extents (cr, text, &ex);
417 const int text_x = (width - ex.width)/2;
418 const int text_y = (height + ex.height - 3)/2;
419 cairo_move_to (cr, text_x, text_y);
420 GdkColor color_text = CONFIG_BG_COLOR;
421 color_contrast (&color_text);
422 cairo_set_source_rgba (cr, color_text.red/65535.f, color_text.green/65535.f, color_text.blue/65535.f, 1);
423 cairo_show_text (cr, text);
424 cairo_restore (cr);
425 }
426 deadbeef->pl_item_unref (trk);
427 }
428 }
429
430 static void
waveform_draw(void * user_data,int shaded)431 waveform_draw (void *user_data, int shaded)
432 {
433 waveform_t *w = user_data;
434 GtkAllocation a;
435 gtk_widget_get_allocation (w->drawarea, &a);
436
437 const double width = a.width;
438 const double height = a.height;
439 const double left = 0;
440 double waveform_height = height * 0.9;
441 double top = (height - waveform_height)/2;
442 w->width = width;
443 w->height = height;
444
445 COLOUR color_fg, color_rms;
446 if (CONFIG_SHADE_WAVEFORM == 1 && shaded == 1) {
447 color_fg = (COLOUR) { CONFIG_PB_COLOR.red/65535.f,CONFIG_PB_COLOR.green/65535.f,CONFIG_PB_COLOR.blue/65535.f, 1.0};
448 color_rms = (COLOUR) { (CONFIG_PB_COLOR.red - 0.2 * CONFIG_PB_COLOR.red)/65535.f,
449 (CONFIG_PB_COLOR.green - 0.2 * CONFIG_PB_COLOR.green)/65535.f,
450 (CONFIG_PB_COLOR.blue - 0.2 * CONFIG_PB_COLOR.blue)/65535.f, 1.0};
451 }
452 else {
453 color_fg = render.c_fg;
454 color_rms = render.c_rms;
455 }
456
457 if (!shaded || !w->surf || cairo_image_surface_get_width (w->surf) != width || cairo_image_surface_get_height (w->surf) != height) {
458 if (w->surf) {
459 cairo_surface_destroy (w->surf);
460 w->surf = NULL;
461 }
462 w->surf = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height);
463 }
464 if (shaded || !w->surf_shaded || cairo_image_surface_get_width (w->surf_shaded) != width || cairo_image_surface_get_height (w->surf_shaded) != height) {
465 if (w->surf_shaded) {
466 cairo_surface_destroy (w->surf_shaded);
467 w->surf_shaded = NULL;
468 }
469 w->surf_shaded = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height);
470 }
471
472 cairo_surface_t *surface;
473 if (shaded == 0) {
474 surface = w->surf;
475 }
476 else {
477 surface = w->surf_shaded;
478 }
479 cairo_surface_flush (surface);
480 cairo_t *cr = cairo_create (surface);
481 cairo_t *max_cr = cairo_create (surface);
482 cairo_t *min_cr = cairo_create (surface);
483 cairo_t *rms_max_cr = cairo_create (surface);
484 cairo_t *rms_min_cr = cairo_create (surface);
485
486 // Draw background
487 draw_cairo_rectangle (cr, &CONFIG_BG_COLOR, 65535, 0, 0, width, height);
488
489 cairo_set_line_width (cr, LINE_WIDTH);
490 cairo_set_line_width (max_cr, LINE_WIDTH);
491 cairo_set_line_width (min_cr, LINE_WIDTH);
492 cairo_set_line_width (rms_max_cr, LINE_WIDTH);
493 cairo_set_line_width (rms_min_cr, LINE_WIDTH);
494
495 if (CONFIG_RENDER_METHOD == BARS) {
496 cairo_set_line_width (min_cr, 1);
497 cairo_set_antialias (min_cr, CAIRO_ANTIALIAS_NONE);
498 cairo_set_line_width (max_cr, 1);
499 cairo_set_antialias (max_cr, CAIRO_ANTIALIAS_NONE);
500 cairo_set_line_width (rms_min_cr, 1);
501 cairo_set_antialias (rms_min_cr, CAIRO_ANTIALIAS_NONE);
502 cairo_set_line_width (rms_max_cr, 1);
503 cairo_set_antialias (rms_max_cr, CAIRO_ANTIALIAS_NONE);
504 cairo_set_source_rgba (min_cr, color_fg.r, color_fg.g, color_fg.b, 1);
505 cairo_set_source_rgba (max_cr, color_fg.r, color_fg.g, color_fg.b, 1);
506 cairo_set_source_rgba (rms_min_cr, color_rms.r, color_rms.g, color_rms.b, 1);
507 cairo_set_source_rgba (rms_max_cr, color_rms.r, color_rms.g, color_rms.b, 1);
508 }
509
510 const float x_off = 0.5;
511
512 int channels = w->wave->channels;
513 int samples_size = VALUES_PER_SAMPLE * channels;
514 float samples_per_x;
515
516 if (channels != 0) {
517 samples_per_x = w->wave->data_len / (float)(width * samples_size);
518 waveform_height /= channels;
519 top /= channels;
520 }
521 else {
522 samples_per_x = w->wave->data_len / (float)(width * VALUES_PER_SAMPLE);
523 }
524 int max_samples_per_x = 1 + ceilf (samples_per_x);
525 int samples_per_buf = floorf (samples_per_x * (float)(samples_size));
526
527 if (CONFIG_MIX_TO_MONO) {
528 samples_size = VALUES_PER_SAMPLE;
529 channels = 1;
530 samples_per_x = w->wave->data_len / ((float)width * samples_size);
531 max_samples_per_x = 1 + ceilf (samples_per_x);
532 samples_per_buf = floorf (samples_per_x * (float)(samples_size));
533 waveform_height = height * 0.9;
534 top = (height - waveform_height)/2;
535 }
536
537 const int samples_per_buf_temp = samples_per_buf;
538 float min, max, rms;
539 float pmin = 0;
540 float pmax = 0;
541 float prms = 0;
542
543 for (int ch = 0; ch < channels; ch++, top += (height / channels)) {
544 if (w->wave->channels == 0) {
545 break;
546 }
547
548 double center;
549 if (CONFIG_SOUNDCLOUD_STYLE) {
550 center = floor (top + waveform_height * 0.7 + 0.5);
551 }
552 else {
553 center = floor (top + waveform_height/2 + 0.5);
554 }
555
556 if (CONFIG_RENDER_METHOD == SPIKES) {
557 cairo_move_to (max_cr, left, center);
558 cairo_move_to (min_cr, left, center);
559 cairo_move_to (rms_max_cr, left, center);
560 cairo_move_to (rms_min_cr, left, center);
561 }
562
563 cairo_pattern_t *pat = NULL;
564 cairo_pattern_t *pat1 = NULL;
565 if (CONFIG_SOUNDCLOUD_STYLE) {
566 pat = cairo_pattern_create_linear (0, top, 0, center);
567 cairo_pattern_add_color_stop_rgba (pat, 1, color_fg.r, color_fg.g, color_fg.b, 1);
568 cairo_pattern_add_color_stop_rgba (pat, 0, color_fg.r, color_fg.g, color_fg.b, 0.7);
569
570 pat1 = cairo_pattern_create_linear (0, center, 0, center + 0.3 * waveform_height);
571 cairo_pattern_add_color_stop_rgba (pat1, 0, color_fg.r - 0.1 * color_fg.r, color_fg.g - 0.1 * color_fg.g, color_fg.b - 0.1 * color_fg.b, 1);
572 cairo_pattern_add_color_stop_rgba (pat1, 0, color_fg.r, color_fg.g, color_fg.b, 0.7);
573 if (CONFIG_RENDER_METHOD == BARS) {
574 cairo_set_source (min_cr, pat1);
575 cairo_set_source (max_cr, pat);
576 }
577 }
578
579 int offset = ch * VALUES_PER_SAMPLE;
580 int f_offset = 0;
581 samples_per_buf = samples_per_buf_temp;
582 for (int x = 0; x < width; x++) {
583 if (offset + samples_per_buf > w->wave->data_len) {
584 break;
585 }
586
587 min = 0.0; max = 0.0; rms = 0.0;
588
589 int counter = 0;
590 const int offset_temp = offset;
591 for (; offset < offset_temp + samples_per_buf; offset += samples_size, counter++) {
592 max += (float)w->wave->data[offset]/1000;
593 min += (float)w->wave->data[offset+1]/1000;
594 rms += (float)w->wave->data[offset+2]/1000;
595 }
596
597 max /= counter;
598 min /= counter;
599 rms /= counter;
600
601 if (CONFIG_LOG_ENABLED) {
602 if (max > 0)
603 max = alt_log_meter (coefficient_to_dB (max));
604 else
605 max = -alt_log_meter (coefficient_to_dB (-max));
606
607 if (min > 0)
608 min = alt_log_meter (coefficient_to_dB (min));
609 else
610 min = -alt_log_meter (coefficient_to_dB (-min));
611
612 rms = alt_log_meter (coefficient_to_dB (rms));
613 }
614
615 double yoff;
616 if (CONFIG_SOUNDCLOUD_STYLE) {
617 yoff = 0.7 * waveform_height;
618 min = 0.3 * waveform_height * min;
619 max = 0.7 * waveform_height * max;
620 rms = waveform_height * rms;
621 }
622 else {
623 yoff = 0.5 * waveform_height;
624 min = min * yoff;
625 max = max * yoff;
626 rms = rms * yoff;
627 }
628
629 /* Draw Foreground - line */
630 if (CONFIG_RENDER_METHOD == SPIKES) {
631 DRECT pts0 = { left + x - x_off, top + yoff - pmin, left + x + x_off, top + yoff - min };
632 DRECT pts1 = { left + x - x_off, top + yoff - pmax, left + x + x_off, top + yoff - max };
633 if (CONFIG_FILL_WAVEFORM == 1) {
634 draw_cairo_line_path (min_cr, &pts0, &color_fg);
635 draw_cairo_line_path (max_cr, &pts1, &color_fg);
636 }
637 else {
638 draw_cairo_line (min_cr, &pts0, &color_fg);
639 draw_cairo_line (max_cr, &pts1, &color_fg);
640 }
641 }
642 else if (CONFIG_RENDER_METHOD == BARS) {
643 DRECT pts0 = { left + x, top + yoff, left + x, top + yoff - min };
644 draw_cairo_line (min_cr, &pts0, &color_fg);
645 DRECT pts1 = { left + x, top + yoff, left + x, top + yoff - max };
646 draw_cairo_line (max_cr, &pts1, &color_fg);
647 }
648
649 if (!CONFIG_SOUNDCLOUD_STYLE) {
650 if (CONFIG_DISPLAY_RMS && CONFIG_RENDER_METHOD == SPIKES) {
651 DRECT pts0 = { left + x - x_off, top + yoff - prms, left + x + x_off, top + yoff - rms };
652 DRECT pts1 = { left + x - x_off, top + yoff + prms, left + x + x_off, top + yoff + rms };
653 if (CONFIG_FILL_WAVEFORM == 1) {
654 draw_cairo_line_path (rms_min_cr, &pts0, &color_rms);
655 draw_cairo_line_path (rms_max_cr, &pts1, &color_rms);
656 }
657 else {
658 draw_cairo_line (rms_min_cr, &pts0, &color_rms);
659 draw_cairo_line (rms_max_cr, &pts1, &color_rms);
660 }
661 }
662 else if (CONFIG_DISPLAY_RMS && CONFIG_RENDER_METHOD == BARS) {
663 DRECT pts0 = { left + x, top + yoff, left + x, top + yoff - rms };
664 draw_cairo_line (rms_min_cr, &pts0, &color_rms);
665 DRECT pts1 = { left + x, top + yoff, left + x, top + yoff + rms };
666 draw_cairo_line (rms_max_cr, &pts1, &color_rms);
667 }
668 }
669
670 pmin = min;
671 pmax = max;
672 prms = rms;
673
674 f_offset += samples_per_buf;
675 samples_per_buf = floorf ((x + 1) * samples_per_x * samples_size) - f_offset;
676 samples_per_buf = samples_per_buf > (max_samples_per_x * samples_size) ? (max_samples_per_x * samples_size) : samples_per_buf;
677 samples_per_buf = samples_per_buf + ((samples_size) - (samples_per_buf % samples_size));
678 }
679 if (CONFIG_RENDER_METHOD == SPIKES && CONFIG_FILL_WAVEFORM) {
680 double right = floor (left + width + 0.5);
681 cairo_line_to (max_cr, right, center);
682 cairo_line_to (min_cr, right, center);
683 cairo_line_to (rms_min_cr, right, center);
684 cairo_line_to (rms_max_cr, right, center);
685 cairo_close_path (max_cr);
686 cairo_close_path (min_cr);
687 cairo_close_path (rms_max_cr);
688 cairo_close_path (rms_min_cr);
689 if (CONFIG_SOUNDCLOUD_STYLE) {
690 cairo_set_source (min_cr, pat1);
691 cairo_set_source (max_cr, pat);
692 }
693 cairo_fill (max_cr);
694 cairo_fill (min_cr);
695 cairo_fill (rms_max_cr);
696 cairo_fill (rms_min_cr);
697 }
698
699 if (pat) {
700 cairo_pattern_destroy (pat);
701 }
702 if (pat1) {
703 cairo_pattern_destroy (pat1);
704 }
705 }
706 if (!CONFIG_SHADE_WAVEFORM && shaded == 1) {
707 draw_cairo_rectangle (cr, &CONFIG_PB_COLOR, CONFIG_PB_ALPHA, 0, 0, width, height);
708 }
709 cairo_destroy (cr);
710 cairo_destroy (max_cr);
711 cairo_destroy (min_cr);
712 cairo_destroy (rms_max_cr);
713 cairo_destroy (rms_min_cr);
714 return;
715 }
716
717 static void
waveform_scale(void * user_data,cairo_t * cr,int x,int y,int width,int height)718 waveform_scale (void *user_data, cairo_t *cr, int x, int y, int width, int height)
719 {
720 waveform_t *w = user_data;
721
722 if (height != w->height || width != w->width) {
723 cairo_save (cr);
724 cairo_translate (cr, x, y);
725 cairo_scale (cr, width/w->width, height/w->height);
726 cairo_set_source_surface (cr, w->surf, x, y);
727 cairo_paint (cr);
728 cairo_restore (cr);
729 }
730 else {
731 cairo_set_source_surface (cr, w->surf, x, y);
732 cairo_paint (cr);
733 }
734 }
735
736 static gboolean
waveform_generate_wavedata(gpointer user_data,DB_playItem_t * it,const char * uri,wavedata_t * wavedata)737 waveform_generate_wavedata (gpointer user_data, DB_playItem_t *it, const char *uri, wavedata_t *wavedata)
738 {
739 waveform_t *w = user_data;
740 const double width = CONFIG_NUM_SAMPLES;
741
742 DB_fileinfo_t *fileinfo = NULL;
743
744 deadbeef->pl_lock ();
745 const char *dec_meta = deadbeef->pl_find_meta_raw (it, ":DECODER");
746 char decoder_id[100];
747 if (dec_meta) {
748 strncpy (decoder_id, dec_meta, sizeof (decoder_id));
749 }
750 DB_decoder_t *dec = NULL;
751 DB_decoder_t **decoders = deadbeef->plug_get_decoder_list ();
752 for (int i = 0; decoders[i]; i++) {
753 if (!strcmp (decoders[i]->plugin.id, decoder_id)) {
754 dec = decoders[i];
755 break;
756 }
757 }
758 deadbeef->pl_unlock ();
759
760 wavedata->data_len = 0;
761 wavedata->channels = 0;
762
763 if (dec) {
764 fileinfo = dec->open (0);
765 if (fileinfo && dec->init (fileinfo, DB_PLAYITEM (it)) != 0) {
766 deadbeef->pl_lock ();
767 fprintf (stderr, "waveform: failed to decode file %s\n", deadbeef->pl_find_meta (it, ":URI"));
768 deadbeef->pl_unlock ();
769 goto out;
770 }
771 float *data;
772 float *buffer;
773
774 if (fileinfo) {
775 const float duration = deadbeef->pl_get_item_duration (it);
776 const int num_updates = MAX (1, floorf (duration)/30);
777 const int update_after_nsamples = width/num_updates;
778 if (duration <= 0) {
779 goto out;
780 }
781 const int bytes_per_sample = fileinfo->fmt.bps / 8;
782 const int samplesize = fileinfo->fmt.channels * bytes_per_sample;
783 const int nsamples_per_channel = floorf (duration * (float)fileinfo->fmt.samplerate);
784 const int samples_per_buf = ceilf ((float) nsamples_per_channel / (float) width);
785 const int max_samples_per_buf = 1 + samples_per_buf;
786
787 w->wave->channels = fileinfo->fmt.channels;
788 w->wave->data_len = w->wave->channels * 3 * CONFIG_NUM_SAMPLES;
789 deadbeef->mutex_lock (w->mutex);
790 memset (w->wave->data, 0, sizeof (short) * w->max_buffer_len);
791 deadbeef->mutex_unlock (w->mutex);
792
793 data = malloc (sizeof (float) * max_samples_per_buf * samplesize);
794 if (!data) {
795 trace ("waveform: out of memory.\n");
796 goto out;
797 }
798 memset (data, 0, sizeof (float) * max_samples_per_buf * samplesize);
799
800 buffer = malloc (sizeof (float) * max_samples_per_buf * samplesize);
801 if (!buffer) {
802 trace ("waveform: out of memory.\n");
803 goto out;
804 }
805 memset (buffer, 0, sizeof (float) * max_samples_per_buf * samplesize);
806
807
808 ddb_waveformat_t out_fmt = {
809 .bps = 32,
810 .channels = fileinfo->fmt.channels,
811 .samplerate = fileinfo->fmt.samplerate,
812 .channelmask = fileinfo->fmt.channelmask,
813 .is_float = 1,
814 .is_bigendian = 0
815 };
816
817 int update_counter = 0;
818 int eof = 0;
819 int counter = 0;
820 const long buffer_len = samples_per_buf * samplesize;
821 while (!eof) {
822 int sz = dec->read (fileinfo, (char *)buffer, buffer_len);
823 if (sz != buffer_len) {
824 eof = 1;
825 }
826 else if (sz == 0) {
827 break;
828 }
829
830 deadbeef->pcm_convert (&fileinfo->fmt, (char *)buffer, &out_fmt, (char *)data, sz);
831
832 int sample;
833 float min, max, rms;
834
835 for (int ch = 0; ch < fileinfo->fmt.channels; ch++) {
836 min = 1.0; max = -1.0; rms = 0.0;
837 for (sample = 0; sample < sz/samplesize; sample++) {
838 if (sample * fileinfo->fmt.channels > buffer_len) {
839 fprintf (stderr, "index error!\n");
840 break;
841 }
842 const float sample_val = data [sample * fileinfo->fmt.channels + ch];
843 max = MAX (max, sample_val);
844 min = MIN (min, sample_val);
845 rms += (sample_val * sample_val);
846 }
847 rms /= sample;
848 rms = sqrt (rms);
849 wavedata->data[counter] = (short)(max*1000);
850 wavedata->data[counter+1] = (short)(min*1000);
851 wavedata->data[counter+2] = (short)(rms*1000);
852 counter += 3;
853 }
854 if (update_counter == update_after_nsamples) {
855 DB_playItem_t *playing = deadbeef->streamer_get_playing_track ();
856 if (playing) {
857 if (playing == it) {
858 deadbeef->mutex_lock (w->mutex);
859 w->wave->channels = fileinfo->fmt.channels;
860 w->wave->data_len = w->wave->channels * 3 * CONFIG_NUM_SAMPLES;
861 memset (w->wave->data, 0, sizeof (short) * w->max_buffer_len);
862 memcpy (w->wave->data, wavedata->data, counter * sizeof (short));
863 deadbeef->mutex_unlock (w->mutex);
864 g_idle_add (waveform_redraw_cb, w);
865 }
866 deadbeef->pl_item_unref (playing);
867 }
868 update_counter = 0;
869 }
870 update_counter++;
871 }
872 wavedata->fname = strdup (deadbeef->pl_find_meta_raw (it, ":URI"));
873 wavedata->data_len = counter;
874 wavedata->channels = fileinfo->fmt.channels;
875
876
877 if (data) {
878 free (data);
879 }
880 if (buffer) {
881 free (buffer);
882 }
883 }
884 }
885 out:
886 if (dec && fileinfo) {
887 dec->free (fileinfo);
888 fileinfo = NULL;
889 }
890
891 return TRUE;
892 }
893
894 static void
waveform_db_cache(gpointer user_data,DB_playItem_t * it,wavedata_t * wavedata)895 waveform_db_cache (gpointer user_data, DB_playItem_t *it, wavedata_t *wavedata)
896 {
897 waveform_t *w = user_data;
898 char *key = waveform_format_uri (it, wavedata->fname);
899 if (!key) {
900 return;
901 }
902 deadbeef->mutex_lock (w->mutex);
903 waveform_db_write (key, wavedata->data, wavedata->data_len * sizeof (short), wavedata->channels, 0);
904 deadbeef->mutex_unlock (w->mutex);
905 if (key) {
906 free (key);
907 }
908 }
909
910 static int
waveform_valid_track(DB_playItem_t * it,const char * uri)911 waveform_valid_track (DB_playItem_t *it, const char *uri)
912 {
913 if (!deadbeef->is_local_file (uri)) {
914 return 0;
915 }
916 if (deadbeef->pl_get_item_duration (it)/60 >= CONFIG_MAX_FILE_LENGTH && CONFIG_MAX_FILE_LENGTH != -1) {
917 return 0;
918 }
919
920 deadbeef->pl_lock ();
921 const char *file_meta = deadbeef->pl_find_meta_raw (it, ":FILETYPE");
922 if (file_meta && strcmp (file_meta,"cdda") == 0) {
923 deadbeef->pl_unlock ();
924 return 0;
925 }
926 deadbeef->pl_unlock ();
927 return 1;
928 }
929
930 static int
waveform_delete(DB_playItem_t * it,const char * uri)931 waveform_delete (DB_playItem_t *it, const char *uri)
932 {
933 char *key = waveform_format_uri (it, uri);
934 if (!key) {
935 return 0;
936 }
937 int result = waveform_db_delete (key);
938 if (key) {
939 free (key);
940 }
941 return result;
942 }
943
944 static int
waveform_is_cached(DB_playItem_t * it,const char * uri)945 waveform_is_cached (DB_playItem_t *it, const char *uri)
946 {
947 char *key = waveform_format_uri (it, uri);
948 if (!key) {
949 return 0;
950 }
951 int result = waveform_db_cached (key);
952 if (key) {
953 free (key);
954 }
955 return result;
956 }
957
958 static void
waveform_get_from_cache(gpointer user_data,DB_playItem_t * it,const char * uri)959 waveform_get_from_cache (gpointer user_data, DB_playItem_t *it, const char *uri)
960 {
961 waveform_t *w = user_data;
962 char *key = waveform_format_uri (it, uri);
963 if (!key) {
964 return;
965 }
966 deadbeef->mutex_lock (w->mutex);
967 w->wave->data_len = waveform_db_read (key, w->wave->data, w->max_buffer_len, &w->wave->channels);
968 deadbeef->mutex_unlock (w->mutex);
969 if (key) {
970 free (key);
971 }
972 }
973
974 static void
waveform_get_wavedata(gpointer user_data)975 waveform_get_wavedata (gpointer user_data)
976 {
977 waveform_t *w = user_data;
978 deadbeef->background_job_increment ();
979 DB_playItem_t *it = deadbeef->streamer_get_playing_track ();
980 if (it) {
981 char *uri = strdup (deadbeef->pl_find_meta_raw (it, ":URI"));
982 if (uri && waveform_valid_track (it, uri)) {
983 if (CONFIG_CACHE_ENABLED && waveform_is_cached (it, uri)) {
984 waveform_get_from_cache (w, it, uri);
985 g_idle_add (waveform_redraw_cb, w);
986 }
987 else if (queue_add (uri)) {
988 wavedata_t *wavedata = malloc (sizeof (wavedata_t));
989 wavedata->data = malloc (sizeof (short) * w->max_buffer_len);
990 memset (wavedata->data, 0, sizeof (short) * w->max_buffer_len);
991 wavedata->fname = NULL;
992
993 waveform_generate_wavedata (w, it, uri, wavedata);
994 if (CONFIG_CACHE_ENABLED) {
995 waveform_db_cache (w, it, wavedata);
996 }
997 queue_pop (uri);
998
999 DB_playItem_t *playing = deadbeef->streamer_get_playing_track ();
1000 if (playing && it && it == playing) {
1001 deadbeef->mutex_lock (w->mutex);
1002 memcpy (w->wave->data, wavedata->data, wavedata->data_len * sizeof (short));
1003 w->wave->data_len = wavedata->data_len;
1004 w->wave->channels = wavedata->channels;
1005 deadbeef->mutex_unlock (w->mutex);
1006 g_idle_add (waveform_redraw_cb, w);
1007
1008 }
1009 if (playing) {
1010 deadbeef->pl_item_unref (playing);
1011 }
1012
1013 if (wavedata->data) {
1014 free (wavedata->data);
1015 wavedata->data = NULL;
1016 }
1017 if (wavedata->fname) {
1018 free (wavedata->fname);
1019 wavedata->fname = NULL;
1020 }
1021 if (wavedata) {
1022 free (wavedata);
1023 wavedata = NULL;
1024 }
1025 }
1026 }
1027 if (uri) {
1028 free (uri);
1029 }
1030 }
1031 if (it) {
1032 deadbeef->pl_item_unref (it);
1033 }
1034 deadbeef->background_job_decrement ();
1035 }
1036
1037 static gboolean
waveform_set_refresh_interval(gpointer user_data,int interval)1038 waveform_set_refresh_interval (gpointer user_data, int interval)
1039 {
1040 waveform_t *w = user_data;
1041 if (!w || interval <= 0) {
1042 return FALSE;
1043 }
1044 if (w->drawtimer) {
1045 g_source_remove (w->drawtimer);
1046 w->drawtimer = 0;
1047 }
1048 w->drawtimer = g_timeout_add (interval, waveform_draw_cb, w);
1049 return TRUE;
1050 }
1051
1052 static void
ruler_expose_event(GtkWidget * widget,GdkEventExpose * event,gpointer user_data)1053 ruler_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer user_data)
1054 {
1055 waveform_t *w = user_data;
1056 GtkAllocation a;
1057 gtk_widget_get_allocation (w->ruler, &a);
1058 cairo_t *cr = gdk_cairo_create (gtk_widget_get_window (w->ruler));
1059
1060 const int width = a.width;
1061 const int height = a.height;
1062 draw_cairo_rectangle (cr, &CONFIG_BG_COLOR, 65535, 0, 0, width, height);
1063 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
1064 cairo_set_line_width (cr, 1);
1065 cairo_set_source_rgba (cr, 0.2, 0.2, 0.2, 1);
1066 cairo_move_to (cr, 0, a.height);
1067 cairo_line_to (cr, a.width, a.height);
1068 cairo_stroke (cr);
1069
1070 DB_playItem_t *trk = deadbeef->streamer_get_playing_track ();
1071 if (trk) {
1072 const float duration = deadbeef->pl_get_item_duration (trk);
1073 const float rel = (float)a.width/duration;
1074 const float values[] = {3600.f, 1800.f, 600.f, 60.f, 30.f, 10.f, 5.f, 1.f, 0.5f, 0.1f};
1075
1076 char text[100];
1077 snprintf (text, sizeof (text), "%f", duration);
1078 cairo_set_font_size (cr, 8);
1079 cairo_text_extents_t ex;
1080 cairo_text_extents (cr, text, &ex);
1081
1082 int bar_h = 12;
1083
1084 int steps = floorf (duration/values[0]);
1085 int pos = 0;
1086 while (a.width/MAX (1, steps) > 3) {
1087 if (steps > 0) {
1088 float prev_time = 0.f;
1089 for (int i = 1; i <= steps; i++) {
1090 const float time = i*values[pos];
1091 int stop = 0;
1092 for (int j = 0; j < pos; j++) {
1093 if (fmod (time, values[j]) == 0.f) {
1094 stop = 1;
1095 }
1096 }
1097 if (stop) {
1098 continue;
1099 }
1100 cairo_move_to (cr, rel * time, a.height);
1101 cairo_line_to (cr, rel * time, a.height - bar_h);
1102 cairo_stroke (cr);
1103
1104 if (prev_time == time) {
1105 continue;
1106 }
1107 prev_time = time;
1108
1109 if (duration > 2.f && a.width/steps > 50) {
1110 const int hr = time/3600;
1111 const int mn = (time-hr*3600)/60;
1112 const int sc = time-hr*3600-mn*60;
1113 const int ms = (time-hr*3600-mn*60-sc)*10;
1114
1115 if (hr > 0) {
1116 snprintf (text, sizeof (text), "%d:%02d:%02d", hr, mn, sc);
1117 }
1118 else if (duration > 20) {
1119 snprintf (text, sizeof (text), "%d:%02d", mn, sc);
1120 }
1121 else {
1122 snprintf (text, sizeof (text), "%2d,%d", sc, ms);
1123 }
1124 const int text_x = rel * time + 2;
1125 const int text_y = ex.height;
1126 cairo_move_to (cr, text_x, text_y);
1127 cairo_show_text (cr, text);
1128 }
1129 }
1130 bar_h -= 3;
1131 }
1132 pos++;
1133 if (pos < sizeof (values)/sizeof (float)) {
1134 steps = floorf (duration/values[pos]);
1135 }
1136 else {
1137 break;
1138 }
1139 }
1140 deadbeef->pl_item_unref (trk);
1141 }
1142 cairo_destroy (cr);
1143 }
1144
1145 static void
waveform_expose_event(GtkWidget * widget,GdkEventExpose * event,gpointer user_data)1146 waveform_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer user_data)
1147 {
1148 waveform_t *w = user_data;
1149 if (playback_status != PLAYING) {
1150 if (w->drawtimer) {
1151 g_source_remove (w->drawtimer);
1152 w->drawtimer = 0;
1153 }
1154 }
1155 GtkAllocation a;
1156 gtk_widget_get_allocation (w->drawarea, &a);
1157 cairo_t *cr = gdk_cairo_create (gtk_widget_get_window (w->drawarea));
1158
1159 const int x = 0;
1160 const int y = 0;
1161 const int width = a.width;
1162 const int height = a.height;
1163 waveform_scale (w, cr, x, y, width, height);
1164 waveform_seekbar_draw (w, cr, x, y, width, height);
1165 cairo_destroy (cr);
1166 }
1167
1168 static gboolean
waveform_configure_event(GtkWidget * widget,GdkEvent * event,gpointer user_data)1169 waveform_configure_event (GtkWidget *widget, GdkEvent *event, gpointer user_data)
1170 {
1171 waveform_t *w = user_data;
1172 if (!w) {
1173 return FALSE;
1174 }
1175 if (w->resizetimer) {
1176 g_source_remove (w->resizetimer);
1177 w->resizetimer = 0;
1178 }
1179 w->resizetimer = g_timeout_add (500, waveform_redraw_cb, w);
1180 return FALSE;
1181 }
1182
1183 static gboolean
waveform_motion_notify_event(GtkWidget * widget,GdkEventButton * event,gpointer user_data)1184 waveform_motion_notify_event (GtkWidget *widget, GdkEventButton *event, gpointer user_data)
1185 {
1186 waveform_t *w = user_data;
1187 GtkAllocation a;
1188 gtk_widget_get_allocation (w->drawarea, &a);
1189
1190 if (w->seekbar_moving || w->seekbar_move_x_clicked) {
1191 if (event->x < -DISTANCE_THRESHOLD
1192 || event->x > a.width + DISTANCE_THRESHOLD
1193 || event->y < -DISTANCE_THRESHOLD
1194 || event->y > a.height + DISTANCE_THRESHOLD) {
1195 w->seekbar_moving = 0;
1196 return TRUE;
1197 }
1198 w->seekbar_moving = 1;
1199 w->seekbar_move_x = event->x - a.x;
1200 gtk_widget_queue_draw (w->drawarea);
1201 }
1202 return TRUE;
1203 }
1204
1205 static gboolean
waveform_scroll_event(GtkWidget * widget,GdkEvent * event,gpointer user_data)1206 waveform_scroll_event (GtkWidget *widget, GdkEvent *event, gpointer user_data)
1207 {
1208 GdkEventScroll *ev = (GdkEventScroll *)event;
1209 if (!CONFIG_SCROLL_ENABLED) {
1210 return TRUE;
1211 }
1212
1213 DB_playItem_t *trk = deadbeef->streamer_get_playing_track ();
1214 if (trk) {
1215 const int duration = (int)(deadbeef->pl_get_item_duration (trk) * 1000);
1216 const int time = (int)(deadbeef->streamer_get_playpos () * 1000);
1217 const int step = CLAMP (duration / 30, 1000, 3600000);
1218
1219 switch (ev->direction) {
1220 case GDK_SCROLL_UP:
1221 deadbeef->sendmessage (DB_EV_SEEK, 0, MIN (duration, time + step), 0);
1222 break;
1223 case GDK_SCROLL_DOWN:
1224 deadbeef->sendmessage (DB_EV_SEEK, 0, MAX (0, time - step), 0);
1225 break;
1226 default:
1227 break;
1228 }
1229 deadbeef->pl_item_unref (trk);
1230 }
1231 return TRUE;
1232 }
1233
1234 static gboolean
waveform_button_press_event(GtkWidget * widget,GdkEventButton * event,gpointer user_data)1235 waveform_button_press_event (GtkWidget *widget, GdkEventButton *event, gpointer user_data)
1236 {
1237 waveform_t *w = user_data;
1238 if (event->button == 3 || event->button == 2) {
1239 return TRUE;
1240 }
1241 GtkAllocation a;
1242 gtk_widget_get_allocation (w->drawarea, &a);
1243
1244 w->seekbar_moving = 1;
1245 w->seekbar_move_x = event->x - a.x;
1246 w->seekbar_move_x_clicked = event->x - a.x;
1247 return TRUE;
1248 }
1249
1250 static gboolean
waveform_button_release_event(GtkWidget * widget,GdkEventButton * event,gpointer user_data)1251 waveform_button_release_event (GtkWidget *widget, GdkEventButton *event, gpointer user_data)
1252 {
1253 waveform_t *w = user_data;
1254 if (event->button == 3) {
1255 gtk_menu_popup (GTK_MENU (w->popup), NULL, NULL, NULL, w->drawarea, 0, gtk_get_current_event_time ());
1256 return TRUE;
1257 }
1258 if (event->button == 2) {
1259 deadbeef->sendmessage (DB_EV_TOGGLE_PAUSE, 0, 0, 0);
1260 return TRUE;
1261 }
1262 w->seekbar_move_x_clicked = 0;
1263 if (w->seekbar_moving) {
1264 DB_playItem_t *trk = deadbeef->streamer_get_playing_track ();
1265 if (trk) {
1266 GtkAllocation a;
1267 gtk_widget_get_allocation (w->drawarea, &a);
1268 const float time = MAX (0, (event->x - a.x) * deadbeef->pl_get_item_duration (trk) / (a.width) * 1000.f);
1269 deadbeef->sendmessage (DB_EV_SEEK, 0, time, 0);
1270 deadbeef->pl_item_unref (trk);
1271 }
1272 gtk_widget_queue_draw (widget);
1273 }
1274 w->seekbar_moving = 0;
1275 return TRUE;
1276 }
1277
1278 static int
waveform_message(ddb_gtkui_widget_t * widget,uint32_t id,uintptr_t ctx,uint32_t p1,uint32_t p2)1279 waveform_message (ddb_gtkui_widget_t *widget, uint32_t id, uintptr_t ctx, uint32_t p1, uint32_t p2)
1280 {
1281 waveform_t *w = (waveform_t *)widget;
1282 intptr_t tid;
1283
1284 switch (id) {
1285 case DB_EV_SONGSTARTED:
1286 //ddb_event_track_t *ev (ddb_event_track_t *)ctx;
1287 playback_status = PLAYING;
1288 //deadbeef->mutex_lock (w->mutex);
1289 //memset (w->wave->data, 0, sizeof (short) * w->max_buffer_len);
1290 //w->wave->data_len = 0;
1291 //w->wave->channels = 0;
1292 //deadbeef->mutex_unlock (w->mutex);
1293 //queue_add (deadbeef->pl_find_meta_raw (ev->track, ":URI"));
1294 waveform_set_refresh_interval (w, CONFIG_REFRESH_INTERVAL);
1295 g_idle_add (waveform_redraw_cb, w);
1296 g_idle_add (ruler_redraw_cb, w);
1297 tid = deadbeef->thread_start_low_priority (waveform_get_wavedata, w);
1298 if (tid) {
1299 deadbeef->thread_detach (tid);
1300 }
1301 break;
1302 case DB_EV_STOP:
1303 playback_status = STOPPED;
1304 deadbeef->mutex_lock (w->mutex);
1305 memset (w->wave->data, 0, sizeof (short) * w->max_buffer_len);
1306 w->wave->data_len = 0;
1307 w->wave->channels = 0;
1308 deadbeef->mutex_unlock (w->mutex);
1309 g_idle_add (waveform_redraw_cb, w);
1310 break;
1311 case DB_EV_CONFIGCHANGED:
1312 on_config_changed (w);
1313 break;
1314 case DB_EV_PAUSED:
1315 if (deadbeef->get_output ()->state () == OUTPUT_STATE_PLAYING) {
1316 playback_status = PLAYING;
1317 waveform_set_refresh_interval (w, CONFIG_REFRESH_INTERVAL);
1318 }
1319 else {
1320 playback_status = PAUSED;
1321 }
1322 break;
1323 }
1324 return 0;
1325 }
1326
1327 static void
waveform_destroy(ddb_gtkui_widget_t * widget)1328 waveform_destroy (ddb_gtkui_widget_t *widget)
1329 {
1330 waveform_t *w = (waveform_t *)widget;
1331 deadbeef->mutex_lock (w->mutex);
1332 waveform_db_close ();
1333 if (w->drawtimer) {
1334 g_source_remove (w->drawtimer);
1335 w->drawtimer = 0;
1336 }
1337 if (w->resizetimer) {
1338 g_source_remove (w->resizetimer);
1339 w->resizetimer = 0;
1340 }
1341 if (w->surf) {
1342 cairo_surface_destroy (w->surf);
1343 w->surf = NULL;
1344 }
1345 if (w->surf_shaded) {
1346 cairo_surface_destroy (w->surf_shaded);
1347 w->surf_shaded = NULL;
1348 }
1349 if (w->wave->data) {
1350 free (w->wave->data);
1351 w->wave->data = NULL;
1352 }
1353 if (w->wave->fname) {
1354 free (w->wave->fname);
1355 w->wave->fname = NULL;
1356 }
1357 if (w->wave) {
1358 free (w->wave);
1359 w->wave = NULL;
1360 }
1361 deadbeef->mutex_unlock (w->mutex);
1362 if (w->mutex) {
1363 deadbeef->mutex_free (w->mutex);
1364 w->mutex = 0;
1365 }
1366 }
1367
1368 static void
waveform_init(ddb_gtkui_widget_t * w)1369 waveform_init (ddb_gtkui_widget_t *w)
1370 {
1371 waveform_t *wf = (waveform_t *)w;
1372 GtkAllocation a;
1373 gtk_widget_get_allocation (wf->drawarea, &a);
1374 load_config ();
1375 wf->max_buffer_len = MAX_SAMPLES * VALUES_PER_SAMPLE * MAX_CHANNELS * sizeof (short);
1376 deadbeef->mutex_lock (wf->mutex);
1377 wf->wave = malloc (sizeof (wavedata_t));
1378 wf->wave->data = malloc (sizeof (short) * wf->max_buffer_len);
1379 memset (wf->wave->data, 0, sizeof (short) * wf->max_buffer_len);
1380 wf->wave->fname = NULL;
1381 wf->wave->data_len = 0;
1382 wf->wave->channels = 0;
1383 wf->surf = cairo_image_surface_create (CAIRO_FORMAT_RGB24, a.width, a.height);
1384 wf->surf_shaded = cairo_image_surface_create (CAIRO_FORMAT_RGB24, a.width, a.height);
1385 deadbeef->mutex_unlock (wf->mutex);
1386 wf->seekbar_moving = 0;
1387 wf->height = a.height;
1388 wf->width = a.width;
1389
1390 cache_path_size = make_cache_dir (cache_path, sizeof (cache_path));
1391
1392 deadbeef->mutex_lock (wf->mutex);
1393 waveform_db_open (cache_path, cache_path_size);
1394 waveform_db_init (NULL);
1395 deadbeef->mutex_unlock (wf->mutex);
1396
1397 DB_playItem_t *it = deadbeef->streamer_get_playing_track ();
1398 if (it) {
1399 playback_status = PLAYING;
1400 intptr_t tid = deadbeef->thread_start_low_priority (waveform_get_wavedata, w);
1401 if (tid) {
1402 deadbeef->thread_detach (tid);
1403 }
1404 deadbeef->pl_item_unref (it);
1405 }
1406 wf->resizetimer = 0;
1407
1408 on_config_changed (w);
1409 }
1410
1411 static ddb_gtkui_widget_t *
waveform_create(void)1412 waveform_create (void)
1413 {
1414 waveform_t *w = malloc (sizeof (waveform_t));
1415 memset (w, 0, sizeof (waveform_t));
1416
1417 w->base.widget = gtk_event_box_new ();
1418 w->base.init = waveform_init;
1419 w->base.destroy = waveform_destroy;
1420 w->base.message = waveform_message;
1421 w->drawarea = gtk_drawing_area_new ();
1422 w->ruler = gtk_drawing_area_new ();
1423 #if !GTK_CHECK_VERSION(3,0,0)
1424 GtkWidget *vbox = gtk_vbox_new (FALSE, 0);
1425 #else
1426 GtkWidget *vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
1427 #endif
1428 w->frame = gtk_frame_new (NULL);
1429 w->popup = gtk_menu_new ();
1430 w->popup_item = gtk_menu_item_new_with_mnemonic ("Configure");
1431 w->mutex = deadbeef->mutex_create ();
1432 gtk_widget_set_size_request (w->base.widget, 300, 96);
1433 gtk_widget_set_size_request (w->ruler, -1, 12);
1434 gtk_widget_set_size_request (w->drawarea, -1, -1);
1435 gtk_widget_add_events (w->base.widget, GDK_SCROLL_MASK);
1436 gtk_container_add (GTK_CONTAINER (w->base.widget), w->frame);
1437 gtk_container_add (GTK_CONTAINER (w->frame), vbox);
1438 gtk_container_add (GTK_CONTAINER (vbox), w->ruler);
1439 gtk_container_add (GTK_CONTAINER (vbox), w->drawarea);
1440 gtk_container_add (GTK_CONTAINER (w->popup), w->popup_item);
1441 gtk_box_set_child_packing (GTK_BOX (vbox), w->drawarea, TRUE, TRUE, 0, 0);
1442 gtk_box_set_child_packing (GTK_BOX (vbox), w->ruler, FALSE, TRUE, 0, 0);
1443 gtk_widget_show (w->drawarea);
1444 gtk_widget_show (vbox);
1445 gtk_widget_show (w->frame);
1446 gtk_widget_show (w->popup);
1447 gtk_widget_show (w->ruler);
1448 gtk_widget_show (w->popup_item);
1449
1450 #if !GTK_CHECK_VERSION(3,0,0)
1451 g_signal_connect_after ((gpointer) w->drawarea, "expose_event", G_CALLBACK (waveform_expose_event), w);
1452 #else
1453 g_signal_connect_after ((gpointer) w->drawarea, "draw", G_CALLBACK (waveform_expose_event), w);
1454 #endif
1455 #if !GTK_CHECK_VERSION(3,0,0)
1456 g_signal_connect_after ((gpointer) w->ruler, "expose_event", G_CALLBACK (ruler_expose_event), w);
1457 #else
1458 g_signal_connect_after ((gpointer) w->ruler, "draw", G_CALLBACK (ruler_expose_event), w);
1459 #endif
1460 g_signal_connect_after ((gpointer) w->drawarea, "configure_event", G_CALLBACK (waveform_configure_event), w);
1461 g_signal_connect_after ((gpointer) w->base.widget, "button_press_event", G_CALLBACK (waveform_button_press_event), w);
1462 g_signal_connect_after ((gpointer) w->base.widget, "button_release_event", G_CALLBACK (waveform_button_release_event), w);
1463 g_signal_connect_after ((gpointer) w->base.widget, "scroll-event", G_CALLBACK (waveform_scroll_event), w);
1464 g_signal_connect_after ((gpointer) w->base.widget, "motion_notify_event", G_CALLBACK (waveform_motion_notify_event), w);
1465 g_signal_connect_after ((gpointer) w->popup_item, "activate", G_CALLBACK (on_button_config), w);
1466 gtkui_plugin->w_override_signals (w->base.widget, w);
1467 return (ddb_gtkui_widget_t *)w;
1468 }
1469
1470 static int
waveform_connect(void)1471 waveform_connect (void)
1472 {
1473 gtkui_plugin = (ddb_gtkui_t *) deadbeef->plug_get_for_id (DDB_GTKUI_PLUGIN_ID);
1474 if (gtkui_plugin) {
1475 trace ("using '%s' plugin %d.%d\n", DDB_GTKUI_PLUGIN_ID, gtkui_plugin->gui.plugin.version_major, gtkui_plugin->gui.plugin.version_minor );
1476 if (gtkui_plugin->gui.plugin.version_major == 2) {
1477 gtkui_plugin->w_reg_widget ("Waveform Seekbar", DDB_WF_SINGLE_INSTANCE, waveform_create, "waveform_seekbar", NULL);
1478 return 0;
1479 }
1480 }
1481 return -1;
1482 }
1483
1484 static int
waveform_start(void)1485 waveform_start (void)
1486 {
1487 load_config ();
1488 return 0;
1489 }
1490
1491 static int
waveform_stop(void)1492 waveform_stop (void)
1493 {
1494 save_config ();
1495 return 0;
1496 }
1497
1498 static int
waveform_disconnect(void)1499 waveform_disconnect (void)
1500 {
1501 if (gtkui_plugin) {
1502 gtkui_plugin->w_unreg_widget ("waveform_seekbar");
1503 }
1504 gtkui_plugin = NULL;
1505 return 0;
1506 }
1507
1508 static int
waveform_action_lookup(DB_plugin_action_t * action,int ctx)1509 waveform_action_lookup (DB_plugin_action_t *action, int ctx)
1510 {
1511 DB_playItem_t *it = NULL;
1512 deadbeef->pl_lock ();
1513 if (ctx == DDB_ACTION_CTX_SELECTION) {
1514 ddb_playlist_t *plt = deadbeef->plt_get_curr ();
1515 if (plt) {
1516 it = deadbeef->plt_get_first (plt, PL_MAIN);
1517 while (it) {
1518 if (deadbeef->pl_is_selected (it)) {
1519 const char *uri = deadbeef->pl_find_meta_raw (it, ":URI");
1520 if (waveform_is_cached (it, uri)) {
1521 waveform_delete (it, uri);
1522 }
1523 }
1524 DB_playItem_t *next = deadbeef->pl_get_next (it, PL_MAIN);
1525 deadbeef->pl_item_unref (it);
1526 it = next;
1527 }
1528 deadbeef->plt_unref (plt);
1529 }
1530 }
1531 if (it) {
1532 deadbeef->pl_item_unref (it);
1533 }
1534 deadbeef->pl_unlock ();
1535 return 0;
1536 }
1537
1538 static DB_plugin_action_t lookup_action = {
1539 .title = "Remove Waveform From Cache",
1540 .name = "waveform_lookup",
1541 .flags = DB_ACTION_MULTIPLE_TRACKS | DB_ACTION_ADD_MENU,
1542 .callback2 = waveform_action_lookup,
1543 .next = NULL
1544 };
1545
1546 static DB_plugin_action_t *
waveform_get_actions(DB_playItem_t * it)1547 waveform_get_actions (DB_playItem_t *it)
1548 {
1549 deadbeef->pl_lock ();
1550 lookup_action.flags |= DB_ACTION_DISABLED;
1551 DB_playItem_t *current = deadbeef->pl_get_first (PL_MAIN);
1552 while (current) {
1553 if (deadbeef->pl_is_selected (current) && waveform_is_cached (current, deadbeef->pl_find_meta_raw (current, ":URI"))) {
1554 lookup_action.flags &= ~DB_ACTION_DISABLED;
1555 deadbeef->pl_item_unref (current);
1556 break;
1557 }
1558 DB_playItem_t *next = deadbeef->pl_get_next (current, PL_MAIN);
1559 deadbeef->pl_item_unref (current);
1560 current = next;
1561 }
1562 deadbeef->pl_unlock ();
1563 return &lookup_action;
1564 }
1565
1566
1567 static const char settings_dlg[] =
1568 "property \"Refresh interval (ms): \" spinbtn[10,1000,1] " CONFSTR_WF_REFRESH_INTERVAL " 33 ;\n"
1569 "property \"Border width: \" spinbtn[0,1,1] " CONFSTR_WF_BORDER_WIDTH " 1 ;\n"
1570 "property \"Cursor width: \" spinbtn[0,3,1] " CONFSTR_WF_CURSOR_WIDTH " 3 ;\n"
1571 "property \"Font size: \" spinbtn[8,20,1] " CONFSTR_WF_FONT_SIZE " 18 ;\n"
1572 "property \"Ignore files longer than x minutes "
1573 "(-1 scans every file): \" spinbtn[-1,9999,1] " CONFSTR_WF_MAX_FILE_LENGTH " 180 ;\n"
1574 "property \"Use cache \" checkbox " CONFSTR_WF_CACHE_ENABLED " 1 ;\n"
1575 "property \"Scroll wheel to seek \" checkbox " CONFSTR_WF_SCROLL_ENABLED " 1 ;\n"
1576 "property \"Number of samples (per channel): \" spinbtn[2048,4092,2048] " CONFSTR_WF_NUM_SAMPLES " 2048 ;\n"
1577 ;
1578
1579 static DB_misc_t plugin = {
1580 //DB_PLUGIN_SET_API_VERSION
1581 .plugin.type = DB_PLUGIN_MISC,
1582 .plugin.api_vmajor = 1,
1583 .plugin.api_vminor = 5,
1584 .plugin.version_major = 0,
1585 .plugin.version_minor = 5,
1586 #if GTK_CHECK_VERSION(3,0,0)
1587 .plugin.id = "waveform_seekbar-gtk3",
1588 #else
1589 .plugin.id = "waveform_seekbar",
1590 #endif
1591 .plugin.name = "Waveform Seekbar",
1592 .plugin.descr = "Waveform Seekbar",
1593 .plugin.copyright =
1594 "Copyright (C) 2014 Christian Boxdörfer <christian.boxdoerfer@posteo.de>\n"
1595 "\n"
1596 "Based on sndfile-tools waveform by Erik de Castro Lopo.\n"
1597 "\n"
1598 "This program is free software; you can redistribute it and/or\n"
1599 "modify it under the terms of the GNU General Public License\n"
1600 "as published by the Free Software Foundation; either version 2\n"
1601 "of the License, or (at your option) any later version.\n"
1602 "\n"
1603 "This program is distributed in the hope that it will be useful,\n"
1604 "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
1605 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
1606 "GNU General Public License for more details.\n"
1607 "\n"
1608 "You should have received a copy of the GNU General Public License\n"
1609 "along with this program; if not, write to the Free Software\n"
1610 "Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n"
1611 ,
1612 .plugin.website = "https://github.com/cboxdoerfer/ddb_waveform_seekbar",
1613 .plugin.start = waveform_start,
1614 .plugin.stop = waveform_stop,
1615 .plugin.connect = waveform_connect,
1616 .plugin.disconnect = waveform_disconnect,
1617 .plugin.configdialog = settings_dlg,
1618 .plugin.get_actions = waveform_get_actions,
1619 };
1620
1621 #if !GTK_CHECK_VERSION(3,0,0)
1622 DB_plugin_t *
ddb_misc_waveform_GTK2_load(DB_functions_t * ddb)1623 ddb_misc_waveform_GTK2_load (DB_functions_t *ddb)
1624 {
1625 deadbeef = ddb;
1626 return &plugin.plugin;
1627 }
1628 #else
1629 DB_plugin_t *
ddb_misc_waveform_GTK3_load(DB_functions_t * ddb)1630 ddb_misc_waveform_GTK3_load (DB_functions_t *ddb)
1631 {
1632 deadbeef = ddb;
1633 return &plugin.plugin;
1634 }
1635 #endif
1636