A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2009 Delyan Kratunov
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include "plugin.h"
22
23#include "lib/helper.h"
24#include "lib/pluginlib_exit.h"
25#include "lib/configfile.h"
26#include "lib/xlcd.h"
27#include "math.h"
28#include "fracmul.h"
29#ifndef HAVE_LCD_COLOR
30#include "lib/grey.h"
31#endif
32#include "lib/mylcd.h"
33#include "lib/osd.h"
34
35#ifndef HAVE_LCD_COLOR
36#if defined(SIMULATOR)
37// Use the one in pluginlib/osd
38extern GREY_INFO_STRUCT
39#else
40GREY_INFO_STRUCT
41#endif
42#endif
43
44#include "lib/pluginlib_actions.h"
45
46/* this set the context to use with PLA */
47static const struct button_mapping *plugin_contexts[] = { pla_main_ctx };
48#define FFT_PREV_GRAPH PLA_LEFT
49#define FFT_NEXT_GRAPH PLA_RIGHT
50#define FFT_ORIENTATION PLA_CANCEL
51#define FFT_WINDOW PLA_SELECT_REL
52#define FFT_AMP_SCALE PLA_SELECT_REPEAT
53#define FFT_AMP_SCALE_PRE PLA_SELECT
54#define FFT_FREQ_SCALE PLA_DOWN
55
56
57#if (CONFIG_KEYPAD == IPOD_1G2G_PAD) \
58 || (CONFIG_KEYPAD == IPOD_3G_PAD) \
59 || (CONFIG_KEYPAD == IPOD_4G_PAD)
60#define FFT_QUIT PLA_UP
61#else
62#define FFT_QUIT PLA_EXIT
63#endif
64
65#ifdef HAVE_LCD_COLOR
66#include "pluginbitmaps/fft_colors.h"
67#endif
68
69#include "kiss_fftr.h"
70#include "_kiss_fft_guts.h" /* sizeof(struct kiss_fft_state) */
71#include "const.h"
72
73
74/******************************* FFT globals *******************************/
75
76#define LCD_SIZE MAX(LCD_WIDTH, LCD_HEIGHT)
77
78#if (LCD_SIZE <= 511)
79#define FFT_SIZE 1024 /* 512*2 */
80#elif (LCD_SIZE <= 1023)
81#define FFT_SIZE 2048 /* 1024*2 */
82#else
83#define FFT_SIZE 4096 /* 2048*2 */
84#endif
85
86#define ARRAYLEN_IN (FFT_SIZE)
87#define ARRAYLEN_OUT (FFT_SIZE)
88#define ARRAYLEN_PLOT (FFT_SIZE/2-1) /* FFT is symmetric, ignore DC */
89#define BUFSIZE_FFT (sizeof(struct kiss_fft_state)+\
90 sizeof(kiss_fft_cpx)*(FFT_SIZE-1))
91
92#define __COEFF(type,size) type##_##size
93#define _COEFF(x, y) __COEFF(x,y) /* force CPP evaluation of FFT_SIZE */
94#define HANN_COEFF _COEFF(hann, FFT_SIZE)
95#define HAMMING_COEFF _COEFF(hamming, FFT_SIZE)
96
97/* cacheline-aligned buffers with COP, otherwise word-aligned */
98/* CPU/COP only applies when compiled for more than one core */
99
100#define CACHEALIGN_UP_SIZE(type, len) \
101 (CACHEALIGN_UP((len)*sizeof(type) + (sizeof(type)-1)) / sizeof(type))
102/* Shared */
103/* COP + CPU PCM */
104static kiss_fft_cpx input[CACHEALIGN_UP_SIZE(kiss_fft_scalar, ARRAYLEN_IN)]
105 CACHEALIGN_AT_LEAST_ATTR(4);
106/* CPU+COP */
107#if NUM_CORES > 1
108/* Output queue indexes */
109static volatile int output_head SHAREDBSS_ATTR = 0;
110static volatile int output_tail SHAREDBSS_ATTR = 0;
111/* The result is nfft/2 complex frequency bins from DC to Nyquist. */
112static kiss_fft_cpx output[2][CACHEALIGN_UP_SIZE(kiss_fft_cpx, ARRAYLEN_OUT)]
113 SHAREDBSS_ATTR;
114#else
115/* Only one output buffer */
116#define output_head 0
117#define output_tail 0
118/* The result is nfft/2 complex frequency bins from DC to Nyquist. */
119static kiss_fft_cpx output[1][ARRAYLEN_OUT];
120#endif
121
122/* Unshared */
123/* COP */
124static kiss_fft_cfg fft_state SHAREDBSS_ATTR;
125static char fft_buffer[CACHEALIGN_UP_SIZE(char, BUFSIZE_FFT)]
126 CACHEALIGN_AT_LEAST_ATTR(4);
127/* CPU */
128static uint32_t linf_magnitudes[ARRAYLEN_PLOT]; /* ling freq bin plot */
129static uint32_t logf_magnitudes[ARRAYLEN_PLOT]; /* log freq plot output */
130static uint32_t *plot; /* use this to plot */
131static struct
132{
133 int16_t bin; /* integer bin number */
134 uint16_t frac; /* interpolation fraction */
135} binlog[ARRAYLEN_PLOT] __attribute__((aligned(4)));
136
137/**************************** End of FFT globals ***************************/
138
139
140/********************************* Settings ********************************/
141
142enum fft_orientation
143{
144 FFT_MIN_OR = 0,
145 FFT_OR_VERT = 0, /* Amplitude vertical, frequency horizontal * */
146 FFT_OR_HORZ, /* Amplitude horizontal, frequency vertical */
147 FFT_MAX_OR,
148};
149
150enum fft_display_mode
151{
152 FFT_MIN_DM = 0,
153 FFT_DM_LINES = 0, /* Bands are displayed as single-pixel lines * */
154 FFT_DM_BARS, /* Bands are combined into wide bars */
155 FFT_DM_SPECTROGRAM, /* Band amplitudes are denoted by color */
156 FFT_MAX_DM,
157};
158
159enum fft_amp_scale
160{
161 FFT_MIN_AS = 0,
162 FFT_AS_LOG = 0, /* Amplitude is plotted on log scale * */
163 FFT_AS_LIN, /* Amplitude is plotted on linear scale */
164 FFT_MAX_AS,
165};
166
167enum fft_freq_scale
168{
169 FFT_MIN_FS = 0,
170 FFT_FS_LOG = 0, /* Frequency is plotted on log scale * */
171 FFT_FS_LIN, /* Frequency is plotted on linear scale */
172 FFT_MAX_FS
173};
174
175enum fft_window_func
176{
177 FFT_MIN_WF = 0,
178 FFT_WF_HAMMING = 0, /* Hamming window applied to each input frame * */
179 FFT_WF_HANN, /* Hann window applied to each input frame */
180 FFT_MAX_WF,
181};
182
183static struct fft_config
184{
185 int orientation;
186 int drawmode;
187 int amp_scale;
188 int freq_scale;
189 int window_func;
190} fft_disk =
191{
192 /* Defaults */
193 .orientation = FFT_OR_VERT,
194 .drawmode = FFT_DM_LINES,
195 .amp_scale = FFT_AS_LOG,
196 .freq_scale = FFT_FS_LOG,
197 .window_func = FFT_WF_HAMMING,
198};
199
200#define CFGFILE_VERSION 0
201#define CFGFILE_MINVERSION 0
202
203static const char cfg_filename[] = "fft.cfg";
204static struct configdata disk_config[] =
205{
206 { TYPE_ENUM, FFT_MIN_OR, FFT_MAX_OR,
207 { .int_p = &fft_disk.orientation }, "orientation",
208 (char * []){ [FFT_OR_VERT] = "vertical",
209 [FFT_OR_HORZ] = "horizontal" } },
210 { TYPE_ENUM, FFT_MIN_DM, FFT_MAX_DM,
211 { .int_p = &fft_disk.drawmode }, "drawmode",
212 (char * []){ [FFT_DM_LINES] = "lines",
213 [FFT_DM_BARS] = "bars",
214 [FFT_DM_SPECTROGRAM] = "spectrogram" } },
215 { TYPE_ENUM, FFT_MIN_AS, FFT_MAX_AS,
216 { .int_p = &fft_disk.amp_scale }, "amp scale",
217 (char * []){ [FFT_AS_LOG] = "logarithmic",
218 [FFT_AS_LIN] = "linear" } },
219 { TYPE_ENUM, FFT_MIN_FS, FFT_MAX_FS,
220 { .int_p = &fft_disk.freq_scale }, "freq scale",
221 (char * []){ [FFT_FS_LOG] = "logarithmic",
222 [FFT_FS_LIN] = "linear" } },
223 { TYPE_ENUM, FFT_MIN_WF, FFT_MAX_WF,
224 { .int_p = &fft_disk.window_func }, "window function",
225 (char * []){ [FFT_WF_HAMMING] = "hamming",
226 [FFT_WF_HANN] = "hann" } },
227};
228
229/* Hint flags for setting changes */
230enum fft_setting_flags
231{
232 FFT_SETF_OR = 1 << 0,
233 FFT_SETF_DM = 1 << 1,
234 FFT_SETF_AS = 1 << 2,
235 FFT_SETF_FS = 1 << 3,
236 FFT_SETF_WF = 1 << 4,
237 FFT_SETF_ALL = 0x1f
238};
239
240/***************************** End of settings *****************************/
241
242
243/**************************** Operational data *****************************/
244
245#define COLOR_DEFAULT_FG MYLCD_DEFAULT_FG
246#define COLOR_DEFAULT_BG MYLCD_DEFAULT_BG
247
248#ifdef HAVE_LCD_COLOR
249#define COLOR_MESSAGE_FRAME LCD_RGBPACK(0xc6, 0x00, 0x00)
250#define COLOR_MESSAGE_BG LCD_BLACK
251#define COLOR_MESSAGE_FG LCD_WHITE
252#else
253#define COLOR_MESSAGE_FRAME GREY_DARKGRAY
254#define COLOR_MESSAGE_BG GREY_WHITE
255#define COLOR_MESSAGE_FG GREY_BLACK
256#endif
257
258#define FFT_OSD_MARGIN_SIZE 1
259
260#define FFT_PERIOD (HZ/50) /* How fast to try to go */
261
262/* Based on feeding-in a 0db sinewave at FS/4 */
263#define QLOG_MAX 0x0009154B
264/* Fudge it a little or it's not very visbile */
265#define QLIN_MAX (0x00002266 >> 1)
266
267static struct fft_config fft;
268typedef void (* fft_drawfn_t)(unsigned, unsigned);
269static fft_drawfn_t fft_drawfn = NULL; /* plotting function */
270static int fft_spectrogram_pos = -1; /* row or column - only used by one at a time */
271static uint32_t fft_graph_scale = 0; /* max level over time, for scaling display */
272static int fft_message_id = -1; /* current message id displayed */
273static char fft_osd_message[32]; /* current message string displayed */
274static long fft_next_frame_tick = 0; /* next tick to attempt drawing */
275
276#ifdef HAVE_LCD_COLOR
277#define SHADES BMPWIDTH_fft_colors
278#define SPECTROGRAPH_PALETTE(index) (fft_colors[index])
279#else
280#define SHADES 256
281#define SPECTROGRAPH_PALETTE(index) (255 - (index))
282#endif
283
284/************************* End of operational data *************************/
285
286
287/***************************** Math functions ******************************/
288
289/* Apply window function to input */
290static void apply_window_func(enum fft_window_func mode)
291{
292 static const int16_t * const coefs[] =
293 {
294 [FFT_WF_HAMMING] = HAMMING_COEFF,
295 [FFT_WF_HANN] = HANN_COEFF,
296 };
297
298 const int16_t * const c = coefs[mode];
299
300 for(int i = 0; i < ARRAYLEN_IN; ++i)
301 input[i].r = (input[i].r * c[i] + 16384) >> 15;
302}
303
304/* Calculates the magnitudes from complex numbers and returns the maximum */
305static unsigned calc_magnitudes(enum fft_amp_scale scale)
306{
307 /* A major assumption made when calculating the Q*MAX constants
308 * is that the maximum magnitude is 29 bits long. */
309 unsigned this_max = 0;
310 kiss_fft_cpx *this_output = output[output_head] + 1; /* skip DC */
311
312 /* Calculate the magnitude, discarding the phase. */
313 for(int i = 0; i < ARRAYLEN_PLOT; ++i)
314 {
315 int32_t re = this_output[i].r;
316 int32_t im = this_output[i].i;
317
318 uint32_t d = re*re + im*im;
319
320 if(d > 0)
321 {
322 if(d > 0x7FFFFFFF) /* clip */
323 {
324 d = 0x7FFFFFFF; /* if our assumptions are correct,
325 this should never happen. It's just
326 a safeguard. */
327 }
328
329 if(scale == FFT_AS_LOG)
330 {
331 if(d < 0x8000) /* be more precise */
332 {
333 /* ln(x ^ .5) = .5*ln(x) */
334 d = fp16_log(d << 16) >> 1;
335 }
336 else
337 {
338 d = fp_sqrt(d, 0); /* linear scaling, nothing
339 bad should happen */
340 d = fp16_log(d << 16); /* the log function
341 expects s15.16 values */
342 }
343 }
344 else
345 {
346 d = fp_sqrt(d, 0); /* linear scaling, nothing
347 bad should happen */
348 }
349 }
350
351 /* Length 2 moving average - last transform and this one */
352 linf_magnitudes[i] = (linf_magnitudes[i] + d) >> 1;
353
354 if(d > this_max)
355 this_max = d;
356 }
357
358 return this_max;
359}
360
361/* Move plot bins into a logarithmic scale by sliding them towards the
362 * Nyquist bin according to the translation in the binlog array. */
363static void log_plot_translate(void)
364{
365 for(int i = ARRAYLEN_PLOT-1; i > 0; --i)
366 {
367 int s = binlog[i].bin;
368 int e = binlog[i-1].bin;
369 unsigned frac = binlog[i].frac;
370
371 int bin = linf_magnitudes[s];
372
373 if(frac)
374 {
375 /* slope < 1, Interpolate stretched bins (linear for now) */
376 int diff = linf_magnitudes[s+1] - bin;
377
378 do
379 {
380 logf_magnitudes[i] = bin + FRACMUL(frac << 15, diff);
381 frac = binlog[--i].frac;
382 }
383 while(frac);
384 }
385 else
386 {
387 /* slope > 1, Find peak of two or more bins */
388 while(--s > e)
389 {
390 int val = linf_magnitudes[s];
391
392 if (val > bin)
393 bin = val;
394 }
395 }
396
397 logf_magnitudes[i] = bin;
398 }
399}
400
401/* Calculates the translation for logarithmic plot bins */
402static void logarithmic_plot_init(void)
403{
404 /*
405 * log: y = round(n * ln(x) / ln(n))
406 * anti: y = round(exp(x * ln(n) / n))
407 */
408 int j = fp16_log((ARRAYLEN_PLOT - 1) << 16);
409 for(int i = 0; i < ARRAYLEN_PLOT; ++i)
410 {
411 binlog[i].bin = (fp16_exp(i * j / (ARRAYLEN_PLOT - 1)) + 32768) >> 16;
412 }
413
414 /* setup fractions for interpolation of stretched bins */
415 for(int i = 0; i < ARRAYLEN_PLOT-1; i = j)
416 {
417 j = i + 1;
418
419 /* stop when we have two different values */
420 while(binlog[j].bin == binlog[i].bin)
421 j++; /* if here, local slope of curve is < 1 */
422
423 if(j > i + 1)
424 {
425 /* distribute pieces evenly over stretched interval */
426 int diff = j - i;
427 int x = 0;
428 do
429 {
430 binlog[i].frac = (x++ << 16) / diff;
431 }
432 while(++i < j);
433 }
434 }
435}
436
437/************************** End of math functions **************************/
438
439
440/*********************** Plotting functions (modes) ************************/
441
442static void draw_lines_vertical(unsigned this_max, unsigned graph_max)
443{
444#if LCD_WIDTH < ARRAYLEN_PLOT /* graph compression */
445 const int offset = 0;
446 const int plotwidth = LCD_WIDTH;
447#else
448 const int offset = (LCD_HEIGHT - ARRAYLEN_PLOT) / 2;
449 const int plotwidth = ARRAYLEN_PLOT;
450#endif
451
452 mylcd_clear_display();
453
454 if(this_max == 0)
455 {
456 mylcd_hline(0, LCD_WIDTH - 1, LCD_HEIGHT - 1); /* Draw all "zero" */
457 return;
458 }
459
460 /* take the maximum of neighboring bins if we have to scale down the
461 * graph horizontally */
462 if(LCD_WIDTH < ARRAYLEN_PLOT) /* graph compression */
463 {
464 int bins_acc = LCD_WIDTH / 2;
465 unsigned bins_max = 0;
466
467 for(int i = 0, x = 0; i < ARRAYLEN_PLOT; ++i)
468 {
469 unsigned bin = plot[i];
470
471 if(bin > bins_max)
472 bins_max = bin;
473
474 bins_acc += LCD_WIDTH;
475
476 if(bins_acc >= ARRAYLEN_PLOT)
477 {
478 int h = LCD_HEIGHT*bins_max / graph_max;
479 mylcd_vline(x, LCD_HEIGHT - h, LCD_HEIGHT-1);
480
481 x++;
482 bins_acc -= ARRAYLEN_PLOT;
483 bins_max = 0;
484 }
485 }
486 }
487 else
488 {
489 for(int i = 0; i < plotwidth; ++i)
490 {
491 int h = LCD_HEIGHT*plot[i] / graph_max;
492 mylcd_vline(i + offset, LCD_HEIGHT - h, LCD_HEIGHT-1);
493 }
494 }
495}
496
497static void draw_lines_horizontal(unsigned this_max, unsigned graph_max)
498{
499#if LCD_WIDTH < ARRAYLEN_PLOT /* graph compression */
500 const int offset = 0;
501 const int plotwidth = LCD_HEIGHT;
502#else
503 const int offset = (LCD_HEIGHT - ARRAYLEN_PLOT) / 2;
504 const int plotwidth = ARRAYLEN_PLOT;
505#endif
506
507 mylcd_clear_display();
508
509 if(this_max == 0)
510 {
511 mylcd_vline(0, 0, LCD_HEIGHT-1); /* Draw all "zero" */
512 return;
513 }
514
515 /* take the maximum of neighboring bins if we have to scale the graph
516 * horizontally */
517 if(LCD_HEIGHT < ARRAYLEN_PLOT) /* graph compression */
518 {
519 int bins_acc = LCD_HEIGHT / 2;
520 unsigned bins_max = 0;
521
522 for(int i = 0, y = 0; i < ARRAYLEN_PLOT; ++i)
523 {
524 unsigned bin = plot[i];
525
526 if(bin > bins_max)
527 bins_max = bin;
528
529 bins_acc += LCD_HEIGHT;
530
531 if(bins_acc >= ARRAYLEN_PLOT)
532 {
533 int w = LCD_WIDTH*bins_max / graph_max;
534 mylcd_hline(0, w - 1, y);
535
536 y++;
537 bins_acc -= ARRAYLEN_PLOT;
538 bins_max = 0;
539 }
540 }
541 }
542 else
543 {
544 for(int i = 0; i < plotwidth; ++i)
545 {
546 int w = LCD_WIDTH*plot[i] / graph_max;
547 mylcd_hline(0, w - 1, i + offset);
548 }
549 }
550}
551
552static void draw_bars_vertical(unsigned this_max, unsigned graph_max)
553{
554#if LCD_WIDTH < LCD_HEIGHT
555 const int bars = 15;
556#else
557 const int bars = 20;
558#endif
559 const int border = 2;
560 const int barwidth = LCD_WIDTH / (bars + border);
561 const int width = barwidth - border;
562 const int offset = (LCD_WIDTH - bars*barwidth + border) / 2;
563
564 mylcd_clear_display();
565 mylcd_hline(0, LCD_WIDTH-1, LCD_HEIGHT-1); /* Draw baseline */
566
567 if(this_max == 0)
568 return; /* nothing more to draw */
569
570 int bins_acc = bars / 2;
571 unsigned bins_max = 0;
572
573 for(int i = 0, x = offset;; ++i)
574 {
575 unsigned bin = plot[i];
576
577 if(bin > bins_max)
578 bins_max = bin;
579
580 bins_acc += bars;
581
582 if(bins_acc >= ARRAYLEN_PLOT)
583 {
584 int h = LCD_HEIGHT*bins_max / graph_max;
585 mylcd_fillrect(x, LCD_HEIGHT - h, width, h - 1);
586
587 if(i >= ARRAYLEN_PLOT-1)
588 break;
589
590 x += barwidth;
591 bins_acc -= ARRAYLEN_PLOT;
592 bins_max = 0;
593 }
594 }
595}
596
597static void draw_bars_horizontal(unsigned this_max, unsigned graph_max)
598{
599#if LCD_WIDTH < LCD_HEIGHT
600 const int bars = 20;
601#else
602 const int bars = 15;
603#endif
604 const int border = 2;
605 const int barwidth = LCD_HEIGHT / (bars + border);
606 const int height = barwidth - border;
607 const int offset = (LCD_HEIGHT - bars*barwidth + border) / 2;
608
609 mylcd_clear_display();
610 mylcd_vline(0, 0, LCD_HEIGHT-1); /* Draw baseline */
611
612 if(this_max == 0)
613 return; /* nothing more to draw */
614
615 int bins_acc = bars / 2;
616 unsigned bins_max = 0;
617
618 for(int i = 0, y = offset;; ++i)
619 {
620 unsigned bin = plot[i];
621
622 if(bin > bins_max)
623 bins_max = bin;
624
625 bins_acc += bars;
626
627 if(bins_acc >= ARRAYLEN_PLOT)
628 {
629 int w = LCD_WIDTH*bins_max / graph_max;
630 mylcd_fillrect(1, y, w, height);
631
632 if(i >= ARRAYLEN_PLOT-1)
633 break;
634
635 y += barwidth;
636 bins_acc -= ARRAYLEN_PLOT;
637 bins_max = 0;
638 }
639 }
640}
641
642static void draw_spectrogram_vertical(unsigned this_max, unsigned graph_max)
643{
644 const int scale_factor = MIN(LCD_HEIGHT, ARRAYLEN_PLOT);
645
646 if(fft_spectrogram_pos < LCD_WIDTH-1)
647 fft_spectrogram_pos++;
648 else
649 mylcd_scroll_left(1);
650
651 int bins_acc = scale_factor / 2;
652 unsigned bins_max = 0;
653
654 for(int i = 0, y = LCD_HEIGHT-1;; ++i)
655 {
656 unsigned bin = plot[i];
657
658 if(bin > bins_max)
659 bins_max = bin;
660
661 bins_acc += scale_factor;
662
663 if(bins_acc >= ARRAYLEN_PLOT)
664 {
665 unsigned index = (SHADES-1)*bins_max / graph_max;
666 unsigned color;
667
668 /* These happen because we exaggerate the graph a little for
669 * linear mode */
670 if(index >= SHADES)
671 index = SHADES-1;
672
673 color = FB_UNPACK_SCALAR_LCD(SPECTROGRAPH_PALETTE(index));
674 mylcd_set_foreground(color);
675 mylcd_drawpixel(fft_spectrogram_pos, y);
676
677 if(--y < 0)
678 break;
679
680 bins_acc -= ARRAYLEN_PLOT;
681 bins_max = 0;
682 }
683 }
684
685 (void)this_max;
686}
687
688static void draw_spectrogram_horizontal(unsigned this_max, unsigned graph_max)
689{
690 const int scale_factor = MIN(LCD_WIDTH, ARRAYLEN_PLOT);
691
692 if(fft_spectrogram_pos < LCD_HEIGHT-1)
693 fft_spectrogram_pos++;
694 else
695 mylcd_scroll_up(1);
696
697 int bins_acc = scale_factor / 2;
698 unsigned bins_max = 0;
699
700 for(int i = 0, x = 0;; ++i)
701 {
702 unsigned bin = plot[i];
703
704 if(bin > bins_max)
705 bins_max = bin;
706
707 bins_acc += scale_factor;
708
709 if(bins_acc >= ARRAYLEN_PLOT)
710 {
711 unsigned index = (SHADES-1)*bins_max / graph_max;
712 unsigned color;
713
714 /* These happen because we exaggerate the graph a little for
715 * linear mode */
716 if(index >= SHADES)
717 index = SHADES-1;
718
719 color = FB_UNPACK_SCALAR_LCD(SPECTROGRAPH_PALETTE(index));
720 mylcd_set_foreground(color);
721 mylcd_drawpixel(x, fft_spectrogram_pos);
722
723 if(++x >= LCD_WIDTH)
724 break;
725
726 bins_acc -= ARRAYLEN_PLOT;
727 bins_max = 0;
728 }
729 }
730
731 (void)this_max;
732}
733
734/******************** End of plotting functions (modes) ********************/
735
736
737/***************************** FFT functions *******************************/
738
739static bool is_playing(void)
740{
741 return rb->mixer_channel_status(PCM_MIXER_CHAN_PLAYBACK) == CHANNEL_PLAYING;
742}
743
744/** functions use in single/multi configuration **/
745static inline bool fft_init_fft_lib(void)
746{
747 size_t size = sizeof(fft_buffer);
748 fft_state = kiss_fft_alloc(FFT_SIZE, 0, fft_buffer, &size);
749
750 if(fft_state == NULL)
751 {
752 DEBUGF("needed data: %i", (int) size);
753 return false;
754 }
755
756 return true;
757}
758
759static inline bool fft_get_fft(void)
760{
761 int count;
762 const int16_t *value =
763 rb->mixer_channel_get_buffer(PCM_MIXER_CHAN_PLAYBACK, &count);
764 /* This block can introduce discontinuities in our data. Meaning, the
765 * FFT will not be done a continuous segment of the signal. Which can
766 * be bad. Or not.
767 *
768 * Anyway, this is a demo, not a scientific tool. If you want accuracy,
769 * do a proper spectrum analysis.*/
770
771 /* there are cases when we don't have enough data to fill the buffer */
772 if(count != ARRAYLEN_IN)
773 {
774 if(count < ARRAYLEN_IN)
775 return false;
776
777 count = ARRAYLEN_IN; /* too much - limit */
778 }
779
780 int fft_idx = 0; /* offset in 'input' */
781
782 do
783 {
784 kiss_fft_scalar left = *value++;
785 kiss_fft_scalar right = *value++;
786 input[fft_idx].r = (left + right) >> 1; /* to mono */
787 } while (fft_idx++, --count > 0);
788
789 apply_window_func(fft.window_func);
790
791 rb->yield();
792
793 kiss_fft(fft_state, input, output[output_tail]);
794
795 rb->yield();
796
797 return true;
798}
799
800#if NUM_CORES > 1
801/* use a worker thread if there is another processor core */
802static volatile bool fft_thread_run SHAREDDATA_ATTR = false;
803static unsigned long fft_thread = 0;
804
805static long fft_thread_stack[CACHEALIGN_UP(DEFAULT_STACK_SIZE*4/sizeof(long))]
806 CACHEALIGN_AT_LEAST_ATTR(4);
807
808static void fft_thread_entry(void)
809{
810 if(!fft_init_fft_lib())
811 {
812 output_tail = -1; /* tell that we bailed */
813 fft_thread_run = true;
814 return;
815 }
816
817 fft_thread_run = true;
818
819 while(fft_thread_run)
820 {
821 if (!is_playing())
822 {
823 rb->sleep(HZ/5);
824 continue;
825 }
826
827 if (!fft_get_fft())
828 {
829 rb->sleep(0); /* not enough - ease up */
830 continue;
831 }
832
833 /* write back output for other processor and invalidate for next
834 frame read */
835 rb->commit_discard_dcache();
836
837 int new_tail = output_tail ^ 1;
838
839 /* if full, block waiting until reader has freed a slot */
840 while(fft_thread_run)
841 {
842 if(new_tail != output_head)
843 {
844 output_tail = new_tail;
845 break;
846 }
847
848 rb->sleep(0);
849 }
850 }
851}
852
853static bool fft_have_fft(void)
854{
855 return output_head != output_tail;
856}
857
858/* Call only after fft_have_fft() has returned true */
859static inline void fft_free_fft_output(void)
860{
861 output_head ^= 1; /* finished with this */
862}
863
864static bool fft_init_fft(void)
865{
866 /* create worker thread - on the COP for dual-core targets */
867 fft_thread = rb->create_thread(fft_thread_entry,
868 fft_thread_stack, sizeof(fft_thread_stack), 0, "fft output thread"
869 IF_PRIO(, PRIORITY_USER_INTERFACE+1) IF_COP(, COP));
870
871 if(fft_thread == 0)
872 {
873 rb->splash(HZ, "FFT thread failed create");
874 return false;
875 }
876
877 /* wait for it to indicate 'ready' */
878 while(fft_thread_run == false)
879 rb->sleep(0);
880
881 if(output_tail == -1)
882 {
883 /* FFT thread bailed-out like The Fed */
884 rb->thread_wait(fft_thread);
885 rb->splash(HZ, "FFT thread failed to init");
886 return false;
887 }
888
889 return true;
890}
891
892static void fft_close_fft(void)
893{
894 /* Handle our FFT thread. */
895 fft_thread_run = false;
896 rb->thread_wait(fft_thread);
897 rb->commit_discard_dcache();
898}
899#else /* NUM_CORES == 1 */
900/* everything serialize on single-core and FFT gets to use IRAM main stack if
901 * target uses IRAM */
902static bool fft_have_fft(void)
903{
904 return is_playing() && fft_get_fft();
905}
906
907static inline void fft_free_fft_output(void)
908{
909 /* nothing to do */
910}
911
912static bool fft_init_fft(void)
913{
914 return fft_init_fft_lib();
915}
916
917static inline void fft_close_fft(void)
918{
919 /* nothing to do */
920}
921#endif /* NUM_CORES */
922
923/************************** End of FFT functions ***************************/
924
925
926/****************************** OSD functions ******************************/
927
928/* Format a message to display */
929static void fft_osd_format_message(enum fft_setting_flags id)
930{
931 const char *msg = "";
932
933 switch (id)
934 {
935 case FFT_SETF_DM:
936 msg = (const char * [FFT_MAX_DM]) {
937 [FFT_DM_LINES] = "Lines",
938 [FFT_DM_BARS] = "Bars",
939 [FFT_DM_SPECTROGRAM] = "Spectrogram",
940 }[fft.drawmode];
941 break;
942
943 case FFT_SETF_WF:
944 msg = (const char * [FFT_MAX_WF]) {
945 [FFT_WF_HAMMING] = "Hamming window",
946 [FFT_WF_HANN] = "Hann window",
947 }[fft.window_func];
948 break;
949
950 case FFT_SETF_AS:
951 msg = (const char * [FFT_MAX_AS]) {
952 [FFT_AS_LOG] = "Logarithmic amplitude",
953 [FFT_AS_LIN] = "Linear amplitude"
954 }[fft.amp_scale];
955 break;
956
957 case FFT_SETF_FS:
958 msg = (const char * [FFT_MAX_FS]) {
959 [FFT_FS_LOG] = "Logarithmic frequency",
960 [FFT_FS_LIN] = "Linear frequency",
961 }[fft.freq_scale];
962 break;
963
964 case FFT_SETF_OR:
965 rb->snprintf(fft_osd_message, sizeof (fft_osd_message),
966 (const char * [FFT_MAX_OR]) {
967 [FFT_OR_VERT] = "Vertical %s",
968 [FFT_OR_HORZ] = "Horizontal %s",
969 }[fft.orientation],
970 (const char * [FFT_MAX_DM]) {
971 [FFT_DM_LINES ... FFT_DM_BARS] = "amplitude",
972 [FFT_DM_SPECTROGRAM] = "frequency"
973 }[fft.drawmode]);
974 return;
975
976#if 0
977 /* Pertentially */
978 case FFT_SETF_VOLUME:
979 rb->snprintf(fft_osd_message, sizeof (fft_osd_message),
980 "Volume: %d%s",
981 rb->sound_val2phys(SOUND_VOLUME, global_status.volume),
982 rb->sound_unit(SOUND_VOLUME));
983 return;
984#endif
985
986 default:
987 break;
988 }
989
990 /* Default action: copy string */
991 rb->strlcpy(fft_osd_message, msg, sizeof (fft_osd_message));
992}
993
994static void fft_osd_draw_cb(int x, int y, int width, int height)
995{
996#if LCD_DEPTH > 1
997 mylcd_set_foreground(COLOR_MESSAGE_FG);
998 mylcd_set_background(COLOR_MESSAGE_BG);
999#endif
1000#if FFT_OSD_MARGIN_SIZE != 0
1001 mylcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
1002 mylcd_fillrect(1, 1, width - 2, height - 2);
1003 mylcd_set_drawmode(DRMODE_SOLID);
1004#endif
1005 mylcd_putsxy(1+FFT_OSD_MARGIN_SIZE, 1+FFT_OSD_MARGIN_SIZE,
1006 fft_osd_message);
1007#if LCD_DEPTH > 1
1008 mylcd_set_foreground(COLOR_MESSAGE_FRAME);
1009#endif
1010
1011 mylcd_drawrect(0, 0, width, height);
1012
1013 (void)x; (void)y;
1014}
1015
1016static void fft_osd_show_message(enum fft_setting_flags id)
1017{
1018 fft_osd_format_message(id);
1019
1020 if(!myosd_enabled())
1021 return;
1022
1023 int width, height;
1024 int maxwidth, maxheight;
1025
1026 mylcd_set_viewport(myosd_get_viewport());
1027 myosd_get_max_dims(&maxwidth, &maxheight);
1028 mylcd_setfont(FONT_UI);
1029 mylcd_getstringsize(fft_osd_message, &width, &height);
1030 mylcd_set_viewport(NULL);
1031
1032 width += 2 + 2*FFT_OSD_MARGIN_SIZE;
1033 if(width > maxwidth)
1034 width = maxwidth;
1035
1036 height += 2 + 2*FFT_OSD_MARGIN_SIZE;
1037 if(height > maxheight)
1038 height = maxheight;
1039
1040 bool drawn = myosd_update_pos((LCD_WIDTH - width) / 2,
1041 (LCD_HEIGHT - height) / 2,
1042 width, height);
1043
1044 myosd_show(OSD_SHOW | (drawn ? 0 : OSD_UPDATENOW));
1045}
1046
1047static void fft_popupmsg(enum fft_setting_flags id)
1048{
1049 fft_message_id = id;
1050}
1051
1052/************************** End of OSD functions ***************************/
1053
1054
1055static void fft_setting_update(unsigned which)
1056{
1057 static fft_drawfn_t fft_drawfns[FFT_MAX_DM][FFT_MAX_OR] =
1058 {
1059 [FFT_DM_LINES] =
1060 {
1061 [FFT_OR_HORZ] = draw_lines_horizontal,
1062 [FFT_OR_VERT] = draw_lines_vertical,
1063 },
1064 [FFT_DM_BARS] =
1065 {
1066 [FFT_OR_HORZ] = draw_bars_horizontal,
1067 [FFT_OR_VERT] = draw_bars_vertical,
1068 },
1069 [FFT_DM_SPECTROGRAM] =
1070 {
1071 [FFT_OR_HORZ] = draw_spectrogram_horizontal,
1072 [FFT_OR_VERT] = draw_spectrogram_vertical,
1073 },
1074 };
1075
1076 if(which & (FFT_SETF_DM | FFT_SETF_OR))
1077 {
1078 fft_drawfn = fft_drawfns[fft.drawmode]
1079 [fft.orientation];
1080
1081 if(fft.drawmode == FFT_DM_SPECTROGRAM)
1082 {
1083 fft_spectrogram_pos = -1;
1084 myosd_lcd_update_prepare();
1085 mylcd_clear_display();
1086 myosd_lcd_update();
1087 }
1088 }
1089
1090 if(which & (FFT_SETF_DM | FFT_SETF_AS))
1091 {
1092 if(fft.drawmode == FFT_DM_SPECTROGRAM)
1093 {
1094 fft_graph_scale = fft.amp_scale == FFT_AS_LIN ?
1095 QLIN_MAX : QLOG_MAX;
1096 }
1097 else
1098 {
1099 fft_graph_scale = 0;
1100 }
1101 }
1102
1103 if(which & FFT_SETF_FS)
1104 {
1105 plot = fft.freq_scale == FFT_FS_LIN ?
1106 linf_magnitudes : logf_magnitudes;
1107 }
1108
1109 if(which & FFT_SETF_AS)
1110 {
1111 memset(linf_magnitudes, 0, sizeof (linf_magnitudes));
1112 memset(logf_magnitudes, 0, sizeof (logf_magnitudes));
1113 }
1114}
1115
1116static long fft_draw(void)
1117{
1118 long tick = *rb->current_tick;
1119
1120 if(fft_message_id != -1)
1121 {
1122 /* Show a new message */
1123 fft_osd_show_message((enum fft_setting_flags)fft_message_id);
1124 fft_message_id = -1;
1125 }
1126 else
1127 {
1128 /* Monitor OSD timeout */
1129 myosd_monitor_timeout();
1130 }
1131
1132 if(TIME_BEFORE(tick, fft_next_frame_tick))
1133 return fft_next_frame_tick - tick; /* Too early */
1134
1135 unsigned this_max;
1136
1137 if(!fft_have_fft())
1138 {
1139 if(is_playing())
1140 return HZ/100;
1141
1142 /* All magnitudes == 0 thus this_max == 0 */
1143 for(int i = 0; i < ARRAYLEN_PLOT; i++)
1144 linf_magnitudes[i] >>= 1; /* decay */
1145
1146 this_max = 0;
1147 }
1148 else
1149 {
1150 this_max = calc_magnitudes(fft.amp_scale);
1151
1152 fft_free_fft_output(); /* COP only */
1153
1154 if(fft.drawmode != FFT_DM_SPECTROGRAM &&
1155 this_max > fft_graph_scale)
1156 {
1157 fft_graph_scale = this_max;
1158 }
1159 }
1160
1161 if (fft.freq_scale == FFT_FS_LOG)
1162 log_plot_translate();
1163
1164 myosd_lcd_update_prepare();
1165
1166 mylcd_set_foreground(COLOR_DEFAULT_FG);
1167 mylcd_set_background(COLOR_DEFAULT_BG);
1168
1169 fft_drawfn(this_max, fft_graph_scale);
1170
1171 myosd_lcd_update();
1172
1173 fft_next_frame_tick = tick + FFT_PERIOD;
1174 return fft_next_frame_tick - *rb->current_tick;
1175}
1176
1177static void fft_osd_init(void *buf, size_t bufsize)
1178{
1179 int width, height;
1180 mylcd_setfont(FONT_UI);
1181 mylcd_getstringsize("M", NULL, &height);
1182 width = LCD_WIDTH;
1183 height += 2 + 2*FFT_OSD_MARGIN_SIZE;
1184 myosd_init(OSD_INIT_MAJOR_HEIGHT | OSD_INIT_MINOR_MAX, buf, bufsize,
1185 fft_osd_draw_cb, &width, &height, NULL);
1186 myosd_set_timeout(HZ);
1187}
1188
1189static void fft_cleanup(void)
1190{
1191 myosd_destroy();
1192
1193 fft_close_fft();
1194
1195#ifdef HAVE_ADJUSTABLE_CPU_FREQ
1196 rb->cancel_cpu_boost();
1197#endif
1198#ifndef HAVE_LCD_COLOR
1199 grey_release();
1200#endif
1201
1202 backlight_use_settings();
1203
1204 /* save settings if changed */
1205 if (rb->memcmp(&fft, &fft_disk, sizeof(fft)))
1206 {
1207 fft_disk = fft;
1208 configfile_save(cfg_filename, disk_config, ARRAYLEN(disk_config),
1209 CFGFILE_VERSION);
1210 }
1211}
1212
1213static bool fft_setup(void)
1214{
1215 atexit(fft_cleanup);
1216
1217 configfile_load(cfg_filename, disk_config, ARRAYLEN(disk_config),
1218 CFGFILE_MINVERSION);
1219 fft = fft_disk; /* copy to running config */
1220
1221 if(!fft_init_fft())
1222 return false;
1223
1224 /* get the remainder of the plugin buffer for OSD and perhaps
1225 greylib */
1226 size_t bufsize = 0;
1227 unsigned char *buf = rb->plugin_get_buffer(&bufsize);
1228
1229#ifndef HAVE_LCD_COLOR
1230 /* initialize the greyscale buffer.*/
1231 long grey_size;
1232 if(!grey_init(buf, bufsize, GREY_ON_COP | GREY_BUFFERED,
1233 LCD_WIDTH, LCD_HEIGHT, &grey_size))
1234 {
1235 rb->splash(HZ, "Couldn't init greyscale display");
1236 return false;
1237 }
1238
1239 grey_show(true);
1240
1241 buf += grey_size;
1242 bufsize -= grey_size;
1243#endif /* !HAVE_LCD_COLOR */
1244
1245 fft_osd_init(buf, bufsize);
1246
1247#if LCD_DEPTH > 1
1248 myosd_lcd_update_prepare();
1249 rb->lcd_set_backdrop(NULL);
1250 mylcd_clear_display();
1251 myosd_lcd_update();
1252#endif
1253
1254 backlight_ignore_timeout();
1255
1256#ifdef HAVE_ADJUSTABLE_CPU_FREQ
1257 rb->trigger_cpu_boost();
1258#endif
1259
1260 logarithmic_plot_init();
1261 fft_setting_update(FFT_SETF_ALL);
1262 fft_next_frame_tick = *rb->current_tick;
1263
1264 return true;
1265}
1266
1267enum plugin_status plugin_start(const void* parameter)
1268{
1269 bool run = true;
1270
1271 if(!fft_setup())
1272 return PLUGIN_ERROR;
1273
1274#if defined(FFT_AMP_SCALE_PRE)
1275 int lastbutton = BUTTON_NONE;
1276#endif
1277
1278 while(run)
1279 {
1280 long delay = fft_draw();
1281
1282 if(delay <= 0)
1283 {
1284 delay = 0;
1285 rb->yield(); /* tmo = 0 won't yield */
1286 }
1287
1288 int button = pluginlib_getaction(TIMEOUT_NOBLOCK, plugin_contexts, ARRAYLEN(plugin_contexts));
1289
1290 switch (button)
1291 {
1292 case FFT_QUIT:
1293 run = false;
1294 break;
1295
1296 case FFT_ORIENTATION:
1297 if (++fft.orientation >= FFT_MAX_OR)
1298 fft.orientation = FFT_MIN_OR;
1299
1300 fft_setting_update(FFT_SETF_OR);
1301 fft_popupmsg(FFT_SETF_OR);
1302 break;
1303
1304 case FFT_PREV_GRAPH:
1305 if (fft.drawmode-- <= FFT_MIN_DM)
1306 fft.drawmode = FFT_MAX_DM-1;
1307
1308 fft_setting_update(FFT_SETF_DM);
1309 fft_popupmsg(FFT_SETF_DM);
1310 break;
1311
1312 case FFT_NEXT_GRAPH:
1313 if (++fft.drawmode >= FFT_MAX_DM)
1314 fft.drawmode = FFT_MIN_DM;
1315
1316 fft_setting_update(FFT_SETF_DM);
1317 fft_popupmsg(FFT_SETF_DM);
1318 break;
1319
1320 case FFT_AMP_SCALE:
1321#ifdef FFT_AMP_SCALE_PRE
1322 if (lastbutton != FFT_AMP_SCALE_PRE)
1323 break;
1324#endif
1325 if (++fft.amp_scale >= FFT_MAX_AS)
1326 fft.amp_scale = FFT_MIN_AS;
1327
1328 fft_setting_update(FFT_SETF_AS);
1329 fft_popupmsg(FFT_SETF_AS);
1330 break;
1331
1332#ifdef FFT_FREQ_SCALE /* 'Till all keymaps are defined */
1333 case FFT_FREQ_SCALE:
1334 if (++fft.freq_scale >= FFT_MAX_FS)
1335 fft.freq_scale = FFT_MIN_FS;
1336
1337 fft_setting_update(FFT_SETF_FS);
1338 fft_popupmsg(FFT_SETF_FS);
1339 break;
1340#endif
1341 case FFT_WINDOW:
1342 if(++fft.window_func >= FFT_MAX_WF)
1343 fft.window_func = FFT_MIN_WF;
1344
1345 fft_setting_update(FFT_SETF_WF);
1346 fft_popupmsg(FFT_SETF_WF);
1347 break;
1348
1349 default:
1350 exit_on_usb(button);
1351 break;
1352 }
1353
1354#if defined(FFT_AMP_SCALE_PRE)
1355 if (button != BUTTON_NONE)
1356 lastbutton = button;
1357#endif
1358 }
1359
1360 return PLUGIN_OK;
1361 (void)parameter;
1362}