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