rockbox/apps/plugins/fft/fft.c
Simon Rothen 0b5ad60c26 Introducing Targets iBasso DX50 & iBasso DX90
The port to for this two targets has been entirely developped by Ilia Sergachev (alias Il or xzcc). His source
can be found at https://bitbucket.org/isergachev/rockbox . The few necesary modifications for the DX90 port
was done by headwhacker form head-fi.org. Unfortunately i could not try out the final state of the DX90 port.
The port is hosted on android (without java) as standalone app. The official Firmware is required to run this port.
Ilia did modify the source files for the "android" target in the rockbox source to make the DX port work. The work I did
was to separate the code for DX50 (&DX90) from the android target.
On this Target Ilia used source from tinyalsa from AOSP. I did not touch that part of the code because I do not understand it.
What else I changed from Ilias sources besides the separation from the target "android":
* removed a dirty hack to keep backlight off
* changed value battery meter to voltage battery meter
* made all plugins compile (named target as "standalone") and added keymaps
* i added the graphics for the manual but did not do anything else for the manual yet
* minor optimizations

known bugs:
* timers are slowed donw when playback is active (tinyalsa related?)
* some minor bugs

Things to do:
* The main prolem will be how to install the app correctly. A guy called DOC2008 added a CWM (by androtab.info) to the
  official firmware and Ilia made a CWM installation script and a dualboot selector (rbutils/ibassoboot, build with
  ndk-build). We will have to find a way to install rockbox in a proper way without breaking any copyrights.
  Maybe ADB is an option but it is not enable with OF by default. Patching the OF is probably the way to go.
* All the wiki and manual

to build:
needed: android ndk installed, android sdk installed with additional build-tools 19.1.0 installed

./tools/configure
select iBasso DX50 or iBasso DX90
make -j apk

the content of rockbox.zip/.rockbox needs to be copied to /system/rockbox/app_rockbox/rockbox/ (rockbox app not needed)
the content of libs/armeabi to /system/rockbox/lib/ (rockbox app needed)

The boot selector is needed as /system/bin/MangoPlayer and the iBasso app as /system/bin/MangoPlayer_original. There
is also the "vold" file. The one from OF does not work with DX50 rockbox (DX90 works!?), the one from Ilia is necessary.

Until we have found a proper way to install it, it can only be installed following the instructions of Ilia on his
bitbucket page, using the CWM-OF and his installation script package.

Change-Id: Ic4faaf84824c162aabcc08e492cee6e0068719d0
Reviewed-on: http://gerrit.rockbox.org/941
Tested: Chiwen Chang <rock1104.tw@yahoo.com.tw>
Reviewed-by: Michael Giacomelli <giac2000@hotmail.com>
2014-09-18 18:19:01 +02:00

1636 lines
45 KiB
C

/***************************************************************************
* __________ __ ___.
* 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/pluginlib_exit.h"
#include "lib/configfile.h"
#include "lib/xlcd.h"
#include "math.h"
#include "fracmul.h"
#ifndef HAVE_LCD_COLOR
#include "lib/grey.h"
#endif
#include "lib/mylcd.h"
#include "lib/osd.h"
#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_AMP_SCALE BUTTON_UP
# define FFT_QUIT BUTTON_OFF
/* Need FFT_FREQ_SCALE key */
#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_AMP_SCALE BUTTON_UP
# define FFT_FREQ_SCALE BUTTON_DOWN
# 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_AMP_SCALE BUTTON_MENU
# define FFT_FREQ_SCALE BUTTON_PLAY
# 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_AMP_SCALE BUTTON_UP
# define FFT_FREQ_SCALE BUTTON_DOWN
# define FFT_QUIT BUTTON_POWER
#elif (CONFIG_KEYPAD == GIGABEAT_PAD)
# define FFT_PREV_GRAPH BUTTON_LEFT
# define FFT_NEXT_GRAPH BUTTON_RIGHT
# define FFT_AMP_SCALE BUTTON_UP
# define FFT_FREQ_SCALE BUTTON_DOWN
# 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_AMP_SCALE BUTTON_UP
# define FFT_FREQ_SCALE BUTTON_DOWN
# 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_AMP_SCALE BUTTON_UP
# define FFT_FREQ_SCALE BUTTON_DOWN
# define FFT_QUIT (BUTTON_HOME|BUTTON_REPEAT)
#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_AMP_SCALE BUTTON_SELECT
# define FFT_QUIT BUTTON_POWER
/* Need FFT_FREQ_SCALE key */
#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_AMP_SCALE BUTTON_SELECT
# define FFT_QUIT BUTTON_POWER
/* Need FFT_FREQ_SCALE key */
#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_FREQ_SCALE BUTTON_DOWN
# define FFT_WINDOW BUTTON_HOME
# define FFT_AMP_SCALE BUTTON_SELECT
# define FFT_QUIT BUTTON_POWER
/* Need FFT_FREQ_SCALE key */
#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_AMP_SCALE BUTTON_REW
# define FFT_FREQ_SCALE BUTTON_PLAY
# 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_AMP_SCALE BUTTON_UP
# define FFT_FREQ_SCALE BUTTON_DOWN
# 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_AMP_SCALE BUTTON_UP
# define FFT_FREQ_SCALE BUTTON_DOWN
# 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_AMP_SCALE BUTTON_RC_VOL_UP
# define FFT_QUIT BUTTON_RC_REC
/* Need FFT_FREQ_SCALE key */
#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 == CREATIVE_ZENXFI3_PAD
# define FFT_PREV_GRAPH BUTTON_BACK
# define FFT_NEXT_GRAPH BUTTON_MENU
# define FFT_ORIENTATION (BUTTON_PLAY | BUTTON_LEFT)
# define FFT_WINDOW (BUTTON_PLAY | BUTTON_RIGHT)
# define FFT_AMP_SCALE BUTTON_UP
# define FFT_FREQ_SCALE BUTTON_DOWN
# define FFT_QUIT (BUTTON_PLAY|BUTTON_REPEAT)
#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_AMP_SCALE BUTTON_UP
# define FFT_FREQ_SCALE BUTTON_DOWN
# 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_AMP_SCALE BUTTON_UP
# define FFT_FREQ_SCALE BUTTON_DOWN
# define FFT_QUIT BUTTON_POWER
#elif CONFIG_KEYPAD == PHILIPS_HDD6330_PAD
# define FFT_PREV_GRAPH BUTTON_PREV
# define FFT_NEXT_GRAPH BUTTON_NEXT
# define FFT_ORIENTATION BUTTON_PLAY
# define FFT_WINDOW BUTTON_MENU
# define FFT_AMP_SCALE BUTTON_UP
# define FFT_FREQ_SCALE BUTTON_DOWN
# define FFT_QUIT BUTTON_POWER
#elif CONFIG_KEYPAD == PHILIPS_SA9200_PAD
# define FFT_PREV_GRAPH BUTTON_PREV
# define FFT_NEXT_GRAPH BUTTON_NEXT
# define FFT_ORIENTATION BUTTON_PLAY
# define FFT_WINDOW BUTTON_MENU
# define FFT_AMP_SCALE BUTTON_UP
# define FFT_FREQ_SCALE BUTTON_DOWN
# define FFT_QUIT BUTTON_POWER
#elif (CONFIG_KEYPAD == SAMSUNG_YH820_PAD) || \
(CONFIG_KEYPAD == SAMSUNG_YH920_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_AMP_SCALE BUTTON_FFWD
# define FFT_QUIT BUTTON_PLAY
/* Need FFT_FREQ_SCALE key */
#elif (CONFIG_KEYPAD == MROBE500_PAD)
# define FFT_QUIT BUTTON_POWER
#elif (CONFIG_KEYPAD == ONDAVX747_PAD)
# define FFT_QUIT BUTTON_POWER
#elif (CONFIG_KEYPAD == ONDAVX777_PAD)
# define FFT_QUIT BUTTON_POWER
#elif (CONFIG_KEYPAD == PBELL_VIBE500_PAD)
# define FFT_PREV_GRAPH BUTTON_PREV
# define FFT_NEXT_GRAPH BUTTON_NEXT
# define FFT_ORIENTATION BUTTON_MENU
# define FFT_WINDOW BUTTON_OK
# define FFT_AMP_SCALE BUTTON_PLAY
# define FFT_QUIT BUTTON_REC
/* Need FFT_FREQ_SCALE key */
#elif CONFIG_KEYPAD == MPIO_HD200_PAD
# define FFT_PREV_GRAPH BUTTON_REW
# define FFT_NEXT_GRAPH BUTTON_FF
# define FFT_ORIENTATION BUTTON_REC
# define FFT_WINDOW BUTTON_FUNC
# define FFT_AMP_SCALE BUTTON_PLAY
# define FFT_QUIT (BUTTON_REC | BUTTON_PLAY)
/* Need FFT_FREQ_SCALE key */
#elif CONFIG_KEYPAD == MPIO_HD300_PAD
# define FFT_PREV_GRAPH BUTTON_REW
# define FFT_NEXT_GRAPH BUTTON_FF
# define FFT_ORIENTATION BUTTON_REC
# define FFT_WINDOW BUTTON_ENTER
# define FFT_AMP_SCALE BUTTON_PLAY
# define FFT_QUIT (BUTTON_REC | BUTTON_REPEAT)
/* Need FFT_FREQ_SCALE key */
#elif CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD
# define FFT_PREV_GRAPH BUTTON_LEFT
# define FFT_NEXT_GRAPH BUTTON_RIGHT
# define FFT_ORIENTATION BUTTON_PLAYPAUSE
# define FFT_WINDOW BUTTON_SELECT
# define FFT_AMP_SCALE BUTTON_BOTTOMLEFT
# define FFT_FREQ_SCALE BUTTON_BOTTOMRIGHT
# define FFT_QUIT BUTTON_POWER
#elif (CONFIG_KEYPAD == SANSA_CONNECT_PAD)
# define FFT_PREV_GRAPH BUTTON_LEFT
# define FFT_NEXT_GRAPH BUTTON_RIGHT
# define FFT_ORIENTATION BUTTON_SELECT
# define FFT_WINDOW BUTTON_VOL_DOWN
# define FFT_AMP_SCALE BUTTON_UP
# define FFT_FREQ_SCALE BUTTON_DOWN
# define FFT_QUIT BUTTON_POWER
#elif CONFIG_KEYPAD == SAMSUNG_YPR0_PAD
# define FFT_PREV_GRAPH BUTTON_LEFT
# define FFT_NEXT_GRAPH BUTTON_RIGHT
# define FFT_ORIENTATION BUTTON_USER
# define FFT_WINDOW BUTTON_MENU
# define FFT_AMP_SCALE BUTTON_SELECT
# define FFT_FREQ_SCALE BUTTON_DOWN
# define FFT_QUIT BUTTON_BACK
#elif (CONFIG_KEYPAD == HM60X_PAD)
# define FFT_PREV_GRAPH BUTTON_LEFT
# define FFT_NEXT_GRAPH BUTTON_RIGHT
# define FFT_AMP_SCALE BUTTON_UP
# define FFT_FREQ_SCALE BUTTON_DOWN
# define FFT_ORIENTATION BUTTON_SELECT
# define FFT_WINDOW (BUTTON_POWER|BUTTON_SELECT)
# define FFT_QUIT BUTTON_POWER
#elif (CONFIG_KEYPAD == HM801_PAD)
# define FFT_PREV_GRAPH BUTTON_LEFT
# define FFT_NEXT_GRAPH BUTTON_RIGHT
# define FFT_AMP_SCALE BUTTON_UP
# define FFT_FREQ_SCALE BUTTON_DOWN
# define FFT_ORIENTATION BUTTON_SELECT
# define FFT_WINDOW BUTTON_PLAY
# define FFT_QUIT BUTTON_POWER
#elif (CONFIG_KEYPAD == SONY_NWZ_PAD)
# define FFT_PREV_GRAPH BUTTON_LEFT
# define FFT_NEXT_GRAPH BUTTON_RIGHT
# define FFT_AMP_SCALE BUTTON_UP
# define FFT_FREQ_SCALE BUTTON_DOWN
# define FFT_QUIT BUTTON_BACK
# define FFT_ORIENTATION BUTTON_POWER
# define FFT_WINDOW BUTTON_PLAY
#elif (CONFIG_KEYPAD == CREATIVE_ZEN_PAD)
# define FFT_PREV_GRAPH BUTTON_LEFT
# define FFT_NEXT_GRAPH BUTTON_RIGHT
# define FFT_AMP_SCALE BUTTON_UP
# define FFT_FREQ_SCALE BUTTON_DOWN
# define FFT_QUIT BUTTON_BACK
# define FFT_ORIENTATION BUTTON_SELECT
# define FFT_WINDOW BUTTON_MENU
#elif (CONFIG_KEYPAD == DX50_PAD)
# define FFT_QUIT (BUTTON_POWER|BUTTON_REL)
# define FFT_PREV_GRAPH BUTTON_VOL_UP
# define FFT_NEXT_GRAPH BUTTON_VOL_DOWN
#elif !defined(HAVE_TOUCHSCREEN)
#error No keymap defined!
#endif
#ifdef HAVE_TOUCHSCREEN
#ifndef FFT_PREV_GRAPH
# define FFT_PREV_GRAPH BUTTON_MIDLEFT
#endif
#ifndef FFT_NEXT_GRAPH
# define FFT_NEXT_GRAPH BUTTON_MIDRIGHT
#endif
#ifndef FFT_ORIENTATION
# define FFT_ORIENTATION BUTTON_CENTER
#endif
#ifndef FFT_WINDOW
# define FFT_WINDOW BUTTON_TOPLEFT
#endif
#ifndef FFT_AMP_SCALE
# define FFT_AMP_SCALE BUTTON_TOPRIGHT
#endif
#ifndef FFT_QUIT
# define FFT_QUIT BUTTON_BOTTOMLEFT
#endif
/* Need FFT_FREQ_SCALE key */
#endif /* HAVE_TOUCHSCREEN */
#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"
/******************************* FFT globals *******************************/
#define LCD_SIZE MAX(LCD_WIDTH, LCD_HEIGHT)
#if (LCD_SIZE <= 511)
#define FFT_SIZE 1024 /* 512*2 */
#elif (LCD_SIZE <= 1023)
#define FFT_SIZE 2048 /* 1024*2 */
#else
#define FFT_SIZE 4096 /* 2048*2 */
#endif
#define ARRAYLEN_IN (FFT_SIZE)
#define ARRAYLEN_OUT (FFT_SIZE)
#define ARRAYLEN_PLOT (FFT_SIZE/2-1) /* FFT is symmetric, ignore DC */
#define BUFSIZE_FFT (sizeof(struct kiss_fft_state)+\
sizeof(kiss_fft_cpx)*(FFT_SIZE-1))
#define __COEFF(type,size) type##_##size
#define _COEFF(x, y) __COEFF(x,y) /* force CPP evaluation of FFT_SIZE */
#define HANN_COEFF _COEFF(hann, FFT_SIZE)
#define HAMMING_COEFF _COEFF(hamming, FFT_SIZE)
/* cacheline-aligned buffers with COP, otherwise word-aligned */
/* CPU/COP only applies when compiled for more than one core */
#define CACHEALIGN_UP_SIZE(type, len) \
(CACHEALIGN_UP((len)*sizeof(type) + (sizeof(type)-1)) / sizeof(type))
/* Shared */
/* COP + CPU PCM */
static kiss_fft_cpx input[CACHEALIGN_UP_SIZE(kiss_fft_scalar, ARRAYLEN_IN)]
CACHEALIGN_AT_LEAST_ATTR(4);
/* CPU+COP */
#if NUM_CORES > 1
/* Output queue indexes */
static volatile int output_head SHAREDBSS_ATTR = 0;
static volatile int output_tail SHAREDBSS_ATTR = 0;
/* The result is nfft/2 complex frequency bins from DC to Nyquist. */
static kiss_fft_cpx output[2][CACHEALIGN_UP_SIZE(kiss_fft_cpx, ARRAYLEN_OUT)]
SHAREDBSS_ATTR;
#else
/* Only one output buffer */
#define output_head 0
#define output_tail 0
/* The result is nfft/2 complex frequency bins from DC to Nyquist. */
static kiss_fft_cpx output[1][ARRAYLEN_OUT];
#endif
/* Unshared */
/* COP */
static kiss_fft_cfg fft_state SHAREDBSS_ATTR;
static char fft_buffer[CACHEALIGN_UP_SIZE(char, BUFSIZE_FFT)]
CACHEALIGN_AT_LEAST_ATTR(4);
/* CPU */
static uint32_t linf_magnitudes[ARRAYLEN_PLOT]; /* ling freq bin plot */
static uint32_t logf_magnitudes[ARRAYLEN_PLOT]; /* log freq plot output */
static uint32_t *plot; /* use this to plot */
static struct
{
int16_t bin; /* integer bin number */
uint16_t frac; /* interpolation fraction */
} binlog[ARRAYLEN_PLOT] __attribute__((aligned(4)));
/**************************** End of FFT globals ***************************/
/********************************* Settings ********************************/
enum fft_orientation
{
FFT_MIN_OR = 0,
FFT_OR_VERT = 0, /* Amplitude vertical, frequency horizontal * */
FFT_OR_HORZ, /* Amplitude horizontal, frequency vertical */
FFT_MAX_OR,
};
enum fft_display_mode
{
FFT_MIN_DM = 0,
FFT_DM_LINES = 0, /* Bands are displayed as single-pixel lines * */
FFT_DM_BARS, /* Bands are combined into wide bars */
FFT_DM_SPECTROGRAM, /* Band amplitudes are denoted by color */
FFT_MAX_DM,
};
enum fft_amp_scale
{
FFT_MIN_AS = 0,
FFT_AS_LOG = 0, /* Amplitude is plotted on log scale * */
FFT_AS_LIN, /* Amplitude is plotted on linear scale */
FFT_MAX_AS,
};
enum fft_freq_scale
{
FFT_MIN_FS = 0,
FFT_FS_LOG = 0, /* Frequency is plotted on log scale * */
FFT_FS_LIN, /* Frequency is plotted on linear scale */
FFT_MAX_FS
};
enum fft_window_func
{
FFT_MIN_WF = 0,
FFT_WF_HAMMING = 0, /* Hamming window applied to each input frame * */
FFT_WF_HANN, /* Hann window applied to each input frame */
FFT_MAX_WF,
};
static struct fft_config
{
int orientation;
int drawmode;
int amp_scale;
int freq_scale;
int window_func;
} fft_disk =
{
/* Defaults */
.orientation = FFT_OR_VERT,
.drawmode = FFT_DM_LINES,
.amp_scale = FFT_AS_LOG,
.freq_scale = FFT_FS_LOG,
.window_func = FFT_WF_HAMMING,
};
#define CFGFILE_VERSION 0
#define CFGFILE_MINVERSION 0
static const char cfg_filename[] = "fft.cfg";
static struct configdata disk_config[] =
{
{ TYPE_ENUM, FFT_MIN_OR, FFT_MAX_OR,
{ .int_p = &fft_disk.orientation }, "orientation",
(char * []){ [FFT_OR_VERT] = "vertical",
[FFT_OR_HORZ] = "horizontal" } },
{ TYPE_ENUM, FFT_MIN_DM, FFT_MAX_DM,
{ .int_p = &fft_disk.drawmode }, "drawmode",
(char * []){ [FFT_DM_LINES] = "lines",
[FFT_DM_BARS] = "bars",
[FFT_DM_SPECTROGRAM] = "spectrogram" } },
{ TYPE_ENUM, FFT_MIN_AS, FFT_MAX_AS,
{ .int_p = &fft_disk.amp_scale }, "amp scale",
(char * []){ [FFT_AS_LOG] = "logarithmic",
[FFT_AS_LIN] = "linear" } },
{ TYPE_ENUM, FFT_MIN_FS, FFT_MAX_FS,
{ .int_p = &fft_disk.freq_scale }, "freq scale",
(char * []){ [FFT_FS_LOG] = "logarithmic",
[FFT_FS_LIN] = "linear" } },
{ TYPE_ENUM, FFT_MIN_WF, FFT_MAX_WF,
{ .int_p = &fft_disk.window_func }, "window function",
(char * []){ [FFT_WF_HAMMING] = "hamming",
[FFT_WF_HANN] = "hann" } },
};
/* Hint flags for setting changes */
enum fft_setting_flags
{
FFT_SETF_OR = 1 << 0,
FFT_SETF_DM = 1 << 1,
FFT_SETF_AS = 1 << 2,
FFT_SETF_FS = 1 << 3,
FFT_SETF_WF = 1 << 4,
FFT_SETF_ALL = 0x1f
};
/***************************** End of settings *****************************/
/**************************** Operational data *****************************/
#define COLOR_DEFAULT_FG MYLCD_DEFAULT_FG
#define COLOR_DEFAULT_BG MYLCD_DEFAULT_BG
#ifdef HAVE_LCD_COLOR
#define COLOR_MESSAGE_FRAME LCD_RGBPACK(0xc6, 0x00, 0x00)
#define COLOR_MESSAGE_BG LCD_BLACK
#define COLOR_MESSAGE_FG LCD_WHITE
#else
#define COLOR_MESSAGE_FRAME GREY_DARKGRAY
#define COLOR_MESSAGE_BG GREY_WHITE
#define COLOR_MESSAGE_FG GREY_BLACK
#endif
#define FFT_OSD_MARGIN_SIZE 1
#define FFT_PERIOD (HZ/50) /* How fast to try to go */
/* Based on feeding-in a 0db sinewave at FS/4 */
#define QLOG_MAX 0x0009154B
/* Fudge it a little or it's not very visbile */
#define QLIN_MAX (0x00002266 >> 1)
static struct fft_config fft;
typedef void (* fft_drawfn_t)(unsigned, unsigned);
static fft_drawfn_t fft_drawfn = NULL; /* plotting function */
static int fft_spectrogram_pos = -1; /* row or column - only used by one at a time */
static uint32_t fft_graph_scale = 0; /* max level over time, for scaling display */
static int fft_message_id = -1; /* current message id displayed */
static char fft_osd_message[32]; /* current message string displayed */
static long fft_next_frame_tick = 0; /* next tick to attempt drawing */
#ifdef HAVE_LCD_COLOR
#define SHADES BMPWIDTH_fft_colors
#define SPECTROGRAPH_PALETTE(index) (fft_colors[index])
#else
#define SHADES 256
#define SPECTROGRAPH_PALETTE(index) (255 - (index))
#endif
/************************* End of operational data *************************/
/***************************** Math functions ******************************/
/* Apply window function to input */
static void apply_window_func(enum fft_window_func mode)
{
static const int16_t * const coefs[] =
{
[FFT_WF_HAMMING] = HAMMING_COEFF,
[FFT_WF_HANN] = HANN_COEFF,
};
const int16_t * const c = coefs[mode];
for(int i = 0; i < ARRAYLEN_IN; ++i)
input[i].r = (input[i].r * c[i] + 16384) >> 15;
}
/* Calculates the magnitudes from complex numbers and returns the maximum */
static unsigned calc_magnitudes(enum fft_amp_scale scale)
{
/* A major assumption made when calculating the Q*MAX constants
* is that the maximum magnitude is 29 bits long. */
unsigned this_max = 0;
kiss_fft_cpx *this_output = output[output_head] + 1; /* skip DC */
/* Calculate the magnitude, discarding the phase. */
for(int i = 0; i < ARRAYLEN_PLOT; ++i)
{
int32_t re = this_output[i].r;
int32_t im = this_output[i].i;
uint32_t d = re*re + im*im;
if(d > 0)
{
if(d > 0x7FFFFFFF) /* clip */
{
d = 0x7FFFFFFF; /* if our assumptions are correct,
this should never happen. It's just
a safeguard. */
}
if(scale == FFT_AS_LOG)
{
if(d < 0x8000) /* be more precise */
{
/* ln(x ^ .5) = .5*ln(x) */
d = fp16_log(d << 16) >> 1;
}
else
{
d = isqrt(d); /* linear scaling, nothing
bad should happen */
d = fp16_log(d << 16); /* the log function
expects s15.16 values */
}
}
else
{
d = isqrt(d); /* linear scaling, nothing
bad should happen */
}
}
/* Length 2 moving average - last transform and this one */
linf_magnitudes[i] = (linf_magnitudes[i] + d) >> 1;
if(d > this_max)
this_max = d;
}
return this_max;
}
/* Move plot bins into a logarithmic scale by sliding them towards the
* Nyquist bin according to the translation in the binlog array. */
static void log_plot_translate(void)
{
for(int i = ARRAYLEN_PLOT-1; i > 0; --i)
{
int s = binlog[i].bin;
int e = binlog[i-1].bin;
unsigned frac = binlog[i].frac;
int bin = linf_magnitudes[s];
if(frac)
{
/* slope < 1, Interpolate stretched bins (linear for now) */
int diff = linf_magnitudes[s+1] - bin;
do
{
logf_magnitudes[i] = bin + FRACMUL(frac << 15, diff);
frac = binlog[--i].frac;
}
while(frac);
}
else
{
/* slope > 1, Find peak of two or more bins */
while(--s > e)
{
int val = linf_magnitudes[s];
if (val > bin)
bin = val;
}
}
logf_magnitudes[i] = bin;
}
}
/* Calculates the translation for logarithmic plot bins */
static void logarithmic_plot_init(void)
{
/*
* log: y = round(n * ln(x) / ln(n))
* anti: y = round(exp(x * ln(n) / n))
*/
int j = fp16_log((ARRAYLEN_PLOT - 1) << 16);
for(int i = 0; i < ARRAYLEN_PLOT; ++i)
{
binlog[i].bin = (fp16_exp(i * j / (ARRAYLEN_PLOT - 1)) + 32768) >> 16;
}
/* setup fractions for interpolation of stretched bins */
for(int i = 0; i < ARRAYLEN_PLOT-1; i = j)
{
j = i + 1;
/* stop when we have two different values */
while(binlog[j].bin == binlog[i].bin)
j++; /* if here, local slope of curve is < 1 */
if(j > i + 1)
{
/* distribute pieces evenly over stretched interval */
int diff = j - i;
int x = 0;
do
{
binlog[i].frac = (x++ << 16) / diff;
}
while(++i < j);
}
}
}
/************************** End of math functions **************************/
/*********************** Plotting functions (modes) ************************/
static void draw_lines_vertical(unsigned this_max, unsigned graph_max)
{
#if LCD_WIDTH < ARRAYLEN_PLOT /* graph compression */
const int offset = 0;
const int plotwidth = LCD_WIDTH;
#else
const int offset = (LCD_HEIGHT - ARRAYLEN_PLOT) / 2;
const int plotwidth = ARRAYLEN_PLOT;
#endif
mylcd_clear_display();
if(this_max == 0)
{
mylcd_hline(0, LCD_WIDTH - 1, LCD_HEIGHT - 1); /* Draw all "zero" */
return;
}
/* take the maximum of neighboring bins if we have to scale down the
* graph horizontally */
if(LCD_WIDTH < ARRAYLEN_PLOT) /* graph compression */
{
int bins_acc = LCD_WIDTH / 2;
unsigned bins_max = 0;
for(int i = 0, x = 0; i < ARRAYLEN_PLOT; ++i)
{
unsigned bin = plot[i];
if(bin > bins_max)
bins_max = bin;
bins_acc += LCD_WIDTH;
if(bins_acc >= ARRAYLEN_PLOT)
{
int h = LCD_HEIGHT*bins_max / graph_max;
mylcd_vline(x, LCD_HEIGHT - h, LCD_HEIGHT-1);
x++;
bins_acc -= ARRAYLEN_PLOT;
bins_max = 0;
}
}
}
else
{
for(int i = 0; i < plotwidth; ++i)
{
int h = LCD_HEIGHT*plot[i] / graph_max;
mylcd_vline(i + offset, LCD_HEIGHT - h, LCD_HEIGHT-1);
}
}
}
static void draw_lines_horizontal(unsigned this_max, unsigned graph_max)
{
#if LCD_WIDTH < ARRAYLEN_PLOT /* graph compression */
const int offset = 0;
const int plotwidth = LCD_HEIGHT;
#else
const int offset = (LCD_HEIGHT - ARRAYLEN_PLOT) / 2;
const int plotwidth = ARRAYLEN_PLOT;
#endif
mylcd_clear_display();
if(this_max == 0)
{
mylcd_vline(0, 0, LCD_HEIGHT-1); /* Draw all "zero" */
return;
}
/* take the maximum of neighboring bins if we have to scale the graph
* horizontally */
if(LCD_HEIGHT < ARRAYLEN_PLOT) /* graph compression */
{
int bins_acc = LCD_HEIGHT / 2;
unsigned bins_max = 0;
for(int i = 0, y = 0; i < ARRAYLEN_PLOT; ++i)
{
unsigned bin = plot[i];
if(bin > bins_max)
bins_max = bin;
bins_acc += LCD_HEIGHT;
if(bins_acc >= ARRAYLEN_PLOT)
{
int w = LCD_WIDTH*bins_max / graph_max;
mylcd_hline(0, w - 1, y);
y++;
bins_acc -= ARRAYLEN_PLOT;
bins_max = 0;
}
}
}
else
{
for(int i = 0; i < plotwidth; ++i)
{
int w = LCD_WIDTH*plot[i] / graph_max;
mylcd_hline(0, w - 1, i + offset);
}
}
}
static void draw_bars_vertical(unsigned this_max, unsigned graph_max)
{
#if LCD_WIDTH < LCD_HEIGHT
const int bars = 15;
#else
const int bars = 20;
#endif
const int border = 2;
const int barwidth = LCD_WIDTH / (bars + border);
const int width = barwidth - border;
const int offset = (LCD_WIDTH - bars*barwidth + border) / 2;
mylcd_clear_display();
mylcd_hline(0, LCD_WIDTH-1, LCD_HEIGHT-1); /* Draw baseline */
if(this_max == 0)
return; /* nothing more to draw */
int bins_acc = bars / 2;
unsigned bins_max = 0;
for(int i = 0, x = offset;; ++i)
{
unsigned bin = plot[i];
if(bin > bins_max)
bins_max = bin;
bins_acc += bars;
if(bins_acc >= ARRAYLEN_PLOT)
{
int h = LCD_HEIGHT*bins_max / graph_max;
mylcd_fillrect(x, LCD_HEIGHT - h, width, h - 1);
if(i >= ARRAYLEN_PLOT-1)
break;
x += barwidth;
bins_acc -= ARRAYLEN_PLOT;
bins_max = 0;
}
}
}
static void draw_bars_horizontal(unsigned this_max, unsigned graph_max)
{
#if LCD_WIDTH < LCD_HEIGHT
const int bars = 20;
#else
const int bars = 15;
#endif
const int border = 2;
const int barwidth = LCD_HEIGHT / (bars + border);
const int height = barwidth - border;
const int offset = (LCD_HEIGHT - bars*barwidth + border) / 2;
mylcd_clear_display();
mylcd_vline(0, 0, LCD_HEIGHT-1); /* Draw baseline */
if(this_max == 0)
return; /* nothing more to draw */
int bins_acc = bars / 2;
unsigned bins_max = 0;
for(int i = 0, y = offset;; ++i)
{
unsigned bin = plot[i];
if(bin > bins_max)
bins_max = bin;
bins_acc += bars;
if(bins_acc >= ARRAYLEN_PLOT)
{
int w = LCD_WIDTH*bins_max / graph_max;
mylcd_fillrect(1, y, w, height);
if(i >= ARRAYLEN_PLOT-1)
break;
y += barwidth;
bins_acc -= ARRAYLEN_PLOT;
bins_max = 0;
}
}
}
static void draw_spectrogram_vertical(unsigned this_max, unsigned graph_max)
{
const int scale_factor = MIN(LCD_HEIGHT, ARRAYLEN_PLOT);
if(fft_spectrogram_pos < LCD_WIDTH-1)
fft_spectrogram_pos++;
else
mylcd_scroll_left(1);
int bins_acc = scale_factor / 2;
unsigned bins_max = 0;
for(int i = 0, y = LCD_HEIGHT-1;; ++i)
{
unsigned bin = plot[i];
if(bin > bins_max)
bins_max = bin;
bins_acc += scale_factor;
if(bins_acc >= ARRAYLEN_PLOT)
{
unsigned index = (SHADES-1)*bins_max / graph_max;
unsigned color;
/* These happen because we exaggerate the graph a little for
* linear mode */
if(index >= SHADES)
index = SHADES-1;
color = FB_UNPACK_SCALAR_LCD(SPECTROGRAPH_PALETTE(index));
mylcd_set_foreground(color);
mylcd_drawpixel(fft_spectrogram_pos, y);
if(--y < 0)
break;
bins_acc -= ARRAYLEN_PLOT;
bins_max = 0;
}
}
(void)this_max;
}
static void draw_spectrogram_horizontal(unsigned this_max, unsigned graph_max)
{
const int scale_factor = MIN(LCD_WIDTH, ARRAYLEN_PLOT);
if(fft_spectrogram_pos < LCD_HEIGHT-1)
fft_spectrogram_pos++;
else
mylcd_scroll_up(1);
int bins_acc = scale_factor / 2;
unsigned bins_max = 0;
for(int i = 0, x = 0;; ++i)
{
unsigned bin = plot[i];
if(bin > bins_max)
bins_max = bin;
bins_acc += scale_factor;
if(bins_acc >= ARRAYLEN_PLOT)
{
unsigned index = (SHADES-1)*bins_max / graph_max;
unsigned color;
/* These happen because we exaggerate the graph a little for
* linear mode */
if(index >= SHADES)
index = SHADES-1;
color = FB_UNPACK_SCALAR_LCD(SPECTROGRAPH_PALETTE(index));
mylcd_set_foreground(color);
mylcd_drawpixel(x, fft_spectrogram_pos);
if(++x >= LCD_WIDTH)
break;
bins_acc -= ARRAYLEN_PLOT;
bins_max = 0;
}
}
(void)this_max;
}
/******************** End of plotting functions (modes) ********************/
/***************************** FFT functions *******************************/
static bool is_playing(void)
{
return rb->mixer_channel_status(PCM_MIXER_CHAN_PLAYBACK) == CHANNEL_PLAYING;
}
/** functions use in single/multi configuration **/
static inline bool fft_init_fft_lib(void)
{
size_t size = sizeof(fft_buffer);
fft_state = kiss_fft_alloc(FFT_SIZE, 0, fft_buffer, &size);
if(fft_state == NULL)
{
DEBUGF("needed data: %i", (int) size);
return false;
}
return true;
}
static inline bool fft_get_fft(void)
{
int count;
const int16_t *value =
rb->mixer_channel_get_buffer(PCM_MIXER_CHAN_PLAYBACK, &count);
/* 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.*/
/* there are cases when we don't have enough data to fill the buffer */
if(count != ARRAYLEN_IN)
{
if(count < ARRAYLEN_IN)
return false;
count = ARRAYLEN_IN; /* too much - limit */
}
int fft_idx = 0; /* offset in 'input' */
do
{
kiss_fft_scalar left = *value++;
kiss_fft_scalar right = *value++;
input[fft_idx].r = (left + right) >> 1; /* to mono */
} while (fft_idx++, --count > 0);
apply_window_func(fft.window_func);
rb->yield();
kiss_fft(fft_state, input, output[output_tail]);
rb->yield();
return true;
}
#if NUM_CORES > 1
/* use a worker thread if there is another processor core */
static volatile bool fft_thread_run SHAREDDATA_ATTR = false;
static unsigned long fft_thread = 0;
static long fft_thread_stack[CACHEALIGN_UP(DEFAULT_STACK_SIZE*4/sizeof(long))]
CACHEALIGN_AT_LEAST_ATTR(4);
static void fft_thread_entry(void)
{
if(!fft_init_fft_lib())
{
output_tail = -1; /* tell that we bailed */
fft_thread_run = true;
return;
}
fft_thread_run = true;
while(fft_thread_run)
{
if (!is_playing())
{
rb->sleep(HZ/5);
continue;
}
if (!fft_get_fft())
{
rb->sleep(0); /* not enough - ease up */
continue;
}
/* write back output for other processor and invalidate for next
frame read */
rb->commit_discard_dcache();
int new_tail = output_tail ^ 1;
/* if full, block waiting until reader has freed a slot */
while(fft_thread_run)
{
if(new_tail != output_head)
{
output_tail = new_tail;
break;
}
rb->sleep(0);
}
}
}
static bool fft_have_fft(void)
{
return output_head != output_tail;
}
/* Call only after fft_have_fft() has returned true */
static inline void fft_free_fft_output(void)
{
output_head ^= 1; /* finished with this */
}
static bool fft_init_fft(void)
{
/* create worker thread - on the COP for dual-core targets */
fft_thread = rb->create_thread(fft_thread_entry,
fft_thread_stack, sizeof(fft_thread_stack), 0, "fft output thread"
IF_PRIO(, PRIORITY_USER_INTERFACE+1) IF_COP(, COP));
if(fft_thread == 0)
{
rb->splash(HZ, "FFT thread failed create");
return false;
}
/* wait for it to indicate 'ready' */
while(fft_thread_run == false)
rb->sleep(0);
if(output_tail == -1)
{
/* FFT thread bailed-out like The Fed */
rb->thread_wait(fft_thread);
rb->splash(HZ, "FFT thread failed to init");
return false;
}
return true;
}
static void fft_close_fft(void)
{
/* Handle our FFT thread. */
fft_thread_run = false;
rb->thread_wait(fft_thread);
rb->commit_discard_dcache();
}
#else /* NUM_CORES == 1 */
/* everything serialize on single-core and FFT gets to use IRAM main stack if
* target uses IRAM */
static bool fft_have_fft(void)
{
return is_playing() && fft_get_fft();
}
static inline void fft_free_fft_output(void)
{
/* nothing to do */
}
static bool fft_init_fft(void)
{
return fft_init_fft_lib();
}
static inline void fft_close_fft(void)
{
/* nothing to do */
}
#endif /* NUM_CORES */
/************************** End of FFT functions ***************************/
/****************************** OSD functions ******************************/
/* Format a message to display */
static void fft_osd_format_message(enum fft_setting_flags id)
{
const char *msg = "";
switch (id)
{
case FFT_SETF_DM:
msg = (const char * [FFT_MAX_DM]) {
[FFT_DM_LINES] = "Lines",
[FFT_DM_BARS] = "Bars",
[FFT_DM_SPECTROGRAM] = "Spectrogram",
}[fft.drawmode];
break;
case FFT_SETF_WF:
msg = (const char * [FFT_MAX_WF]) {
[FFT_WF_HAMMING] = "Hamming window",
[FFT_WF_HANN] = "Hann window",
}[fft.window_func];
break;
case FFT_SETF_AS:
msg = (const char * [FFT_MAX_AS]) {
[FFT_AS_LOG] = "Logarithmic amplitude",
[FFT_AS_LIN] = "Linear amplitude"
}[fft.amp_scale];
break;
case FFT_SETF_FS:
msg = (const char * [FFT_MAX_FS]) {
[FFT_FS_LOG] = "Logarithmic frequency",
[FFT_FS_LIN] = "Linear frequency",
}[fft.freq_scale];
break;
case FFT_SETF_OR:
rb->snprintf(fft_osd_message, sizeof (fft_osd_message),
(const char * [FFT_MAX_OR]) {
[FFT_OR_VERT] = "Vertical %s",
[FFT_OR_HORZ] = "Horizontal %s",
}[fft.orientation],
(const char * [FFT_MAX_DM]) {
[FFT_DM_LINES ... FFT_DM_BARS] = "amplitude",
[FFT_DM_SPECTROGRAM] = "frequency"
}[fft.drawmode]);
return;
#if 0
/* Pertentially */
case FFT_SETF_VOLUME:
rb->snprintf(fft_osd_message, sizeof (fft_osd_message),
"Volume: %d%s",
rb->sound_val2phys(SOUND_VOLUME, global_settings.volume),
rb->sound_unit(SOUND_VOLUME));
return;
#endif
default:
break;
}
/* Default action: copy string */
rb->strlcpy(fft_osd_message, msg, sizeof (fft_osd_message));
}
static void fft_osd_draw_cb(int x, int y, int width, int height)
{
#if LCD_DEPTH > 1
mylcd_set_foreground(COLOR_MESSAGE_FG);
mylcd_set_background(COLOR_MESSAGE_BG);
#endif
#if FFT_OSD_MARGIN_SIZE != 0
mylcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
mylcd_fillrect(1, 1, width - 2, height - 2);
mylcd_set_drawmode(DRMODE_SOLID);
#endif
mylcd_putsxy(1+FFT_OSD_MARGIN_SIZE, 1+FFT_OSD_MARGIN_SIZE,
fft_osd_message);
#if LCD_DEPTH > 1
mylcd_set_foreground(COLOR_MESSAGE_FRAME);
#endif
mylcd_drawrect(0, 0, width, height);
(void)x; (void)y;
}
static void fft_osd_show_message(enum fft_setting_flags id)
{
fft_osd_format_message(id);
if(!myosd_enabled())
return;
int width, height;
int maxwidth, maxheight;
mylcd_set_viewport(myosd_get_viewport());
myosd_get_max_dims(&maxwidth, &maxheight);
mylcd_setfont(FONT_UI);
mylcd_getstringsize(fft_osd_message, &width, &height);
mylcd_set_viewport(NULL);
width += 2 + 2*FFT_OSD_MARGIN_SIZE;
if(width > maxwidth)
width = maxwidth;
height += 2 + 2*FFT_OSD_MARGIN_SIZE;
if(height > maxheight)
height = maxheight;
bool drawn = myosd_update_pos((LCD_WIDTH - width) / 2,
(LCD_HEIGHT - height) / 2,
width, height);
myosd_show(OSD_SHOW | (drawn ? 0 : OSD_UPDATENOW));
}
static void fft_popupmsg(enum fft_setting_flags id)
{
fft_message_id = id;
}
/************************** End of OSD functions ***************************/
static void fft_setting_update(unsigned which)
{
static fft_drawfn_t fft_drawfns[FFT_MAX_DM][FFT_MAX_OR] =
{
[FFT_DM_LINES] =
{
[FFT_OR_HORZ] = draw_lines_horizontal,
[FFT_OR_VERT] = draw_lines_vertical,
},
[FFT_DM_BARS] =
{
[FFT_OR_HORZ] = draw_bars_horizontal,
[FFT_OR_VERT] = draw_bars_vertical,
},
[FFT_DM_SPECTROGRAM] =
{
[FFT_OR_HORZ] = draw_spectrogram_horizontal,
[FFT_OR_VERT] = draw_spectrogram_vertical,
},
};
if(which & (FFT_SETF_DM | FFT_SETF_OR))
{
fft_drawfn = fft_drawfns[fft.drawmode]
[fft.orientation];
if(fft.drawmode == FFT_DM_SPECTROGRAM)
{
fft_spectrogram_pos = -1;
myosd_lcd_update_prepare();
mylcd_clear_display();
myosd_lcd_update();
}
}
if(which & (FFT_SETF_DM | FFT_SETF_AS))
{
if(fft.drawmode == FFT_DM_SPECTROGRAM)
{
fft_graph_scale = fft.amp_scale == FFT_AS_LIN ?
QLIN_MAX : QLOG_MAX;
}
else
{
fft_graph_scale = 0;
}
}
if(which & FFT_SETF_FS)
{
plot = fft.freq_scale == FFT_FS_LIN ?
linf_magnitudes : logf_magnitudes;
}
if(which & FFT_SETF_AS)
{
memset(linf_magnitudes, 0, sizeof (linf_magnitudes));
memset(logf_magnitudes, 0, sizeof (logf_magnitudes));
}
}
static long fft_draw(void)
{
long tick = *rb->current_tick;
if(fft_message_id != -1)
{
/* Show a new message */
fft_osd_show_message((enum fft_setting_flags)fft_message_id);
fft_message_id = -1;
}
else
{
/* Monitor OSD timeout */
myosd_monitor_timeout();
}
if(TIME_BEFORE(tick, fft_next_frame_tick))
return fft_next_frame_tick - tick; /* Too early */
unsigned this_max;
if(!fft_have_fft())
{
if(is_playing())
return HZ/100;
/* All magnitudes == 0 thus this_max == 0 */
for(int i = 0; i < ARRAYLEN_PLOT; i++)
linf_magnitudes[i] >>= 1; /* decay */
this_max = 0;
}
else
{
this_max = calc_magnitudes(fft.amp_scale);
fft_free_fft_output(); /* COP only */
if(fft.drawmode != FFT_DM_SPECTROGRAM &&
this_max > fft_graph_scale)
{
fft_graph_scale = this_max;
}
}
if (fft.freq_scale == FFT_FS_LOG)
log_plot_translate();
myosd_lcd_update_prepare();
mylcd_set_foreground(COLOR_DEFAULT_FG);
mylcd_set_background(COLOR_DEFAULT_BG);
fft_drawfn(this_max, fft_graph_scale);
myosd_lcd_update();
fft_next_frame_tick = tick + FFT_PERIOD;
return fft_next_frame_tick - *rb->current_tick;
}
static void fft_osd_init(void *buf, size_t bufsize)
{
int width, height;
mylcd_setfont(FONT_UI);
mylcd_getstringsize("M", NULL, &height);
width = LCD_WIDTH;
height += 2 + 2*FFT_OSD_MARGIN_SIZE;
myosd_init(OSD_INIT_MAJOR_HEIGHT | OSD_INIT_MINOR_MAX, buf, bufsize,
fft_osd_draw_cb, &width, &height, NULL);
myosd_set_timeout(HZ);
}
static void fft_cleanup(void)
{
myosd_destroy();
fft_close_fft();
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
rb->cancel_cpu_boost();
#endif
#ifndef HAVE_LCD_COLOR
grey_release();
#endif
backlight_use_settings();
/* save settings if changed */
if (rb->memcmp(&fft, &fft_disk, sizeof(fft)))
{
fft_disk = fft;
configfile_save(cfg_filename, disk_config, ARRAYLEN(disk_config),
CFGFILE_VERSION);
}
}
static bool fft_setup(void)
{
atexit(fft_cleanup);
configfile_load(cfg_filename, disk_config, ARRAYLEN(disk_config),
CFGFILE_MINVERSION);
fft = fft_disk; /* copy to running config */
if(!fft_init_fft())
return false;
/* get the remainder of the plugin buffer for OSD and perhaps
greylib */
size_t bufsize = 0;
unsigned char *buf = rb->plugin_get_buffer(&bufsize);
#ifndef HAVE_LCD_COLOR
/* initialize the greyscale buffer.*/
long grey_size;
if(!grey_init(buf, bufsize, GREY_ON_COP | GREY_BUFFERED,
LCD_WIDTH, LCD_HEIGHT, &grey_size))
{
rb->splash(HZ, "Couldn't init greyscale display");
return false;
}
grey_show(true);
buf += grey_size;
bufsize -= grey_size;
#endif /* !HAVE_LCD_COLOR */
fft_osd_init(buf, bufsize);
#if LCD_DEPTH > 1
myosd_lcd_update_prepare();
rb->lcd_set_backdrop(NULL);
mylcd_clear_display();
myosd_lcd_update();
#endif
backlight_ignore_timeout();
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
rb->trigger_cpu_boost();
#endif
logarithmic_plot_init();
fft_setting_update(FFT_SETF_ALL);
fft_next_frame_tick = *rb->current_tick;
return true;
}
enum plugin_status plugin_start(const void* parameter)
{
bool run = true;
if(!fft_setup())
return PLUGIN_ERROR;
while(run)
{
long delay = fft_draw();
if(delay <= 0)
{
delay = 0;
rb->yield(); /* tmo = 0 won't yield */
}
int button = rb->button_get_w_tmo(delay);
switch (button)
{
case FFT_QUIT:
run = false;
break;
case FFT_ORIENTATION:
if (++fft.orientation >= FFT_MAX_OR)
fft.orientation = FFT_MIN_OR;
fft_setting_update(FFT_SETF_OR);
fft_popupmsg(FFT_SETF_OR);
break;
case FFT_PREV_GRAPH:
if (fft.drawmode-- <= FFT_MIN_DM)
fft.drawmode = FFT_MAX_DM-1;
fft_setting_update(FFT_SETF_DM);
fft_popupmsg(FFT_SETF_DM);
break;
case FFT_NEXT_GRAPH:
if (++fft.drawmode >= FFT_MAX_DM)
fft.drawmode = FFT_MIN_DM;
fft_setting_update(FFT_SETF_DM);
fft_popupmsg(FFT_SETF_DM);
break;
case FFT_AMP_SCALE:
if (++fft.amp_scale >= FFT_MAX_AS)
fft.amp_scale = FFT_MIN_AS;
fft_setting_update(FFT_SETF_AS);
fft_popupmsg(FFT_SETF_AS);
break;
#ifdef FFT_FREQ_SCALE /* 'Till all keymaps are defined */
case FFT_FREQ_SCALE:
if (++fft.freq_scale >= FFT_MAX_FS)
fft.freq_scale = FFT_MIN_FS;
fft_setting_update(FFT_SETF_FS);
fft_popupmsg(FFT_SETF_FS);
break;
#endif
case FFT_WINDOW:
if(++fft.window_func >= FFT_MAX_WF)
fft.window_func = FFT_MIN_WF;
fft_setting_update(FFT_SETF_WF);
fft_popupmsg(FFT_SETF_WF);
break;
default:
exit_on_usb(button);
break;
}
}
return PLUGIN_OK;
(void)parameter;
}