1 /***************************************************************************
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
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 ****************************************************************************/
23 #include "lib/helper.h"
27 #ifndef HAVE_LCD_COLOR
30 #include "lib/mylcd.h"
34 #ifndef HAVE_LCD_COLOR
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 == PHILIPS_HDD6330_PAD
185 # define FFT_PREV_GRAPH BUTTON_PREV
186 # define FFT_NEXT_GRAPH BUTTON_NEXT
187 # define FFT_ORIENTATION BUTTON_PLAY
188 # define FFT_WINDOW BUTTON_MENU
189 # define FFT_AMP_SCALE BUTTON_UP
190 # define FFT_FREQ_SCALE BUTTON_DOWN
191 # define FFT_QUIT BUTTON_POWER
193 #elif CONFIG_KEYPAD == PHILIPS_SA9200_PAD
194 # define FFT_PREV_GRAPH BUTTON_PREV
195 # define FFT_NEXT_GRAPH BUTTON_NEXT
196 # define FFT_ORIENTATION BUTTON_PLAY
197 # define FFT_WINDOW BUTTON_MENU
198 # define FFT_AMP_SCALE BUTTON_UP
199 # define FFT_FREQ_SCALE BUTTON_DOWN
200 # define FFT_QUIT BUTTON_POWER
202 #elif (CONFIG_KEYPAD == SAMSUNG_YH_PAD)
203 # define FFT_PREV_GRAPH BUTTON_LEFT
204 # define FFT_NEXT_GRAPH BUTTON_RIGHT
205 # define FFT_ORIENTATION BUTTON_UP
206 # define FFT_WINDOW BUTTON_DOWN
207 # define FFT_AMP_SCALE BUTTON_FFWD
208 # define FFT_QUIT BUTTON_PLAY
210 #elif (CONFIG_KEYPAD == MROBE500_PAD)
211 # define FFT_QUIT BUTTON_POWER
213 #elif (CONFIG_KEYPAD == ONDAVX747_PAD)
214 # define FFT_QUIT BUTTON_POWER
216 #elif (CONFIG_KEYPAD == ONDAVX777_PAD)
217 # define FFT_QUIT BUTTON_POWER
219 #elif (CONFIG_KEYPAD == PBELL_VIBE500_PAD)
220 # define FFT_PREV_GRAPH BUTTON_PREV
221 # define FFT_NEXT_GRAPH BUTTON_NEXT
222 # define FFT_ORIENTATION BUTTON_MENU
223 # define FFT_WINDOW BUTTON_OK
224 # define FFT_AMP_SCALE BUTTON_PLAY
225 # define FFT_QUIT BUTTON_REC
227 #elif CONFIG_KEYPAD == MPIO_HD200_PAD
228 # define FFT_PREV_GRAPH BUTTON_REW
229 # define FFT_NEXT_GRAPH BUTTON_FF
230 # define FFT_ORIENTATION BUTTON_REC
231 # define FFT_WINDOW BUTTON_FUNC
232 # define FFT_AMP_SCALE BUTTON_PLAY
233 # define FFT_QUIT (BUTTON_REC | BUTTON_PLAY)
235 #elif CONFIG_KEYPAD == MPIO_HD300_PAD
236 # define FFT_PREV_GRAPH BUTTON_REW
237 # define FFT_NEXT_GRAPH BUTTON_FF
238 # define FFT_ORIENTATION BUTTON_REC
239 # define FFT_WINDOW BUTTON_ENTER
240 # define FFT_AMP_SCALE BUTTON_PLAY
241 # define FFT_QUIT (BUTTON_REC | BUTTON_REPEAT)
244 #error No keymap defined!
247 #ifdef HAVE_TOUCHSCREEN
248 #ifndef FFT_PREV_GRAPH
249 # define FFT_PREV_GRAPH BUTTON_MIDLEFT
251 #ifndef FFT_NEXT_GRAPH
252 # define FFT_NEXT_GRAPH BUTTON_MIDRIGHT
254 #ifndef FFT_ORIENTATION
255 # define FFT_ORIENTATION BUTTON_CENTER
258 # define FFT_WINDOW BUTTON_TOPLEFT
260 #ifndef FFT_AMP_SCALE
261 # define FFT_AMP_SCALE BUTTON_TOPRIGHT
264 # define FFT_QUIT BUTTON_BOTTOMLEFT
266 #endif /* HAVE_TOUCHSCREEN */
268 #ifdef HAVE_LCD_COLOR
269 #include "pluginbitmaps/fft_colors.h"
272 #include "kiss_fftr.h"
273 #include "_kiss_fft_guts.h" /* sizeof(struct kiss_fft_state) */
276 #define LCD_SIZE MAX(LCD_WIDTH, LCD_HEIGHT)
278 #if (LCD_SIZE <= 511)
279 #define FFT_SIZE 1024 /* 512*2 */
280 #elif (LCD_SIZE <= 1023)
281 #define FFT_SIZE 2048 /* 1024*2 */
283 #define FFT_SIZE 4096 /* 2048*2 */
286 #define ARRAYLEN_IN (FFT_SIZE)
287 #define ARRAYLEN_OUT (FFT_SIZE)
288 #define ARRAYLEN_PLOT (FFT_SIZE/2-1) /* FFT is symmetric, ignore DC */
289 #define BUFSIZE_FFT (sizeof(struct kiss_fft_state)+sizeof(kiss_fft_cpx)*(FFT_SIZE-1))
291 #define __COEFF(type,size) type##_##size
292 #define _COEFF(x, y) __COEFF(x,y) /* force the preprocessor to evaluate FFT_SIZE) */
293 #define HANN_COEFF _COEFF(hann, FFT_SIZE)
294 #define HAMMING_COEFF _COEFF(hamming, FFT_SIZE)
296 /****************************** Globals ****************************/
297 /* cacheline-aligned buffers with COP, otherwise word-aligned */
298 /* CPU/COP only applies when compiled for more than one core */
300 #define CACHEALIGN_UP_SIZE(type, len) \
301 (CACHEALIGN_UP((len)*sizeof(type) + (sizeof(type)-1)) / sizeof(type))
304 static kiss_fft_cpx input
[CACHEALIGN_UP_SIZE(kiss_fft_scalar
, ARRAYLEN_IN
)]
305 CACHEALIGN_AT_LEAST_ATTR(4);
308 /* Output queue indexes */
309 static volatile int output_head SHAREDBSS_ATTR
= 0;
310 static volatile int output_tail SHAREDBSS_ATTR
= 0;
311 /* The result is nfft/2 complex frequency bins from DC to Nyquist. */
312 static kiss_fft_cpx output
[2][CACHEALIGN_UP_SIZE(kiss_fft_cpx
, ARRAYLEN_OUT
)]
315 /* Only one output buffer */
316 #define output_head 0
317 #define output_tail 0
318 /* The result is nfft/2 complex frequency bins from DC to Nyquist. */
319 static kiss_fft_cpx output
[1][ARRAYLEN_OUT
];
324 static kiss_fft_cfg fft_state SHAREDBSS_ATTR
;
325 static char fft_buffer
[CACHEALIGN_UP_SIZE(char, BUFSIZE_FFT
)]
326 CACHEALIGN_AT_LEAST_ATTR(4);
328 static int32_t plot_history
[ARRAYLEN_PLOT
];
329 static int32_t plot
[ARRAYLEN_PLOT
];
332 int16_t bin
; /* integer bin number */
333 uint16_t frac
; /* interpolation fraction */
334 } binlog
[ARRAYLEN_PLOT
] __attribute__((aligned(4)));
342 #define FFT_WF_COUNT (FFT_WF_HANN+1)
344 enum fft_display_mode
351 #define FFT_DM_COUNT (FFT_DM_SPECTROGRAPH+1)
353 static const unsigned char* const modes_text
[FFT_DM_COUNT
] =
354 { "Lines", "Bars", "Spectrogram" };
356 static const unsigned char* const amp_scales_text
[2] =
357 { "Linear amplitude", "Logarithmic amplitude" };
359 static const unsigned char* const freq_scales_text
[2] =
360 { "Linear frequency", "Logarithmic frequency" };
362 static const unsigned char* const window_text
[FFT_WF_COUNT
] =
363 { "Hamming window", "Hann window" };
366 bool orientation_vertical
;
367 enum fft_display_mode mode
;
368 bool logarithmic_amp
;
369 bool logarithmic_freq
;
370 enum fft_window_func window_func
;
371 int spectrogram_pos
; /* row or column - only used by one at a time */
376 bool orientation
: 1;
380 bool window_func
: 1;
383 bool clear_all
; /* Write 'false' to clear all above */
385 } graph_settings SHAREDDATA_ATTR
=
388 .orientation_vertical
= true,
389 .mode
= FFT_DM_LINES
,
390 .logarithmic_amp
= true,
391 .logarithmic_freq
= true,
392 .window_func
= FFT_WF_HAMMING
,
393 .spectrogram_pos
= 0,
394 .changed
= { .clear_all
= false },
397 #ifdef HAVE_LCD_COLOR
398 #define SHADES BMPWIDTH_fft_colors
399 #define SPECTROGRAPH_PALETTE(index) (fft_colors[index])
402 #define SPECTROGRAPH_PALETTE(index) (255 - (index))
405 /************************* End of globals *************************/
407 /************************* Math functions *************************/
409 /* Based on feeding-in a 0db sinewave at FS/4 */
410 #define QLOG_MAX 0x0009154B
411 /* fudge it a little or it's not very visbile */
412 #define QLIN_MAX (0x00002266 >> 1)
414 /* Apply window function to input */
415 static void apply_window_func(enum fft_window_func mode
)
422 for(i
= 0; i
< ARRAYLEN_IN
; ++i
)
424 input
[i
].r
= (input
[i
].r
* HAMMING_COEFF
[i
] + 16384) >> 15;
429 for(i
= 0; i
< ARRAYLEN_IN
; ++i
)
431 input
[i
].r
= (input
[i
].r
* HANN_COEFF
[i
] + 16384) >> 15;
437 /* Calculates the magnitudes from complex numbers and returns the maximum */
438 static int32_t calc_magnitudes(bool logarithmic_amp
)
440 /* A major assumption made when calculating the Q*MAX constants
441 * is that the maximum magnitude is 29 bits long. */
443 kiss_fft_cpx
*this_output
= output
[output_head
] + 1; /* skip DC */
446 /* Calculate the magnitude, discarding the phase. */
447 for(i
= 0; i
< ARRAYLEN_PLOT
; ++i
)
449 int32_t re
= this_output
[i
].r
;
450 int32_t im
= this_output
[i
].i
;
452 uint32_t tmp
= re
*re
+ im
*im
;
456 if(tmp
> 0x7FFFFFFF) /* clip */
458 tmp
= 0x7FFFFFFF; /* if our assumptions are correct,
459 this should never happen. It's just
465 if(tmp
< 0x8000) /* be more precise */
467 /* ln(x ^ .5) = .5*ln(x) */
468 tmp
= fp16_log(tmp
<< 16) >> 1;
472 tmp
= isqrt(tmp
); /* linear scaling, nothing
474 tmp
= fp16_log(tmp
<< 16); /* the log function
475 expects s15.16 values */
480 tmp
= isqrt(tmp
); /* linear scaling, nothing
485 /* Length 2 moving average - last transform and this one */
486 tmp
= (plot_history
[i
] + tmp
) >> 1;
488 plot_history
[i
] = tmp
;
497 /* Move plot bins into a logarithmic scale by sliding them towards the
498 * Nyquist bin according to the translation in the binlog array. */
499 static void logarithmic_plot_translate(void)
503 for(i
= ARRAYLEN_PLOT
-1; i
> 0; --i
)
506 int s
= binlog
[i
].bin
;
507 int e
= binlog
[i
-1].bin
;
508 int frac
= binlog
[i
].frac
;
514 /* slope < 1, Interpolate stretched bins (linear for now) */
515 int diff
= plot
[s
+1] - bin
;
519 plot
[i
] = bin
+ FRACMUL(frac
<< 15, diff
);
520 frac
= binlog
[--i
].frac
;
526 /* slope > 1, Find peak of two or more bins */
540 /* Calculates the translation for logarithmic plot bins */
541 static void logarithmic_plot_init(void)
545 * log: y = round(n * ln(x) / ln(n))
546 * anti: y = round(exp(x * ln(n) / n))
548 j
= fp16_log((ARRAYLEN_PLOT
- 1) << 16);
549 for(i
= 0; i
< ARRAYLEN_PLOT
; ++i
)
551 binlog
[i
].bin
= (fp16_exp(i
* j
/ (ARRAYLEN_PLOT
- 1)) + 32768) >> 16;
554 /* setup fractions for interpolation of stretched bins */
555 for(i
= 0; i
< ARRAYLEN_PLOT
-1; i
= j
)
559 /* stop when we have two different values */
560 while(binlog
[j
].bin
== binlog
[i
].bin
)
561 j
++; /* if here, local slope of curve is < 1 */
565 /* distribute pieces evenly over stretched interval */
570 binlog
[i
].frac
= (x
++ << 16) / diff
;
577 /************************ End of math functions ***********************/
579 /********************* Plotting functions (modes) *********************/
580 static void draw_lines_vertical(void);
581 static void draw_lines_horizontal(void);
582 static void draw_bars_vertical(void);
583 static void draw_bars_horizontal(void);
584 static void draw_spectrogram_vertical(void);
585 static void draw_spectrogram_horizontal(void);
587 #define COLOR_DEFAULT_FG MYLCD_DEFAULT_FG
588 #define COLOR_DEFAULT_BG MYLCD_DEFAULT_BG
590 #ifdef HAVE_LCD_COLOR
591 #define COLOR_MESSAGE_FRAME LCD_RGBPACK(0xc6, 0x00, 0x00)
592 #define COLOR_MESSAGE_BG LCD_BLACK
593 #define COLOR_MESSAGE_FG LCD_WHITE
595 #define COLOR_MESSAGE_FRAME GREY_DARKGRAY
596 #define COLOR_MESSAGE_BG GREY_WHITE
597 #define COLOR_MESSAGE_FG GREY_BLACK
600 #define POPUP_HPADDING 3 /* 3 px of horizontal padding and */
601 #define POPUP_VPADDING 2 /* 2 px of vertical padding */
603 static void draw_message_string(const unsigned char *message
, bool active
)
606 mylcd_getstringsize(message
, &x
, &y
);
608 /* x and y give the size of the box for the popup */
609 x
+= POPUP_HPADDING
*2;
610 y
+= POPUP_VPADDING
*2;
612 /* In vertical spectrogram mode, leave space for the popup
613 * before actually drawing it (if space is needed) */
615 graph_settings
.mode
== FFT_DM_SPECTROGRAPH
&&
616 graph_settings
.orientation_vertical
&&
617 graph_settings
.spectrogram_pos
>= LCD_WIDTH
- x
)
619 mylcd_scroll_left(graph_settings
.spectrogram_pos
-
621 graph_settings
.spectrogram_pos
= LCD_WIDTH
- x
- 1;
624 mylcd_set_foreground(COLOR_MESSAGE_FRAME
);
625 mylcd_fillrect(LCD_WIDTH
- x
, 0, LCD_WIDTH
- 1, y
);
627 mylcd_set_foreground(COLOR_MESSAGE_FG
);
628 mylcd_set_background(COLOR_MESSAGE_BG
);
629 mylcd_putsxy(LCD_WIDTH
- x
+ POPUP_HPADDING
,
630 POPUP_VPADDING
, message
);
631 mylcd_set_foreground(COLOR_DEFAULT_FG
);
632 mylcd_set_background(COLOR_DEFAULT_BG
);
635 static void draw(const unsigned char* message
)
637 static long show_message_tick
= 0;
638 static const unsigned char* last_message
= 0;
642 last_message
= message
;
643 show_message_tick
= (*rb
->current_tick
+ HZ
) | 1;
646 /* maybe take additional actions depending upon the changed setting */
647 if(graph_settings
.changed
.orientation
)
649 graph_settings
.changed
.amp_scale
= true;
650 graph_settings
.changed
.do_clear
= true;
653 if(graph_settings
.changed
.mode
)
655 graph_settings
.changed
.amp_scale
= true;
656 graph_settings
.changed
.do_clear
= true;
659 if(graph_settings
.changed
.amp_scale
)
660 memset(plot_history
, 0, sizeof (plot_history
));
662 if(graph_settings
.changed
.freq_scale
)
663 graph_settings
.changed
.freq_scale
= true;
665 mylcd_set_foreground(COLOR_DEFAULT_FG
);
666 mylcd_set_background(COLOR_DEFAULT_BG
);
668 switch (graph_settings
.mode
)
673 mylcd_clear_display();
675 if (graph_settings
.orientation_vertical
)
676 draw_lines_vertical();
678 draw_lines_horizontal();
683 mylcd_clear_display();
685 if(graph_settings
.orientation_vertical
)
686 draw_bars_vertical();
688 draw_bars_horizontal();
692 case FFT_DM_SPECTROGRAPH
: {
694 if(graph_settings
.changed
.do_clear
)
696 graph_settings
.spectrogram_pos
= 0;
697 mylcd_clear_display();
700 if(graph_settings
.orientation_vertical
)
701 draw_spectrogram_vertical();
703 draw_spectrogram_horizontal();
708 if(show_message_tick
!= 0)
710 if(TIME_BEFORE(*rb
->current_tick
, show_message_tick
))
712 /* We have a message to show */
713 draw_message_string(last_message
, true);
717 /* Stop drawing message */
718 show_message_tick
= 0;
721 else if(last_message
!= NULL
)
723 if(graph_settings
.mode
== FFT_DM_SPECTROGRAPH
)
725 /* Spectrogram mode - need to erase the popup */
727 mylcd_getstringsize(last_message
, &x
, &y
);
728 /* Recalculate the size */
729 x
+= POPUP_HPADDING
*2;
730 y
+= POPUP_VPADDING
*2;
732 if(!graph_settings
.orientation_vertical
)
734 /* In horizontal spectrogram mode, just scroll up by Y lines */
736 graph_settings
.spectrogram_pos
-= y
;
737 if(graph_settings
.spectrogram_pos
< 0)
738 graph_settings
.spectrogram_pos
= 0;
742 /* In vertical spectrogram mode, erase the popup */
743 mylcd_set_foreground(COLOR_DEFAULT_BG
);
744 mylcd_fillrect(graph_settings
.spectrogram_pos
+ 1, 0,
746 mylcd_set_foreground(COLOR_DEFAULT_FG
);
749 /* else These modes clear the screen themselves */
756 graph_settings
.changed
.clear_all
= false;
759 static void draw_lines_vertical(void)
763 #if LCD_WIDTH < ARRAYLEN_PLOT /* graph compression */
764 const int offset
= 0;
765 const int plotwidth
= LCD_WIDTH
;
767 const int offset
= (LCD_HEIGHT
- ARRAYLEN_PLOT
) / 2;
768 const int plotwidth
= ARRAYLEN_PLOT
;
774 if(graph_settings
.changed
.amp_scale
)
775 max
= 0; /* reset the graph on scaling mode change */
777 this_max
= calc_magnitudes(graph_settings
.logarithmic_amp
);
781 mylcd_hline(0, LCD_WIDTH
- 1, LCD_HEIGHT
- 1); /* Draw all "zero" */
785 if(graph_settings
.logarithmic_freq
)
786 logarithmic_plot_translate();
788 /* take the maximum of neighboring bins if we have to scale the graph
790 if(LCD_WIDTH
< ARRAYLEN_PLOT
) /* graph compression */
792 int bins_acc
= LCD_WIDTH
/ 2;
804 bins_acc
+= LCD_WIDTH
;
806 if(bins_acc
>= ARRAYLEN_PLOT
)
816 bins_acc
-= ARRAYLEN_PLOT
;
827 for(x
= 0; x
< plotwidth
; ++x
)
829 int h
= LCD_HEIGHT
*plot
[x
] / max
;
830 mylcd_vline(x
+ offset
, LCD_HEIGHT
- h
, LCD_HEIGHT
-1);
834 static void draw_lines_horizontal(void)
838 #if LCD_WIDTH < ARRAYLEN_PLOT /* graph compression */
839 const int offset
= 0;
840 const int plotwidth
= LCD_HEIGHT
;
842 const int offset
= (LCD_HEIGHT
- ARRAYLEN_PLOT
) / 2;
843 const int plotwidth
= ARRAYLEN_PLOT
;
849 if(graph_settings
.changed
.amp_scale
)
850 max
= 0; /* reset the graph on scaling mode change */
852 this_max
= calc_magnitudes(graph_settings
.logarithmic_amp
);
856 mylcd_vline(0, 0, LCD_HEIGHT
-1); /* Draw all "zero" */
860 if(graph_settings
.logarithmic_freq
)
861 logarithmic_plot_translate();
863 /* take the maximum of neighboring bins if we have to scale the graph
865 if(LCD_HEIGHT
< ARRAYLEN_PLOT
) /* graph compression */
867 int bins_acc
= LCD_HEIGHT
/ 2;
880 bins_acc
+= LCD_HEIGHT
;
882 if(bins_acc
>= ARRAYLEN_PLOT
)
889 if(++y
>= LCD_HEIGHT
)
892 bins_acc
-= ARRAYLEN_PLOT
;
903 for(y
= 0; y
< plotwidth
; ++y
)
905 int w
= LCD_WIDTH
*plot
[y
] / max
;
906 mylcd_hline(0, w
- 1, y
+ offset
);
910 static void draw_bars_vertical(void)
914 #if LCD_WIDTH < LCD_HEIGHT
919 const int border
= 2;
920 const int barwidth
= LCD_WIDTH
/ (bars
+ border
);
921 const int width
= barwidth
- border
;
922 const int offset
= (LCD_WIDTH
- bars
*barwidth
) / 2;
924 if(graph_settings
.changed
.amp_scale
)
925 max
= 0; /* reset the graph on scaling mode change */
927 mylcd_hline(0, LCD_WIDTH
-1, LCD_HEIGHT
-1); /* Draw baseline */
929 if(calc_magnitudes(graph_settings
.logarithmic_amp
) == 0)
930 return; /* nothing more to draw */
932 if(graph_settings
.logarithmic_freq
)
933 logarithmic_plot_translate();
935 int bins_acc
= bars
/ 2;
948 if(bins_acc
>= ARRAYLEN_PLOT
)
958 bins_acc
-= ARRAYLEN_PLOT
;
963 for(i
= 0, x
= offset
; i
< bars
; ++i
, x
+= barwidth
)
965 int h
= LCD_HEIGHT
* plot
[i
] / max
;
966 mylcd_fillrect(x
, LCD_HEIGHT
- h
, width
, h
- 1);
970 static void draw_bars_horizontal(void)
974 #if LCD_WIDTH < LCD_HEIGHT
979 const int border
= 2;
980 const int barwidth
= LCD_HEIGHT
/ (bars
+ border
);
981 const int height
= barwidth
- border
;
982 const int offset
= (LCD_HEIGHT
- bars
*barwidth
) / 2;
984 if(graph_settings
.changed
.amp_scale
)
985 max
= 0; /* reset the graph on scaling mode change */
987 mylcd_vline(0, 0, LCD_HEIGHT
-1); /* Draw baseline */
989 if(calc_magnitudes(graph_settings
.logarithmic_amp
) == 0)
990 return; /* nothing more to draw */
992 if(graph_settings
.logarithmic_freq
)
993 logarithmic_plot_translate();
995 int bins_acc
= bars
/ 2;
1001 int bin
= plot
[i
++];
1008 if(bins_acc
>= ARRAYLEN_PLOT
)
1018 bins_acc
-= ARRAYLEN_PLOT
;
1023 for(i
= 0, y
= offset
; i
< bars
; ++i
, y
+= barwidth
)
1025 int w
= LCD_WIDTH
* plot
[i
] / max
;
1026 mylcd_fillrect(1, y
, w
, height
);
1030 static void draw_spectrogram_vertical(void)
1032 const int32_t scale_factor
= MIN(LCD_HEIGHT
, ARRAYLEN_PLOT
);
1034 calc_magnitudes(graph_settings
.logarithmic_amp
);
1036 if(graph_settings
.logarithmic_freq
)
1037 logarithmic_plot_translate();
1039 int bins_acc
= scale_factor
/ 2;
1045 int bin
= plot
[i
++];
1050 bins_acc
+= scale_factor
;
1052 if(bins_acc
>= ARRAYLEN_PLOT
)
1056 if(graph_settings
.logarithmic_amp
)
1057 index
= (SHADES
-1)*bins_max
/ QLOG_MAX
;
1059 index
= (SHADES
-1)*bins_max
/ QLIN_MAX
;
1061 /* These happen because we exaggerate the graph a little for
1066 mylcd_set_foreground(SPECTROGRAPH_PALETTE(index
));
1067 mylcd_drawpixel(graph_settings
.spectrogram_pos
,
1068 scale_factor
-1 - y
);
1070 if(++y
>= scale_factor
)
1073 bins_acc
-= ARRAYLEN_PLOT
;
1078 if(graph_settings
.spectrogram_pos
< LCD_WIDTH
-1)
1079 graph_settings
.spectrogram_pos
++;
1081 mylcd_scroll_left(1);
1084 static void draw_spectrogram_horizontal(void)
1086 const int32_t scale_factor
= MIN(LCD_WIDTH
, ARRAYLEN_PLOT
);
1088 calc_magnitudes(graph_settings
.logarithmic_amp
);
1090 if(graph_settings
.logarithmic_freq
)
1091 logarithmic_plot_translate();
1093 int bins_acc
= scale_factor
/ 2;
1099 int bin
= plot
[i
++];
1104 bins_acc
+= scale_factor
;
1106 if(bins_acc
>= ARRAYLEN_PLOT
)
1110 if(graph_settings
.logarithmic_amp
)
1111 index
= (SHADES
-1)*bins_max
/ QLOG_MAX
;
1113 index
= (SHADES
-1)*bins_max
/ QLIN_MAX
;
1115 /* These happen because we exaggerate the graph a little for
1120 mylcd_set_foreground(SPECTROGRAPH_PALETTE(index
));
1121 mylcd_drawpixel(x
, graph_settings
.spectrogram_pos
);
1123 if(++x
>= scale_factor
)
1126 bins_acc
-= ARRAYLEN_PLOT
;
1131 if(graph_settings
.spectrogram_pos
< LCD_HEIGHT
-1)
1132 graph_settings
.spectrogram_pos
++;
1137 /********************* End of plotting functions (modes) *********************/
1139 /****************************** FFT functions ********************************/
1141 /** functions use in single/multi configuration **/
1142 static inline bool fft_init_fft_lib(void)
1144 size_t size
= sizeof(fft_buffer
);
1145 fft_state
= kiss_fft_alloc(FFT_SIZE
, 0, fft_buffer
, &size
);
1147 if(fft_state
== NULL
)
1149 DEBUGF("needed data: %i", (int) size
);
1156 static inline bool fft_get_fft(void)
1159 int16_t *value
= (int16_t *) rb
->pcm_get_peak_buffer(&count
);
1160 /* This block can introduce discontinuities in our data. Meaning, the
1161 * FFT will not be done a continuous segment of the signal. Which can
1164 * Anyway, this is a demo, not a scientific tool. If you want accuracy,
1165 * do a proper spectrum analysis.*/
1167 /* there are cases when we don't have enough data to fill the buffer */
1168 if(count
!= ARRAYLEN_IN
)
1170 if(count
< ARRAYLEN_IN
)
1173 count
= ARRAYLEN_IN
; /* too much - limit */
1176 int fft_idx
= 0; /* offset in 'input' */
1180 kiss_fft_scalar left
= *value
++;
1181 kiss_fft_scalar right
= *value
++;
1182 input
[fft_idx
].r
= (left
+ right
) >> 1; /* to mono */
1183 } while (fft_idx
++, --count
> 0);
1185 apply_window_func(graph_settings
.window_func
);
1189 kiss_fft(fft_state
, input
, output
[output_tail
]);
1197 /* use a worker thread if there is another processor core */
1198 static volatile bool fft_thread_run SHAREDDATA_ATTR
= false;
1199 static unsigned long fft_thread
;
1201 static long fft_thread_stack
[CACHEALIGN_UP(DEFAULT_STACK_SIZE
*4/sizeof(long))]
1202 CACHEALIGN_AT_LEAST_ATTR(4);
1204 static void fft_thread_entry(void)
1206 if (!fft_init_fft_lib())
1208 output_tail
= -1; /* tell that we bailed */
1209 fft_thread_run
= true;
1213 fft_thread_run
= true;
1215 while(fft_thread_run
)
1217 if (!rb
->pcm_is_playing())
1225 rb
->sleep(0); /* not enough - ease up */
1229 /* write back output for other processor and invalidate for next frame read */
1230 rb
->cpucache_invalidate();
1232 int new_tail
= output_tail
^ 1;
1234 /* if full, block waiting until reader has freed a slot */
1235 while(fft_thread_run
)
1237 if(new_tail
!= output_head
)
1239 output_tail
= new_tail
;
1248 static bool fft_have_fft(void)
1250 return output_head
!= output_tail
;
1253 /* Call only after fft_have_fft() has returned true */
1254 static inline void fft_free_fft_output(void)
1256 output_head
^= 1; /* finished with this */
1259 static bool fft_init_fft(void)
1261 /* create worker thread - on the COP for dual-core targets */
1262 fft_thread
= rb
->create_thread(fft_thread_entry
,
1263 fft_thread_stack
, sizeof(fft_thread_stack
), 0, "fft output thread"
1264 IF_PRIO(, PRIORITY_USER_INTERFACE
+1) IF_COP(, COP
));
1268 rb
->splash(HZ
, "FFT thread failed create");
1272 /* wait for it to indicate 'ready' */
1273 while(fft_thread_run
== false)
1276 if(output_tail
== -1)
1278 /* FFT thread bailed-out like The Fed */
1279 rb
->thread_wait(fft_thread
);
1280 rb
->splash(HZ
, "FFT thread failed to init");
1287 static void fft_close_fft(void)
1289 /* Handle our FFT thread. */
1290 fft_thread_run
= false;
1291 rb
->thread_wait(fft_thread
);
1292 rb
->cpucache_invalidate();
1294 #else /* NUM_CORES == 1 */
1295 /* everything serialize on single-core and FFT gets to use IRAM main stack if
1296 * target uses IRAM */
1297 static bool fft_have_fft(void)
1299 return rb
->pcm_is_playing() && fft_get_fft();
1302 static inline void fft_free_fft_output(void)
1307 static bool fft_init_fft(void)
1309 return fft_init_fft_lib();
1312 static inline void fft_close_fft(void)
1316 #endif /* NUM_CORES */
1317 /*************************** End of FFT functions ****************************/
1319 enum plugin_status
plugin_start(const void* parameter
)
1323 bool showing_warning
= false;
1325 if (!fft_init_fft())
1326 return PLUGIN_ERROR
;
1328 #ifndef HAVE_LCD_COLOR
1329 unsigned char *gbuf
;
1330 size_t gbuf_size
= 0;
1331 /* get the remainder of the plugin buffer */
1332 gbuf
= (unsigned char *) rb
->plugin_get_buffer(&gbuf_size
);
1334 /* initialize the greyscale buffer.*/
1335 if (!grey_init(gbuf
, gbuf_size
, GREY_ON_COP
| GREY_BUFFERED
,
1336 LCD_WIDTH
, LCD_HEIGHT
, NULL
))
1338 rb
->splash(HZ
, "Couldn't init greyscale display");
1340 return PLUGIN_ERROR
;
1345 logarithmic_plot_init();
1348 rb
->lcd_set_backdrop(NULL
);
1349 mylcd_clear_display();
1352 backlight_ignore_timeout();
1354 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
1355 rb
->cpu_boost(true);
1360 /* Unless otherwise specified, HZ/50 is around the window length
1361 * and quite fast. We want to be done with drawing by this time. */
1362 long next_frame_tick
= *rb
->current_tick
+ HZ
/50;
1365 while (!fft_have_fft())
1369 if(!rb
->pcm_is_playing())
1371 showing_warning
= true;
1372 mylcd_clear_display();
1373 draw_message_string("No audio playing", false);
1381 showing_warning
= false;
1382 mylcd_clear_display();
1386 timeout
= HZ
/100; /* 'till end of curent tick, don't use 100% CPU */
1389 /* Make sure the FFT has produced something before doing anything
1390 * but watching for buttons. Music might not be playing or things
1391 * just aren't going well for picking up buffers so keys are
1392 * scanned to avoid lockup. */
1393 button
= rb
->button_get_w_tmo(timeout
);
1394 if (button
!= BUTTON_NONE
)
1400 fft_free_fft_output(); /* COP only */
1402 long tick
= *rb
->current_tick
;
1403 if(TIME_BEFORE(tick
, next_frame_tick
))
1405 tick
= next_frame_tick
- tick
;
1409 rb
->yield(); /* tmo = 0 won't yield */
1413 button
= rb
->button_get_w_tmo(tick
);
1420 case FFT_PREV_GRAPH
: {
1421 if (graph_settings
.mode
-- <= FFT_DM_FIRST
)
1422 graph_settings
.mode
= FFT_DM_COUNT
-1;
1423 graph_settings
.changed
.mode
= true;
1424 draw(modes_text
[graph_settings
.mode
]);
1427 case FFT_NEXT_GRAPH
: {
1428 if (++graph_settings
.mode
>= FFT_DM_COUNT
)
1429 graph_settings
.mode
= FFT_DM_FIRST
;
1430 graph_settings
.changed
.mode
= true;
1431 draw(modes_text
[graph_settings
.mode
]);
1435 if(++graph_settings
.window_func
>= FFT_WF_COUNT
)
1436 graph_settings
.window_func
= FFT_WF_FIRST
;
1437 graph_settings
.changed
.window_func
= true;
1438 draw(window_text
[graph_settings
.window_func
]);
1441 case FFT_AMP_SCALE
: {
1442 graph_settings
.logarithmic_amp
= !graph_settings
.logarithmic_amp
;
1443 graph_settings
.changed
.amp_scale
= true;
1444 draw(amp_scales_text
[graph_settings
.logarithmic_amp
? 1 : 0]);
1447 #ifdef FFT_FREQ_SCALE /* 'Till all keymaps are defined */
1448 case FFT_FREQ_SCALE
: {
1449 graph_settings
.logarithmic_freq
= !graph_settings
.logarithmic_freq
;
1450 graph_settings
.changed
.freq_scale
= true;
1451 draw(freq_scales_text
[graph_settings
.logarithmic_freq
? 1 : 0]);
1455 case FFT_ORIENTATION
: {
1456 graph_settings
.orientation_vertical
=
1457 !graph_settings
.orientation_vertical
;
1458 graph_settings
.changed
.orientation
= true;
1463 if (rb
->default_event_handler(button
) == SYS_USB_CONNECTED
)
1464 return PLUGIN_USB_CONNECTED
;
1472 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
1473 rb
->cpu_boost(false);
1475 #ifndef HAVE_LCD_COLOR
1478 backlight_use_settings();