drop 'req' from gas macro, not available in binutils 2.16
[kugel-rb.git] / apps / plugins / fft / fft.c
blobe352c1c04146da6bbd40d9a3d21f570cfbbec7fb
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 "fracmul.h"
27 #ifndef HAVE_LCD_COLOR
28 #include "lib/grey.h"
29 #endif
30 #include "lib/mylcd.h"
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_AMP_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_AMP_SCALE BUTTON_UP
53 # define FFT_FREQ_SCALE BUTTON_DOWN
54 # define FFT_QUIT BUTTON_OFF
56 #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
57 (CONFIG_KEYPAD == IPOD_3G_PAD) || \
58 (CONFIG_KEYPAD == IPOD_1G2G_PAD)
59 # define MINESWP_SCROLLWHEEL
60 # define FFT_PREV_GRAPH BUTTON_LEFT
61 # define FFT_NEXT_GRAPH BUTTON_RIGHT
62 # define FFT_ORIENTATION (BUTTON_SELECT | BUTTON_LEFT)
63 # define FFT_WINDOW (BUTTON_SELECT | BUTTON_RIGHT)
64 # define FFT_AMP_SCALE BUTTON_MENU
65 # define FFT_FREQ_SCALE BUTTON_PLAY
66 # define FFT_QUIT (BUTTON_SELECT | BUTTON_MENU)
68 #elif (CONFIG_KEYPAD == IAUDIO_X5M5_PAD)
69 # define FFT_PREV_GRAPH BUTTON_LEFT
70 # define FFT_NEXT_GRAPH BUTTON_RIGHT
71 # define FFT_ORIENTATION BUTTON_SELECT
72 # define FFT_WINDOW BUTTON_PLAY
73 # define FFT_AMP_SCALE BUTTON_UP
74 # define FFT_FREQ_SCALE BUTTON_DOWN
75 # define FFT_QUIT BUTTON_POWER
77 #elif (CONFIG_KEYPAD == GIGABEAT_PAD)
78 # define FFT_PREV_GRAPH BUTTON_LEFT
79 # define FFT_NEXT_GRAPH BUTTON_RIGHT
80 # define FFT_AMP_SCALE BUTTON_UP
81 # define FFT_FREQ_SCALE BUTTON_DOWN
82 # define FFT_ORIENTATION BUTTON_SELECT
83 # define FFT_WINDOW BUTTON_A
84 # define FFT_QUIT BUTTON_POWER
86 #elif (CONFIG_KEYPAD == SANSA_E200_PAD)
87 # define FFT_PREV_GRAPH BUTTON_LEFT
88 # define FFT_NEXT_GRAPH BUTTON_RIGHT
89 # define FFT_ORIENTATION BUTTON_SELECT
90 # define FFT_WINDOW BUTTON_REC
91 # define FFT_AMP_SCALE BUTTON_UP
92 # define FFT_FREQ_SCALE BUTTON_DOWN
93 # define FFT_QUIT BUTTON_POWER
95 #elif (CONFIG_KEYPAD == SANSA_FUZE_PAD)
96 # define FFT_PREV_GRAPH BUTTON_LEFT
97 # define FFT_NEXT_GRAPH BUTTON_RIGHT
98 # define FFT_ORIENTATION (BUTTON_SELECT | BUTTON_LEFT)
99 # define FFT_WINDOW (BUTTON_SELECT | BUTTON_RIGHT)
100 # define FFT_AMP_SCALE BUTTON_UP
101 # define FFT_FREQ_SCALE BUTTON_DOWN
102 # define FFT_QUIT (BUTTON_HOME|BUTTON_REPEAT)
104 #elif (CONFIG_KEYPAD == SANSA_C200_PAD)
105 # define FFT_PREV_GRAPH BUTTON_LEFT
106 # define FFT_NEXT_GRAPH BUTTON_RIGHT
107 # define FFT_ORIENTATION BUTTON_UP
108 # define FFT_WINDOW BUTTON_REC
109 # define FFT_AMP_SCALE BUTTON_SELECT
110 # define FFT_QUIT BUTTON_POWER
111 #elif (CONFIG_KEYPAD == SANSA_M200_PAD)
112 # define FFT_PREV_GRAPH BUTTON_LEFT
113 # define FFT_NEXT_GRAPH BUTTON_RIGHT
114 # define FFT_ORIENTATION BUTTON_UP
115 # define FFT_WINDOW BUTTON_DOWN
116 # define FFT_AMP_SCALE BUTTON_SELECT
117 # define FFT_QUIT BUTTON_POWER
118 #elif (CONFIG_KEYPAD == SANSA_CLIP_PAD)
119 # define FFT_PREV_GRAPH BUTTON_LEFT
120 # define FFT_NEXT_GRAPH BUTTON_RIGHT
121 # define FFT_ORIENTATION BUTTON_UP
122 # define FFT_WINDOW BUTTON_HOME
123 # define FFT_AMP_SCALE BUTTON_SELECT
124 # define FFT_QUIT BUTTON_POWER
126 #elif (CONFIG_KEYPAD == IRIVER_H10_PAD)
127 # define FFT_PREV_GRAPH BUTTON_LEFT
128 # define FFT_NEXT_GRAPH BUTTON_RIGHT
129 # define FFT_ORIENTATION BUTTON_FF
130 # define FFT_WINDOW BUTTON_SCROLL_UP
131 # define FFT_AMP_SCALE BUTTON_REW
132 # define FFT_FREQ_SCALE BUTTON_PLAY
133 # define FFT_QUIT BUTTON_POWER
135 #elif (CONFIG_KEYPAD == GIGABEAT_S_PAD)
136 # define FFT_PREV_GRAPH BUTTON_LEFT
137 # define FFT_NEXT_GRAPH BUTTON_RIGHT
138 # define FFT_ORIENTATION BUTTON_MENU
139 # define FFT_WINDOW BUTTON_PREV
140 # define FFT_AMP_SCALE BUTTON_UP
141 # define FFT_FREQ_SCALE BUTTON_DOWN
142 # define FFT_QUIT BUTTON_BACK
144 #elif (CONFIG_KEYPAD == MROBE100_PAD)
145 # define FFT_PREV_GRAPH BUTTON_LEFT
146 # define FFT_NEXT_GRAPH BUTTON_RIGHT
147 # define FFT_ORIENTATION BUTTON_PLAY
148 # define FFT_WINDOW BUTTON_SELECT
149 # define FFT_AMP_SCALE BUTTON_UP
150 # define FFT_FREQ_SCALE BUTTON_DOWN
151 # define FFT_QUIT BUTTON_POWER
153 #elif CONFIG_KEYPAD == IAUDIO_M3_PAD
154 # define FFT_PREV_GRAPH BUTTON_RC_REW
155 # define FFT_NEXT_GRAPH BUTTON_RC_FF
156 # define FFT_ORIENTATION BUTTON_RC_MODE
157 # define FFT_WINDOW BUTTON_RC_PLAY
158 # define FFT_AMP_SCALE BUTTON_RC_VOL_UP
159 # define FFT_QUIT BUTTON_RC_REC
161 #elif (CONFIG_KEYPAD == COWON_D2_PAD)
162 # define FFT_QUIT BUTTON_POWER
163 # define FFT_PREV_GRAPH BUTTON_PLUS
164 # define FFT_NEXT_GRAPH BUTTON_MINUS
166 #elif CONFIG_KEYPAD == CREATIVEZVM_PAD
167 # define FFT_PREV_GRAPH BUTTON_LEFT
168 # define FFT_NEXT_GRAPH BUTTON_RIGHT
169 # define FFT_ORIENTATION BUTTON_MENU
170 # define FFT_WINDOW BUTTON_SELECT
171 # define FFT_AMP_SCALE BUTTON_UP
172 # define FFT_FREQ_SCALE BUTTON_DOWN
173 # define FFT_QUIT BUTTON_BACK
175 #elif CONFIG_KEYPAD == PHILIPS_HDD1630_PAD
176 # define FFT_PREV_GRAPH BUTTON_LEFT
177 # define FFT_NEXT_GRAPH BUTTON_RIGHT
178 # define FFT_ORIENTATION BUTTON_SELECT
179 # define FFT_WINDOW BUTTON_MENU
180 # define FFT_AMP_SCALE BUTTON_UP
181 # define FFT_FREQ_SCALE BUTTON_DOWN
182 # define FFT_QUIT BUTTON_POWER
184 #elif (CONFIG_KEYPAD == SAMSUNG_YH_PAD)
185 # define FFT_PREV_GRAPH BUTTON_LEFT
186 # define FFT_NEXT_GRAPH BUTTON_RIGHT
187 # define FFT_ORIENTATION BUTTON_UP
188 # define FFT_WINDOW BUTTON_DOWN
189 # define FFT_AMP_SCALE BUTTON_FFWD
190 # define FFT_QUIT BUTTON_PLAY
192 #elif (CONFIG_KEYPAD == MROBE500_PAD)
193 # define FFT_QUIT BUTTON_POWER
195 #elif (CONFIG_KEYPAD == ONDAVX747_PAD)
196 # define FFT_QUIT BUTTON_POWER
198 #elif (CONFIG_KEYPAD == ONDAVX777_PAD)
199 # define FFT_QUIT BUTTON_POWER
201 #elif (CONFIG_KEYPAD == PBELL_VIBE500_PAD)
202 # define FFT_PREV_GRAPH BUTTON_PREV
203 # define FFT_NEXT_GRAPH BUTTON_NEXT
204 # define FFT_ORIENTATION BUTTON_MENU
205 # define FFT_WINDOW BUTTON_OK
206 # define FFT_AMP_SCALE BUTTON_PLAY
207 # define FFT_QUIT BUTTON_REC
209 #elif CONFIG_KEYPAD == MPIO_HD200_PAD
210 # define FFT_PREV_GRAPH BUTTON_PREV
211 # define FFT_NEXT_GRAPH BUTTON_NEXT
212 # define FFT_ORIENTATION BUTTON_REC
213 # define FFT_WINDOW BUTTON_SELECT
214 # define FFT_AMP_SCALE BUTTON_PLAY
215 # define FFT_QUIT (BUTTON_REC | BUTTON_PLAY)
217 #else
218 #error No keymap defined!
219 #endif
221 #ifdef HAVE_TOUCHSCREEN
222 #ifndef FFT_PREV_GRAPH
223 # define FFT_PREV_GRAPH BUTTON_MIDLEFT
224 #endif
225 #ifndef FFT_NEXT_GRAPH
226 # define FFT_NEXT_GRAPH BUTTON_MIDRIGHT
227 #endif
228 #ifndef FFT_ORIENTATION
229 # define FFT_ORIENTATION BUTTON_CENTER
230 #endif
231 #ifndef FFT_WINDOW
232 # define FFT_WINDOW BUTTON_TOPLEFT
233 #endif
234 #ifndef FFT_AMP_SCALE
235 # define FFT_AMP_SCALE BUTTON_TOPRIGHT
236 #endif
237 #ifndef FFT_QUIT
238 # define FFT_QUIT BUTTON_BOTTOMLEFT
239 #endif
240 #endif /* HAVE_TOUCHSCREEN */
242 #ifdef HAVE_LCD_COLOR
243 #include "pluginbitmaps/fft_colors.h"
244 #endif
246 #include "kiss_fftr.h"
247 #include "_kiss_fft_guts.h" /* sizeof(struct kiss_fft_state) */
248 #include "const.h"
250 #define LCD_SIZE MAX(LCD_WIDTH, LCD_HEIGHT)
252 #if (LCD_SIZE <= 511)
253 #define FFT_SIZE 1024 /* 512*2 */
254 #elif (LCD_SIZE <= 1023)
255 #define FFT_SIZE 2048 /* 1024*2 */
256 #else
257 #define FFT_SIZE 4096 /* 2048*2 */
258 #endif
260 #define ARRAYLEN_IN (FFT_SIZE)
261 #define ARRAYLEN_OUT (FFT_SIZE)
262 #define ARRAYLEN_PLOT (FFT_SIZE/2-1) /* FFT is symmetric, ignore DC */
263 #define BUFSIZE_FFT (sizeof(struct kiss_fft_state)+sizeof(kiss_fft_cpx)*(FFT_SIZE-1))
265 #define __COEFF(type,size) type##_##size
266 #define _COEFF(x, y) __COEFF(x,y) /* force the preprocessor to evaluate FFT_SIZE) */
267 #define HANN_COEFF _COEFF(hann, FFT_SIZE)
268 #define HAMMING_COEFF _COEFF(hamming, FFT_SIZE)
270 /****************************** Globals ****************************/
271 /* cacheline-aligned buffers with COP, otherwise word-aligned */
272 /* CPU/COP only applies when compiled for more than one core */
274 #define CACHEALIGN_UP_SIZE(type, len) \
275 (CACHEALIGN_UP((len)*sizeof(type) + (sizeof(type)-1)) / sizeof(type))
276 /* Shared */
277 /* COP + CPU PCM */
278 static kiss_fft_cpx input[CACHEALIGN_UP_SIZE(kiss_fft_scalar, ARRAYLEN_IN)]
279 CACHEALIGN_AT_LEAST_ATTR(4);
280 /* CPU+COP */
281 #if NUM_CORES > 1
282 /* Output queue indexes */
283 static volatile int output_head SHAREDBSS_ATTR = 0;
284 static volatile int output_tail SHAREDBSS_ATTR = 0;
285 /* The result is nfft/2 complex frequency bins from DC to Nyquist. */
286 static kiss_fft_cpx output[2][CACHEALIGN_UP_SIZE(kiss_fft_cpx, ARRAYLEN_OUT)]
287 SHAREDBSS_ATTR;
288 #else
289 /* Only one output buffer */
290 #define output_head 0
291 #define output_tail 0
292 /* The result is nfft/2 complex frequency bins from DC to Nyquist. */
293 static kiss_fft_cpx output[1][ARRAYLEN_OUT];
294 #endif
296 /* Unshared */
297 /* COP */
298 static kiss_fft_cfg fft_state SHAREDBSS_ATTR;
299 static char fft_buffer[CACHEALIGN_UP_SIZE(char, BUFSIZE_FFT)]
300 CACHEALIGN_AT_LEAST_ATTR(4);
301 /* CPU */
302 static int32_t plot_history[ARRAYLEN_PLOT];
303 static int32_t plot[ARRAYLEN_PLOT];
304 static struct
306 int16_t bin; /* integer bin number */
307 uint16_t frac; /* interpolation fraction */
308 } binlog[ARRAYLEN_PLOT] __attribute__((aligned(4)));
310 enum fft_window_func
312 FFT_WF_FIRST = 0,
313 FFT_WF_HAMMING = 0,
314 FFT_WF_HANN,
316 #define FFT_WF_COUNT (FFT_WF_HANN+1)
318 enum fft_display_mode
320 FFT_DM_FIRST = 0,
321 FFT_DM_LINES = 0,
322 FFT_DM_BARS,
323 FFT_DM_SPECTROGRAPH,
325 #define FFT_DM_COUNT (FFT_DM_SPECTROGRAPH+1)
327 static const unsigned char* const modes_text[FFT_DM_COUNT] =
328 { "Lines", "Bars", "Spectrogram" };
330 static const unsigned char* const amp_scales_text[2] =
331 { "Linear amplitude", "Logarithmic amplitude" };
333 static const unsigned char* const freq_scales_text[2] =
334 { "Linear frequency", "Logarithmic frequency" };
336 static const unsigned char* const window_text[FFT_WF_COUNT] =
337 { "Hamming window", "Hann window" };
339 static struct {
340 bool orientation_vertical;
341 enum fft_display_mode mode;
342 bool logarithmic_amp;
343 bool logarithmic_freq;
344 enum fft_window_func window_func;
345 int spectrogram_pos; /* row or column - only used by one at a time */
346 union
348 struct
350 bool orientation : 1;
351 bool mode : 1;
352 bool amp_scale : 1;
353 bool freq_scale : 1;
354 bool window_func : 1;
355 bool do_clear : 1;
357 bool clear_all; /* Write 'false' to clear all above */
358 } changed;
359 } graph_settings SHAREDDATA_ATTR =
361 /* Defaults */
362 .orientation_vertical = true,
363 .mode = FFT_DM_LINES,
364 .logarithmic_amp = true,
365 .logarithmic_freq = true,
366 .window_func = FFT_WF_HAMMING,
367 .spectrogram_pos = 0,
368 .changed = { .clear_all = false },
371 #ifdef HAVE_LCD_COLOR
372 #define SHADES BMPWIDTH_fft_colors
373 #define SPECTROGRAPH_PALETTE(index) (fft_colors[index])
374 #else
375 #define SHADES 256
376 #define SPECTROGRAPH_PALETTE(index) (255 - (index))
377 #endif
379 /************************* End of globals *************************/
381 /************************* Math functions *************************/
383 /* Based on feeding-in a 0db sinewave at FS/4 */
384 #define QLOG_MAX 0x0009154B
385 /* fudge it a little or it's not very visbile */
386 #define QLIN_MAX (0x00002266 >> 1)
388 /* Apply window function to input */
389 static void apply_window_func(enum fft_window_func mode)
391 int i;
393 switch(mode)
395 case FFT_WF_HAMMING:
396 for(i = 0; i < ARRAYLEN_IN; ++i)
398 input[i].r = (input[i].r * HAMMING_COEFF[i] + 16384) >> 15;
400 break;
402 case FFT_WF_HANN:
403 for(i = 0; i < ARRAYLEN_IN; ++i)
405 input[i].r = (input[i].r * HANN_COEFF[i] + 16384) >> 15;
407 break;
411 /* Calculates the magnitudes from complex numbers and returns the maximum */
412 static int32_t calc_magnitudes(bool logarithmic_amp)
414 /* A major assumption made when calculating the Q*MAX constants
415 * is that the maximum magnitude is 29 bits long. */
416 uint32_t max = 0;
417 kiss_fft_cpx *this_output = output[output_head] + 1; /* skip DC */
418 int i;
420 /* Calculate the magnitude, discarding the phase. */
421 for(i = 0; i < ARRAYLEN_PLOT; ++i)
423 int32_t re = this_output[i].r;
424 int32_t im = this_output[i].i;
426 uint32_t tmp = re*re + im*im;
428 if(tmp > 0)
430 if(tmp > 0x7FFFFFFF) /* clip */
432 tmp = 0x7FFFFFFF; /* if our assumptions are correct,
433 this should never happen. It's just
434 a safeguard. */
437 if(logarithmic_amp)
439 if(tmp < 0x8000) /* be more precise */
441 /* ln(x ^ .5) = .5*ln(x) */
442 tmp = fp16_log(tmp << 16) >> 1;
444 else
446 tmp = isqrt(tmp); /* linear scaling, nothing
447 bad should happen */
448 tmp = fp16_log(tmp << 16); /* the log function
449 expects s15.16 values */
452 else
454 tmp = isqrt(tmp); /* linear scaling, nothing
455 bad should happen */
459 /* Length 2 moving average - last transform and this one */
460 tmp = (plot_history[i] + tmp) >> 1;
461 plot[i] = tmp;
462 plot_history[i] = tmp;
464 if(tmp > max)
465 max = tmp;
468 return max;
471 /* Move plot bins into a logarithmic scale by sliding them towards the
472 * Nyquist bin according to the translation in the binlog array. */
473 static void logarithmic_plot_translate(void)
475 int i;
477 for(i = ARRAYLEN_PLOT-1; i > 0; --i)
479 int bin;
480 int s = binlog[i].bin;
481 int e = binlog[i-1].bin;
482 int frac = binlog[i].frac;
484 bin = plot[s];
486 if(frac)
488 /* slope < 1, Interpolate stretched bins (linear for now) */
489 int diff = plot[s+1] - bin;
493 plot[i] = bin + FRACMUL(frac << 15, diff);
494 frac = binlog[--i].frac;
496 while(frac);
498 else
500 /* slope > 1, Find peak of two or more bins */
501 while(--s > e)
503 int val = plot[s];
505 if (val > bin)
506 bin = val;
510 plot[i] = bin;
514 /* Calculates the translation for logarithmic plot bins */
515 static void logarithmic_plot_init(void)
517 int i, j;
519 * log: y = round(n * ln(x) / ln(n))
520 * anti: y = round(exp(x * ln(n) / n))
522 j = fp16_log((ARRAYLEN_PLOT - 1) << 16);
523 for(i = 0; i < ARRAYLEN_PLOT; ++i)
525 binlog[i].bin = (fp16_exp(i * j / (ARRAYLEN_PLOT - 1)) + 32768) >> 16;
528 /* setup fractions for interpolation of stretched bins */
529 for(i = 0; i < ARRAYLEN_PLOT-1; i = j)
531 j = i + 1;
533 /* stop when we have two different values */
534 while(binlog[j].bin == binlog[i].bin)
535 j++; /* if here, local slope of curve is < 1 */
537 if(j > i + 1)
539 /* distribute pieces evenly over stretched interval */
540 int diff = j - i;
541 int x = 0;
544 binlog[i].frac = (x++ << 16) / diff;
546 while(++i < j);
551 /************************ End of math functions ***********************/
553 /********************* Plotting functions (modes) *********************/
554 static void draw_lines_vertical(void);
555 static void draw_lines_horizontal(void);
556 static void draw_bars_vertical(void);
557 static void draw_bars_horizontal(void);
558 static void draw_spectrogram_vertical(void);
559 static void draw_spectrogram_horizontal(void);
561 #define COLOR_DEFAULT_FG MYLCD_DEFAULT_FG
562 #define COLOR_DEFAULT_BG MYLCD_DEFAULT_BG
564 #ifdef HAVE_LCD_COLOR
565 #define COLOR_MESSAGE_FRAME LCD_RGBPACK(0xc6, 0x00, 0x00)
566 #define COLOR_MESSAGE_BG LCD_BLACK
567 #define COLOR_MESSAGE_FG LCD_WHITE
568 #else
569 #define COLOR_MESSAGE_FRAME GREY_DARKGRAY
570 #define COLOR_MESSAGE_BG GREY_WHITE
571 #define COLOR_MESSAGE_FG GREY_BLACK
572 #endif
574 #define POPUP_HPADDING 3 /* 3 px of horizontal padding and */
575 #define POPUP_VPADDING 2 /* 2 px of vertical padding */
577 static void draw_message_string(const unsigned char *message, bool active)
579 int x, y;
580 mylcd_getstringsize(message, &x, &y);
582 /* x and y give the size of the box for the popup */
583 x += POPUP_HPADDING*2;
584 y += POPUP_VPADDING*2;
586 /* In vertical spectrogram mode, leave space for the popup
587 * before actually drawing it (if space is needed) */
588 if(active &&
589 graph_settings.mode == FFT_DM_SPECTROGRAPH &&
590 graph_settings.orientation_vertical &&
591 graph_settings.spectrogram_pos >= LCD_WIDTH - x)
593 mylcd_scroll_left(graph_settings.spectrogram_pos -
594 LCD_WIDTH + x);
595 graph_settings.spectrogram_pos = LCD_WIDTH - x - 1;
598 mylcd_set_foreground(COLOR_MESSAGE_FRAME);
599 mylcd_fillrect(LCD_WIDTH - x, 0, LCD_WIDTH - 1, y);
601 mylcd_set_foreground(COLOR_MESSAGE_FG);
602 mylcd_set_background(COLOR_MESSAGE_BG);
603 mylcd_putsxy(LCD_WIDTH - x + POPUP_HPADDING,
604 POPUP_VPADDING, message);
605 mylcd_set_foreground(COLOR_DEFAULT_FG);
606 mylcd_set_background(COLOR_DEFAULT_BG);
609 static void draw(const unsigned char* message)
611 static long show_message_tick = 0;
612 static const unsigned char* last_message = 0;
614 if(message != NULL)
616 last_message = message;
617 show_message_tick = (*rb->current_tick + HZ) | 1;
620 /* maybe take additional actions depending upon the changed setting */
621 if(graph_settings.changed.orientation)
623 graph_settings.changed.amp_scale = true;
624 graph_settings.changed.do_clear = true;
627 if(graph_settings.changed.mode)
629 graph_settings.changed.amp_scale = true;
630 graph_settings.changed.do_clear = true;
633 if(graph_settings.changed.amp_scale)
634 memset(plot_history, 0, sizeof (plot_history));
636 if(graph_settings.changed.freq_scale)
637 graph_settings.changed.freq_scale = true;
639 mylcd_set_foreground(COLOR_DEFAULT_FG);
640 mylcd_set_background(COLOR_DEFAULT_BG);
642 switch (graph_settings.mode)
644 default:
645 case FFT_DM_LINES: {
647 mylcd_clear_display();
649 if (graph_settings.orientation_vertical)
650 draw_lines_vertical();
651 else
652 draw_lines_horizontal();
653 break;
655 case FFT_DM_BARS: {
657 mylcd_clear_display();
659 if(graph_settings.orientation_vertical)
660 draw_bars_vertical();
661 else
662 draw_bars_horizontal();
664 break;
666 case FFT_DM_SPECTROGRAPH: {
668 if(graph_settings.changed.do_clear)
670 graph_settings.spectrogram_pos = 0;
671 mylcd_clear_display();
674 if(graph_settings.orientation_vertical)
675 draw_spectrogram_vertical();
676 else
677 draw_spectrogram_horizontal();
678 break;
682 if(show_message_tick != 0)
684 if(TIME_BEFORE(*rb->current_tick, show_message_tick))
686 /* We have a message to show */
687 draw_message_string(last_message, true);
689 else
691 /* Stop drawing message */
692 show_message_tick = 0;
695 else if(last_message != NULL)
697 if(graph_settings.mode == FFT_DM_SPECTROGRAPH)
699 /* Spectrogram mode - need to erase the popup */
700 int x, y;
701 mylcd_getstringsize(last_message, &x, &y);
702 /* Recalculate the size */
703 x += POPUP_HPADDING*2;
704 y += POPUP_VPADDING*2;
706 if(!graph_settings.orientation_vertical)
708 /* In horizontal spectrogram mode, just scroll up by Y lines */
709 mylcd_scroll_up(y);
710 graph_settings.spectrogram_pos -= y;
711 if(graph_settings.spectrogram_pos < 0)
712 graph_settings.spectrogram_pos = 0;
714 else
716 /* In vertical spectrogram mode, erase the popup */
717 mylcd_set_foreground(COLOR_DEFAULT_BG);
718 mylcd_fillrect(graph_settings.spectrogram_pos + 1, 0,
719 LCD_WIDTH, y);
720 mylcd_set_foreground(COLOR_DEFAULT_FG);
723 /* else These modes clear the screen themselves */
725 last_message = NULL;
728 mylcd_update();
730 graph_settings.changed.clear_all = false;
733 static void draw_lines_vertical(void)
735 static int max = 0;
737 #if LCD_WIDTH < ARRAYLEN_PLOT /* graph compression */
738 const int offset = 0;
739 const int plotwidth = LCD_WIDTH;
740 #else
741 const int offset = (LCD_HEIGHT - ARRAYLEN_PLOT) / 2;
742 const int plotwidth = ARRAYLEN_PLOT;
743 #endif
745 int this_max;
746 int i, x;
748 if(graph_settings.changed.amp_scale)
749 max = 0; /* reset the graph on scaling mode change */
751 this_max = calc_magnitudes(graph_settings.logarithmic_amp);
753 if(this_max == 0)
755 mylcd_hline(0, LCD_WIDTH - 1, LCD_HEIGHT - 1); /* Draw all "zero" */
756 return;
759 if(graph_settings.logarithmic_freq)
760 logarithmic_plot_translate();
762 /* take the maximum of neighboring bins if we have to scale the graph
763 * horizontally */
764 if(LCD_WIDTH < ARRAYLEN_PLOT) /* graph compression */
766 int bins_acc = LCD_WIDTH / 2;
767 int bins_max = 0;
769 i = 0, x = 0;
771 for(;;)
773 int bin = plot[i++];
775 if(bin > bins_max)
776 bins_max = bin;
778 bins_acc += LCD_WIDTH;
780 if(bins_acc >= ARRAYLEN_PLOT)
782 plot[x] = bins_max;
784 if(bins_max > max)
785 max = bins_max;
787 if(++x >= LCD_WIDTH)
788 break;
790 bins_acc -= ARRAYLEN_PLOT;
791 bins_max = 0;
795 else
797 if(this_max > max)
798 max = this_max;
801 for(x = 0; x < plotwidth; ++x)
803 int h = LCD_HEIGHT*plot[x] / max;
804 mylcd_vline(x + offset, LCD_HEIGHT - h, LCD_HEIGHT-1);
808 static void draw_lines_horizontal(void)
810 static int max = 0;
812 #if LCD_WIDTH < ARRAYLEN_PLOT /* graph compression */
813 const int offset = 0;
814 const int plotwidth = LCD_HEIGHT;
815 #else
816 const int offset = (LCD_HEIGHT - ARRAYLEN_PLOT) / 2;
817 const int plotwidth = ARRAYLEN_PLOT;
818 #endif
820 int this_max;
821 int y;
823 if(graph_settings.changed.amp_scale)
824 max = 0; /* reset the graph on scaling mode change */
826 this_max = calc_magnitudes(graph_settings.logarithmic_amp);
828 if(this_max == 0)
830 mylcd_vline(0, 0, LCD_HEIGHT-1); /* Draw all "zero" */
831 return;
834 if(graph_settings.logarithmic_freq)
835 logarithmic_plot_translate();
837 /* take the maximum of neighboring bins if we have to scale the graph
838 * horizontally */
839 if(LCD_HEIGHT < ARRAYLEN_PLOT) /* graph compression */
841 int bins_acc = LCD_HEIGHT / 2;
842 int bins_max = 0;
843 int i = 0;
845 y = 0;
847 for(;;)
849 int bin = plot[i++];
851 if (bin > bins_max)
852 bins_max = bin;
854 bins_acc += LCD_HEIGHT;
856 if(bins_acc >= ARRAYLEN_PLOT)
858 plot[y] = bins_max;
860 if(bins_max > max)
861 max = bins_max;
863 if(++y >= LCD_HEIGHT)
864 break;
866 bins_acc -= ARRAYLEN_PLOT;
867 bins_max = 0;
871 else
873 if(this_max > max)
874 max = this_max;
877 for(y = 0; y < plotwidth; ++y)
879 int w = LCD_WIDTH*plot[y] / max;
880 mylcd_hline(0, w - 1, y + offset);
884 static void draw_bars_vertical(void)
886 static int max = 0;
888 #if LCD_WIDTH < LCD_HEIGHT
889 const int bars = 15;
890 #else
891 const int bars = 20;
892 #endif
893 const int border = 2;
894 const int barwidth = LCD_WIDTH / (bars + border);
895 const int width = barwidth - border;
896 const int offset = (LCD_WIDTH - bars*barwidth) / 2;
898 if(graph_settings.changed.amp_scale)
899 max = 0; /* reset the graph on scaling mode change */
901 mylcd_hline(0, LCD_WIDTH-1, LCD_HEIGHT-1); /* Draw baseline */
903 if(calc_magnitudes(graph_settings.logarithmic_amp) == 0)
904 return; /* nothing more to draw */
906 if(graph_settings.logarithmic_freq)
907 logarithmic_plot_translate();
909 int bins_acc = bars / 2;
910 int bins_max = 0;
911 int x = 0, i = 0;
913 for(;;)
915 int bin = plot[i++];
917 if(bin > bins_max)
918 bins_max = bin;
920 bins_acc += bars;
922 if(bins_acc >= ARRAYLEN_PLOT)
924 plot[x] = bins_max;
926 if(bins_max > max)
927 max = bins_max;
929 if(++x >= bars)
930 break;
932 bins_acc -= ARRAYLEN_PLOT;
933 bins_max = 0;
937 for(i = 0, x = offset; i < bars; ++i, x += barwidth)
939 int h = LCD_HEIGHT * plot[i] / max;
940 mylcd_fillrect(x, LCD_HEIGHT - h, width, h - 1);
944 static void draw_bars_horizontal(void)
946 static int max = 0;
948 #if LCD_WIDTH < LCD_HEIGHT
949 const int bars = 20;
950 #else
951 const int bars = 15;
952 #endif
953 const int border = 2;
954 const int barwidth = LCD_HEIGHT / (bars + border);
955 const int height = barwidth - border;
956 const int offset = (LCD_HEIGHT - bars*barwidth) / 2;
958 if(graph_settings.changed.amp_scale)
959 max = 0; /* reset the graph on scaling mode change */
961 mylcd_vline(0, 0, LCD_HEIGHT-1); /* Draw baseline */
963 if(calc_magnitudes(graph_settings.logarithmic_amp) == 0)
964 return; /* nothing more to draw */
966 if(graph_settings.logarithmic_freq)
967 logarithmic_plot_translate();
969 int bins_acc = bars / 2;
970 int bins_max = 0;
971 int y = 0, i = 0;
973 for(;;)
975 int bin = plot[i++];
977 if (bin > bins_max)
978 bins_max = bin;
980 bins_acc += bars;
982 if(bins_acc >= ARRAYLEN_PLOT)
984 plot[y] = bins_max;
986 if(bins_max > max)
987 max = bins_max;
989 if(++y >= bars)
990 break;
992 bins_acc -= ARRAYLEN_PLOT;
993 bins_max = 0;
997 for(i = 0, y = offset; i < bars; ++i, y += barwidth)
999 int w = LCD_WIDTH * plot[i] / max;
1000 mylcd_fillrect(1, y, w, height);
1004 static void draw_spectrogram_vertical(void)
1006 const int32_t scale_factor = MIN(LCD_HEIGHT, ARRAYLEN_PLOT);
1008 calc_magnitudes(graph_settings.logarithmic_amp);
1010 if(graph_settings.logarithmic_freq)
1011 logarithmic_plot_translate();
1013 int bins_acc = scale_factor / 2;
1014 int bins_max = 0;
1015 int y = 0, i = 0;
1017 for(;;)
1019 int bin = plot[i++];
1021 if(bin > bins_max)
1022 bins_max = bin;
1024 bins_acc += scale_factor;
1026 if(bins_acc >= ARRAYLEN_PLOT)
1028 unsigned index;
1030 if(graph_settings.logarithmic_amp)
1031 index = (SHADES-1)*bins_max / QLOG_MAX;
1032 else
1033 index = (SHADES-1)*bins_max / QLIN_MAX;
1035 /* These happen because we exaggerate the graph a little for
1036 * linear mode */
1037 if(index >= SHADES)
1038 index = SHADES-1;
1040 mylcd_set_foreground(SPECTROGRAPH_PALETTE(index));
1041 mylcd_drawpixel(graph_settings.spectrogram_pos,
1042 scale_factor-1 - y);
1044 if(++y >= scale_factor)
1045 break;
1047 bins_acc -= ARRAYLEN_PLOT;
1048 bins_max = 0;
1052 if(graph_settings.spectrogram_pos < LCD_WIDTH-1)
1053 graph_settings.spectrogram_pos++;
1054 else
1055 mylcd_scroll_left(1);
1058 static void draw_spectrogram_horizontal(void)
1060 const int32_t scale_factor = MIN(LCD_WIDTH, ARRAYLEN_PLOT);
1062 calc_magnitudes(graph_settings.logarithmic_amp);
1064 if(graph_settings.logarithmic_freq)
1065 logarithmic_plot_translate();
1067 int bins_acc = scale_factor / 2;
1068 int bins_max = 0;
1069 int x = 0, i = 0;
1071 for(;;)
1073 int bin = plot[i++];
1075 if(bin > bins_max)
1076 bins_max = bin;
1078 bins_acc += scale_factor;
1080 if(bins_acc >= ARRAYLEN_PLOT)
1082 unsigned index;
1084 if(graph_settings.logarithmic_amp)
1085 index = (SHADES-1)*bins_max / QLOG_MAX;
1086 else
1087 index = (SHADES-1)*bins_max / QLIN_MAX;
1089 /* These happen because we exaggerate the graph a little for
1090 * linear mode */
1091 if(index >= SHADES)
1092 index = SHADES-1;
1094 mylcd_set_foreground(SPECTROGRAPH_PALETTE(index));
1095 mylcd_drawpixel(x, graph_settings.spectrogram_pos);
1097 if(++x >= scale_factor)
1098 break;
1100 bins_acc -= ARRAYLEN_PLOT;
1101 bins_max = 0;
1105 if(graph_settings.spectrogram_pos < LCD_HEIGHT-1)
1106 graph_settings.spectrogram_pos++;
1107 else
1108 mylcd_scroll_up(1);
1111 /********************* End of plotting functions (modes) *********************/
1113 /****************************** FFT functions ********************************/
1115 /** functions use in single/multi configuration **/
1116 static inline bool fft_init_fft_lib(void)
1118 size_t size = sizeof(fft_buffer);
1119 fft_state = kiss_fft_alloc(FFT_SIZE, 0, fft_buffer, &size);
1121 if(fft_state == NULL)
1123 DEBUGF("needed data: %i", (int) size);
1124 return false;
1127 return true;
1130 static inline bool fft_get_fft(void)
1132 int count;
1133 int16_t *value = (int16_t *) rb->pcm_get_peak_buffer(&count);
1134 /* This block can introduce discontinuities in our data. Meaning, the
1135 * FFT will not be done a continuous segment of the signal. Which can
1136 * be bad. Or not.
1138 * Anyway, this is a demo, not a scientific tool. If you want accuracy,
1139 * do a proper spectrum analysis.*/
1141 /* there are cases when we don't have enough data to fill the buffer */
1142 if(count != ARRAYLEN_IN)
1144 if(count < ARRAYLEN_IN)
1145 return false;
1147 count = ARRAYLEN_IN; /* too much - limit */
1150 int fft_idx = 0; /* offset in 'input' */
1154 kiss_fft_scalar left = *value++;
1155 kiss_fft_scalar right = *value++;
1156 input[fft_idx].r = (left + right) >> 1; /* to mono */
1157 } while (fft_idx++, --count > 0);
1159 apply_window_func(graph_settings.window_func);
1161 rb->yield();
1163 kiss_fft(fft_state, input, output[output_tail]);
1165 rb->yield();
1167 return true;
1170 #if NUM_CORES > 1
1171 /* use a worker thread if there is another processor core */
1172 static volatile bool fft_thread_run SHAREDDATA_ATTR = false;
1173 static unsigned long fft_thread;
1175 static long fft_thread_stack[CACHEALIGN_UP(DEFAULT_STACK_SIZE*4/sizeof(long))]
1176 CACHEALIGN_AT_LEAST_ATTR(4);
1178 static void fft_thread_entry(void)
1180 if (!fft_init_fft_lib())
1182 output_tail = -1; /* tell that we bailed */
1183 fft_thread_run = true;
1184 return;
1187 fft_thread_run = true;
1189 while(fft_thread_run)
1191 if (!rb->pcm_is_playing())
1193 rb->sleep(HZ/5);
1194 continue;
1197 if (!fft_get_fft())
1199 rb->sleep(0); /* not enough - ease up */
1200 continue;
1203 #if NUM_CORES > 1
1204 /* write back output for other processor and invalidate for next frame read */
1205 rb->cpucache_invalidate();
1206 #endif
1207 int new_tail = output_tail ^ 1;
1209 /* if full, block waiting until reader has freed a slot */
1210 while(fft_thread_run)
1212 if(new_tail != output_head)
1214 output_tail = new_tail;
1215 break;
1218 rb->sleep(0);
1223 static bool fft_have_fft(void)
1225 return output_head != output_tail;
1228 /* Call only after fft_have_fft() has returned true */
1229 static inline void fft_free_fft_output(void)
1231 output_head ^= 1; /* finished with this */
1234 static bool fft_init_fft(void)
1236 /* create worker thread - on the COP for dual-core targets */
1237 fft_thread = rb->create_thread(fft_thread_entry,
1238 fft_thread_stack, sizeof(fft_thread_stack), 0, "fft output thread"
1239 IF_PRIO(, PRIORITY_USER_INTERFACE+1) IF_COP(, COP));
1241 if(fft_thread == 0)
1243 rb->splash(HZ, "FFT thread failed create");
1244 return false;
1247 /* wait for it to indicate 'ready' */
1248 while(fft_thread_run == false)
1249 rb->sleep(0);
1251 if(output_tail == -1)
1253 /* FFT thread bailed-out like The Fed */
1254 rb->thread_wait(fft_thread);
1255 rb->splash(HZ, "FFT thread failed to init");
1256 return false;
1259 return true;
1262 static void fft_close_fft(void)
1264 /* Handle our FFT thread. */
1265 fft_thread_run = false;
1266 rb->thread_wait(fft_thread);
1267 #if NUM_CORES > 1
1268 rb->cpucache_invalidate();
1269 #endif
1271 #else /* NUM_CORES == 1 */
1272 /* everything serialize on single-core and FFT gets to use IRAM main stack if
1273 * target uses IRAM */
1274 static bool fft_have_fft(void)
1276 return rb->pcm_is_playing() && fft_get_fft();
1279 static inline void fft_free_fft_output(void)
1281 /* nothing to do */
1284 static bool fft_init_fft(void)
1286 return fft_init_fft_lib();
1289 static inline void fft_close_fft(void)
1291 /* nothing to do */
1293 #endif /* NUM_CORES */
1294 /*************************** End of FFT functions ****************************/
1296 enum plugin_status plugin_start(const void* parameter)
1298 /* Defaults */
1299 bool run = true;
1300 bool showing_warning = false;
1302 if (!fft_init_fft())
1303 return PLUGIN_ERROR;
1305 #ifndef HAVE_LCD_COLOR
1306 unsigned char *gbuf;
1307 size_t gbuf_size = 0;
1308 /* get the remainder of the plugin buffer */
1309 gbuf = (unsigned char *) rb->plugin_get_buffer(&gbuf_size);
1311 /* initialize the greyscale buffer.*/
1312 if (!grey_init(gbuf, gbuf_size, GREY_ON_COP | GREY_BUFFERED,
1313 LCD_WIDTH, LCD_HEIGHT, NULL))
1315 rb->splash(HZ, "Couldn't init greyscale display");
1316 fft_close_fft();
1317 return PLUGIN_ERROR;
1319 grey_show(true);
1320 #endif
1322 logarithmic_plot_init();
1324 #if LCD_DEPTH > 1
1325 rb->lcd_set_backdrop(NULL);
1326 mylcd_clear_display();
1327 mylcd_update();
1328 #endif
1329 backlight_force_on();
1331 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
1332 rb->cpu_boost(true);
1333 #endif
1335 while (run)
1337 /* Unless otherwise specified, HZ/50 is around the window length
1338 * and quite fast. We want to be done with drawing by this time. */
1339 long next_frame_tick = *rb->current_tick + HZ/50;
1340 int button;
1342 while (!fft_have_fft())
1344 int timeout;
1346 if(!rb->pcm_is_playing())
1348 showing_warning = true;
1349 mylcd_clear_display();
1350 draw_message_string("No audio playing", false);
1351 mylcd_update();
1352 timeout = HZ/5;
1354 else
1356 if(showing_warning)
1358 showing_warning = false;
1359 mylcd_clear_display();
1360 mylcd_update();
1363 timeout = HZ/100; /* 'till end of curent tick, don't use 100% CPU */
1366 /* Make sure the FFT has produced something before doing anything
1367 * but watching for buttons. Music might not be playing or things
1368 * just aren't going well for picking up buffers so keys are
1369 * scanned to avoid lockup. */
1370 button = rb->button_get_w_tmo(timeout);
1371 if (button != BUTTON_NONE)
1372 goto read_button;
1375 draw(NULL);
1377 fft_free_fft_output(); /* COP only */
1379 long tick = *rb->current_tick;
1380 if(TIME_BEFORE(tick, next_frame_tick))
1382 tick = next_frame_tick - tick;
1384 else
1386 rb->yield(); /* tmo = 0 won't yield */
1387 tick = 0;
1390 button = rb->button_get_w_tmo(tick);
1391 read_button:
1392 switch (button)
1394 case FFT_QUIT:
1395 run = false;
1396 break;
1397 case FFT_PREV_GRAPH: {
1398 if (graph_settings.mode-- <= FFT_DM_FIRST)
1399 graph_settings.mode = FFT_DM_COUNT-1;
1400 graph_settings.changed.mode = true;
1401 draw(modes_text[graph_settings.mode]);
1402 break;
1404 case FFT_NEXT_GRAPH: {
1405 if (++graph_settings.mode >= FFT_DM_COUNT)
1406 graph_settings.mode = FFT_DM_FIRST;
1407 graph_settings.changed.mode = true;
1408 draw(modes_text[graph_settings.mode]);
1409 break;
1411 case FFT_WINDOW: {
1412 if(++graph_settings.window_func >= FFT_WF_COUNT)
1413 graph_settings.window_func = FFT_WF_FIRST;
1414 graph_settings.changed.window_func = true;
1415 draw(window_text[graph_settings.window_func]);
1416 break;
1418 case FFT_AMP_SCALE: {
1419 graph_settings.logarithmic_amp = !graph_settings.logarithmic_amp;
1420 graph_settings.changed.amp_scale = true;
1421 draw(amp_scales_text[graph_settings.logarithmic_amp ? 1 : 0]);
1422 break;
1424 #ifdef FFT_FREQ_SCALE /* 'Till all keymaps are defined */
1425 case FFT_FREQ_SCALE: {
1426 graph_settings.logarithmic_freq = !graph_settings.logarithmic_freq;
1427 graph_settings.changed.freq_scale = true;
1428 draw(freq_scales_text[graph_settings.logarithmic_freq ? 1 : 0]);
1429 break;
1431 #endif
1432 case FFT_ORIENTATION: {
1433 graph_settings.orientation_vertical =
1434 !graph_settings.orientation_vertical;
1435 graph_settings.changed.orientation = true;
1436 draw(NULL);
1437 break;
1439 default: {
1440 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
1441 return PLUGIN_USB_CONNECTED;
1447 fft_close_fft();
1449 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
1450 rb->cpu_boost(false);
1451 #endif
1452 #ifndef HAVE_LCD_COLOR
1453 grey_release();
1454 #endif
1455 backlight_use_settings();
1456 return PLUGIN_OK;
1457 (void)parameter;