summaryrefslogtreecommitdiff
path: root/apps/plugins/fft/fft.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/fft/fft.c')
-rw-r--r--apps/plugins/fft/fft.c1165
1 files changed, 1165 insertions, 0 deletions
diff --git a/apps/plugins/fft/fft.c b/apps/plugins/fft/fft.c
new file mode 100644
index 0000000000..531c9af4cb
--- /dev/null
+++ b/apps/plugins/fft/fft.c
@@ -0,0 +1,1165 @@
1/***************************************************************************
2* __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2009 Delyan Kratunov
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include "plugin.h"
22
23#include "lib/helper.h"
24#include "lib/xlcd.h"
25#include "math.h"
26#include "thread.h"
27
28#ifndef HAVE_LCD_COLOR
29#include "lib/grey.h"
30#endif
31
32PLUGIN_HEADER
33
34#ifndef HAVE_LCD_COLOR
35GREY_INFO_STRUCT
36#endif
37
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
45
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
54
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)
65
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
73
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
81
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
89
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
97
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
119
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
127
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
135
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
143
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
151
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
156
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
164
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
172
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
180
181#else
182#error No keymap defined!
183#endif
184
185#ifdef HAVE_LCD_COLOR
186#include "pluginbitmaps/fft_colors.h"
187#endif
188
189#include "kiss_fftr.h"
190#include "_kiss_fft_guts.h" /* sizeof(struct kiss_fft_state) */
191#include "const.h"
192
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
203
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)
208
209/****************************** Globals ****************************/
210
211static kiss_fft_scalar input[ARRAYSIZE_IN];
212static kiss_fft_cpx output[ARRAYSIZE_OUT];
213static int32_t plot[ARRAYSIZE_PLOT];
214static char buffer[BUFSIZE];
215
216#if LCD_DEPTH > 1 /* greyscale or color, enable spectrogram */
217#define MODES_COUNT 3
218#else
219#define MODES_COUNT 2
220#endif
221
222const unsigned char* modes_text[] = { "Lines", "Bars", "Spectrogram" };
223const unsigned char* scales_text[] = { "Linear scale", "Logarithmic scale" };
224const unsigned char* window_text[] = { "Hamming window", "Hann window" };
225
226struct mutex input_mutex;
227bool input_thread_run = true;
228bool input_thread_has_data = false;
229
230struct {
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;
245
246#define COLORS BMPWIDTH_fft_colors
247
248/************************* End of globals *************************/
249
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)
255
256/* Returns logarithmically scaled values in S15.16 format */
257inline int32_t get_log_value(int32_t value)
258{
259 return Q16_DIV(fp16_log(value), QLN_10);
260}
261
262/* Apply window function to input
263 * 0 - Hamming window
264 * 1 - Hann window */
265#define WINDOW_COUNT 2
266void apply_window_func(char mode)
267{
268 switch(mode)
269 {
270 case 0: /* Hamming window */
271 {
272 size_t i;
273 for (i = 0; i < ARRAYSIZE_IN; ++i)
274 {
275 input[i] = Q15_MUL(input[i] << 15, HAMMING_COEFF[i]) >> 15;
276 }
277 break;
278 }
279 case 1: /* Hann window */
280 {
281 size_t i;
282 for (i = 0; i < ARRAYSIZE_IN; ++i)
283 {
284 input[i] = Q15_MUL(input[i] << 15, HANN_COEFF[i]) >> 15;
285 }
286 break;
287 }
288 }
289}
290
291/* Calculates the magnitudes from complex numbers and returns the maximum */
292int32_t calc_magnitudes(bool logarithmic)
293{
294 int64_t tmp;
295 size_t i;
296
297 int32_t max = -2147483647;
298
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)
303 {
304 tmp = output[i].r * output[i].r + output[i].i * output[i].i;
305 tmp <<= 16;
306
307 tmp = fsqrt64(tmp, 16);
308
309 if (logarithmic)
310 tmp = get_log_value(tmp & 0x7FFFFFFF);
311
312 plot[i] = tmp;
313
314 if (plot[i] > max)
315 max = plot[i];
316 }
317 return max;
318}
319/************************ End of math functions ***********************/
320
321/********************* Plotting functions (modes) *********************/
322void draw_lines_vertical(void);
323void draw_lines_horizontal(void);
324void draw_bars_vertical(void);
325void draw_bars_horizontal(void);
326void draw_spectrogram_vertical(void);
327void draw_spectrogram_horizontal(void);
328
329void draw(const unsigned char* message)
330{
331 static uint32_t show_message = 0;
332 static unsigned char* last_message = 0;
333
334 static char last_mode = 0;
335 static bool last_orientation = true, last_scale = true;
336
337 if (message != 0)
338 {
339 last_message = (unsigned char*) message;
340 show_message = 5;
341 }
342
343 if(last_mode != graph_settings.mode)
344 {
345 last_mode = graph_settings.mode;
346 graph_settings.changed.mode = true;
347 }
348 if(last_scale != graph_settings.logarithmic)
349 {
350 last_scale = graph_settings.logarithmic;
351 graph_settings.changed.scale = true;
352 }
353 if(last_orientation != graph_settings.orientation_vertical)
354 {
355 last_orientation = graph_settings.orientation_vertical;
356 graph_settings.changed.orientation = true;
357 }
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
365
366 switch (graph_settings.mode)
367 {
368 default:
369 case 0: {
370
371#ifdef HAVE_LCD_COLOR
372 rb->lcd_clear_display();
373#else
374 grey_clear_display();
375#endif
376
377 if (graph_settings.orientation_vertical)
378 draw_lines_vertical();
379 else
380 draw_lines_horizontal();
381 break;
382 }
383 case 1: {
384
385#ifdef HAVE_LCD_COLOR
386 rb->lcd_clear_display();
387#else
388 grey_clear_display();
389#endif
390
391 if(graph_settings.orientation_vertical)
392 draw_bars_vertical();
393 else
394 draw_bars_horizontal();
395
396 break;
397 }
398 case 2: {
399 if(graph_settings.orientation_vertical)
400 draw_spectrogram_vertical();
401 else
402 draw_spectrogram_horizontal();
403 break;
404 }
405 }
406
407 if (show_message > 0)
408 {
409 /* We have a message to show */
410
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 */
420
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)
426 {
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;
435 }
436
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);
440
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);
448
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
454
455 show_message--;
456 }
457 else if(last_message != 0)
458 {
459 if(graph_settings.mode != 2)
460 {
461 /* These modes clear the screen themselves */
462 last_message = 0;
463 }
464 else /* Spectrogram mode - need to erase the popup */
465 {
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 */
475
476 if(!graph_settings.orientation_vertical)
477 {
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;
487 }
488 else
489 {
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
500 }
501
502 last_message = 0;
503 }
504 }
505#ifdef HAVE_LCD_COLOR
506 rb->lcd_update();
507#else
508 grey_update();
509#endif
510
511 graph_settings.changed.mode = false;
512 graph_settings.changed.orientation = false;
513 graph_settings.changed.scale = false;
514}
515
516void draw_lines_vertical(void)
517{
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;
523
524 if (old_scale != graph_settings.logarithmic)
525 old_scale = graph_settings.logarithmic, max = 0; /* reset the graph on scaling mode change */
526
527 int32_t new_max = calc_magnitudes(graph_settings.logarithmic);
528
529 if (new_max > max)
530 {
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 */
534 }
535
536 if (new_max == 0 || max == 0) /* nothing to draw */
537 return;
538
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)
545 {
546 int32_t x = 0, y = 0;
547
548 x = Q16_MUL(hfactor, i << 16) >> 16;
549 //x = (x + (1 << 15)) >> 16;
550
551 if (hfactor < 65536) /* hfactor < 0, graph compression */
552 {
553 draw = false;
554 bins_avg += plot[i];
555
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
559 */
560 const int32_t div = bins_per_pixel > 0 ? bins_per_pixel : 1;
561 if ((i + 1) % div == 0)
562 {
563 y = Q16_MUL(vfactor_count, bins_avg) >> 16;
564
565 bins_avg = 0;
566 draw = true;
567 }
568 }
569 else
570 {
571 y = Q16_MUL(vfactor, plot[i]) >> 16;
572 draw = true;
573 }
574
575 if (draw)
576 {
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
582 }
583 }
584}
585
586void draw_lines_horizontal(void)
587{
588 static int max = 0;
589
590 static const int32_t vfactor =
591 Q16_DIV(LCD_HEIGHT << 16, (ARRAYSIZE_PLOT) << 16),
592 bins_per_pixel = (ARRAYSIZE_PLOT) / LCD_HEIGHT;
593
594 if (graph_settings.changed.scale)
595 max = 0; /* reset the graph on scaling mode change */
596
597 int32_t new_max = calc_magnitudes(graph_settings.logarithmic);
598
599 if (new_max > max)
600 max = new_max;
601
602 if (new_max == 0 || max == 0) /* nothing to draw */
603 return;
604
605 int32_t hfactor;
606
607 hfactor = Q16_DIV((LCD_WIDTH - 1) << 16, max); /* s15.16 */
608
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)
615 {
616 int32_t x = 0, y = 0;
617
618 y = Q16_MUL(vfactor, i << 16) + (1 << 15);
619 y >>= 16;
620
621 if (vfactor < 65536) /* vfactor < 0, graph compression */
622 {
623 draw = false;
624 bins_avg += plot[i];
625
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
629 */
630 const int32_t div = bins_per_pixel > 0 ? bins_per_pixel : 1;
631 if ((i + 1) % div == 0)
632 {
633 bins_avg = Q16_DIV(bins_avg, div << 16);
634 x = Q16_MUL(hfactor, bins_avg) >> 16;
635
636 bins_avg = 0;
637 draw = true;
638 }
639 }
640 else
641 {
642 y = Q16_MUL(hfactor, plot[i]) >> 16;
643 draw = true;
644 }
645
646 if (draw)
647 {
648#ifdef HAVE_LCD_COLOR
649 rb->lcd_hline(0, x, y);
650#else
651 grey_hline(0, x, y);
652#endif
653 }
654 }
655}
656
657void draw_bars_vertical(void)
658{
659 static const unsigned int bars = 20, border = 2, items = ARRAYSIZE_PLOT
660 / bars, width = (LCD_WIDTH - ((bars - 1) * border)) / bars;
661
662 calc_magnitudes(graph_settings.logarithmic);
663
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)
667 {
668 avg += plot[i];
669 if ((i + 1) % items == 0)
670 {
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;
675
676 if (bars_values[bars_idx] > bars_max)
677 bars_max = bars_values[bars_idx];
678
679 bars_idx++;
680 avg = 0;
681 }
682 }
683
684 if(bars_max == 0) /* nothing to draw */
685 return;
686
687 /* Give the graph some headroom */
688 bars_max = Q16_MUL(bars_max, float_q16(1.1));
689
690 uint64_t vfactor = Q16_DIV(LCD_HEIGHT << 16, bars_max);
691
692 for (i = 0; i < bars; ++i)
693 {
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
703 }
704}
705
706void draw_bars_horizontal(void)
707{
708 static const unsigned int bars = 14, border = 3, items = ARRAYSIZE_PLOT
709 / bars, height = (LCD_HEIGHT - ((bars - 1) * border)) / bars;
710
711 calc_magnitudes(graph_settings.logarithmic);
712
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)
716 {
717 avg += plot[i];
718 if ((i + 1) % items == 0)
719 {
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;
724
725 if (bars_values[bars_idx] > bars_max)
726 bars_max = bars_values[bars_idx];
727
728 bars_idx++;
729 avg = 0;
730 }
731 }
732
733 if(bars_max == 0) /* nothing to draw */
734 return;
735
736 /* Give the graph some headroom */
737 bars_max = Q16_MUL(bars_max, float_q16(1.1));
738
739 int64_t hfactor = Q16_DIV(LCD_WIDTH << 16, bars_max);
740
741 for (i = 0; i < bars; ++i)
742 {
743 int y = (i) * (border + height);
744 int x;
745 x = Q16_MUL(hfactor, bars_values[i]) + (1 << 15);
746 x >>= 16;
747
748#ifdef HAVE_LCD_COLOR
749 rb->lcd_fillrect(0, y, x, height);
750#else
751 grey_fillrect(0, y, x, height);
752#endif
753 }
754}
755
756void draw_spectrogram_vertical(void)
757{
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
766 ;
767
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;
773
774 calc_magnitudes(graph_settings.logarithmic);
775 if(graph_settings.changed.mode || graph_settings.changed.orientation)
776 {
777 graph_settings.spectrogram.column = 0;
778#ifdef HAVE_LCD_COLOR
779 rb->lcd_clear_display();
780#else
781 grey_clear_display();
782#endif
783 }
784
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)
789 {
790 if(plot[i] > 0)
791 avg += plot[i];
792 ++count;
793 ++rem_count;
794
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))
799 {
800 ++i;
801 if(plot[i] > 0)
802 avg += plot[i];
803 rem_count = 0;
804 added_extra_value = true;
805 }
806
807 if(count >= scale_factor)
808 {
809 if(added_extra_value)
810 { ++count; added_extra_value = false; }
811
812 int32_t color;
813
814 avg = Q16_DIV(avg, count << 16);
815
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;
825
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
836
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
844
845 y--;
846
847 avg = 0;
848 count = 0;
849 }
850 if(y < 0)
851 break;
852 }
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
861}
862
863void draw_spectrogram_horizontal(void)
864{
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
873 ;
874
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;
880
881 calc_magnitudes(graph_settings.logarithmic);
882 if(graph_settings.changed.mode || graph_settings.changed.orientation)
883 {
884 graph_settings.spectrogram.row = 0;
885#ifdef HAVE_LCD_COLOR
886 rb->lcd_clear_display();
887#else
888 grey_clear_display();
889#endif
890 }
891
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)
896 {
897 if(plot[i] > 0)
898 avg += plot[i];
899 ++count;
900 ++rem_count;
901
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))
906 {
907 ++i;
908 if(plot[i] > 0)
909 avg += plot[i];
910 rem_count = 0;
911 added_extra_value = true;
912 }
913
914 if(count >= scale_factor)
915 {
916 if(added_extra_value)
917 { ++count; added_extra_value = false; }
918
919 int32_t color;
920
921 avg = Q16_DIV(avg, count << 16);
922
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;
932
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
943
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
951
952 x++;
953
954 avg = 0;
955 count = 0;
956 }
957 if(x >= LCD_WIDTH)
958 break;
959 }
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
968}
969
970/********************* End of plotting functions (modes) *********************/
971
972static long thread_stack[DEFAULT_STACK_SIZE/sizeof(long)];
973void input_thread_entry(void)
974{
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)
981 {
982 rb->mutex_lock(&input_mutex);
983 if(!input_thread_run)
984 rb->thread_exit();
985
986 value = (kiss_fft_scalar*) rb->pcm_get_peak_buffer(&count);
987
988 if (value == 0 || count == 0)
989 {
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.
995 *
996 * Anyway, this is a demo, not a scientific tool. If you want accuracy, do a proper
997 * spectrum analysis.*/
998 }
999 else
1000 {
1001 idx = fft_idx = 0;
1002 do
1003 {
1004 left = *(value + idx);
1005 idx += 2;
1006
1007 input[fft_idx] = left;
1008 fft_idx++;
1009
1010 if (fft_idx == ARRAYSIZE_IN)
1011 break;
1012 } while (idx < count);
1013 }
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;
1016
1017 rb->mutex_unlock(&input_mutex);
1018 rb->yield();
1019 }
1020}
1021
1022
1023enum plugin_status plugin_start(const void* parameter)
1024{
1025 (void) parameter;
1026 if ((rb->audio_status() & AUDIO_STATUS_PLAY) == 0)
1027 {
1028 rb->splash(HZ * 2, "No track playing. Exiting..");
1029 return PLUGIN_OK;
1030 }
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);
1036
1037 /* initialize the greyscale buffer.*/
1038 if (!grey_init(gbuf, gbuf_size, GREY_ON_COP | GREY_BUFFERED,
1039 LCD_WIDTH, LCD_HEIGHT, NULL))
1040 {
1041 rb->splash(HZ, "Couldn't init greyscale display");
1042 return PLUGIN_ERROR;
1043 }
1044 grey_show(true);
1045#endif
1046
1047#if LCD_DEPTH > 1
1048 rb->lcd_set_backdrop(NULL);
1049#endif
1050 backlight_force_on();
1051
1052#ifdef HAVE_ADJUSTABLE_CPU_FREQ
1053 rb->cpu_boost(true);
1054#endif
1055
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;
1067
1068 bool changed_window = false;
1069
1070 size_t size = sizeof(buffer);
1071 FFT_CFG state = FFT_ALLOC(FFT_SIZE, 0, buffer, &size);
1072
1073 if (state == 0)
1074 {
1075 DEBUGF("needed data: %i", (int) size);
1076 return PLUGIN_ERROR;
1077 }
1078
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)
1082 {
1083 rb->mutex_lock(&input_mutex);
1084 if(!input_thread_has_data)
1085 {
1086 /* Make sure the input thread has started before doing anything else */
1087 rb->mutex_unlock(&input_mutex);
1088 rb->yield();
1089 continue;
1090 }
1091 apply_window_func(graph_settings.window_func);
1092 FFT_FFT(state, input, output);
1093
1094 if(changed_window)
1095 {
1096 draw(window_text[graph_settings.window_func]);
1097 changed_window = false;
1098 }
1099 else
1100 draw(0);
1101
1102 input_thread_has_data = false;
1103 rb->mutex_unlock(&input_mutex);
1104 rb->yield();
1105
1106 int button = rb->button_get(false);
1107 switch (button)
1108 {
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;
1118 }
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;
1125 }
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;
1132 }
1133 case FFT_SCALE: {
1134 graph_settings.logarithmic = !graph_settings.logarithmic;
1135 draw(scales_text[graph_settings.logarithmic ? 1 : 0]);
1136 break;
1137 }
1138 case FFT_ORIENTATION: {
1139 graph_settings.orientation_vertical = !graph_settings.orientation_vertical;
1140 draw(0);
1141 break;
1142 }
1143 default: {
1144 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
1145 return PLUGIN_USB_CONNECTED;
1146 }
1147
1148 }
1149 }
1150
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);
1156
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;
1165}