summaryrefslogtreecommitdiff
path: root/apps/plugins/fft/fft.c
diff options
context:
space:
mode:
authorFrank Gevaerts <frank@gevaerts.be>2010-02-10 19:44:11 +0000
committerFrank Gevaerts <frank@gevaerts.be>2010-02-10 19:44:11 +0000
commit43264a946f0222e49ac2c10da7d00b36efc3f40e (patch)
treed0ef77e8280819bfc23f9ef101e1374011ad6e1b /apps/plugins/fft/fft.c
parentfa4ab10bbbd5b3588bc0e7057b338d1068939fda (diff)
New plugin: FFT, A frequency analyzer plugin
There is some more work needed: - Keymaps are definitely not perfect, touchscreen targets are disabled due to no keymap - There is no manual yet Author: Delyan Kratunov Flyspray: FS#10065 git-svn-id: svn://svn.rockbox.org/rockbox/trunk@24587 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'apps/plugins/fft/fft.c')
-rw-r--r--apps/plugins/fft/fft.c1165
1 files changed, 1165 insertions, 0 deletions
diff --git a/apps/plugins/fft/fft.c b/apps/plugins/fft/fft.c
new file mode 100644
index 0000000000..531c9af4cb
--- /dev/null
+++ b/apps/plugins/fft/fft.c
@@ -0,0 +1,1165 @@
+/***************************************************************************
+* __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2009 Delyan Kratunov
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include "plugin.h"
+
+#include "lib/helper.h"
+#include "lib/xlcd.h"
+#include "math.h"
+#include "thread.h"
+
+#ifndef HAVE_LCD_COLOR
+#include "lib/grey.h"
+#endif
+
+PLUGIN_HEADER
+
+#ifndef HAVE_LCD_COLOR
+GREY_INFO_STRUCT
+#endif
+
+#if CONFIG_KEYPAD == ARCHOS_AV300_PAD
+# define FFT_PREV_GRAPH BUTTON_LEFT
+# define FFT_NEXT_GRAPH BUTTON_RIGHT
+# define FFT_ORIENTATION BUTTON_F3
+# define FFT_WINDOW BUTTON_F1
+# define FFT_SCALE BUTTON_UP
+# define FFT_QUIT BUTTON_OFF
+
+#elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \
+ (CONFIG_KEYPAD == IRIVER_H300_PAD)
+# define FFT_PREV_GRAPH BUTTON_LEFT
+# define FFT_NEXT_GRAPH BUTTON_RIGHT
+# define FFT_ORIENTATION BUTTON_REC
+# define FFT_WINDOW BUTTON_SELECT
+# define FFT_SCALE BUTTON_UP
+# define FFT_QUIT BUTTON_OFF
+
+#elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
+ (CONFIG_KEYPAD == IPOD_3G_PAD) || \
+ (CONFIG_KEYPAD == IPOD_1G2G_PAD)
+# define MINESWP_SCROLLWHEEL
+# define FFT_PREV_GRAPH BUTTON_LEFT
+# define FFT_NEXT_GRAPH BUTTON_RIGHT
+# define FFT_ORIENTATION (BUTTON_SELECT | BUTTON_LEFT)
+# define FFT_WINDOW (BUTTON_SELECT | BUTTON_RIGHT)
+# define FFT_SCALE BUTTON_MENU
+# define FFT_QUIT (BUTTON_SELECT | BUTTON_MENU)
+
+#elif (CONFIG_KEYPAD == IAUDIO_X5M5_PAD)
+# define FFT_PREV_GRAPH BUTTON_LEFT
+# define FFT_NEXT_GRAPH BUTTON_RIGHT
+# define FFT_ORIENTATION BUTTON_SELECT
+# define FFT_WINDOW BUTTON_PLAY
+# define FFT_SCALE BUTTON_UP
+# define FFT_QUIT BUTTON_POWER
+
+#elif (CONFIG_KEYPAD == GIGABEAT_PAD)
+# define FFT_PREV_GRAPH BUTTON_LEFT
+# define FFT_NEXT_GRAPH BUTTON_RIGHT
+# define FFT_SCALE BUTTON_UP
+# define FFT_ORIENTATION BUTTON_SELECT
+# define FFT_WINDOW BUTTON_A
+# define FFT_QUIT BUTTON_POWER
+
+#elif (CONFIG_KEYPAD == SANSA_E200_PAD)
+# define FFT_PREV_GRAPH BUTTON_LEFT
+# define FFT_NEXT_GRAPH BUTTON_RIGHT
+# define FFT_ORIENTATION BUTTON_SELECT
+# define FFT_WINDOW BUTTON_REC
+# define FFT_SCALE BUTTON_UP
+# define FFT_QUIT BUTTON_POWER
+
+#elif (CONFIG_KEYPAD == SANSA_FUZE_PAD)
+# define FFT_PREV_GRAPH BUTTON_LEFT
+# define FFT_NEXT_GRAPH BUTTON_RIGHT
+# define FFT_ORIENTATION (BUTTON_SELECT | BUTTON_LEFT)
+# define FFT_WINDOW (BUTTON_SELECT | BUTTON_RIGHT)
+# define FFT_SCALE BUTTON_UP
+# define FFT_QUIT BUTTON_POWER
+
+#elif (CONFIG_KEYPAD == SANSA_C200_PAD)
+# define FFT_PREV_GRAPH BUTTON_LEFT
+# define FFT_NEXT_GRAPH BUTTON_RIGHT
+# define FFT_ORIENTATION BUTTON_UP
+# define FFT_WINDOW BUTTON_REC
+# define FFT_SCALE BUTTON_SELECT
+# define FFT_QUIT BUTTON_POWER
+#elif (CONFIG_KEYPAD == SANSA_M200_PAD)
+# define FFT_PREV_GRAPH BUTTON_LEFT
+# define FFT_NEXT_GRAPH BUTTON_RIGHT
+# define FFT_ORIENTATION BUTTON_UP
+# define FFT_WINDOW BUTTON_DOWN
+# define FFT_SCALE BUTTON_SELECT
+# define FFT_QUIT BUTTON_POWER
+#elif (CONFIG_KEYPAD == SANSA_CLIP_PAD)
+# define FFT_PREV_GRAPH BUTTON_LEFT
+# define FFT_NEXT_GRAPH BUTTON_RIGHT
+# define FFT_ORIENTATION BUTTON_UP
+# define FFT_WINDOW BUTTON_HOME
+# define FFT_SCALE BUTTON_SELECT
+# define FFT_QUIT BUTTON_POWER
+
+#elif (CONFIG_KEYPAD == IRIVER_H10_PAD)
+# define FFT_PREV_GRAPH BUTTON_LEFT
+# define FFT_NEXT_GRAPH BUTTON_RIGHT
+# define FFT_ORIENTATION BUTTON_FF
+# define FFT_WINDOW BUTTON_SCROLL_UP
+# define FFT_SCALE BUTTON_REW
+# define FFT_QUIT BUTTON_POWER
+
+#elif (CONFIG_KEYPAD == GIGABEAT_S_PAD)
+# define FFT_PREV_GRAPH BUTTON_LEFT
+# define FFT_NEXT_GRAPH BUTTON_RIGHT
+# define FFT_ORIENTATION BUTTON_MENU
+# define FFT_WINDOW BUTTON_PREV
+# define FFT_SCALE BUTTON_UP
+# define FFT_QUIT BUTTON_BACK
+
+#elif (CONFIG_KEYPAD == MROBE100_PAD)
+# define FFT_PREV_GRAPH BUTTON_LEFT
+# define FFT_NEXT_GRAPH BUTTON_RIGHT
+# define FFT_ORIENTATION BUTTON_PLAY
+# define FFT_WINDOW BUTTON_SELECT
+# define FFT_SCALE BUTTON_UP
+# define FFT_QUIT BUTTON_POWER
+
+#elif CONFIG_KEYPAD == IAUDIO_M3_PAD
+# define FFT_PREV_GRAPH BUTTON_RC_REW
+# define FFT_NEXT_GRAPH BUTTON_RC_FF
+# define FFT_ORIENTATION BUTTON_RC_MODE
+# define FFT_WINDOW BUTTON_RC_PLAY
+# define FFT_SCALE BUTTON_RC_VOL_UP
+# define FFT_QUIT BUTTON_RC_REC
+
+#elif (CONFIG_KEYPAD == COWON_D2_PAD)
+# define FFT_QUIT BUTTON_POWER
+# define FFT_PREV_GRAPH BUTTON_PLUS
+# define FFT_NEXT_GRAPH BUTTON_MINUS
+
+#elif CONFIG_KEYPAD == CREATIVEZVM_PAD
+# define FFT_PREV_GRAPH BUTTON_LEFT
+# define FFT_NEXT_GRAPH BUTTON_RIGHT
+# define FFT_ORIENTATION BUTTON_MENU
+# define FFT_WINDOW BUTTON_SELECT
+# define FFT_SCALE BUTTON_UP
+# define FFT_QUIT BUTTON_BACK
+
+#elif CONFIG_KEYPAD == PHILIPS_HDD1630_PAD
+# define FFT_PREV_GRAPH BUTTON_LEFT
+# define FFT_NEXT_GRAPH BUTTON_RIGHT
+# define FFT_ORIENTATION BUTTON_SELECT
+# define FFT_WINDOW BUTTON_MENU
+# define FFT_SCALE BUTTON_UP
+# define FFT_QUIT BUTTON_POWER
+
+#elif (CONFIG_KEYPAD == SAMSUNG_YH_PAD)
+# define FFT_PREV_GRAPH BUTTON_LEFT
+# define FFT_NEXT_GRAPH BUTTON_RIGHT
+# define FFT_ORIENTATION BUTTON_UP
+# define FFT_WINDOW BUTTON_DOWN
+# define FFT_SCALE BUTTON_FFWD
+# define FFT_QUIT BUTTON_PLAY
+
+#else
+#error No keymap defined!
+#endif
+
+#ifdef HAVE_LCD_COLOR
+#include "pluginbitmaps/fft_colors.h"
+#endif
+
+#include "kiss_fftr.h"
+#include "_kiss_fft_guts.h" /* sizeof(struct kiss_fft_state) */
+#include "const.h"
+
+#define FFT_SIZE 2048
+#define ARRAYSIZE_IN (FFT_SIZE)
+#define ARRAYSIZE_OUT (FFT_SIZE/2)
+#define ARRAYSIZE_PLOT (FFT_SIZE/4)
+#define BUFSIZE_FFT (sizeof(struct kiss_fft_state)+sizeof(kiss_fft_cpx)*(FFT_SIZE-1))
+#define BUFSIZE_FFTR (BUFSIZE_FFT+sizeof(struct kiss_fftr_state)+sizeof(kiss_fft_cpx)*(FFT_SIZE*3/2))
+#define BUFSIZE BUFSIZE_FFTR
+#define FFT_ALLOC kiss_fftr_alloc
+#define FFT_FFT kiss_fftr
+#define FFT_CFG kiss_fftr_cfg
+
+#define __COEFF(type,size) type##_##size
+#define _COEFF(x, y) __COEFF(x,y) /* force the preprocessor to evaluate FFT_SIZE) */
+#define HANN_COEFF _COEFF(hann, FFT_SIZE)
+#define HAMMING_COEFF _COEFF(hamming, FFT_SIZE)
+
+/****************************** Globals ****************************/
+
+static kiss_fft_scalar input[ARRAYSIZE_IN];
+static kiss_fft_cpx output[ARRAYSIZE_OUT];
+static int32_t plot[ARRAYSIZE_PLOT];
+static char buffer[BUFSIZE];
+
+#if LCD_DEPTH > 1 /* greyscale or color, enable spectrogram */
+#define MODES_COUNT 3
+#else
+#define MODES_COUNT 2
+#endif
+
+const unsigned char* modes_text[] = { "Lines", "Bars", "Spectrogram" };
+const unsigned char* scales_text[] = { "Linear scale", "Logarithmic scale" };
+const unsigned char* window_text[] = { "Hamming window", "Hann window" };
+
+struct mutex input_mutex;
+bool input_thread_run = true;
+bool input_thread_has_data = false;
+
+struct {
+ int32_t mode;
+ bool logarithmic;
+ bool orientation_vertical;
+ int window_func;
+ struct {
+ int column;
+ int row;
+ } spectrogram;
+ struct {
+ bool orientation;
+ bool mode;
+ bool scale;
+ } changed;
+} graph_settings;
+
+#define COLORS BMPWIDTH_fft_colors
+
+/************************* End of globals *************************/
+
+/************************* Math functions *************************/
+#define QLOG_MAX 286286
+#define QLIN_MAX 1534588906
+#define QLN_10 float_q16(2.302585093)
+#define LIN_MAX (QLIN_MAX >> 16)
+
+/* Returns logarithmically scaled values in S15.16 format */
+inline int32_t get_log_value(int32_t value)
+{
+ return Q16_DIV(fp16_log(value), QLN_10);
+}
+
+/* Apply window function to input
+ * 0 - Hamming window
+ * 1 - Hann window */
+#define WINDOW_COUNT 2
+void apply_window_func(char mode)
+{
+ switch(mode)
+ {
+ case 0: /* Hamming window */
+ {
+ size_t i;
+ for (i = 0; i < ARRAYSIZE_IN; ++i)
+ {
+ input[i] = Q15_MUL(input[i] << 15, HAMMING_COEFF[i]) >> 15;
+ }
+ break;
+ }
+ case 1: /* Hann window */
+ {
+ size_t i;
+ for (i = 0; i < ARRAYSIZE_IN; ++i)
+ {
+ input[i] = Q15_MUL(input[i] << 15, HANN_COEFF[i]) >> 15;
+ }
+ break;
+ }
+ }
+}
+
+/* Calculates the magnitudes from complex numbers and returns the maximum */
+int32_t calc_magnitudes(bool logarithmic)
+{
+ int64_t tmp;
+ size_t i;
+
+ int32_t max = -2147483647;
+
+ /* Calculate the magnitude, discarding the phase.
+ * The sum of the squares can easily overflow the 15-bit (s15.16)
+ * requirement for fsqrt, so we scale the data down */
+ for (i = 0; i < ARRAYSIZE_PLOT; ++i)
+ {
+ tmp = output[i].r * output[i].r + output[i].i * output[i].i;
+ tmp <<= 16;
+
+ tmp = fsqrt64(tmp, 16);
+
+ if (logarithmic)
+ tmp = get_log_value(tmp & 0x7FFFFFFF);
+
+ plot[i] = tmp;
+
+ if (plot[i] > max)
+ max = plot[i];
+ }
+ return max;
+}
+/************************ End of math functions ***********************/
+
+/********************* Plotting functions (modes) *********************/
+void draw_lines_vertical(void);
+void draw_lines_horizontal(void);
+void draw_bars_vertical(void);
+void draw_bars_horizontal(void);
+void draw_spectrogram_vertical(void);
+void draw_spectrogram_horizontal(void);
+
+void draw(const unsigned char* message)
+{
+ static uint32_t show_message = 0;
+ static unsigned char* last_message = 0;
+
+ static char last_mode = 0;
+ static bool last_orientation = true, last_scale = true;
+
+ if (message != 0)
+ {
+ last_message = (unsigned char*) message;
+ show_message = 5;
+ }
+
+ if(last_mode != graph_settings.mode)
+ {
+ last_mode = graph_settings.mode;
+ graph_settings.changed.mode = true;
+ }
+ if(last_scale != graph_settings.logarithmic)
+ {
+ last_scale = graph_settings.logarithmic;
+ graph_settings.changed.scale = true;
+ }
+ if(last_orientation != graph_settings.orientation_vertical)
+ {
+ last_orientation = graph_settings.orientation_vertical;
+ graph_settings.changed.orientation = true;
+ }
+#ifdef HAVE_LCD_COLOR
+ rb->lcd_set_foreground(LCD_DEFAULT_FG);
+ rb->lcd_set_background(LCD_DEFAULT_BG);
+#else
+ grey_set_foreground(GREY_BLACK);
+ grey_set_background(GREY_WHITE);
+#endif
+
+ switch (graph_settings.mode)
+ {
+ default:
+ case 0: {
+
+#ifdef HAVE_LCD_COLOR
+ rb->lcd_clear_display();
+#else
+ grey_clear_display();
+#endif
+
+ if (graph_settings.orientation_vertical)
+ draw_lines_vertical();
+ else
+ draw_lines_horizontal();
+ break;
+ }
+ case 1: {
+
+#ifdef HAVE_LCD_COLOR
+ rb->lcd_clear_display();
+#else
+ grey_clear_display();
+#endif
+
+ if(graph_settings.orientation_vertical)
+ draw_bars_vertical();
+ else
+ draw_bars_horizontal();
+
+ break;
+ }
+ case 2: {
+ if(graph_settings.orientation_vertical)
+ draw_spectrogram_vertical();
+ else
+ draw_spectrogram_horizontal();
+ break;
+ }
+ }
+
+ if (show_message > 0)
+ {
+ /* We have a message to show */
+
+ int x, y;
+#ifdef HAVE_LCD_COLOR
+ rb->lcd_getstringsize(last_message, &x, &y);
+#else
+ grey_getstringsize(last_message, &x, &y);
+#endif
+ /* x and y give the size of the box for the popup */
+ x += 6; /* 3 px of horizontal padding and */
+ y += 4; /* 2 px of vertical padding */
+
+ /* In vertical spectrogram mode, leave space for the popup
+ * before actually drawing it (if space is needed) */
+ if(graph_settings.mode == 2 &&
+ graph_settings.orientation_vertical &&
+ graph_settings.spectrogram.column > LCD_WIDTH-x-2)
+ {
+#ifdef HAVE_LCD_COLOR
+ xlcd_scroll_left(graph_settings.spectrogram.column -
+ (LCD_WIDTH - x - 1));
+#else
+ grey_scroll_left(graph_settings.spectrogram.column -
+ (LCD_WIDTH - x - 1));
+#endif
+ graph_settings.spectrogram.column = LCD_WIDTH - x - 2;
+ }
+
+#ifdef HAVE_LCD_COLOR
+ rb->lcd_set_foreground(LCD_DARKGRAY);
+ rb->lcd_fillrect(LCD_WIDTH-1-x, 0, LCD_WIDTH-1, y);
+
+ rb->lcd_set_foreground(LCD_DEFAULT_FG);
+ rb->lcd_set_background(LCD_DARKGRAY);
+ rb->lcd_putsxy(LCD_WIDTH-1-x+3, 2, last_message);
+ rb->lcd_set_background(LCD_DEFAULT_BG);
+#else
+ grey_set_foreground(GREY_LIGHTGRAY);
+ grey_fillrect(LCD_WIDTH-1-x, 0, LCD_WIDTH-1, y);
+
+ grey_set_foreground(GREY_BLACK);
+ grey_set_background(GREY_LIGHTGRAY);
+ grey_putsxy(LCD_WIDTH-1-x+3, 2, last_message);
+ grey_set_background(GREY_WHITE);
+#endif
+
+ show_message--;
+ }
+ else if(last_message != 0)
+ {
+ if(graph_settings.mode != 2)
+ {
+ /* These modes clear the screen themselves */
+ last_message = 0;
+ }
+ else /* Spectrogram mode - need to erase the popup */
+ {
+ int x, y;
+#ifdef HAVE_LCD_COLOR
+ rb->lcd_getstringsize(last_message, &x, &y);
+#else
+ grey_getstringsize(last_message, &x, &y);
+#endif
+ /* Recalculate the size */
+ x += 6; /* 3 px of horizontal padding and */
+ y += 4; /* 2 px of vertical padding */
+
+ if(!graph_settings.orientation_vertical)
+ {
+ /* In horizontal spectrogram mode, just scroll up by Y lines */
+#ifdef HAVE_LCD_COLOR
+ xlcd_scroll_up(y);
+#else
+ grey_scroll_up(y);
+#endif
+ graph_settings.spectrogram.row -= y;
+ if(graph_settings.spectrogram.row < 0)
+ graph_settings.spectrogram.row = 0;
+ }
+ else
+ {
+ /* In vertical spectrogram mode, erase the popup */
+#ifdef HAVE_LCD_COLOR
+ rb->lcd_set_foreground(LCD_DEFAULT_BG);
+ rb->lcd_fillrect(LCD_WIDTH-2-x, 0, LCD_WIDTH-1, y);
+ rb->lcd_set_foreground(LCD_DEFAULT_FG);
+#else
+ grey_set_foreground(GREY_WHITE);
+ grey_fillrect(LCD_WIDTH-2-x, 0, LCD_WIDTH-1, y);
+ grey_set_foreground(GREY_BLACK);
+#endif
+ }
+
+ last_message = 0;
+ }
+ }
+#ifdef HAVE_LCD_COLOR
+ rb->lcd_update();
+#else
+ grey_update();
+#endif
+
+ graph_settings.changed.mode = false;
+ graph_settings.changed.orientation = false;
+ graph_settings.changed.scale = false;
+}
+
+void draw_lines_vertical(void)
+{
+ static int32_t max = 0, vfactor = 0, vfactor_count = 0;
+ static const int32_t hfactor =
+ Q16_DIV(LCD_WIDTH << 16, (ARRAYSIZE_PLOT) << 16),
+ bins_per_pixel = (ARRAYSIZE_PLOT) / LCD_WIDTH;
+ static bool old_scale = true;
+
+ if (old_scale != graph_settings.logarithmic)
+ old_scale = graph_settings.logarithmic, max = 0; /* reset the graph on scaling mode change */
+
+ int32_t new_max = calc_magnitudes(graph_settings.logarithmic);
+
+ if (new_max > max)
+ {
+ max = new_max;
+ vfactor = Q16_DIV(LCD_HEIGHT << 16, max); /* s15.16 */
+ vfactor_count = Q16_DIV(vfactor, bins_per_pixel << 16); /* s15.16 */
+ }
+
+ if (new_max == 0 || max == 0) /* nothing to draw */
+ return;
+
+ /* take the average of neighboring bins
+ * if we have to scale the graph horizontally */
+ int64_t bins_avg = 0;
+ bool draw = true;
+ int32_t i;
+ for (i = 0; i < ARRAYSIZE_PLOT; ++i)
+ {
+ int32_t x = 0, y = 0;
+
+ x = Q16_MUL(hfactor, i << 16) >> 16;
+ //x = (x + (1 << 15)) >> 16;
+
+ if (hfactor < 65536) /* hfactor < 0, graph compression */
+ {
+ draw = false;
+ bins_avg += plot[i];
+
+ /* fix the division by zero warning:
+ * bins_per_pixel is zero when the graph is expanding;
+ * execution won't even reach this point - this is a dummy constant
+ */
+ const int32_t div = bins_per_pixel > 0 ? bins_per_pixel : 1;
+ if ((i + 1) % div == 0)
+ {
+ y = Q16_MUL(vfactor_count, bins_avg) >> 16;
+
+ bins_avg = 0;
+ draw = true;
+ }
+ }
+ else
+ {
+ y = Q16_MUL(vfactor, plot[i]) >> 16;
+ draw = true;
+ }
+
+ if (draw)
+ {
+#ifdef HAVE_LCD_COLOR
+ rb->lcd_vline(x, LCD_HEIGHT-1, LCD_HEIGHT-y-1);
+#else
+ grey_vline(x, LCD_HEIGHT-1, LCD_HEIGHT-y-1);
+#endif
+ }
+ }
+}
+
+void draw_lines_horizontal(void)
+{
+ static int max = 0;
+
+ static const int32_t vfactor =
+ Q16_DIV(LCD_HEIGHT << 16, (ARRAYSIZE_PLOT) << 16),
+ bins_per_pixel = (ARRAYSIZE_PLOT) / LCD_HEIGHT;
+
+ if (graph_settings.changed.scale)
+ max = 0; /* reset the graph on scaling mode change */
+
+ int32_t new_max = calc_magnitudes(graph_settings.logarithmic);
+
+ if (new_max > max)
+ max = new_max;
+
+ if (new_max == 0 || max == 0) /* nothing to draw */
+ return;
+
+ int32_t hfactor;
+
+ hfactor = Q16_DIV((LCD_WIDTH - 1) << 16, max); /* s15.16 */
+
+ /* take the average of neighboring bins
+ * if we have to scale the graph horizontally */
+ int64_t bins_avg = 0;
+ bool draw = true;
+ int32_t i;
+ for (i = 0; i < ARRAYSIZE_PLOT; ++i)
+ {
+ int32_t x = 0, y = 0;
+
+ y = Q16_MUL(vfactor, i << 16) + (1 << 15);
+ y >>= 16;
+
+ if (vfactor < 65536) /* vfactor < 0, graph compression */
+ {
+ draw = false;
+ bins_avg += plot[i];
+
+ /* fix the division by zero warning:
+ * bins_per_pixel is zero when the graph is expanding;
+ * execution won't even reach this point - this is a dummy constant
+ */
+ const int32_t div = bins_per_pixel > 0 ? bins_per_pixel : 1;
+ if ((i + 1) % div == 0)
+ {
+ bins_avg = Q16_DIV(bins_avg, div << 16);
+ x = Q16_MUL(hfactor, bins_avg) >> 16;
+
+ bins_avg = 0;
+ draw = true;
+ }
+ }
+ else
+ {
+ y = Q16_MUL(hfactor, plot[i]) >> 16;
+ draw = true;
+ }
+
+ if (draw)
+ {
+#ifdef HAVE_LCD_COLOR
+ rb->lcd_hline(0, x, y);
+#else
+ grey_hline(0, x, y);
+#endif
+ }
+ }
+}
+
+void draw_bars_vertical(void)
+{
+ static const unsigned int bars = 20, border = 2, items = ARRAYSIZE_PLOT
+ / bars, width = (LCD_WIDTH - ((bars - 1) * border)) / bars;
+
+ calc_magnitudes(graph_settings.logarithmic);
+
+ uint64_t bars_values[bars], bars_max = 0, avg = 0;
+ unsigned int i, bars_idx = 0;
+ for (i = 0; i < ARRAYSIZE_PLOT; ++i)
+ {
+ avg += plot[i];
+ if ((i + 1) % items == 0)
+ {
+ /* Calculate the average value and keep the fractional part
+ * for some added precision */
+ avg = Q16_DIV(avg, items << 16);
+ bars_values[bars_idx] = avg;
+
+ if (bars_values[bars_idx] > bars_max)
+ bars_max = bars_values[bars_idx];
+
+ bars_idx++;
+ avg = 0;
+ }
+ }
+
+ if(bars_max == 0) /* nothing to draw */
+ return;
+
+ /* Give the graph some headroom */
+ bars_max = Q16_MUL(bars_max, float_q16(1.1));
+
+ uint64_t vfactor = Q16_DIV(LCD_HEIGHT << 16, bars_max);
+
+ for (i = 0; i < bars; ++i)
+ {
+ int x = (i) * (border + width);
+ int y;
+ y = Q16_MUL(vfactor, bars_values[i]) + (1 << 15);
+ y >>= 16;
+#ifdef HAVE_LCD_COLOR
+ rb->lcd_fillrect(x, LCD_HEIGHT - y - 1, width, y);
+#else
+ grey_fillrect(x, LCD_HEIGHT - y - 1, width, y);
+#endif
+ }
+}
+
+void draw_bars_horizontal(void)
+{
+ static const unsigned int bars = 14, border = 3, items = ARRAYSIZE_PLOT
+ / bars, height = (LCD_HEIGHT - ((bars - 1) * border)) / bars;
+
+ calc_magnitudes(graph_settings.logarithmic);
+
+ int64_t bars_values[bars], bars_max = 0, avg = 0;
+ unsigned int i, bars_idx = 0;
+ for (i = 0; i < ARRAYSIZE_PLOT; ++i)
+ {
+ avg += plot[i];
+ if ((i + 1) % items == 0)
+ {
+ /* Calculate the average value and keep the fractional part
+ * for some added precision */
+ avg = Q16_DIV(avg, items << 16); /* s15.16 */
+ bars_values[bars_idx] = avg;
+
+ if (bars_values[bars_idx] > bars_max)
+ bars_max = bars_values[bars_idx];
+
+ bars_idx++;
+ avg = 0;
+ }
+ }
+
+ if(bars_max == 0) /* nothing to draw */
+ return;
+
+ /* Give the graph some headroom */
+ bars_max = Q16_MUL(bars_max, float_q16(1.1));
+
+ int64_t hfactor = Q16_DIV(LCD_WIDTH << 16, bars_max);
+
+ for (i = 0; i < bars; ++i)
+ {
+ int y = (i) * (border + height);
+ int x;
+ x = Q16_MUL(hfactor, bars_values[i]) + (1 << 15);
+ x >>= 16;
+
+#ifdef HAVE_LCD_COLOR
+ rb->lcd_fillrect(0, y, x, height);
+#else
+ grey_fillrect(0, y, x, height);
+#endif
+ }
+}
+
+void draw_spectrogram_vertical(void)
+{
+ const int32_t scale_factor = ARRAYSIZE_PLOT / LCD_HEIGHT
+#ifdef HAVE_LCD_COLOR
+ ,colors_per_val_log = Q16_DIV((COLORS-1) << 16, QLOG_MAX),
+ colors_per_val_lin = Q16_DIV((COLORS-1) << 16, QLIN_MAX)
+#else
+ ,grey_vals_per_val_log = Q16_DIV(255 << 16, QLOG_MAX),
+ grey_vals_per_val_lin = Q16_DIV(255 << 16, QLIN_MAX)
+#endif
+ ;
+
+ const int32_t remaining_div =
+ (ARRAYSIZE_PLOT-scale_factor*LCD_HEIGHT) > 0 ?
+ ( Q16_DIV((scale_factor*LCD_HEIGHT) << 16,
+ (ARRAYSIZE_PLOT-scale_factor*LCD_HEIGHT) << 16)
+ + (1<<15) ) >> 16 : 0;
+
+ calc_magnitudes(graph_settings.logarithmic);
+ if(graph_settings.changed.mode || graph_settings.changed.orientation)
+ {
+ graph_settings.spectrogram.column = 0;
+#ifdef HAVE_LCD_COLOR
+ rb->lcd_clear_display();
+#else
+ grey_clear_display();
+#endif
+ }
+
+ int i, y = LCD_HEIGHT-1, count = 0, rem_count = 0;
+ uint64_t avg = 0;
+ bool added_extra_value = false;
+ for(i = 0; i < ARRAYSIZE_PLOT; ++i)
+ {
+ if(plot[i] > 0)
+ avg += plot[i];
+ ++count;
+ ++rem_count;
+
+ /* Kinda hacky - due to the rounding in scale_factor, we try to
+ * uniformly interweave the extra values in our calculations */
+ if(remaining_div > 0 && rem_count >= remaining_div &&
+ i < (ARRAYSIZE_PLOT-1))
+ {
+ ++i;
+ if(plot[i] > 0)
+ avg += plot[i];
+ rem_count = 0;
+ added_extra_value = true;
+ }
+
+ if(count >= scale_factor)
+ {
+ if(added_extra_value)
+ { ++count; added_extra_value = false; }
+
+ int32_t color;
+
+ avg = Q16_DIV(avg, count << 16);
+
+#ifdef HAVE_LCD_COLOR
+ if(graph_settings.logarithmic)
+ color = Q16_MUL(avg, colors_per_val_log) >> 16;
+ else
+ color = Q16_MUL(avg, colors_per_val_lin) >> 16;
+ if(color >= COLORS) /* TODO These happen because we don't normalize the values to be above 1 and log() returns negative numbers. I think. */
+ color = COLORS-1;
+ else if (color < 0)
+ color = 0;
+
+#else
+ if(graph_settings.logarithmic)
+ color = Q16_MUL(avg, grey_vals_per_val_log) >> 16;
+ else
+ color = Q16_MUL(avg, grey_vals_per_val_lin) >> 16;
+ if(color > 255)
+ color = 255;
+ else if (color < 0)
+ color = 0;
+#endif
+
+#ifdef HAVE_LCD_COLOR
+ rb->lcd_set_foreground(fft_colors[color]);
+ rb->lcd_drawpixel(graph_settings.spectrogram.column, y);
+#else
+ grey_set_foreground(255 - color);
+ grey_drawpixel(graph_settings.spectrogram.column, y);
+#endif
+
+ y--;
+
+ avg = 0;
+ count = 0;
+ }
+ if(y < 0)
+ break;
+ }
+ if(graph_settings.spectrogram.column != LCD_WIDTH-1)
+ graph_settings.spectrogram.column++;
+ else
+#ifdef HAVE_LCD_COLOR
+ xlcd_scroll_left(1);
+#else
+ grey_scroll_left(1);
+#endif
+}
+
+void draw_spectrogram_horizontal(void)
+{
+ const int32_t scale_factor = ARRAYSIZE_PLOT / LCD_WIDTH
+#ifdef HAVE_LCD_COLOR
+ ,colors_per_val_log = Q16_DIV((COLORS-1) << 16, QLOG_MAX),
+ colors_per_val_lin = Q16_DIV((COLORS-1) << 16, QLIN_MAX)
+#else
+ ,grey_vals_per_val_log = Q16_DIV(255 << 16, QLOG_MAX),
+ grey_vals_per_val_lin = Q16_DIV(255 << 16, QLIN_MAX)
+#endif
+ ;
+
+ const int32_t remaining_div =
+ (ARRAYSIZE_PLOT-scale_factor*LCD_WIDTH) > 0 ?
+ ( Q16_DIV((scale_factor*LCD_WIDTH) << 16,
+ (ARRAYSIZE_PLOT-scale_factor*LCD_WIDTH) << 16)
+ + (1<<15) ) >> 16 : 0;
+
+ calc_magnitudes(graph_settings.logarithmic);
+ if(graph_settings.changed.mode || graph_settings.changed.orientation)
+ {
+ graph_settings.spectrogram.row = 0;
+#ifdef HAVE_LCD_COLOR
+ rb->lcd_clear_display();
+#else
+ grey_clear_display();
+#endif
+ }
+
+ int i, x = 0, count = 0, rem_count = 0;
+ uint64_t avg = 0;
+ bool added_extra_value = false;
+ for(i = 0; i < ARRAYSIZE_PLOT; ++i)
+ {
+ if(plot[i] > 0)
+ avg += plot[i];
+ ++count;
+ ++rem_count;
+
+ /* Kinda hacky - due to the rounding in scale_factor, we try to
+ * uniformly interweave the extra values in our calculations */
+ if(remaining_div > 0 && rem_count >= remaining_div &&
+ i < (ARRAYSIZE_PLOT-1))
+ {
+ ++i;
+ if(plot[i] > 0)
+ avg += plot[i];
+ rem_count = 0;
+ added_extra_value = true;
+ }
+
+ if(count >= scale_factor)
+ {
+ if(added_extra_value)
+ { ++count; added_extra_value = false; }
+
+ int32_t color;
+
+ avg = Q16_DIV(avg, count << 16);
+
+#ifdef HAVE_LCD_COLOR
+ if(graph_settings.logarithmic)
+ color = Q16_MUL(avg, colors_per_val_log) >> 16;
+ else
+ color = Q16_MUL(avg, colors_per_val_lin) >> 16;
+ if(color >= COLORS) /* TODO same as _vertical */
+ color = COLORS-1;
+ else if (color < 0)
+ color = 0;
+
+#else
+ if(graph_settings.logarithmic)
+ color = Q16_MUL(avg, grey_vals_per_val_log) >> 16;
+ else
+ color = Q16_MUL(avg, grey_vals_per_val_lin) >> 16;
+ if(color > 255)
+ color = 255;
+ else if (color < 0)
+ color = 0;
+#endif
+
+#ifdef HAVE_LCD_COLOR
+ rb->lcd_set_foreground(fft_colors[color]);
+ rb->lcd_drawpixel(x, graph_settings.spectrogram.row);
+#else
+ grey_set_foreground(255 - color);
+ grey_drawpixel(x, graph_settings.spectrogram.row);
+#endif
+
+ x++;
+
+ avg = 0;
+ count = 0;
+ }
+ if(x >= LCD_WIDTH)
+ break;
+ }
+ if(graph_settings.spectrogram.row != LCD_HEIGHT-1)
+ graph_settings.spectrogram.row++;
+ else
+#ifdef HAVE_LCD_COLOR
+ xlcd_scroll_up(1);
+#else
+ grey_scroll_up(1);
+#endif
+}
+
+/********************* End of plotting functions (modes) *********************/
+
+static long thread_stack[DEFAULT_STACK_SIZE/sizeof(long)];
+void input_thread_entry(void)
+{
+ kiss_fft_scalar * value;
+ kiss_fft_scalar left;
+ int count;
+ int idx = 0; /* offset in the buffer */
+ int fft_idx = 0; /* offset in input */
+ while(true)
+ {
+ rb->mutex_lock(&input_mutex);
+ if(!input_thread_run)
+ rb->thread_exit();
+
+ value = (kiss_fft_scalar*) rb->pcm_get_peak_buffer(&count);
+
+ if (value == 0 || count == 0)
+ {
+ rb->mutex_unlock(&input_mutex);
+ rb->yield();
+ continue;
+ /* This block can introduce discontinuities in our data. Meaning, the FFT
+ * will not be done a continuous segment of the signal. Which can be bad. Or not.
+ *
+ * Anyway, this is a demo, not a scientific tool. If you want accuracy, do a proper
+ * spectrum analysis.*/
+ }
+ else
+ {
+ idx = fft_idx = 0;
+ do
+ {
+ left = *(value + idx);
+ idx += 2;
+
+ input[fft_idx] = left;
+ fft_idx++;
+
+ if (fft_idx == ARRAYSIZE_IN)
+ break;
+ } while (idx < count);
+ }
+ if(fft_idx == ARRAYSIZE_IN) /* there are cases when we don't have enough data to fill the buffer */
+ input_thread_has_data = true;
+
+ rb->mutex_unlock(&input_mutex);
+ rb->yield();
+ }
+}
+
+
+enum plugin_status plugin_start(const void* parameter)
+{
+ (void) parameter;
+ if ((rb->audio_status() & AUDIO_STATUS_PLAY) == 0)
+ {
+ rb->splash(HZ * 2, "No track playing. Exiting..");
+ return PLUGIN_OK;
+ }
+#ifndef HAVE_LCD_COLOR
+ unsigned char *gbuf;
+ size_t gbuf_size = 0;
+ /* get the remainder of the plugin buffer */
+ gbuf = (unsigned char *) rb->plugin_get_buffer(&gbuf_size);
+
+ /* initialize the greyscale buffer.*/
+ if (!grey_init(gbuf, gbuf_size, GREY_ON_COP | GREY_BUFFERED,
+ LCD_WIDTH, LCD_HEIGHT, NULL))
+ {
+ rb->splash(HZ, "Couldn't init greyscale display");
+ return PLUGIN_ERROR;
+ }
+ grey_show(true);
+#endif
+
+#if LCD_DEPTH > 1
+ rb->lcd_set_backdrop(NULL);
+#endif
+ backlight_force_on();
+
+#ifdef HAVE_ADJUSTABLE_CPU_FREQ
+ rb->cpu_boost(true);
+#endif
+
+ /* Defaults */
+ bool run = true;
+ graph_settings.mode = 0;
+ graph_settings.logarithmic = true;
+ graph_settings.orientation_vertical = true;
+ graph_settings.window_func = 0;
+ graph_settings.changed.mode = false;
+ graph_settings.changed.scale = false;
+ graph_settings.changed.orientation = false;
+ graph_settings.spectrogram.row = 0;
+ graph_settings.spectrogram.column = 0;
+
+ bool changed_window = false;
+
+ size_t size = sizeof(buffer);
+ FFT_CFG state = FFT_ALLOC(FFT_SIZE, 0, buffer, &size);
+
+ if (state == 0)
+ {
+ DEBUGF("needed data: %i", (int) size);
+ return PLUGIN_ERROR;
+ }
+
+ unsigned int input_thread = rb->create_thread(&input_thread_entry, thread_stack, sizeof(thread_stack), 0, "fft input thread" IF_PRIO(, PRIORITY_BACKGROUND) IF_COP(, CPU));
+ rb->yield();
+ while (run)
+ {
+ rb->mutex_lock(&input_mutex);
+ if(!input_thread_has_data)
+ {
+ /* Make sure the input thread has started before doing anything else */
+ rb->mutex_unlock(&input_mutex);
+ rb->yield();
+ continue;
+ }
+ apply_window_func(graph_settings.window_func);
+ FFT_FFT(state, input, output);
+
+ if(changed_window)
+ {
+ draw(window_text[graph_settings.window_func]);
+ changed_window = false;
+ }
+ else
+ draw(0);
+
+ input_thread_has_data = false;
+ rb->mutex_unlock(&input_mutex);
+ rb->yield();
+
+ int button = rb->button_get(false);
+ switch (button)
+ {
+ case FFT_QUIT:
+ run = false;
+ break;
+ case FFT_PREV_GRAPH: {
+ graph_settings.mode--;
+ if (graph_settings.mode < 0)
+ graph_settings.mode = MODES_COUNT-1;
+ draw(modes_text[graph_settings.mode]);
+ break;
+ }
+ case FFT_NEXT_GRAPH: {
+ graph_settings.mode++;
+ if (graph_settings.mode >= MODES_COUNT)
+ graph_settings.mode = 0;
+ draw(modes_text[graph_settings.mode]);
+ break;
+ }
+ case FFT_WINDOW: {
+ changed_window = true;
+ graph_settings.window_func ++;
+ if(graph_settings.window_func >= WINDOW_COUNT)
+ graph_settings.window_func = 0;
+ break;
+ }
+ case FFT_SCALE: {
+ graph_settings.logarithmic = !graph_settings.logarithmic;
+ draw(scales_text[graph_settings.logarithmic ? 1 : 0]);
+ break;
+ }
+ case FFT_ORIENTATION: {
+ graph_settings.orientation_vertical = !graph_settings.orientation_vertical;
+ draw(0);
+ break;
+ }
+ default: {
+ if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
+ return PLUGIN_USB_CONNECTED;
+ }
+
+ }
+ }
+
+ /* Handle our input thread. We haven't yield()'d since our last mutex_unlock, so we know we have the mutex */
+ rb->mutex_lock(&input_mutex);
+ input_thread_run = false;
+ rb->mutex_unlock(&input_mutex);
+ rb->thread_wait(input_thread);
+
+#ifdef HAVE_ADJUSTABLE_CPU_FREQ
+ rb->cpu_boost(false);
+#endif
+#ifndef HAVE_LCD_COLOR
+ grey_release();
+#endif
+ backlight_use_settings();
+ return PLUGIN_OK;
+}