rockbox/apps/plugins/fft/fft.c
William Wilgus 7100090f99 Remove NVRAM infavor of a separate cfg file
remove nvram and use the existing settings framework for it

add a crc check to the user_settings data to see if we need to save
the user setting file or if we can just save the status file (resume.cfg)

move volume to the system_status struct so we don't write the whole settings file
over volume changes

allow user to still export volume with save sound settings

allow the user to also export pitch and speed

name the file .resume.cfg

Rename all the SYSTEM_STATUS save file variables to TLAs to save space and
discourage tinkering

Cleanup DEBUG_AVAIL_SETTINGS output

when saving user_settings it calls status_save as well this cause the resume
file to be written twice. instead remove the callback for status_save
when setting_save is called

remove header text when saving .resume.cfg

convert status_save() to status_save(bool force)
add SYSTEM_STATUS_UPDATE_TICKS

for ATA device set this to 5 minutes
since we arlready wait for the disk to be up before saving
we don't want to miss our window

for all other every 15 minutes

that way if the battery is too low by the time shutdown comes around you
don't lose much progress

Change-Id: I27214ffd6e5d5494ee5ca83b14f04a41ba426ad7
2025-01-21 00:04:32 -05:00

1362 lines
35 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
#if defined(SIMULATOR)
// Use the one in pluginlib/osd
extern GREY_INFO_STRUCT
#else
GREY_INFO_STRUCT
#endif
#endif
#include "lib/pluginlib_actions.h"
/* this set the context to use with PLA */
static const struct button_mapping *plugin_contexts[] = { pla_main_ctx };
#define FFT_PREV_GRAPH PLA_LEFT
#define FFT_NEXT_GRAPH PLA_RIGHT
#define FFT_ORIENTATION PLA_CANCEL
#define FFT_WINDOW PLA_SELECT_REL
#define FFT_AMP_SCALE PLA_SELECT_REPEAT
#define FFT_AMP_SCALE_PRE PLA_SELECT
#define FFT_FREQ_SCALE PLA_DOWN
#if (CONFIG_KEYPAD == IPOD_1G2G_PAD) \
|| (CONFIG_KEYPAD == IPOD_3G_PAD) \
|| (CONFIG_KEYPAD == IPOD_4G_PAD)
#define FFT_QUIT PLA_UP
#else
#define FFT_QUIT PLA_EXIT
#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"
/******************************* 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 = fp_sqrt(d, 0); /* linear scaling, nothing
bad should happen */
d = fp16_log(d << 16); /* the log function
expects s15.16 values */
}
}
else
{
d = fp_sqrt(d, 0); /* 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_status.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;
#if defined(FFT_AMP_SCALE_PRE)
int lastbutton = BUTTON_NONE;
#endif
while(run)
{
long delay = fft_draw();
if(delay <= 0)
{
delay = 0;
rb->yield(); /* tmo = 0 won't yield */
}
int button = pluginlib_getaction(TIMEOUT_NOBLOCK, plugin_contexts, ARRAYLEN(plugin_contexts));
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:
#ifdef FFT_AMP_SCALE_PRE
if (lastbutton != FFT_AMP_SCALE_PRE)
break;
#endif
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;
}
#if defined(FFT_AMP_SCALE_PRE)
if (button != BUTTON_NONE)
lastbutton = button;
#endif
}
return PLUGIN_OK;
(void)parameter;
}