1 /*
2     Musical Spectrum plugin for the DeaDBeeF audio player
3 
4     Copyright (C) 2015 Christian Boxdörfer <christian.boxdoerfer@posteo.de>
5 
6     Based on DeaDBeeFs stock spectrum.
7     Copyright (c) 2009-2015 Alexey Yakovenko <waker@users.sourceforge.net>
8     Copyright (c) 2011 William Pitcock <nenolod@dereferenced.org>
9 
10     This program is free software; you can redistribute it and/or
11     modify it under the terms of the GNU General Public License
12     as published by the Free Software Foundation; either version 2
13     of the License, or (at your option) any later version.
14 
15     This program is distributed in the hope that it will be useful,
16     but WITHOUT ANY WARRANTY; without even the implied warranty of
17     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18     GNU General Public License for more details.
19 
20     You should have received a copy of the GNU General Public License
21     along with this program; if not, write to the Free Software
22     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
23 */
24 
25 #include <sys/types.h>
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <math.h>
30 #include <gtk/gtk.h>
31 #include <fftw3.h>
32 
33 #include <deadbeef/deadbeef.h>
34 #include <deadbeef/gtkui_api.h>
35 
36 #include "fastftoi.h"
37 #include "config.h"
38 #include "config_dialog.h"
39 #include "utils.h"
40 #include "draw_utils.h"
41 #include "spectrum.h"
42 
43 DB_functions_t *deadbeef = NULL;
44 ddb_gtkui_t *gtkui_plugin = NULL;
45 
46 struct motion_context {
47     uint8_t entered;
48     double x;
49 };
50 
51 static struct motion_context motion_ctx;
52 
53 static enum PLAYBACK_STATUS { STOPPED = 0, PLAYING = 1, PAUSED = 2 };
54 static int playback_status = STOPPED;
55 
56 static char *notes[] = {"C0","C#0","D0","D#0","E0","F0","F#0","G0","G#0","A0","A#0","B0",
57                  "C1","C#1","D1","D#1","E1","F1","F#1","G1","G#1","A1","A#1","B1",
58                  "C2","C#2","D2","D#2","E2","F2","F#2","G2","G#2","A2","A#2","B2",
59                  "C3","C#3","D3","D#3","E3","F3","F#3","G3","G#3","A3","A#3","B3",
60                  "C4","C#4","D4","D#4","E4","F4","F#4","G4","G#4","A4","A#4","B4",
61                  "C5","C#5","D5","D#5","E5","F5","F#5","G5","G#5","A5","A#5","B5",
62                  "C6","C#6","D6","D#6","E6","F6","F#6","G6","G#6","A6","A#6","B6",
63                  "C7","C#7","D7","D#7","E7","F7","F#7","G7","G#7","A7","A#7","B7",
64                  "C8","C#8","D8","D#8","E8","F8","F#8","G8","G#8","A8","A#8","B8",
65                  "C9","C#9","D9","D#9","E9","F9","F#9","G9","G#9","A9","A#9","B9",
66                  "C10","C#10","D10","D#10","E10","F10","F#10","G10","G#10","A10","A#10","B10"
67                 };
68 
69 static int
get_align_pos(int width,int bands,int bar_width)70 get_align_pos (int width, int bands, int bar_width)
71 {
72     int left = 0;
73     switch (CONFIG_ALIGNMENT) {
74         case LEFT:
75             left = 0;
76             break;
77         case RIGHT:
78             left = MIN (width, width - (bar_width * bands));
79             break;
80         case CENTER:
81             left = MAX (0, (width - (bar_width * bands))/2);
82             break;
83         default:
84             left = 0;
85             break;
86     }
87     return left;
88 }
89 
90 static void
do_fft(w_spectrum_t * w)91 do_fft (w_spectrum_t *w)
92 {
93     g_return_if_fail (w->samples && w->buffered >= CONFIG_FFT_SIZE);
94 
95     deadbeef->mutex_lock (w->mutex);
96     double real,imag;
97 
98     for (int i = 0; i < CONFIG_FFT_SIZE; i++) {
99         w->fft_in[i] = w->samples[i] * w->window[i];
100     }
101 
102     fftw_execute (w->p_r2c);
103     for (int i = 0; i < CONFIG_FFT_SIZE/2; i++)
104     {
105         real = w->fft_out[i][0];
106         imag = w->fft_out[i][1];
107         w->spectrum_data[i] = (real*real + imag*imag);
108     }
109     deadbeef->mutex_unlock (w->mutex);
110 }
111 
112 static int need_redraw = 0;
113 
114 static gboolean
spectrum_draw_cb(void * data)115 spectrum_draw_cb (void *data) {
116     w_spectrum_t *s = data;
117     gtk_widget_queue_draw (s->drawarea);
118     return TRUE;
119 }
120 
121 static gboolean
spectrum_redraw_cb(void * data)122 spectrum_redraw_cb (void *data) {
123     w_spectrum_t *s = data;
124     gtk_widget_queue_draw (s->drawarea);
125     return FALSE;
126 }
127 
128 static int
on_config_changed(gpointer user_data,uintptr_t ctx)129 on_config_changed (gpointer user_data, uintptr_t ctx)
130 {
131     need_redraw = 1;
132     w_spectrum_t *w = user_data;
133     load_config ();
134     deadbeef->mutex_lock (w->mutex);
135     if (w->p_r2c) {
136         fftw_destroy_plan (w->p_r2c);
137     }
138     create_window_table (w);
139     create_frequency_table (w);
140     create_gradient_table (w->colors, CONFIG_GRADIENT_COLORS, CONFIG_NUM_COLORS);
141 
142     w->p_r2c = fftw_plan_dft_r2c_1d (CLAMP (CONFIG_FFT_SIZE, 512, MAX_FFT_SIZE), w->fft_in, w->fft_out, FFTW_ESTIMATE);
143     memset (w->spectrum_data, 0, sizeof (double) * MAX_FFT_SIZE);
144     deadbeef->mutex_unlock (w->mutex);
145     g_idle_add (spectrum_redraw_cb, w);
146     return 0;
147 }
148 
149 ///// spectrum vis
150 static void
w_spectrum_destroy(ddb_gtkui_widget_t * w)151 w_spectrum_destroy (ddb_gtkui_widget_t *w) {
152     w_spectrum_t *s = (w_spectrum_t *)w;
153     deadbeef->vis_waveform_unlisten (w);
154     if (s->spectrum_data) {
155         free (s->spectrum_data);
156         s->spectrum_data = NULL;
157     }
158     if (s->samples) {
159         free (s->samples);
160         s->samples = NULL;
161     }
162     if (s->p_r2c) {
163         fftw_destroy_plan (s->p_r2c);
164     }
165     if (s->fft_in) {
166         fftw_free (s->fft_in);
167         s->fft_in = NULL;
168     }
169     if (s->fft_out) {
170         fftw_free (s->fft_out);
171         s->fft_out = NULL;
172     }
173     if (s->drawtimer) {
174         g_source_remove (s->drawtimer);
175         s->drawtimer = 0;
176     }
177     if (s->surf) {
178         cairo_surface_destroy (s->surf);
179         s->surf = NULL;
180     }
181     if (s->surf_data) {
182         free (s->surf_data);
183         s->surf_data = NULL;
184     }
185     if (s->mutex) {
186         deadbeef->mutex_free (s->mutex);
187         s->mutex = 0;
188     }
189 }
190 
191 static gboolean
spectrum_remove_refresh_interval(gpointer user_data)192 spectrum_remove_refresh_interval (gpointer user_data)
193 {
194     w_spectrum_t *w = user_data;
195     if (w->drawtimer) {
196         g_source_remove (w->drawtimer);
197         w->drawtimer = 0;
198     }
199     return TRUE;
200 }
201 
202 static gboolean
spectrum_set_refresh_interval(gpointer user_data,int interval)203 spectrum_set_refresh_interval (gpointer user_data, int interval)
204 {
205     w_spectrum_t *w = user_data;
206     g_return_val_if_fail (w && interval > 0, FALSE);
207 
208     spectrum_remove_refresh_interval (w);
209     w->drawtimer = g_timeout_add (interval, spectrum_draw_cb, w);
210     return TRUE;
211 }
212 
213 static void
spectrum_wavedata_listener(void * ctx,ddb_audio_data_t * data)214 spectrum_wavedata_listener (void *ctx, ddb_audio_data_t *data) {
215     w_spectrum_t *w = ctx;
216     g_return_if_fail (w->samples);
217 
218     deadbeef->mutex_lock (w->mutex);
219     const int nsamples = data->nframes;
220     const int sz = MIN (CONFIG_FFT_SIZE, nsamples);
221     const int n = CONFIG_FFT_SIZE - sz;
222     memmove (w->samples, w->samples + sz, n * sizeof (double));
223 
224     int pos = 0;
225     for (int i = 0; i < sz && pos < nsamples; i++, pos++ ) {
226         w->samples[n+i] = -1000.0;
227         for (int j = 0; j < data->fmt->channels; j++) {
228             w->samples[n + i] = MAX (w->samples[n + i], data->data[ftoi (pos * data->fmt->channels) + j]);
229         }
230     }
231     deadbeef->mutex_unlock (w->mutex);
232     if (w->buffered < CONFIG_FFT_SIZE) {
233         w->buffered += sz;
234     }
235 }
236 
237 static inline float
spectrum_get_value(gpointer user_data,int start,int end)238 spectrum_get_value (gpointer user_data, int start, int end)
239 {
240     w_spectrum_t *w = user_data;
241     if (start >= end) {
242         return w->spectrum_data[end];
243     }
244     float value = 0.0;
245     for (int i = start; i < end; i++) {
246         value = MAX (w->spectrum_data[i],value);
247     }
248     return value;
249 }
250 
251 static float
spectrum_interpolate(gpointer user_data,int bands,int index)252 spectrum_interpolate (gpointer user_data, int bands, int index)
253 {
254     w_spectrum_t *w = user_data;
255     float x = 0.0;
256     if (index <= w->low_res_end+1) {
257         const float v1 = log10f (w->spectrum_data[w->keys[index]]);
258 
259         // find index of next value
260         int j = 0;
261         while (index+j < bands && w->keys[index+j] == w->keys[index]) {
262             j++;
263         }
264         const float v2 = log10f (w->spectrum_data[w->keys[index+j]]);
265 
266         int l = j;
267         while (index+l < bands && w->keys[index+l] == w->keys[index+j]) {
268             l++;
269         }
270         const float v3 = log10f (w->spectrum_data[w->keys[index+l]]);
271 
272         int k = 0;
273         while ((k+index) >= 0 && w->keys[k+index] == w->keys[index]) {
274             j++;
275             k--;
276         }
277         const float v0 = log10f (w->spectrum_data[w->keys[CLAMP(index+k,0,bands-1)]]);
278 
279         //x = linear_interpolate (v1,v2,(1.0/(j-1)) * ((-1 * k) - 1));
280         x = 10 * lagrange_interpolate (v0,v1,v2,v3,1 + (1.0 / (j - 1)) * ((-1 * k) - 1));
281     }
282     else {
283         int start = 0;
284         int end = 0;
285         if (index > 0) {
286             start = (w->keys[index] - w->keys[index-1])/2 + w->keys[index-1];
287             if (start == w->keys[index-1]) start = w->keys[index];
288         }
289         else {
290             start = w->keys[index];
291         }
292         if (index < bands-1) {
293             end = (w->keys[index+1] - w->keys[index])/2 + w->keys[index];
294             if (end == w->keys[index+1]) end = w->keys[index];
295         }
296         else {
297             end = w->keys[index];
298         }
299         x = 10 * log10f (spectrum_get_value (w, start, end));
300     }
301     return x;
302 }
303 
304 static void
spectrum_render(gpointer user_data,int bands)305 spectrum_render (gpointer user_data, int bands)
306 {
307     w_spectrum_t *w = user_data;
308 
309     if (playback_status != STOPPED) {
310         if (playback_status == PAUSED) {
311             spectrum_remove_refresh_interval (w);
312         }
313         else {
314             do_fft (w);
315 
316             const float bar_falloff = CONFIG_BAR_FALLOFF/1000.0 * CONFIG_REFRESH_INTERVAL;
317             const float peak_falloff = CONFIG_PEAK_FALLOFF/1000.0 * CONFIG_REFRESH_INTERVAL;
318             const int bar_delay = ftoi (CONFIG_BAR_DELAY/CONFIG_REFRESH_INTERVAL);
319             const int peak_delay = ftoi (CONFIG_PEAK_DELAY/CONFIG_REFRESH_INTERVAL);
320 
321             for (int i = 0; i < bands; i++) {
322                 // interpolate
323                 float x = spectrum_interpolate (w, bands, i);
324 
325                 // TODO: get rid of hardcoding
326                 x += CONFIG_DB_RANGE - 63;
327                 x = CLAMP (x, 0, CONFIG_DB_RANGE);
328                 w->bars[i] = CLAMP (w->bars[i], 0, CONFIG_DB_RANGE);
329                 w->peaks[i] = CLAMP (w->peaks[i], 0, CONFIG_DB_RANGE);
330 
331                 if (CONFIG_BAR_FALLOFF != -1) {
332                     if (w->delay_bars[i] < 0) {
333                         w->bars[i] -= bar_falloff;
334                     }
335                     else {
336                         w->delay_bars[i]--;
337                     }
338                 }
339                 else {
340                     w->bars[i] = 0;
341                 }
342                 if (CONFIG_PEAK_FALLOFF != -1) {
343                     if (w->delay_peaks[i] < 0) {
344                         w->peaks[i] -= peak_falloff;
345                     }
346                     else {
347                         w->delay_peaks[i]--;
348                     }
349                 }
350                 else {
351                     w->peaks[i] = 0;
352                 }
353 
354                 if (x > w->bars[i])
355                 {
356                     w->bars[i] = x;
357                     w->delay_bars[i] = bar_delay;
358                 }
359                 if (x > w->peaks[i]) {
360                     w->peaks[i] = x;
361                     w->delay_peaks[i] = peak_delay;
362                 }
363                 if (w->peaks[i] < w->bars[i]) {
364                     w->peaks[i] = w->bars[i];
365                 }
366             }
367         }
368     }
369     else if (playback_status == STOPPED) {
370         spectrum_remove_refresh_interval (w);
371         for (int i = 0; i < bands; i++) {
372                 w->bars[i] = 0;
373                 w->delay_bars[i] = 0;
374                 w->peaks[i] = 0;
375                 w->delay_peaks[i] = 0;
376         }
377     }
378 
379 }
380 
381 static void
draw_static_content(unsigned char * data,int stride,int bands,int width,int height)382 draw_static_content (unsigned char *data, int stride, int bands, int width, int height)
383 {
384     g_return_if_fail (data);
385 
386     memset (data, 0, height * stride);
387 
388     int barw;
389     if (CONFIG_GAPS || CONFIG_BAR_W > 1)
390         barw = CLAMP (width / bands, 2, 20);
391     else
392         barw = CLAMP (width / bands, 2, 20) - 1;
393 
394     const int left = get_align_pos (width, bands, barw);
395 
396     //draw background
397     _draw_background (data, width, height, CONFIG_COLOR_BG32);
398     // draw vertical grid
399     if (CONFIG_ENABLE_VGRID && CONFIG_GAPS) {
400         const int num_lines = MIN (width/barw, bands);
401         for (int i = 0; i < num_lines; i++) {
402             _draw_vline (data, stride, left + barw * i, 0, height-1, CONFIG_COLOR_VGRID32);
403         }
404     }
405 
406     // draw octave grid
407     if (CONFIG_ENABLE_OCTAVE_GRID) {
408         const int spectrum_width = MIN (barw * bands, width);
409         const float octave_width = CLAMP (((float)spectrum_width / 11), 1, spectrum_width);
410         int x = 0;
411         for (float i = left; i < spectrum_width - 1 && i < width - 1; i += octave_width) {
412             x = ftoi (i) + (CONFIG_GAPS ? (ftoi (i) % barw) : 0);
413             _draw_vline (data, stride, x, 0, height-1, CONFIG_COLOR_OCTAVE_GRID32);
414         }
415     }
416 
417     const int hgrid_num = CONFIG_DB_RANGE/10;
418     // draw horizontal grid
419     if (CONFIG_ENABLE_HGRID && height > 2*hgrid_num && width > 1) {
420         for (int i = 1; i < hgrid_num; i++) {
421             _draw_hline (data, stride, 0, ftoi (i/(float)hgrid_num * height), width-1, CONFIG_COLOR_HGRID32);
422         }
423     }
424 }
425 
426 static void
spectrum_draw_cairo(gpointer user_data,cairo_t * cr,int bands,int width,int height)427 spectrum_draw_cairo (gpointer user_data, cairo_t *cr, int bands, int width, int height)
428 {
429     w_spectrum_t *w = user_data;
430 
431     const float base_s = (height / (float)CONFIG_DB_RANGE);
432     const int barw = CLAMP (width / bands, 2, 20) - 1;
433     const int left = get_align_pos (width, bands, barw);
434 
435     // draw background
436     cairo_set_source_rgb (cr, CONFIG_COLOR_BG.red/65535.f, CONFIG_COLOR_BG.green/65535.f, CONFIG_COLOR_BG.blue/65535.f);
437     cairo_rectangle (cr, 0, 0, width, height);
438     cairo_fill (cr);
439 
440     // create gradient
441     cairo_pattern_t *pat;
442     if (CONFIG_GRADIENT_ORIENTATION == 0) {
443         pat = cairo_pattern_create_linear (0, 0, 0, height);
444     }
445     else {
446         pat = cairo_pattern_create_linear (0, 0, width, 0);
447     }
448 
449     if (CONFIG_NUM_COLORS > 1) {
450         float step = 1.0/(CONFIG_NUM_COLORS - 1);
451         float grad_pos = 0;
452         for (int i = 0; i < CONFIG_NUM_COLORS; i++) {
453             cairo_pattern_add_color_stop_rgb (pat, grad_pos, CONFIG_GRADIENT_COLORS[i].red/65535.f, CONFIG_GRADIENT_COLORS[i].green/65535.f, CONFIG_GRADIENT_COLORS[i].blue/65535.f);
454             grad_pos += step;
455         }
456         cairo_set_source (cr, pat);
457     }
458     else {
459         cairo_set_source_rgb (cr, CONFIG_GRADIENT_COLORS[0].red/65535.f, CONFIG_GRADIENT_COLORS[0].green/65535.f, CONFIG_GRADIENT_COLORS[0].blue/65535.f);
460     }
461 
462     // draw spectrum
463     cairo_set_line_width (cr, 1);
464     cairo_line_to (cr, 0, height);
465     float py = height - base_s * w->bars[0];
466     cairo_line_to (cr, 0, py);
467     for (gint i = 0; i < bands; i++)
468     {
469         const float x = left + barw * i;
470         const float y = height - base_s * w->bars[i];
471 
472         if (!CONFIG_FILL_SPECTRUM) {
473             cairo_move_to (cr, x - 0.5, py);
474         }
475         cairo_line_to (cr, x + 0.5, y);
476         py = y;
477     }
478     if (CONFIG_FILL_SPECTRUM) {
479         cairo_move_to (cr, left + barw * bands, 0);
480         cairo_line_to (cr, 0, height);
481 
482         cairo_close_path (cr);
483         cairo_fill (cr);
484     }
485     else {
486         cairo_stroke (cr);
487     }
488     cairo_pattern_destroy(pat);
489 
490     // draw octave grid
491     if (CONFIG_ENABLE_OCTAVE_GRID) {
492         cairo_set_source_rgba (cr, CONFIG_COLOR_OCTAVE_GRID.red/65535.f, CONFIG_COLOR_OCTAVE_GRID.green/65535.f, CONFIG_COLOR_OCTAVE_GRID.blue/65535.f, 0.2);
493         int spectrum_width = MIN (barw * bands, width);
494         float octave_width = CLAMP (((float)spectrum_width / 11), 1, spectrum_width);
495         for (float i = left; i < spectrum_width - 1 && i < width - 1; i += octave_width) {
496             cairo_move_to (cr, i, 0);
497             cairo_line_to (cr, i, height);
498             cairo_stroke (cr);
499         }
500     }
501 
502     // draw horizontal grid
503     const int hgrid_num = CONFIG_DB_RANGE/10;
504     if (CONFIG_ENABLE_HGRID && height > 2*hgrid_num && width > 1) {
505         cairo_set_source_rgba (cr, CONFIG_COLOR_HGRID.red/65535.f, CONFIG_COLOR_HGRID.green/65535.f, CONFIG_COLOR_HGRID.blue/65535.f, 0.2);
506         for (int i = 1; i < hgrid_num; i++) {
507             cairo_move_to (cr, 0, i/(float)hgrid_num * height);
508             cairo_line_to (cr, width, i/(float)hgrid_num * height);
509             cairo_stroke (cr);
510         }
511     }
512 
513     // draw octave grid on hover
514     if (CONFIG_DISPLAY_OCTAVES && motion_ctx.entered) {
515         int band_offset = (((int)motion_ctx.x % ((barw * bands) / 11)))/barw;
516         cairo_set_source_rgba (cr, 1, 0, 0, 0.5);
517         for (gint i = 0; i < bands; i++) {
518             int octave_enabled  = (motion_ctx.entered && (i % (bands / 11)) == band_offset) ? 1 : 0;
519             float x = left + barw * i;
520             if (octave_enabled) {
521                 cairo_move_to (cr, x, 0);
522                 cairo_line_to (cr, x, height);
523                 cairo_stroke (cr);
524             }
525         }
526     }
527 }
528 
529 static void
spectrum_draw_custom(gpointer user_data,cairo_t * cr,int bands,int width,int height)530 spectrum_draw_custom (gpointer user_data, cairo_t *cr, int bands, int width, int height)
531 {
532     w_spectrum_t *w = user_data;
533     // start drawing
534     int stride = 0;
535     if (!w->surf || !w->surf_data || cairo_image_surface_get_width (w->surf) != width || cairo_image_surface_get_height (w->surf) != height) {
536         need_redraw = 1;
537         if (w->surf) {
538             cairo_surface_destroy (w->surf);
539             w->surf = NULL;
540         }
541         if (w->surf_data) {
542             free (w->surf_data);
543             w->surf_data = NULL;
544         }
545         w->surf = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height);
546         stride = cairo_image_surface_get_stride (w->surf);
547         w->surf_data = malloc (stride * height);
548     }
549     const float base_s = (height / (float)CONFIG_DB_RANGE);
550 
551     cairo_surface_flush (w->surf);
552 
553     unsigned char *data = cairo_image_surface_get_data (w->surf);
554     g_return_if_fail (data);
555 
556     stride = cairo_image_surface_get_stride (w->surf);
557     if (need_redraw) {
558         // widget size or config changed, background needs to be redrawn
559         draw_static_content (data, stride, bands, width, height);
560         memcpy (w->surf_data, data, stride * height);
561         need_redraw = 0;
562     }
563     else {
564         // just copy pre-rendered background to surface
565         memcpy (data, w->surf_data, stride * height);
566     }
567 
568     int barw;
569     if (CONFIG_GAPS || CONFIG_BAR_W > 1)
570         barw = CLAMP (width / bands, 2, 20);
571     else
572         barw = CLAMP (width / bands, 2, 20) - 1;
573 
574     const int left = get_align_pos (width, bands, barw);
575 
576     int band_offset = (((int)motion_ctx.x % ((barw * bands) / 11)))/barw;
577     for (gint i = 0; i < bands; i++)
578     {
579         int x = left + barw * i;
580         int octave_enabled = 0;
581         if (CONFIG_DISPLAY_OCTAVES && motion_ctx.entered) {
582             octave_enabled = (motion_ctx.entered && (i % (bands / 11)) == band_offset) ? 1 : 0;
583         }
584         int y = CLAMP (height - ftoi (w->bars[i] * base_s), 0, height);
585         int bw;
586 
587         if (CONFIG_GAPS) {
588             bw = barw -1;
589             x += 1;
590         }
591         else {
592             bw = barw;
593         }
594 
595         if (x + bw >= width) {
596             bw = width-x-1;
597         }
598 
599         if ((y >= 0 && y < height - 1) || octave_enabled) {
600             if (CONFIG_GRADIENT_ORIENTATION == 0) {
601                 if (CONFIG_ENABLE_BAR_MODE == 0) {
602                     _draw_bar_gradient_v (w->colors, data, stride, x, y, bw, height-y, height);
603                 }
604                 else {
605                     _draw_bar_gradient_bar_mode_v (w->colors, data, stride, x, y, bw, height-y, height);
606                 }
607             }
608             else {
609                 if (CONFIG_ENABLE_BAR_MODE == 0) {
610                     _draw_bar_gradient_h (w->colors, data, stride, x, y, bw, height-y, width);
611                 }
612                 else {
613                     _draw_bar_gradient_bar_mode_h (w->colors, data, stride, x, y, bw, height-y, width);
614                 }
615             }
616             if (octave_enabled) {
617                 _draw_bar (data, stride, x, y, bw, height - y, 0xFF0000);
618                 _draw_bar (data, stride, x, 0, bw, y, 0x888888);
619             }
620         }
621         y = height - w->peaks[i] * base_s;
622         if (y > 0 && y < height-1) {
623             if (CONFIG_GRADIENT_ORIENTATION == 0) {
624                 _draw_bar_gradient_v (w->colors, data, stride, x, y, bw, 1, height);
625             }
626             else {
627                 _draw_bar_gradient_h (w->colors, data, stride, x, y, bw, 1, width);
628             }
629             if (octave_enabled) {
630                 _draw_bar (data, stride, x, y, bw, 1, 0xFF0000);
631             }
632         }
633     }
634 
635     cairo_surface_mark_dirty (w->surf);
636     cairo_set_source_surface (cr, w->surf, 0, 0);
637     cairo_paint (cr);
638 }
639 
640 static gboolean
spectrum_draw(GtkWidget * widget,cairo_t * cr,gpointer user_data)641 spectrum_draw (GtkWidget *widget, cairo_t *cr, gpointer user_data) {
642     w_spectrum_t *w = user_data;
643     g_return_val_if_fail (w->samples, FALSE);
644 
645     GtkAllocation a;
646     gtk_widget_get_allocation (w->drawarea, &a);
647 
648     static int last_bar_w = -1;
649     if (a.width != last_bar_w || need_redraw) {
650         create_frequency_table(w);
651     }
652     last_bar_w = a.width;
653 
654     const int bands = get_num_bars ();
655     const int width = a.width;
656     const int height = a.height;
657 
658     spectrum_render (w, bands);
659 
660     if (!CONFIG_DRAW_STYLE) {
661         spectrum_draw_custom (w, cr, bands, width, height);
662     }
663     else {
664         spectrum_draw_cairo (w, cr, bands, width, height);
665     }
666 
667     return FALSE;
668 }
669 
670 static gboolean
spectrum_expose_event(GtkWidget * widget,GdkEventExpose * event,gpointer user_data)671 spectrum_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer user_data)
672 {
673     cairo_t *cr = gdk_cairo_create (gtk_widget_get_window (widget));
674     gboolean res = spectrum_draw (widget, cr, user_data);
675     cairo_destroy (cr);
676     return res;
677 }
678 
679 
680 static gboolean
spectrum_button_press_event(GtkWidget * widget,GdkEventButton * event,gpointer user_data)681 spectrum_button_press_event (GtkWidget *widget, GdkEventButton *event, gpointer user_data)
682 {
683     //w_spectrum_t *w = user_data;
684     return TRUE;
685 }
686 
687 static gboolean
spectrum_button_release_event(GtkWidget * widget,GdkEventButton * event,gpointer user_data)688 spectrum_button_release_event (GtkWidget *widget, GdkEventButton *event, gpointer user_data)
689 {
690     w_spectrum_t *w = user_data;
691     if (event->button == 3) {
692       gtk_menu_popup (GTK_MENU (w->popup), NULL, NULL, NULL, w->drawarea, 0, gtk_get_current_event_time ());
693     }
694     return TRUE;
695 }
696 
697 static gboolean
spectrum_enter_notify_event(GtkWidget * widget,GdkEventMotion * event,gpointer user_data)698 spectrum_enter_notify_event (GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
699 {
700     motion_ctx.entered = 1;
701     return FALSE;
702 }
703 
704 static gboolean
spectrum_leave_notify_event(GtkWidget * widget,GdkEventMotion * event,gpointer user_data)705 spectrum_leave_notify_event (GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
706 {
707     w_spectrum_t *w = user_data;
708     motion_ctx.entered = 0;
709     gtk_widget_queue_draw (w->drawarea);
710     return FALSE;
711 }
712 
713 static gboolean
spectrum_motion_notify_event(GtkWidget * widget,GdkEventMotion * event,gpointer user_data)714 spectrum_motion_notify_event (GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
715 {
716     w_spectrum_t *w = user_data;
717     GtkAllocation a;
718     gtk_widget_get_allocation (widget, &a);
719     if (CONFIG_DISPLAY_OCTAVES) {
720         gtk_widget_queue_draw (w->drawarea);
721     }
722 
723     motion_ctx.x = event->x - 1;
724 
725     const int num_bars = get_num_bars ();
726     int barw;
727 
728     if (CONFIG_GAPS && !CONFIG_DRAW_STYLE) {
729         barw = CLAMP (a.width / num_bars, 2, 20);
730     }
731     else {
732         barw = CLAMP (a.width / num_bars, 2, 20) - 1;
733     }
734 
735     const int left = get_align_pos (a.width, num_bars, barw);
736 
737     if (event->x > left && event->x < left + barw * num_bars) {
738         int pos = CLAMP ((int)((event->x-1-left)/barw),0,num_bars-1);
739         int npos = ftoi( pos * 132 / num_bars );
740         char tooltip_text[20];
741         snprintf (tooltip_text, sizeof (tooltip_text), "%5.0f Hz (%s)", w->freq[pos], notes[npos]);
742         gtk_widget_set_tooltip_text (widget, tooltip_text);
743         return TRUE;
744     }
745     return FALSE;
746 }
747 
748 static int
spectrum_message(ddb_gtkui_widget_t * widget,uint32_t id,uintptr_t ctx,uint32_t p1,uint32_t p2)749 spectrum_message (ddb_gtkui_widget_t *widget, uint32_t id, uintptr_t ctx, uint32_t p1, uint32_t p2)
750 {
751     w_spectrum_t *w = (w_spectrum_t *)widget;
752 
753     int samplerate_temp = w->samplerate;
754     switch (id) {
755         case DB_EV_SONGSTARTED:
756             playback_status = PLAYING;
757             w->samplerate = deadbeef->get_output ()->fmt.samplerate;
758             if (w->samplerate == 0) w->samplerate = 44100;
759             if (samplerate_temp != w->samplerate) {
760                 create_frequency_table (w);
761             }
762             spectrum_set_refresh_interval (w, CONFIG_REFRESH_INTERVAL);
763             break;
764         case DB_EV_CONFIGCHANGED:
765             on_config_changed (w, ctx);
766             if (deadbeef->get_output ()->state () == OUTPUT_STATE_PLAYING) {
767                 spectrum_set_refresh_interval (w, CONFIG_REFRESH_INTERVAL);
768             }
769             break;
770         case DB_EV_PAUSED:
771             if (deadbeef->get_output ()->state () == OUTPUT_STATE_PLAYING) {
772                 playback_status = PLAYING;
773                 spectrum_set_refresh_interval (w, CONFIG_REFRESH_INTERVAL);
774             }
775             else {
776                 playback_status = PAUSED;
777             }
778             break;
779         case DB_EV_STOP:
780             playback_status = STOPPED;
781             g_idle_add (spectrum_redraw_cb, w);
782             break;
783     }
784     return 0;
785 }
786 
787 static void
spectrum_init(w_spectrum_t * w)788 spectrum_init (w_spectrum_t *w) {
789     w_spectrum_t *s = (w_spectrum_t *)w;
790     load_config ();
791     deadbeef->mutex_lock (s->mutex);
792     s->samples = malloc (sizeof (double) * MAX_FFT_SIZE);
793     memset (s->samples, 0, sizeof (double) * MAX_FFT_SIZE);
794     s->spectrum_data = malloc (sizeof (double) * MAX_FFT_SIZE);
795     memset (s->spectrum_data, 0, sizeof (double) * MAX_FFT_SIZE);
796 
797     s->fft_in = fftw_malloc (sizeof (double) * MAX_FFT_SIZE);
798     memset (s->fft_in, 0, sizeof (double) * MAX_FFT_SIZE);
799     s->fft_out = fftw_malloc (sizeof (fftw_complex) * MAX_FFT_SIZE);
800     memset (s->fft_out, 0, sizeof (double) * MAX_FFT_SIZE);
801 
802     s->p_r2c = fftw_plan_dft_r2c_1d (CONFIG_FFT_SIZE, s->fft_in, s->fft_out, FFTW_ESTIMATE);
803 
804     s->buffered = 0;
805     s->samplerate = deadbeef->get_output ()->fmt.samplerate;
806     if (s->samplerate == 0) s->samplerate = 44100;
807 
808     create_window_table (s);
809     create_frequency_table (s);
810     create_gradient_table (s->colors, CONFIG_GRADIENT_COLORS, CONFIG_NUM_COLORS);
811 
812     if (deadbeef->get_output ()->state () == OUTPUT_STATE_PLAYING) {
813         playback_status = PLAYING;
814         spectrum_set_refresh_interval (w, CONFIG_REFRESH_INTERVAL);
815     }
816     deadbeef->vis_waveform_listen (w, spectrum_wavedata_listener);
817     deadbeef->mutex_unlock (s->mutex);
818     need_redraw = 1;
819 }
820 
821 static ddb_gtkui_widget_t *
w_musical_spectrum_create(void)822 w_musical_spectrum_create (void) {
823     w_spectrum_t *w = malloc (sizeof (w_spectrum_t));
824     memset (w, 0, sizeof (w_spectrum_t));
825 
826     w->base.widget = gtk_event_box_new ();
827     w->base.destroy  = w_spectrum_destroy;
828     w->base.message = spectrum_message;
829     w->drawarea = gtk_drawing_area_new ();
830     w->popup = gtk_menu_new ();
831     w->popup_item = gtk_menu_item_new_with_mnemonic ("Configure");
832     w->mutex = deadbeef->mutex_create ();
833 
834     gtk_container_add (GTK_CONTAINER (w->base.widget), w->drawarea);
835     gtk_container_add (GTK_CONTAINER (w->popup), w->popup_item);
836     gtk_widget_show (w->drawarea);
837     gtk_widget_show (w->popup);
838     gtk_widget_show (w->popup_item);
839 
840     gtk_widget_add_events (w->drawarea,
841             GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK );
842 
843 #if !GTK_CHECK_VERSION(3,0,0)
844     g_signal_connect_after ((gpointer) w->drawarea, "expose_event", G_CALLBACK (spectrum_expose_event), w);
845 #else
846     g_signal_connect_after ((gpointer) w->drawarea, "draw", G_CALLBACK (spectrum_expose_event), w);
847 #endif
848     g_signal_connect_after ((gpointer) w->drawarea, "button_press_event", G_CALLBACK (spectrum_button_press_event), w);
849     g_signal_connect_after ((gpointer) w->drawarea, "button_release_event", G_CALLBACK (spectrum_button_release_event), w);
850     g_signal_connect_after ((gpointer) w->drawarea, "motion_notify_event", G_CALLBACK (spectrum_motion_notify_event), w);
851     g_signal_connect_after ((gpointer) w->drawarea, "enter_notify_event", G_CALLBACK (spectrum_enter_notify_event), w);
852     g_signal_connect_after ((gpointer) w->drawarea, "leave_notify_event", G_CALLBACK (spectrum_leave_notify_event), w);
853     g_signal_connect_after ((gpointer) w->popup_item, "activate", G_CALLBACK (on_button_config), w);
854     gtkui_plugin->w_override_signals (w->base.widget, w);
855 
856     spectrum_init (w);
857     return (ddb_gtkui_widget_t *)w;
858 }
859 
860 static int
musical_spectrum_connect(void)861 musical_spectrum_connect (void)
862 {
863     gtkui_plugin = (ddb_gtkui_t *) deadbeef->plug_get_for_id (DDB_GTKUI_PLUGIN_ID);
864     if (gtkui_plugin) {
865         //trace("using '%s' plugin %d.%d\n", DDB_GTKUI_PLUGIN_ID, gtkui_plugin->gui.plugin.version_major, gtkui_plugin->gui.plugin.version_minor );
866         if (gtkui_plugin->gui.plugin.version_major == 2) {
867             // 0.6+, use the new widget API
868             gtkui_plugin->w_reg_widget ("Musical Spectrum", DDB_WF_SINGLE_INSTANCE, w_musical_spectrum_create, "musical_spectrum", NULL);
869             return 0;
870         }
871     }
872     return -1;
873 }
874 
875 static int
musical_spectrum_start(void)876 musical_spectrum_start (void)
877 {
878     load_config ();
879     return 0;
880 }
881 
882 static int
musical_spectrum_stop(void)883 musical_spectrum_stop (void)
884 {
885     save_config ();
886     return 0;
887 }
888 
889 static int
musical_spectrum_disconnect(void)890 musical_spectrum_disconnect (void)
891 {
892     gtkui_plugin = NULL;
893     return 0;
894 }
895 
896 static const char settings_dlg[] =
897     "property \"Refresh interval (ms): \"       spinbtn[10,1000,1] "        CONFSTR_MS_REFRESH_INTERVAL         " 25 ;\n"
898     "property \"Bar falloff (dB/s): \"          spinbtn[-1,1000,1] "        CONFSTR_MS_BAR_FALLOFF              " -1 ;\n"
899     "property \"Bar delay (ms): \"              spinbtn[0,10000,100] "      CONFSTR_MS_BAR_DELAY                " 0 ;\n"
900     "property \"Peak falloff (dB/s): \"         spinbtn[-1,1000,1] "        CONFSTR_MS_PEAK_FALLOFF             " 90 ;\n"
901     "property \"Peak delay (ms): \"             spinbtn[0,10000,100] "      CONFSTR_MS_PEAK_DELAY               " 500 ;\n"
902 ;
903 
904 DB_misc_t plugin = {
905     //DB_PLUGIN_SET_API_VERSION
906     .plugin.type            = DB_PLUGIN_MISC,
907     .plugin.api_vmajor      = 1,
908     .plugin.api_vminor      = 5,
909     .plugin.version_major   = 0,
910     .plugin.version_minor   = 2,
911 #if GTK_CHECK_VERSION(3,0,0)
912     .plugin.id              = "musical_spectrum-gtk3",
913 #else
914     .plugin.id              = "musical_spectrum",
915 #endif
916     .plugin.name            = "Musical Spectrum",
917     .plugin.descr           = "Musical Spectrum",
918     .plugin.copyright       =
919         "Copyright (C) 2015 Christian Boxdörfer <christian.boxdoerfer@posteo.de>\n"
920         "\n"
921         "Based on DeaDBeeFs stock spectrum.\n"
922         "\n"
923         "This program is free software; you can redistribute it and/or\n"
924         "modify it under the terms of the GNU General Public License\n"
925         "as published by the Free Software Foundation; either version 2\n"
926         "of the License, or (at your option) any later version.\n"
927         "\n"
928         "This program is distributed in the hope that it will be useful,\n"
929         "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
930         "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
931         "GNU General Public License for more details.\n"
932         "\n"
933         "You should have received a copy of the GNU General Public License\n"
934         "along with this program; if not, write to the Free Software\n"
935         "Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n"
936      ,
937     .plugin.website         = "https://github.com/cboxdoerfer/ddb_musical_spectrum",
938     .plugin.start           = musical_spectrum_start,
939     .plugin.stop            = musical_spectrum_stop,
940     .plugin.connect         = musical_spectrum_connect,
941     .plugin.disconnect      = musical_spectrum_disconnect,
942     .plugin.configdialog    = settings_dlg,
943 };
944 
945 #if !GTK_CHECK_VERSION(3,0,0)
946 DB_plugin_t *
ddb_vis_musical_spectrum_GTK2_load(DB_functions_t * ddb)947 ddb_vis_musical_spectrum_GTK2_load (DB_functions_t *ddb) {
948     deadbeef = ddb;
949     return &plugin.plugin;
950 }
951 #else
952 DB_plugin_t *
ddb_vis_musical_spectrum_GTK3_load(DB_functions_t * ddb)953 ddb_vis_musical_spectrum_GTK3_load (DB_functions_t *ddb) {
954     deadbeef = ddb;
955     return &plugin.plugin;
956 }
957 #endif
958