FFT Plugin: Revamp the main code to rid it of 64-bit math. Use 32-bit kiss_fft_scalar...
[kugel-rb.git] / apps / plugins / fft / fft.c
blob2b9e541f3e281cdb91593dd35efd82403ca412dd
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"
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_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 #if (LCD_WIDTH < LCD_HEIGHT)
251 #define LCD_SIZE LCD_HEIGHT
252 #else
253 #define LCD_SIZE LCD_WIDTH
254 #endif
256 #if (LCD_SIZE < 512)
257 #define FFT_SIZE 2048 /* 512*4 */
258 #elif (LCD_SIZE < 1024)
259 #define FFT_SIZE 4096 /* 1024*4 */
260 #else
261 #define FFT_SIZE 8192 /* 2048*4 */
262 #endif
264 #ifdef HAVE_LCD_COLOR
265 #define lcd_(fn) rb->lcd_##fn
266 #define lcd_scroll_up xlcd_scroll_up
267 #define lcd_scroll_left xlcd_scroll_left
268 #else
269 #define lcd_(fn) grey_##fn
270 #define lcd_scroll_up grey_scroll_up
271 #define lcd_scroll_left grey_scroll_left
272 #endif
274 #define ARRAYLEN_IN (FFT_SIZE)
275 #define ARRAYLEN_OUT (FFT_SIZE/2)
276 #define ARRAYLEN_PLOT ((FFT_SIZE/4)-1) /* -1 to ignore DC bin */
277 #define BUFSIZE_FFT (sizeof(struct kiss_fft_state)+sizeof(kiss_fft_cpx)*(FFT_SIZE-1))
278 #define BUFSIZE_FFTR (BUFSIZE_FFT+sizeof(struct kiss_fftr_state)+sizeof(kiss_fft_cpx)*(FFT_SIZE*3/2))
279 #define BUFSIZE BUFSIZE_FFTR
280 #define FFT_ALLOC kiss_fftr_alloc
281 #define FFT_FFT kiss_fftr
282 #define FFT_CFG kiss_fftr_cfg
284 #define __COEFF(type,size) type##_##size
285 #define _COEFF(x, y) __COEFF(x,y) /* force the preprocessor to evaluate FFT_SIZE) */
286 #define HANN_COEFF _COEFF(hann, FFT_SIZE)
287 #define HAMMING_COEFF _COEFF(hamming, FFT_SIZE)
289 /****************************** Globals ****************************/
291 static volatile int output_head SHAREDBSS_ATTR = 0;
292 static volatile int output_tail SHAREDBSS_ATTR = 0;
293 /* cacheline-aligned buffers with COP, otherwise word-aligned */
295 #define CACHEALIGN_UP_SIZE(type, len) \
296 (CACHEALIGN_UP((len)*sizeof(type) + (sizeof(type)-1)) / sizeof(type))
297 /* Shared */
298 /* COP + CPU PCM */
299 static kiss_fft_scalar input[CACHEALIGN_UP_SIZE(kiss_fft_scalar, ARRAYLEN_IN)]
300 CACHEALIGN_AT_LEAST_ATTR(4);
301 /* CPU+COP */
303 /* The result is nfft/2+1 complex frequency bins from DC to Nyquist. */
304 static kiss_fft_cpx output[2][CACHEALIGN_UP_SIZE(kiss_fft_cpx, ARRAYLEN_OUT+1)]
305 __attribute__((aligned(4))) SHAREDBSS_ATTR;
307 /* Unshared */
308 /* COP */
309 static char buffer[CACHEALIGN_UP_SIZE(char, BUFSIZE)]
310 CACHEALIGN_AT_LEAST_ATTR(4);
311 /* CPU */
312 static int32_t plot_history[ARRAYLEN_PLOT];
313 static int32_t plot[ARRAYLEN_PLOT];
314 static struct
316 int16_t bin; /* integer bin number */
317 uint16_t frac; /* interpolation fraction */
318 } binlog[ARRAYLEN_PLOT] __attribute__((aligned(4)));
320 static volatile bool fft_thread_run SHAREDDATA_ATTR = false;
322 enum fft_window_func
324 FFT_WF_FIRST = 0,
325 FFT_WF_HAMMING = 0,
326 FFT_WF_HANN,
328 #define FFT_WF_COUNT (FFT_WF_HANN+1)
330 enum fft_display_mode
332 FFT_DM_FIRST = 0,
333 FFT_DM_LINES = 0,
334 FFT_DM_BARS,
335 FFT_DM_SPECTROGRAPH,
337 #define FFT_DM_COUNT (FFT_DM_SPECTROGRAPH+1)
339 static const unsigned char* const modes_text[FFT_DM_COUNT] =
340 { "Lines", "Bars", "Spectrogram" };
342 static const unsigned char* const amp_scales_text[2] =
343 { "Linear amplitude", "Logarithmic amplitude" };
345 static const unsigned char* const freq_scales_text[2] =
346 { "Linear frequency", "Logarithmic frequency" };
348 static const unsigned char* const window_text[FFT_WF_COUNT] =
349 { "Hamming window", "Hann window" };
351 static struct {
352 bool orientation_vertical;
353 enum fft_display_mode mode;
354 bool logarithmic_amp;
355 bool logarithmic_freq;
356 enum fft_window_func window_func;
357 int spectrogram_pos; /* row or column - only used by one at a time */
358 union
360 struct
362 bool orientation : 1;
363 bool mode : 1;
364 bool amp_scale : 1;
365 bool freq_scale : 1;
366 bool window_func : 1;
367 bool do_clear : 1;
369 bool clear_all; /* Write 'false' to clear all above */
370 } changed;
371 } graph_settings SHAREDDATA_ATTR =
373 /* Defaults */
374 .orientation_vertical = true,
375 .mode = FFT_DM_LINES,
376 .logarithmic_amp = true,
377 .logarithmic_freq = true,
378 .window_func = FFT_WF_HAMMING,
379 .spectrogram_pos = 0,
380 .changed = { .clear_all = false },
383 #ifdef HAVE_LCD_COLOR
384 #define SHADES BMPWIDTH_fft_colors
385 #define SPECTROGRAPH_PALETTE(index) (fft_colors[index])
386 #else
387 #define SHADES 256
388 #define SPECTROGRAPH_PALETTE(index) (255 - (index))
389 #endif
391 /************************* End of globals *************************/
393 /************************* Math functions *************************/
395 /* Based on playing back a 0dB sweep tone */
396 #define QLOG_MAX 0x000865EF
397 /* fudge it a little or it's not very visbile */
398 #define QLIN_MAX (0x00001157 >> 1)
400 /* Apply window function to input */
401 void apply_window_func(enum fft_window_func mode)
403 int i;
405 switch(mode)
407 case FFT_WF_HAMMING:
408 for(i = 0; i < ARRAYLEN_IN; ++i)
410 input[i] = (input[i] * HAMMING_COEFF[i] + 16384) >> 15;
412 break;
414 case FFT_WF_HANN:
415 for(i = 0; i < ARRAYLEN_IN; ++i)
417 input[i] = (input[i] * HANN_COEFF[i] + 16384) >> 15;
419 break;
423 /* Calculates the magnitudes from complex numbers and returns the maximum */
424 int32_t calc_magnitudes(bool logarithmic_amp)
426 /* A major assumption made when calculating the Q*MAX constants
427 * is that the maximum magnitude is 29 bits long. */
428 uint32_t max = 0;
429 kiss_fft_cpx *this_output = output[output_head] + 1; /* skip DC */
430 int i;
432 /* Calculate the magnitude, discarding the phase. */
433 for(i = 0; i < ARRAYLEN_PLOT; ++i)
435 int32_t re = this_output[i].r;
436 int32_t im = this_output[i].i;
438 uint32_t tmp = re*re + im*im;
440 if(tmp > 0)
442 if(tmp > 0x7FFFFFFF) /* clip */
444 tmp = 0x7FFFFFFF; /* if our assumptions are correct,
445 this should never happen. It's just
446 a safeguard. */
449 if(logarithmic_amp)
451 if(tmp < 0x8000) /* be more precise */
453 /* ln(x ^ .5) = .5*ln(x) */
454 tmp = fp16_log(tmp << 16) >> 1;
456 else
458 tmp = isqrt(tmp); /* linear scaling, nothing
459 bad should happen */
460 tmp = fp16_log(tmp << 16); /* the log function
461 expects s15.16 values */
464 else
466 tmp = isqrt(tmp); /* linear scaling, nothing
467 bad should happen */
471 /* Length 2 moving average - last transform and this one */
472 tmp = (plot_history[i] + tmp) >> 1;
473 plot[i] = tmp;
474 plot_history[i] = tmp;
476 if(tmp > max)
477 max = tmp;
480 return max;
483 /* Move plot bins into a logarithmic scale by sliding them towards the
484 * Nyquist bin according to the translation in the binlog array. */
485 void logarithmic_plot_translate(void)
487 int i;
489 for(i = ARRAYLEN_PLOT-1; i > 0; --i)
491 int bin;
492 int s = binlog[i].bin;
493 int e = binlog[i-1].bin;
494 int frac = binlog[i].frac;
496 bin = plot[s];
498 if(frac)
500 /* slope < 1, Interpolate stretched bins (linear for now) */
501 int diff = plot[s+1] - bin;
505 plot[i] = bin + FRACMUL(frac << 15, diff);
506 frac = binlog[--i].frac;
508 while(frac);
510 else
512 /* slope > 1, Find peak of two or more bins */
513 while(--s > e)
515 int val = plot[s];
517 if (val > bin)
518 bin = val;
522 plot[i] = bin;
526 /* Calculates the translation for logarithmic plot bins */
527 void logarithmic_plot_init(void)
529 int i, j;
531 * log: y = round(n * ln(x) / ln(n))
532 * anti: y = round(exp(x * ln(n) / n))
534 j = fp16_log((ARRAYLEN_PLOT - 1) << 16);
535 for(i = 0; i < ARRAYLEN_PLOT; ++i)
537 binlog[i].bin = (fp16_exp(i * j / (ARRAYLEN_PLOT - 1)) + 32768) >> 16;
540 /* setup fractions for interpolation of stretched bins */
541 for(i = 0; i < ARRAYLEN_PLOT-1; i = j)
543 j = i + 1;
545 /* stop when we have two different values */
546 while(binlog[j].bin == binlog[i].bin)
547 j++; /* if here, local slope of curve is < 1 */
549 if(j > i + 1)
551 /* distribute pieces evenly over stretched interval */
552 int diff = j - i;
553 int x = 0;
556 binlog[i].frac = (x++ << 16) / diff;
558 while(++i < j);
563 /************************ End of math functions ***********************/
565 /********************* Plotting functions (modes) *********************/
566 void draw_lines_vertical(void);
567 void draw_lines_horizontal(void);
568 void draw_bars_vertical(void);
569 void draw_bars_horizontal(void);
570 void draw_spectrogram_vertical(void);
571 void draw_spectrogram_horizontal(void);
573 #ifdef HAVE_LCD_COLOR
574 #define COLOR_DEFAULT_FG LCD_DEFAULT_FG
575 #define COLOR_DEFAULT_BG LCD_DEFAULT_BG
576 #define COLOR_MESSAGE_FRAME LCD_RGBPACK(0xc6, 0x00, 0x00)
577 #define COLOR_MESSAGE_BG LCD_BLACK
578 #define COLOR_MESSAGE_FG LCD_WHITE
579 #else
580 #define COLOR_DEFAULT_FG GREY_BLACK
581 #define COLOR_DEFAULT_BG GREY_WHITE
582 #define COLOR_MESSAGE_FRAME GREY_DARKGRAY
583 #define COLOR_MESSAGE_BG GREY_WHITE
584 #define COLOR_MESSAGE_FG GREY_BLACK
585 #endif
587 #define POPUP_HPADDING 3 /* 3 px of horizontal padding and */
588 #define POPUP_VPADDING 2 /* 2 px of vertical padding */
590 void draw_message_string(const unsigned char *message, bool active)
592 int x, y;
593 lcd_(getstringsize)(message, &x, &y);
595 /* x and y give the size of the box for the popup */
596 x += POPUP_HPADDING*2;
597 y += POPUP_VPADDING*2;
599 /* In vertical spectrogram mode, leave space for the popup
600 * before actually drawing it (if space is needed) */
601 if(active &&
602 graph_settings.mode == FFT_DM_SPECTROGRAPH &&
603 graph_settings.orientation_vertical &&
604 graph_settings.spectrogram_pos >= LCD_WIDTH - x)
606 lcd_scroll_left(graph_settings.spectrogram_pos -
607 LCD_WIDTH + x);
608 graph_settings.spectrogram_pos = LCD_WIDTH - x - 1;
611 lcd_(set_foreground)(COLOR_MESSAGE_FRAME);
612 lcd_(fillrect)(LCD_WIDTH - x, 0, LCD_WIDTH - 1, y);
614 lcd_(set_foreground)(COLOR_MESSAGE_FG);
615 lcd_(set_background)(COLOR_MESSAGE_BG);
616 lcd_(putsxy)(LCD_WIDTH - x + POPUP_HPADDING,
617 POPUP_VPADDING, message);
618 lcd_(set_foreground)(COLOR_DEFAULT_FG);
619 lcd_(set_background)(COLOR_DEFAULT_BG);
622 void draw(const unsigned char* message)
624 static long show_message_tick = 0;
625 static const unsigned char* last_message = 0;
627 if(message != NULL)
629 last_message = message;
630 show_message_tick = (*rb->current_tick + HZ) | 1;
633 /* maybe take additional actions depending upon the changed setting */
634 if(graph_settings.changed.orientation)
636 graph_settings.changed.amp_scale = true;
637 graph_settings.changed.do_clear = true;
640 if(graph_settings.changed.mode)
642 graph_settings.changed.amp_scale = true;
643 graph_settings.changed.do_clear = true;
646 if(graph_settings.changed.amp_scale)
647 memset(plot_history, 0, sizeof (plot_history));
649 if(graph_settings.changed.freq_scale)
650 graph_settings.changed.freq_scale = true;
652 lcd_(set_foreground)(COLOR_DEFAULT_FG);
653 lcd_(set_background)(COLOR_DEFAULT_BG);
655 switch (graph_settings.mode)
657 default:
658 case FFT_DM_LINES: {
660 lcd_(clear_display)();
662 if (graph_settings.orientation_vertical)
663 draw_lines_vertical();
664 else
665 draw_lines_horizontal();
666 break;
668 case FFT_DM_BARS: {
670 lcd_(clear_display());
672 if(graph_settings.orientation_vertical)
673 draw_bars_vertical();
674 else
675 draw_bars_horizontal();
677 break;
679 case FFT_DM_SPECTROGRAPH: {
681 if(graph_settings.changed.do_clear)
683 graph_settings.spectrogram_pos = 0;
684 lcd_(clear_display)();
687 if(graph_settings.orientation_vertical)
688 draw_spectrogram_vertical();
689 else
690 draw_spectrogram_horizontal();
691 break;
695 if(show_message_tick != 0)
697 if(TIME_BEFORE(*rb->current_tick, show_message_tick))
699 /* We have a message to show */
700 draw_message_string(last_message, true);
702 else
704 /* Stop drawing message */
705 show_message_tick = 0;
708 else if(last_message != NULL)
710 if(graph_settings.mode == FFT_DM_SPECTROGRAPH)
712 /* Spectrogram mode - need to erase the popup */
713 int x, y;
714 lcd_(getstringsize)(last_message, &x, &y);
715 /* Recalculate the size */
716 x += POPUP_HPADDING*2;
717 y += POPUP_VPADDING*2;
719 if(!graph_settings.orientation_vertical)
721 /* In horizontal spectrogram mode, just scroll up by Y lines */
722 lcd_scroll_up(y);
723 graph_settings.spectrogram_pos -= y;
724 if(graph_settings.spectrogram_pos < 0)
725 graph_settings.spectrogram_pos = 0;
727 else
729 /* In vertical spectrogram mode, erase the popup */
730 lcd_(set_foreground)(COLOR_DEFAULT_BG);
731 lcd_(fillrect)(graph_settings.spectrogram_pos + 1, 0,
732 LCD_WIDTH, y);
733 lcd_(set_foreground)(COLOR_DEFAULT_FG);
736 /* else These modes clear the screen themselves */
738 last_message = NULL;
741 lcd_(update)();
743 graph_settings.changed.clear_all = false;
746 void draw_lines_vertical(void)
748 static int max = 0;
750 #if LCD_WIDTH < ARRAYLEN_PLOT /* graph compression */
751 const int offset = 0;
752 const int plotwidth = LCD_WIDTH;
753 #else
754 const int offset = (LCD_HEIGHT - ARRAYLEN_PLOT) / 2;
755 const int plotwidth = ARRAYLEN_PLOT;
756 #endif
758 int this_max;
759 int i, x;
761 if(graph_settings.changed.amp_scale)
762 max = 0; /* reset the graph on scaling mode change */
764 this_max = calc_magnitudes(graph_settings.logarithmic_amp);
766 if(this_max == 0)
768 lcd_(hline)(0, LCD_WIDTH - 1, LCD_HEIGHT - 1); /* Draw all "zero" */
769 return;
772 if(graph_settings.logarithmic_freq)
773 logarithmic_plot_translate();
775 /* take the maximum of neighboring bins if we have to scale the graph
776 * horizontally */
777 if(LCD_WIDTH < ARRAYLEN_PLOT) /* graph compression */
779 int bins_acc = LCD_WIDTH / 2;
780 int bins_max = 0;
782 i = 0, x = 0;
784 for(;;)
786 int bin = plot[i++];
788 if(bin > bins_max)
789 bins_max = bin;
791 bins_acc += LCD_WIDTH;
793 if(bins_acc >= ARRAYLEN_PLOT)
795 plot[x] = bins_max;
797 if(bins_max > max)
798 max = bins_max;
800 if(++x >= LCD_WIDTH)
801 break;
803 bins_acc -= ARRAYLEN_PLOT;
804 bins_max = 0;
808 else
810 if(this_max > max)
811 max = this_max;
814 for(x = 0; x < plotwidth; ++x)
816 int h = LCD_HEIGHT*plot[x] / max;
817 lcd_(vline)(x + offset, LCD_HEIGHT - h, LCD_HEIGHT-1);
821 void draw_lines_horizontal(void)
823 static int max = 0;
825 #if LCD_WIDTH < ARRAYLEN_PLOT /* graph compression */
826 const int offset = 0;
827 const int plotwidth = LCD_HEIGHT;
828 #else
829 const int offset = (LCD_HEIGHT - ARRAYLEN_PLOT) / 2;
830 const int plotwidth = ARRAYLEN_PLOT;
831 #endif
833 int this_max;
834 int y;
836 if(graph_settings.changed.amp_scale)
837 max = 0; /* reset the graph on scaling mode change */
839 this_max = calc_magnitudes(graph_settings.logarithmic_amp);
841 if(this_max == 0)
843 lcd_(vline)(0, 0, LCD_HEIGHT-1); /* Draw all "zero" */
844 return;
847 if(graph_settings.logarithmic_freq)
848 logarithmic_plot_translate();
850 /* take the maximum of neighboring bins if we have to scale the graph
851 * horizontally */
852 if(LCD_HEIGHT < ARRAYLEN_PLOT) /* graph compression */
854 int bins_acc = LCD_HEIGHT / 2;
855 int bins_max = 0;
856 int i = 0;
858 y = 0;
860 for(;;)
862 int bin = plot[i++];
864 if (bin > bins_max)
865 bins_max = bin;
867 bins_acc += LCD_HEIGHT;
869 if(bins_acc >= ARRAYLEN_PLOT)
871 plot[y] = bins_max;
873 if(bins_max > max)
874 max = bins_max;
876 if(++y >= LCD_HEIGHT)
877 break;
879 bins_acc -= ARRAYLEN_PLOT;
880 bins_max = 0;
884 else
886 if(this_max > max)
887 max = this_max;
890 for(y = 0; y < plotwidth; ++y)
892 int w = LCD_WIDTH*plot[y] / max;
893 lcd_(hline)(0, w - 1, y + offset);
897 void draw_bars_vertical(void)
899 static int max = 0;
901 #if LCD_WIDTH < LCD_HEIGHT
902 const int bars = 15;
903 #else
904 const int bars = 20;
905 #endif
906 const int border = 2;
907 const int barwidth = LCD_WIDTH / (bars + border);
908 const int width = barwidth - border;
909 const int offset = (LCD_WIDTH - bars*barwidth) / 2;
911 if(graph_settings.changed.amp_scale)
912 max = 0; /* reset the graph on scaling mode change */
914 lcd_(hline)(0, LCD_WIDTH-1, LCD_HEIGHT-1); /* Draw baseline */
916 if(calc_magnitudes(graph_settings.logarithmic_amp) == 0)
917 return; /* nothing more to draw */
919 if(graph_settings.logarithmic_freq)
920 logarithmic_plot_translate();
922 int bins_acc = bars / 2;
923 int bins_max = 0;
924 int x = 0, i = 0;
926 for(;;)
928 int bin = plot[i++];
930 if(bin > bins_max)
931 bins_max = bin;
933 bins_acc += bars;
935 if(bins_acc >= ARRAYLEN_PLOT)
937 plot[x] = bins_max;
939 if(bins_max > max)
940 max = bins_max;
942 if(++x >= bars)
943 break;
945 bins_acc -= ARRAYLEN_PLOT;
946 bins_max = 0;
950 for(i = 0, x = offset; i < bars; ++i, x += barwidth)
952 int h = LCD_HEIGHT * plot[i] / max;
953 lcd_(fillrect)(x, LCD_HEIGHT - h, width, h - 1);
957 void draw_bars_horizontal(void)
959 static int max = 0;
961 #if LCD_WIDTH < LCD_HEIGHT
962 const int bars = 20;
963 #else
964 const int bars = 15;
965 #endif
966 const int border = 2;
967 const int barwidth = LCD_HEIGHT / (bars + border);
968 const int height = barwidth - border;
969 const int offset = (LCD_HEIGHT - bars*barwidth) / 2;
971 if(graph_settings.changed.amp_scale)
972 max = 0; /* reset the graph on scaling mode change */
974 lcd_(vline)(0, 0, LCD_HEIGHT-1); /* Draw baseline */
976 if(calc_magnitudes(graph_settings.logarithmic_amp) == 0)
977 return; /* nothing more to draw */
979 if(graph_settings.logarithmic_freq)
980 logarithmic_plot_translate();
982 int bins_acc = bars / 2;
983 int bins_max = 0;
984 int y = 0, i = 0;
986 for(;;)
988 int bin = plot[i++];
990 if (bin > bins_max)
991 bins_max = bin;
993 bins_acc += bars;
995 if(bins_acc >= ARRAYLEN_PLOT)
997 plot[y] = bins_max;
999 if(bins_max > max)
1000 max = bins_max;
1002 if(++y >= bars)
1003 break;
1005 bins_acc -= ARRAYLEN_PLOT;
1006 bins_max = 0;
1010 for(i = 0, y = offset; i < bars; ++i, y += barwidth)
1012 int w = LCD_WIDTH * plot[i] / max;
1013 lcd_(fillrect)(1, y, w, height);
1017 void draw_spectrogram_vertical(void)
1019 const int32_t scale_factor = MIN(LCD_HEIGHT, ARRAYLEN_PLOT);
1021 calc_magnitudes(graph_settings.logarithmic_amp);
1023 if(graph_settings.logarithmic_freq)
1024 logarithmic_plot_translate();
1026 int bins_acc = scale_factor / 2;
1027 int bins_max = 0;
1028 int y = 0, i = 0;
1030 for(;;)
1032 int bin = plot[i++];
1034 if(bin > bins_max)
1035 bins_max = bin;
1037 bins_acc += scale_factor;
1039 if(bins_acc >= ARRAYLEN_PLOT)
1041 unsigned index;
1043 if(graph_settings.logarithmic_amp)
1044 index = (SHADES-1)*bins_max / QLOG_MAX;
1045 else
1046 index = (SHADES-1)*bins_max / QLIN_MAX;
1048 /* These happen because we exaggerate the graph a little for
1049 * linear mode */
1050 if(index >= SHADES)
1051 index = SHADES-1;
1053 lcd_(set_foreground)(SPECTROGRAPH_PALETTE(index));
1054 lcd_(drawpixel)(graph_settings.spectrogram_pos,
1055 scale_factor-1 - y);
1057 if(++y >= LCD_HEIGHT)
1058 break;
1060 bins_acc -= ARRAYLEN_PLOT;
1061 bins_max = 0;
1065 if(graph_settings.spectrogram_pos < LCD_WIDTH-1)
1066 graph_settings.spectrogram_pos++;
1067 else
1068 lcd_scroll_left(1);
1071 void draw_spectrogram_horizontal(void)
1073 const int32_t scale_factor = MIN(LCD_WIDTH, ARRAYLEN_PLOT);
1075 calc_magnitudes(graph_settings.logarithmic_amp);
1077 if(graph_settings.logarithmic_freq)
1078 logarithmic_plot_translate();
1080 int bins_acc = scale_factor / 2;
1081 int bins_max = 0;
1082 int x = 0, i = 0;
1084 for(;;)
1086 int bin = plot[i++];
1088 if(bin > bins_max)
1089 bins_max = bin;
1091 bins_acc += scale_factor;
1093 if(bins_acc >= ARRAYLEN_PLOT)
1095 unsigned index;
1097 if(graph_settings.logarithmic_amp)
1098 index = (SHADES-1)*bins_max / QLOG_MAX;
1099 else
1100 index = (SHADES-1)*bins_max / QLIN_MAX;
1102 /* These happen because we exaggerate the graph a little for
1103 * linear mode */
1104 if(index >= SHADES)
1105 index = SHADES-1;
1107 lcd_(set_foreground)(SPECTROGRAPH_PALETTE(index));
1108 lcd_(drawpixel)(x, graph_settings.spectrogram_pos);
1110 if(++x >= LCD_WIDTH)
1111 break;
1113 bins_acc -= ARRAYLEN_PLOT;
1114 bins_max = 0;
1118 if(graph_settings.spectrogram_pos < LCD_HEIGHT-1)
1119 graph_settings.spectrogram_pos++;
1120 else
1121 lcd_scroll_up(1);
1124 /********************* End of plotting functions (modes) *********************/
1126 /* TODO: Only have this thread for multicore, otherwise it serves no purpose */
1127 long fft_thread_stack[CACHEALIGN_UP(DEFAULT_STACK_SIZE*4/sizeof(long))]
1128 CACHEALIGN_AT_LEAST_ATTR(4);
1129 void fft_thread_entry(void)
1131 size_t size = sizeof(buffer);
1132 FFT_CFG state = FFT_ALLOC(FFT_SIZE, 0, buffer, &size);
1133 int count;
1135 if(state == 0)
1137 DEBUGF("needed data: %i", (int) size);
1138 output_tail = -1; /* tell that we bailed */
1139 fft_thread_run = true;
1140 return;
1143 fft_thread_run = true;
1145 while(fft_thread_run)
1147 int16_t *value = (int16_t *) rb->pcm_get_peak_buffer(&count);
1148 /* This block can introduce discontinuities in our data. Meaning, the
1149 * FFT will not be done a continuous segment of the signal. Which can
1150 * be bad. Or not.
1152 * Anyway, this is a demo, not a scientific tool. If you want accuracy,
1153 * do a proper spectrum analysis.*/
1155 /* there are cases when we don't have enough data to fill the buffer */
1156 if (!rb->pcm_is_playing())
1158 rb->sleep(HZ/5);
1159 output_tail = output_head; /* set empty */
1160 continue;
1162 else if(count != ARRAYLEN_IN/2)
1164 if(count < ARRAYLEN_IN/2)
1166 rb->sleep(0); /* not enough - ease up */
1167 continue;
1170 count = ARRAYLEN_IN/2; /* too much - limit */
1173 int fft_idx = 0; /* offset in 'input' */
1177 kiss_fft_scalar left = *value++;
1178 kiss_fft_scalar right = *value++;
1179 input[fft_idx++] = (left + right) >> 1; /* to mono */
1180 input[fft_idx++] = 0;
1181 } while (--count > 0);
1183 apply_window_func(graph_settings.window_func);
1184 FFT_FFT(state, input, output[output_tail]);
1185 rb->yield();
1186 #if NUM_CORES > 1
1187 /* write back output for other processor and invalidate for next frame read */
1188 rb->cpucache_invalidate();
1189 #endif
1190 int new_tail = output_tail ^ 1;
1192 /* if full, block waiting until reader has freed a slot */
1193 while(new_tail == output_head && fft_thread_run)
1194 rb->sleep(0);
1196 output_tail = new_tail;
1200 enum plugin_status plugin_start(const void* parameter)
1202 /* Defaults */
1203 bool run = true;
1204 bool showing_warning = false;
1205 int timeout = HZ/100;
1207 /* create worker thread - on the COP for dual-core targets */
1208 unsigned int fft_thread = rb->create_thread(fft_thread_entry,
1209 fft_thread_stack, sizeof(fft_thread_stack), 0, "fft output thread"
1210 IF_PRIO(, PRIORITY_USER_INTERFACE+1) IF_COP(, COP));
1212 if(fft_thread == 0)
1214 rb->splash(HZ, "FFT thread failed create");
1215 return PLUGIN_ERROR;
1218 /* wait for it to indicate 'ready' */
1219 while(fft_thread_run == false)
1220 rb->sleep(0);
1222 if(output_tail == -1)
1224 /* FFT thread bailed-out like The Fed */
1225 rb->thread_wait(fft_thread);
1226 rb->splash(HZ, "FFT thread failed to init");
1227 return PLUGIN_ERROR;
1230 #ifndef HAVE_LCD_COLOR
1231 unsigned char *gbuf;
1232 size_t gbuf_size = 0;
1233 /* get the remainder of the plugin buffer */
1234 gbuf = (unsigned char *) rb->plugin_get_buffer(&gbuf_size);
1236 /* initialize the greyscale buffer.*/
1237 if (!grey_init(gbuf, gbuf_size, GREY_ON_COP | GREY_BUFFERED,
1238 LCD_WIDTH, LCD_HEIGHT, NULL))
1240 rb->splash(HZ, "Couldn't init greyscale display");
1241 fft_thread_run = false;
1242 rb->thread_wait(fft_thread);
1243 return PLUGIN_ERROR;
1245 grey_show(true);
1246 #endif
1248 logarithmic_plot_init();
1250 #if LCD_DEPTH > 1
1251 rb->lcd_set_backdrop(NULL);
1252 lcd_(clear_display)();
1253 lcd_(update)();
1254 #endif
1255 backlight_force_on();
1257 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
1258 rb->cpu_boost(true);
1259 #endif
1261 while (run)
1263 int button;
1265 while(output_head == output_tail)
1267 if(!rb->pcm_is_playing())
1269 showing_warning = true;
1270 lcd_(clear_display)();
1271 draw_message_string("No audio playing", false);
1272 lcd_(update)();
1273 timeout = HZ/5;
1275 else
1277 if(showing_warning)
1279 showing_warning = false;
1280 lcd_(clear_display)();
1281 lcd_(update)();
1282 timeout = HZ/100;
1286 /* Make sure the input thread has produced something before doing
1287 * anything but watching for buttons. Music might not be playing
1288 * or things just aren't going well for picking up buffers so keys
1289 * are scanned to avoid lockup. */
1290 button = rb->button_get_w_tmo(timeout);
1291 if (button != BUTTON_NONE)
1292 goto read_button;
1295 draw(NULL);
1297 output_head ^= 1; /* done drawing, free this buffer */
1298 rb->yield();
1300 button = rb->button_get(false);
1301 read_button:
1302 switch (button)
1304 case FFT_QUIT:
1305 run = false;
1306 break;
1307 case FFT_PREV_GRAPH: {
1308 if (graph_settings.mode-- <= FFT_DM_FIRST)
1309 graph_settings.mode = FFT_DM_COUNT-1;
1310 graph_settings.changed.mode = true;
1311 draw(modes_text[graph_settings.mode]);
1312 break;
1314 case FFT_NEXT_GRAPH: {
1315 if (++graph_settings.mode >= FFT_DM_COUNT)
1316 graph_settings.mode = FFT_DM_FIRST;
1317 graph_settings.changed.mode = true;
1318 draw(modes_text[graph_settings.mode]);
1319 break;
1321 case FFT_WINDOW: {
1322 if(++graph_settings.window_func >= FFT_WF_COUNT)
1323 graph_settings.window_func = FFT_WF_FIRST;
1324 graph_settings.changed.window_func = true;
1325 draw(window_text[graph_settings.window_func]);
1326 break;
1328 case FFT_AMP_SCALE: {
1329 graph_settings.logarithmic_amp = !graph_settings.logarithmic_amp;
1330 graph_settings.changed.amp_scale = true;
1331 draw(amp_scales_text[graph_settings.logarithmic_amp ? 1 : 0]);
1332 break;
1334 #ifdef FFT_FREQ_SCALE /* 'Till all keymaps are defined */
1335 case FFT_FREQ_SCALE: {
1336 graph_settings.logarithmic_freq = !graph_settings.logarithmic_freq;
1337 graph_settings.changed.freq_scale = true;
1338 draw(freq_scales_text[graph_settings.logarithmic_freq ? 1 : 0]);
1339 break;
1341 #endif
1342 case FFT_ORIENTATION: {
1343 graph_settings.orientation_vertical =
1344 !graph_settings.orientation_vertical;
1345 graph_settings.changed.orientation = true;
1346 draw(NULL);
1347 break;
1349 default: {
1350 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
1351 return PLUGIN_USB_CONNECTED;
1357 /* Handle our FFT thread. */
1358 fft_thread_run = false;
1359 rb->thread_wait(fft_thread);
1360 #if NUM_CORES > 1
1361 rb->cpucache_flush();
1362 #endif
1364 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
1365 rb->cpu_boost(false);
1366 #endif
1367 #ifndef HAVE_LCD_COLOR
1368 grey_release();
1369 #endif
1370 backlight_use_settings();
1371 return PLUGIN_OK;
1372 (void)parameter;