New plugin: FFT, A frequency analyzer plugin
[kugel-rb.git] / apps / plugins / fft / fft.c
blob531c9af4cb0defd33a362ea037c989f803b76470
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2009 Delyan Kratunov
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.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
21 #include "plugin.h"
23 #include "lib/helper.h"
24 #include "lib/xlcd.h"
25 #include "math.h"
26 #include "thread.h"
28 #ifndef HAVE_LCD_COLOR
29 #include "lib/grey.h"
30 #endif
32 PLUGIN_HEADER
34 #ifndef HAVE_LCD_COLOR
35 GREY_INFO_STRUCT
36 #endif
38 #if CONFIG_KEYPAD == ARCHOS_AV300_PAD
39 # define FFT_PREV_GRAPH BUTTON_LEFT
40 # define FFT_NEXT_GRAPH BUTTON_RIGHT
41 # define FFT_ORIENTATION BUTTON_F3
42 # define FFT_WINDOW BUTTON_F1
43 # define FFT_SCALE BUTTON_UP
44 # define FFT_QUIT BUTTON_OFF
46 #elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \
47 (CONFIG_KEYPAD == IRIVER_H300_PAD)
48 # define FFT_PREV_GRAPH BUTTON_LEFT
49 # define FFT_NEXT_GRAPH BUTTON_RIGHT
50 # define FFT_ORIENTATION BUTTON_REC
51 # define FFT_WINDOW BUTTON_SELECT
52 # define FFT_SCALE BUTTON_UP
53 # define FFT_QUIT BUTTON_OFF
55 #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
56 (CONFIG_KEYPAD == IPOD_3G_PAD) || \
57 (CONFIG_KEYPAD == IPOD_1G2G_PAD)
58 # define MINESWP_SCROLLWHEEL
59 # define FFT_PREV_GRAPH BUTTON_LEFT
60 # define FFT_NEXT_GRAPH BUTTON_RIGHT
61 # define FFT_ORIENTATION (BUTTON_SELECT | BUTTON_LEFT)
62 # define FFT_WINDOW (BUTTON_SELECT | BUTTON_RIGHT)
63 # define FFT_SCALE BUTTON_MENU
64 # define FFT_QUIT (BUTTON_SELECT | BUTTON_MENU)
66 #elif (CONFIG_KEYPAD == IAUDIO_X5M5_PAD)
67 # define FFT_PREV_GRAPH BUTTON_LEFT
68 # define FFT_NEXT_GRAPH BUTTON_RIGHT
69 # define FFT_ORIENTATION BUTTON_SELECT
70 # define FFT_WINDOW BUTTON_PLAY
71 # define FFT_SCALE BUTTON_UP
72 # define FFT_QUIT BUTTON_POWER
74 #elif (CONFIG_KEYPAD == GIGABEAT_PAD)
75 # define FFT_PREV_GRAPH BUTTON_LEFT
76 # define FFT_NEXT_GRAPH BUTTON_RIGHT
77 # define FFT_SCALE BUTTON_UP
78 # define FFT_ORIENTATION BUTTON_SELECT
79 # define FFT_WINDOW BUTTON_A
80 # define FFT_QUIT BUTTON_POWER
82 #elif (CONFIG_KEYPAD == SANSA_E200_PAD)
83 # define FFT_PREV_GRAPH BUTTON_LEFT
84 # define FFT_NEXT_GRAPH BUTTON_RIGHT
85 # define FFT_ORIENTATION BUTTON_SELECT
86 # define FFT_WINDOW BUTTON_REC
87 # define FFT_SCALE BUTTON_UP
88 # define FFT_QUIT BUTTON_POWER
90 #elif (CONFIG_KEYPAD == SANSA_FUZE_PAD)
91 # define FFT_PREV_GRAPH BUTTON_LEFT
92 # define FFT_NEXT_GRAPH BUTTON_RIGHT
93 # define FFT_ORIENTATION (BUTTON_SELECT | BUTTON_LEFT)
94 # define FFT_WINDOW (BUTTON_SELECT | BUTTON_RIGHT)
95 # define FFT_SCALE BUTTON_UP
96 # define FFT_QUIT BUTTON_POWER
98 #elif (CONFIG_KEYPAD == SANSA_C200_PAD)
99 # define FFT_PREV_GRAPH BUTTON_LEFT
100 # define FFT_NEXT_GRAPH BUTTON_RIGHT
101 # define FFT_ORIENTATION BUTTON_UP
102 # define FFT_WINDOW BUTTON_REC
103 # define FFT_SCALE BUTTON_SELECT
104 # define FFT_QUIT BUTTON_POWER
105 #elif (CONFIG_KEYPAD == SANSA_M200_PAD)
106 # define FFT_PREV_GRAPH BUTTON_LEFT
107 # define FFT_NEXT_GRAPH BUTTON_RIGHT
108 # define FFT_ORIENTATION BUTTON_UP
109 # define FFT_WINDOW BUTTON_DOWN
110 # define FFT_SCALE BUTTON_SELECT
111 # define FFT_QUIT BUTTON_POWER
112 #elif (CONFIG_KEYPAD == SANSA_CLIP_PAD)
113 # define FFT_PREV_GRAPH BUTTON_LEFT
114 # define FFT_NEXT_GRAPH BUTTON_RIGHT
115 # define FFT_ORIENTATION BUTTON_UP
116 # define FFT_WINDOW BUTTON_HOME
117 # define FFT_SCALE BUTTON_SELECT
118 # define FFT_QUIT BUTTON_POWER
120 #elif (CONFIG_KEYPAD == IRIVER_H10_PAD)
121 # define FFT_PREV_GRAPH BUTTON_LEFT
122 # define FFT_NEXT_GRAPH BUTTON_RIGHT
123 # define FFT_ORIENTATION BUTTON_FF
124 # define FFT_WINDOW BUTTON_SCROLL_UP
125 # define FFT_SCALE BUTTON_REW
126 # define FFT_QUIT BUTTON_POWER
128 #elif (CONFIG_KEYPAD == GIGABEAT_S_PAD)
129 # define FFT_PREV_GRAPH BUTTON_LEFT
130 # define FFT_NEXT_GRAPH BUTTON_RIGHT
131 # define FFT_ORIENTATION BUTTON_MENU
132 # define FFT_WINDOW BUTTON_PREV
133 # define FFT_SCALE BUTTON_UP
134 # define FFT_QUIT BUTTON_BACK
136 #elif (CONFIG_KEYPAD == MROBE100_PAD)
137 # define FFT_PREV_GRAPH BUTTON_LEFT
138 # define FFT_NEXT_GRAPH BUTTON_RIGHT
139 # define FFT_ORIENTATION BUTTON_PLAY
140 # define FFT_WINDOW BUTTON_SELECT
141 # define FFT_SCALE BUTTON_UP
142 # define FFT_QUIT BUTTON_POWER
144 #elif CONFIG_KEYPAD == IAUDIO_M3_PAD
145 # define FFT_PREV_GRAPH BUTTON_RC_REW
146 # define FFT_NEXT_GRAPH BUTTON_RC_FF
147 # define FFT_ORIENTATION BUTTON_RC_MODE
148 # define FFT_WINDOW BUTTON_RC_PLAY
149 # define FFT_SCALE BUTTON_RC_VOL_UP
150 # define FFT_QUIT BUTTON_RC_REC
152 #elif (CONFIG_KEYPAD == COWON_D2_PAD)
153 # define FFT_QUIT BUTTON_POWER
154 # define FFT_PREV_GRAPH BUTTON_PLUS
155 # define FFT_NEXT_GRAPH BUTTON_MINUS
157 #elif CONFIG_KEYPAD == CREATIVEZVM_PAD
158 # define FFT_PREV_GRAPH BUTTON_LEFT
159 # define FFT_NEXT_GRAPH BUTTON_RIGHT
160 # define FFT_ORIENTATION BUTTON_MENU
161 # define FFT_WINDOW BUTTON_SELECT
162 # define FFT_SCALE BUTTON_UP
163 # define FFT_QUIT BUTTON_BACK
165 #elif CONFIG_KEYPAD == PHILIPS_HDD1630_PAD
166 # define FFT_PREV_GRAPH BUTTON_LEFT
167 # define FFT_NEXT_GRAPH BUTTON_RIGHT
168 # define FFT_ORIENTATION BUTTON_SELECT
169 # define FFT_WINDOW BUTTON_MENU
170 # define FFT_SCALE BUTTON_UP
171 # define FFT_QUIT BUTTON_POWER
173 #elif (CONFIG_KEYPAD == SAMSUNG_YH_PAD)
174 # define FFT_PREV_GRAPH BUTTON_LEFT
175 # define FFT_NEXT_GRAPH BUTTON_RIGHT
176 # define FFT_ORIENTATION BUTTON_UP
177 # define FFT_WINDOW BUTTON_DOWN
178 # define FFT_SCALE BUTTON_FFWD
179 # define FFT_QUIT BUTTON_PLAY
181 #else
182 #error No keymap defined!
183 #endif
185 #ifdef HAVE_LCD_COLOR
186 #include "pluginbitmaps/fft_colors.h"
187 #endif
189 #include "kiss_fftr.h"
190 #include "_kiss_fft_guts.h" /* sizeof(struct kiss_fft_state) */
191 #include "const.h"
193 #define FFT_SIZE 2048
194 #define ARRAYSIZE_IN (FFT_SIZE)
195 #define ARRAYSIZE_OUT (FFT_SIZE/2)
196 #define ARRAYSIZE_PLOT (FFT_SIZE/4)
197 #define BUFSIZE_FFT (sizeof(struct kiss_fft_state)+sizeof(kiss_fft_cpx)*(FFT_SIZE-1))
198 #define BUFSIZE_FFTR (BUFSIZE_FFT+sizeof(struct kiss_fftr_state)+sizeof(kiss_fft_cpx)*(FFT_SIZE*3/2))
199 #define BUFSIZE BUFSIZE_FFTR
200 #define FFT_ALLOC kiss_fftr_alloc
201 #define FFT_FFT kiss_fftr
202 #define FFT_CFG kiss_fftr_cfg
204 #define __COEFF(type,size) type##_##size
205 #define _COEFF(x, y) __COEFF(x,y) /* force the preprocessor to evaluate FFT_SIZE) */
206 #define HANN_COEFF _COEFF(hann, FFT_SIZE)
207 #define HAMMING_COEFF _COEFF(hamming, FFT_SIZE)
209 /****************************** Globals ****************************/
211 static kiss_fft_scalar input[ARRAYSIZE_IN];
212 static kiss_fft_cpx output[ARRAYSIZE_OUT];
213 static int32_t plot[ARRAYSIZE_PLOT];
214 static char buffer[BUFSIZE];
216 #if LCD_DEPTH > 1 /* greyscale or color, enable spectrogram */
217 #define MODES_COUNT 3
218 #else
219 #define MODES_COUNT 2
220 #endif
222 const unsigned char* modes_text[] = { "Lines", "Bars", "Spectrogram" };
223 const unsigned char* scales_text[] = { "Linear scale", "Logarithmic scale" };
224 const unsigned char* window_text[] = { "Hamming window", "Hann window" };
226 struct mutex input_mutex;
227 bool input_thread_run = true;
228 bool input_thread_has_data = false;
230 struct {
231 int32_t mode;
232 bool logarithmic;
233 bool orientation_vertical;
234 int window_func;
235 struct {
236 int column;
237 int row;
238 } spectrogram;
239 struct {
240 bool orientation;
241 bool mode;
242 bool scale;
243 } changed;
244 } graph_settings;
246 #define COLORS BMPWIDTH_fft_colors
248 /************************* End of globals *************************/
250 /************************* Math functions *************************/
251 #define QLOG_MAX 286286
252 #define QLIN_MAX 1534588906
253 #define QLN_10 float_q16(2.302585093)
254 #define LIN_MAX (QLIN_MAX >> 16)
256 /* Returns logarithmically scaled values in S15.16 format */
257 inline int32_t get_log_value(int32_t value)
259 return Q16_DIV(fp16_log(value), QLN_10);
262 /* Apply window function to input
263 * 0 - Hamming window
264 * 1 - Hann window */
265 #define WINDOW_COUNT 2
266 void apply_window_func(char mode)
268 switch(mode)
270 case 0: /* Hamming window */
272 size_t i;
273 for (i = 0; i < ARRAYSIZE_IN; ++i)
275 input[i] = Q15_MUL(input[i] << 15, HAMMING_COEFF[i]) >> 15;
277 break;
279 case 1: /* Hann window */
281 size_t i;
282 for (i = 0; i < ARRAYSIZE_IN; ++i)
284 input[i] = Q15_MUL(input[i] << 15, HANN_COEFF[i]) >> 15;
286 break;
291 /* Calculates the magnitudes from complex numbers and returns the maximum */
292 int32_t calc_magnitudes(bool logarithmic)
294 int64_t tmp;
295 size_t i;
297 int32_t max = -2147483647;
299 /* Calculate the magnitude, discarding the phase.
300 * The sum of the squares can easily overflow the 15-bit (s15.16)
301 * requirement for fsqrt, so we scale the data down */
302 for (i = 0; i < ARRAYSIZE_PLOT; ++i)
304 tmp = output[i].r * output[i].r + output[i].i * output[i].i;
305 tmp <<= 16;
307 tmp = fsqrt64(tmp, 16);
309 if (logarithmic)
310 tmp = get_log_value(tmp & 0x7FFFFFFF);
312 plot[i] = tmp;
314 if (plot[i] > max)
315 max = plot[i];
317 return max;
319 /************************ End of math functions ***********************/
321 /********************* Plotting functions (modes) *********************/
322 void draw_lines_vertical(void);
323 void draw_lines_horizontal(void);
324 void draw_bars_vertical(void);
325 void draw_bars_horizontal(void);
326 void draw_spectrogram_vertical(void);
327 void draw_spectrogram_horizontal(void);
329 void draw(const unsigned char* message)
331 static uint32_t show_message = 0;
332 static unsigned char* last_message = 0;
334 static char last_mode = 0;
335 static bool last_orientation = true, last_scale = true;
337 if (message != 0)
339 last_message = (unsigned char*) message;
340 show_message = 5;
343 if(last_mode != graph_settings.mode)
345 last_mode = graph_settings.mode;
346 graph_settings.changed.mode = true;
348 if(last_scale != graph_settings.logarithmic)
350 last_scale = graph_settings.logarithmic;
351 graph_settings.changed.scale = true;
353 if(last_orientation != graph_settings.orientation_vertical)
355 last_orientation = graph_settings.orientation_vertical;
356 graph_settings.changed.orientation = true;
358 #ifdef HAVE_LCD_COLOR
359 rb->lcd_set_foreground(LCD_DEFAULT_FG);
360 rb->lcd_set_background(LCD_DEFAULT_BG);
361 #else
362 grey_set_foreground(GREY_BLACK);
363 grey_set_background(GREY_WHITE);
364 #endif
366 switch (graph_settings.mode)
368 default:
369 case 0: {
371 #ifdef HAVE_LCD_COLOR
372 rb->lcd_clear_display();
373 #else
374 grey_clear_display();
375 #endif
377 if (graph_settings.orientation_vertical)
378 draw_lines_vertical();
379 else
380 draw_lines_horizontal();
381 break;
383 case 1: {
385 #ifdef HAVE_LCD_COLOR
386 rb->lcd_clear_display();
387 #else
388 grey_clear_display();
389 #endif
391 if(graph_settings.orientation_vertical)
392 draw_bars_vertical();
393 else
394 draw_bars_horizontal();
396 break;
398 case 2: {
399 if(graph_settings.orientation_vertical)
400 draw_spectrogram_vertical();
401 else
402 draw_spectrogram_horizontal();
403 break;
407 if (show_message > 0)
409 /* We have a message to show */
411 int x, y;
412 #ifdef HAVE_LCD_COLOR
413 rb->lcd_getstringsize(last_message, &x, &y);
414 #else
415 grey_getstringsize(last_message, &x, &y);
416 #endif
417 /* x and y give the size of the box for the popup */
418 x += 6; /* 3 px of horizontal padding and */
419 y += 4; /* 2 px of vertical padding */
421 /* In vertical spectrogram mode, leave space for the popup
422 * before actually drawing it (if space is needed) */
423 if(graph_settings.mode == 2 &&
424 graph_settings.orientation_vertical &&
425 graph_settings.spectrogram.column > LCD_WIDTH-x-2)
427 #ifdef HAVE_LCD_COLOR
428 xlcd_scroll_left(graph_settings.spectrogram.column -
429 (LCD_WIDTH - x - 1));
430 #else
431 grey_scroll_left(graph_settings.spectrogram.column -
432 (LCD_WIDTH - x - 1));
433 #endif
434 graph_settings.spectrogram.column = LCD_WIDTH - x - 2;
437 #ifdef HAVE_LCD_COLOR
438 rb->lcd_set_foreground(LCD_DARKGRAY);
439 rb->lcd_fillrect(LCD_WIDTH-1-x, 0, LCD_WIDTH-1, y);
441 rb->lcd_set_foreground(LCD_DEFAULT_FG);
442 rb->lcd_set_background(LCD_DARKGRAY);
443 rb->lcd_putsxy(LCD_WIDTH-1-x+3, 2, last_message);
444 rb->lcd_set_background(LCD_DEFAULT_BG);
445 #else
446 grey_set_foreground(GREY_LIGHTGRAY);
447 grey_fillrect(LCD_WIDTH-1-x, 0, LCD_WIDTH-1, y);
449 grey_set_foreground(GREY_BLACK);
450 grey_set_background(GREY_LIGHTGRAY);
451 grey_putsxy(LCD_WIDTH-1-x+3, 2, last_message);
452 grey_set_background(GREY_WHITE);
453 #endif
455 show_message--;
457 else if(last_message != 0)
459 if(graph_settings.mode != 2)
461 /* These modes clear the screen themselves */
462 last_message = 0;
464 else /* Spectrogram mode - need to erase the popup */
466 int x, y;
467 #ifdef HAVE_LCD_COLOR
468 rb->lcd_getstringsize(last_message, &x, &y);
469 #else
470 grey_getstringsize(last_message, &x, &y);
471 #endif
472 /* Recalculate the size */
473 x += 6; /* 3 px of horizontal padding and */
474 y += 4; /* 2 px of vertical padding */
476 if(!graph_settings.orientation_vertical)
478 /* In horizontal spectrogram mode, just scroll up by Y lines */
479 #ifdef HAVE_LCD_COLOR
480 xlcd_scroll_up(y);
481 #else
482 grey_scroll_up(y);
483 #endif
484 graph_settings.spectrogram.row -= y;
485 if(graph_settings.spectrogram.row < 0)
486 graph_settings.spectrogram.row = 0;
488 else
490 /* In vertical spectrogram mode, erase the popup */
491 #ifdef HAVE_LCD_COLOR
492 rb->lcd_set_foreground(LCD_DEFAULT_BG);
493 rb->lcd_fillrect(LCD_WIDTH-2-x, 0, LCD_WIDTH-1, y);
494 rb->lcd_set_foreground(LCD_DEFAULT_FG);
495 #else
496 grey_set_foreground(GREY_WHITE);
497 grey_fillrect(LCD_WIDTH-2-x, 0, LCD_WIDTH-1, y);
498 grey_set_foreground(GREY_BLACK);
499 #endif
502 last_message = 0;
505 #ifdef HAVE_LCD_COLOR
506 rb->lcd_update();
507 #else
508 grey_update();
509 #endif
511 graph_settings.changed.mode = false;
512 graph_settings.changed.orientation = false;
513 graph_settings.changed.scale = false;
516 void draw_lines_vertical(void)
518 static int32_t max = 0, vfactor = 0, vfactor_count = 0;
519 static const int32_t hfactor =
520 Q16_DIV(LCD_WIDTH << 16, (ARRAYSIZE_PLOT) << 16),
521 bins_per_pixel = (ARRAYSIZE_PLOT) / LCD_WIDTH;
522 static bool old_scale = true;
524 if (old_scale != graph_settings.logarithmic)
525 old_scale = graph_settings.logarithmic, max = 0; /* reset the graph on scaling mode change */
527 int32_t new_max = calc_magnitudes(graph_settings.logarithmic);
529 if (new_max > max)
531 max = new_max;
532 vfactor = Q16_DIV(LCD_HEIGHT << 16, max); /* s15.16 */
533 vfactor_count = Q16_DIV(vfactor, bins_per_pixel << 16); /* s15.16 */
536 if (new_max == 0 || max == 0) /* nothing to draw */
537 return;
539 /* take the average of neighboring bins
540 * if we have to scale the graph horizontally */
541 int64_t bins_avg = 0;
542 bool draw = true;
543 int32_t i;
544 for (i = 0; i < ARRAYSIZE_PLOT; ++i)
546 int32_t x = 0, y = 0;
548 x = Q16_MUL(hfactor, i << 16) >> 16;
549 //x = (x + (1 << 15)) >> 16;
551 if (hfactor < 65536) /* hfactor < 0, graph compression */
553 draw = false;
554 bins_avg += plot[i];
556 /* fix the division by zero warning:
557 * bins_per_pixel is zero when the graph is expanding;
558 * execution won't even reach this point - this is a dummy constant
560 const int32_t div = bins_per_pixel > 0 ? bins_per_pixel : 1;
561 if ((i + 1) % div == 0)
563 y = Q16_MUL(vfactor_count, bins_avg) >> 16;
565 bins_avg = 0;
566 draw = true;
569 else
571 y = Q16_MUL(vfactor, plot[i]) >> 16;
572 draw = true;
575 if (draw)
577 #ifdef HAVE_LCD_COLOR
578 rb->lcd_vline(x, LCD_HEIGHT-1, LCD_HEIGHT-y-1);
579 #else
580 grey_vline(x, LCD_HEIGHT-1, LCD_HEIGHT-y-1);
581 #endif
586 void draw_lines_horizontal(void)
588 static int max = 0;
590 static const int32_t vfactor =
591 Q16_DIV(LCD_HEIGHT << 16, (ARRAYSIZE_PLOT) << 16),
592 bins_per_pixel = (ARRAYSIZE_PLOT) / LCD_HEIGHT;
594 if (graph_settings.changed.scale)
595 max = 0; /* reset the graph on scaling mode change */
597 int32_t new_max = calc_magnitudes(graph_settings.logarithmic);
599 if (new_max > max)
600 max = new_max;
602 if (new_max == 0 || max == 0) /* nothing to draw */
603 return;
605 int32_t hfactor;
607 hfactor = Q16_DIV((LCD_WIDTH - 1) << 16, max); /* s15.16 */
609 /* take the average of neighboring bins
610 * if we have to scale the graph horizontally */
611 int64_t bins_avg = 0;
612 bool draw = true;
613 int32_t i;
614 for (i = 0; i < ARRAYSIZE_PLOT; ++i)
616 int32_t x = 0, y = 0;
618 y = Q16_MUL(vfactor, i << 16) + (1 << 15);
619 y >>= 16;
621 if (vfactor < 65536) /* vfactor < 0, graph compression */
623 draw = false;
624 bins_avg += plot[i];
626 /* fix the division by zero warning:
627 * bins_per_pixel is zero when the graph is expanding;
628 * execution won't even reach this point - this is a dummy constant
630 const int32_t div = bins_per_pixel > 0 ? bins_per_pixel : 1;
631 if ((i + 1) % div == 0)
633 bins_avg = Q16_DIV(bins_avg, div << 16);
634 x = Q16_MUL(hfactor, bins_avg) >> 16;
636 bins_avg = 0;
637 draw = true;
640 else
642 y = Q16_MUL(hfactor, plot[i]) >> 16;
643 draw = true;
646 if (draw)
648 #ifdef HAVE_LCD_COLOR
649 rb->lcd_hline(0, x, y);
650 #else
651 grey_hline(0, x, y);
652 #endif
657 void draw_bars_vertical(void)
659 static const unsigned int bars = 20, border = 2, items = ARRAYSIZE_PLOT
660 / bars, width = (LCD_WIDTH - ((bars - 1) * border)) / bars;
662 calc_magnitudes(graph_settings.logarithmic);
664 uint64_t bars_values[bars], bars_max = 0, avg = 0;
665 unsigned int i, bars_idx = 0;
666 for (i = 0; i < ARRAYSIZE_PLOT; ++i)
668 avg += plot[i];
669 if ((i + 1) % items == 0)
671 /* Calculate the average value and keep the fractional part
672 * for some added precision */
673 avg = Q16_DIV(avg, items << 16);
674 bars_values[bars_idx] = avg;
676 if (bars_values[bars_idx] > bars_max)
677 bars_max = bars_values[bars_idx];
679 bars_idx++;
680 avg = 0;
684 if(bars_max == 0) /* nothing to draw */
685 return;
687 /* Give the graph some headroom */
688 bars_max = Q16_MUL(bars_max, float_q16(1.1));
690 uint64_t vfactor = Q16_DIV(LCD_HEIGHT << 16, bars_max);
692 for (i = 0; i < bars; ++i)
694 int x = (i) * (border + width);
695 int y;
696 y = Q16_MUL(vfactor, bars_values[i]) + (1 << 15);
697 y >>= 16;
698 #ifdef HAVE_LCD_COLOR
699 rb->lcd_fillrect(x, LCD_HEIGHT - y - 1, width, y);
700 #else
701 grey_fillrect(x, LCD_HEIGHT - y - 1, width, y);
702 #endif
706 void draw_bars_horizontal(void)
708 static const unsigned int bars = 14, border = 3, items = ARRAYSIZE_PLOT
709 / bars, height = (LCD_HEIGHT - ((bars - 1) * border)) / bars;
711 calc_magnitudes(graph_settings.logarithmic);
713 int64_t bars_values[bars], bars_max = 0, avg = 0;
714 unsigned int i, bars_idx = 0;
715 for (i = 0; i < ARRAYSIZE_PLOT; ++i)
717 avg += plot[i];
718 if ((i + 1) % items == 0)
720 /* Calculate the average value and keep the fractional part
721 * for some added precision */
722 avg = Q16_DIV(avg, items << 16); /* s15.16 */
723 bars_values[bars_idx] = avg;
725 if (bars_values[bars_idx] > bars_max)
726 bars_max = bars_values[bars_idx];
728 bars_idx++;
729 avg = 0;
733 if(bars_max == 0) /* nothing to draw */
734 return;
736 /* Give the graph some headroom */
737 bars_max = Q16_MUL(bars_max, float_q16(1.1));
739 int64_t hfactor = Q16_DIV(LCD_WIDTH << 16, bars_max);
741 for (i = 0; i < bars; ++i)
743 int y = (i) * (border + height);
744 int x;
745 x = Q16_MUL(hfactor, bars_values[i]) + (1 << 15);
746 x >>= 16;
748 #ifdef HAVE_LCD_COLOR
749 rb->lcd_fillrect(0, y, x, height);
750 #else
751 grey_fillrect(0, y, x, height);
752 #endif
756 void draw_spectrogram_vertical(void)
758 const int32_t scale_factor = ARRAYSIZE_PLOT / LCD_HEIGHT
759 #ifdef HAVE_LCD_COLOR
760 ,colors_per_val_log = Q16_DIV((COLORS-1) << 16, QLOG_MAX),
761 colors_per_val_lin = Q16_DIV((COLORS-1) << 16, QLIN_MAX)
762 #else
763 ,grey_vals_per_val_log = Q16_DIV(255 << 16, QLOG_MAX),
764 grey_vals_per_val_lin = Q16_DIV(255 << 16, QLIN_MAX)
765 #endif
768 const int32_t remaining_div =
769 (ARRAYSIZE_PLOT-scale_factor*LCD_HEIGHT) > 0 ?
770 ( Q16_DIV((scale_factor*LCD_HEIGHT) << 16,
771 (ARRAYSIZE_PLOT-scale_factor*LCD_HEIGHT) << 16)
772 + (1<<15) ) >> 16 : 0;
774 calc_magnitudes(graph_settings.logarithmic);
775 if(graph_settings.changed.mode || graph_settings.changed.orientation)
777 graph_settings.spectrogram.column = 0;
778 #ifdef HAVE_LCD_COLOR
779 rb->lcd_clear_display();
780 #else
781 grey_clear_display();
782 #endif
785 int i, y = LCD_HEIGHT-1, count = 0, rem_count = 0;
786 uint64_t avg = 0;
787 bool added_extra_value = false;
788 for(i = 0; i < ARRAYSIZE_PLOT; ++i)
790 if(plot[i] > 0)
791 avg += plot[i];
792 ++count;
793 ++rem_count;
795 /* Kinda hacky - due to the rounding in scale_factor, we try to
796 * uniformly interweave the extra values in our calculations */
797 if(remaining_div > 0 && rem_count >= remaining_div &&
798 i < (ARRAYSIZE_PLOT-1))
800 ++i;
801 if(plot[i] > 0)
802 avg += plot[i];
803 rem_count = 0;
804 added_extra_value = true;
807 if(count >= scale_factor)
809 if(added_extra_value)
810 { ++count; added_extra_value = false; }
812 int32_t color;
814 avg = Q16_DIV(avg, count << 16);
816 #ifdef HAVE_LCD_COLOR
817 if(graph_settings.logarithmic)
818 color = Q16_MUL(avg, colors_per_val_log) >> 16;
819 else
820 color = Q16_MUL(avg, colors_per_val_lin) >> 16;
821 if(color >= COLORS) /* TODO These happen because we don't normalize the values to be above 1 and log() returns negative numbers. I think. */
822 color = COLORS-1;
823 else if (color < 0)
824 color = 0;
826 #else
827 if(graph_settings.logarithmic)
828 color = Q16_MUL(avg, grey_vals_per_val_log) >> 16;
829 else
830 color = Q16_MUL(avg, grey_vals_per_val_lin) >> 16;
831 if(color > 255)
832 color = 255;
833 else if (color < 0)
834 color = 0;
835 #endif
837 #ifdef HAVE_LCD_COLOR
838 rb->lcd_set_foreground(fft_colors[color]);
839 rb->lcd_drawpixel(graph_settings.spectrogram.column, y);
840 #else
841 grey_set_foreground(255 - color);
842 grey_drawpixel(graph_settings.spectrogram.column, y);
843 #endif
845 y--;
847 avg = 0;
848 count = 0;
850 if(y < 0)
851 break;
853 if(graph_settings.spectrogram.column != LCD_WIDTH-1)
854 graph_settings.spectrogram.column++;
855 else
856 #ifdef HAVE_LCD_COLOR
857 xlcd_scroll_left(1);
858 #else
859 grey_scroll_left(1);
860 #endif
863 void draw_spectrogram_horizontal(void)
865 const int32_t scale_factor = ARRAYSIZE_PLOT / LCD_WIDTH
866 #ifdef HAVE_LCD_COLOR
867 ,colors_per_val_log = Q16_DIV((COLORS-1) << 16, QLOG_MAX),
868 colors_per_val_lin = Q16_DIV((COLORS-1) << 16, QLIN_MAX)
869 #else
870 ,grey_vals_per_val_log = Q16_DIV(255 << 16, QLOG_MAX),
871 grey_vals_per_val_lin = Q16_DIV(255 << 16, QLIN_MAX)
872 #endif
875 const int32_t remaining_div =
876 (ARRAYSIZE_PLOT-scale_factor*LCD_WIDTH) > 0 ?
877 ( Q16_DIV((scale_factor*LCD_WIDTH) << 16,
878 (ARRAYSIZE_PLOT-scale_factor*LCD_WIDTH) << 16)
879 + (1<<15) ) >> 16 : 0;
881 calc_magnitudes(graph_settings.logarithmic);
882 if(graph_settings.changed.mode || graph_settings.changed.orientation)
884 graph_settings.spectrogram.row = 0;
885 #ifdef HAVE_LCD_COLOR
886 rb->lcd_clear_display();
887 #else
888 grey_clear_display();
889 #endif
892 int i, x = 0, count = 0, rem_count = 0;
893 uint64_t avg = 0;
894 bool added_extra_value = false;
895 for(i = 0; i < ARRAYSIZE_PLOT; ++i)
897 if(plot[i] > 0)
898 avg += plot[i];
899 ++count;
900 ++rem_count;
902 /* Kinda hacky - due to the rounding in scale_factor, we try to
903 * uniformly interweave the extra values in our calculations */
904 if(remaining_div > 0 && rem_count >= remaining_div &&
905 i < (ARRAYSIZE_PLOT-1))
907 ++i;
908 if(plot[i] > 0)
909 avg += plot[i];
910 rem_count = 0;
911 added_extra_value = true;
914 if(count >= scale_factor)
916 if(added_extra_value)
917 { ++count; added_extra_value = false; }
919 int32_t color;
921 avg = Q16_DIV(avg, count << 16);
923 #ifdef HAVE_LCD_COLOR
924 if(graph_settings.logarithmic)
925 color = Q16_MUL(avg, colors_per_val_log) >> 16;
926 else
927 color = Q16_MUL(avg, colors_per_val_lin) >> 16;
928 if(color >= COLORS) /* TODO same as _vertical */
929 color = COLORS-1;
930 else if (color < 0)
931 color = 0;
933 #else
934 if(graph_settings.logarithmic)
935 color = Q16_MUL(avg, grey_vals_per_val_log) >> 16;
936 else
937 color = Q16_MUL(avg, grey_vals_per_val_lin) >> 16;
938 if(color > 255)
939 color = 255;
940 else if (color < 0)
941 color = 0;
942 #endif
944 #ifdef HAVE_LCD_COLOR
945 rb->lcd_set_foreground(fft_colors[color]);
946 rb->lcd_drawpixel(x, graph_settings.spectrogram.row);
947 #else
948 grey_set_foreground(255 - color);
949 grey_drawpixel(x, graph_settings.spectrogram.row);
950 #endif
952 x++;
954 avg = 0;
955 count = 0;
957 if(x >= LCD_WIDTH)
958 break;
960 if(graph_settings.spectrogram.row != LCD_HEIGHT-1)
961 graph_settings.spectrogram.row++;
962 else
963 #ifdef HAVE_LCD_COLOR
964 xlcd_scroll_up(1);
965 #else
966 grey_scroll_up(1);
967 #endif
970 /********************* End of plotting functions (modes) *********************/
972 static long thread_stack[DEFAULT_STACK_SIZE/sizeof(long)];
973 void input_thread_entry(void)
975 kiss_fft_scalar * value;
976 kiss_fft_scalar left;
977 int count;
978 int idx = 0; /* offset in the buffer */
979 int fft_idx = 0; /* offset in input */
980 while(true)
982 rb->mutex_lock(&input_mutex);
983 if(!input_thread_run)
984 rb->thread_exit();
986 value = (kiss_fft_scalar*) rb->pcm_get_peak_buffer(&count);
988 if (value == 0 || count == 0)
990 rb->mutex_unlock(&input_mutex);
991 rb->yield();
992 continue;
993 /* This block can introduce discontinuities in our data. Meaning, the FFT
994 * will not be done a continuous segment of the signal. Which can be bad. Or not.
996 * Anyway, this is a demo, not a scientific tool. If you want accuracy, do a proper
997 * spectrum analysis.*/
999 else
1001 idx = fft_idx = 0;
1004 left = *(value + idx);
1005 idx += 2;
1007 input[fft_idx] = left;
1008 fft_idx++;
1010 if (fft_idx == ARRAYSIZE_IN)
1011 break;
1012 } while (idx < count);
1014 if(fft_idx == ARRAYSIZE_IN) /* there are cases when we don't have enough data to fill the buffer */
1015 input_thread_has_data = true;
1017 rb->mutex_unlock(&input_mutex);
1018 rb->yield();
1023 enum plugin_status plugin_start(const void* parameter)
1025 (void) parameter;
1026 if ((rb->audio_status() & AUDIO_STATUS_PLAY) == 0)
1028 rb->splash(HZ * 2, "No track playing. Exiting..");
1029 return PLUGIN_OK;
1031 #ifndef HAVE_LCD_COLOR
1032 unsigned char *gbuf;
1033 size_t gbuf_size = 0;
1034 /* get the remainder of the plugin buffer */
1035 gbuf = (unsigned char *) rb->plugin_get_buffer(&gbuf_size);
1037 /* initialize the greyscale buffer.*/
1038 if (!grey_init(gbuf, gbuf_size, GREY_ON_COP | GREY_BUFFERED,
1039 LCD_WIDTH, LCD_HEIGHT, NULL))
1041 rb->splash(HZ, "Couldn't init greyscale display");
1042 return PLUGIN_ERROR;
1044 grey_show(true);
1045 #endif
1047 #if LCD_DEPTH > 1
1048 rb->lcd_set_backdrop(NULL);
1049 #endif
1050 backlight_force_on();
1052 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
1053 rb->cpu_boost(true);
1054 #endif
1056 /* Defaults */
1057 bool run = true;
1058 graph_settings.mode = 0;
1059 graph_settings.logarithmic = true;
1060 graph_settings.orientation_vertical = true;
1061 graph_settings.window_func = 0;
1062 graph_settings.changed.mode = false;
1063 graph_settings.changed.scale = false;
1064 graph_settings.changed.orientation = false;
1065 graph_settings.spectrogram.row = 0;
1066 graph_settings.spectrogram.column = 0;
1068 bool changed_window = false;
1070 size_t size = sizeof(buffer);
1071 FFT_CFG state = FFT_ALLOC(FFT_SIZE, 0, buffer, &size);
1073 if (state == 0)
1075 DEBUGF("needed data: %i", (int) size);
1076 return PLUGIN_ERROR;
1079 unsigned int input_thread = rb->create_thread(&input_thread_entry, thread_stack, sizeof(thread_stack), 0, "fft input thread" IF_PRIO(, PRIORITY_BACKGROUND) IF_COP(, CPU));
1080 rb->yield();
1081 while (run)
1083 rb->mutex_lock(&input_mutex);
1084 if(!input_thread_has_data)
1086 /* Make sure the input thread has started before doing anything else */
1087 rb->mutex_unlock(&input_mutex);
1088 rb->yield();
1089 continue;
1091 apply_window_func(graph_settings.window_func);
1092 FFT_FFT(state, input, output);
1094 if(changed_window)
1096 draw(window_text[graph_settings.window_func]);
1097 changed_window = false;
1099 else
1100 draw(0);
1102 input_thread_has_data = false;
1103 rb->mutex_unlock(&input_mutex);
1104 rb->yield();
1106 int button = rb->button_get(false);
1107 switch (button)
1109 case FFT_QUIT:
1110 run = false;
1111 break;
1112 case FFT_PREV_GRAPH: {
1113 graph_settings.mode--;
1114 if (graph_settings.mode < 0)
1115 graph_settings.mode = MODES_COUNT-1;
1116 draw(modes_text[graph_settings.mode]);
1117 break;
1119 case FFT_NEXT_GRAPH: {
1120 graph_settings.mode++;
1121 if (graph_settings.mode >= MODES_COUNT)
1122 graph_settings.mode = 0;
1123 draw(modes_text[graph_settings.mode]);
1124 break;
1126 case FFT_WINDOW: {
1127 changed_window = true;
1128 graph_settings.window_func ++;
1129 if(graph_settings.window_func >= WINDOW_COUNT)
1130 graph_settings.window_func = 0;
1131 break;
1133 case FFT_SCALE: {
1134 graph_settings.logarithmic = !graph_settings.logarithmic;
1135 draw(scales_text[graph_settings.logarithmic ? 1 : 0]);
1136 break;
1138 case FFT_ORIENTATION: {
1139 graph_settings.orientation_vertical = !graph_settings.orientation_vertical;
1140 draw(0);
1141 break;
1143 default: {
1144 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
1145 return PLUGIN_USB_CONNECTED;
1151 /* Handle our input thread. We haven't yield()'d since our last mutex_unlock, so we know we have the mutex */
1152 rb->mutex_lock(&input_mutex);
1153 input_thread_run = false;
1154 rb->mutex_unlock(&input_mutex);
1155 rb->thread_wait(input_thread);
1157 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
1158 rb->cpu_boost(false);
1159 #endif
1160 #ifndef HAVE_LCD_COLOR
1161 grey_release();
1162 #endif
1163 backlight_use_settings();
1164 return PLUGIN_OK;