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