/*************************************************************************** * __________ __ ___. * 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 "fracmul.h" #ifndef HAVE_LCD_COLOR #include "lib/grey.h" #endif #include "lib/mylcd.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 #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 #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 #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_AMP_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_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 #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_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_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_AMP_SCALE BUTTON_FFWD # define FFT_QUIT BUTTON_PLAY #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 #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) #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) #elif CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD # define FFT_PREV_GRAPH BUTTON_LEFT # define FFT_NEXT_GRAPH BUTTON_RIGHT # define FFT_ORIENTATION BUTTON_BACK # define FFT_WINDOW BUTTON_SELECT # define FFT_AMP_SCALE BUTTON_PLAYPAUSE # 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 !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 #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" #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 the preprocessor to evaluate FFT_SIZE) */ #define HANN_COEFF _COEFF(hann, FFT_SIZE) #define HAMMING_COEFF _COEFF(hamming, FFT_SIZE) /****************************** Globals ****************************/ /* 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 int32_t plot_history[ARRAYLEN_PLOT]; static int32_t plot[ARRAYLEN_PLOT]; static struct { int16_t bin; /* integer bin number */ uint16_t frac; /* interpolation fraction */ } binlog[ARRAYLEN_PLOT] __attribute__((aligned(4))); enum fft_window_func { FFT_WF_FIRST = 0, FFT_WF_HAMMING = 0, FFT_WF_HANN, }; #define FFT_WF_COUNT (FFT_WF_HANN+1) enum fft_display_mode { FFT_DM_FIRST = 0, FFT_DM_LINES = 0, FFT_DM_BARS, FFT_DM_SPECTROGRAPH, }; #define FFT_DM_COUNT (FFT_DM_SPECTROGRAPH+1) static const unsigned char* const modes_text[FFT_DM_COUNT] = { "Lines", "Bars", "Spectrogram" }; static const unsigned char* const amp_scales_text[2] = { "Linear amplitude", "Logarithmic amplitude" }; static const unsigned char* const freq_scales_text[2] = { "Linear frequency", "Logarithmic frequency" }; static const unsigned char* const window_text[FFT_WF_COUNT] = { "Hamming window", "Hann window" }; static struct { bool orientation_vertical; enum fft_display_mode mode; bool logarithmic_amp; bool logarithmic_freq; enum fft_window_func window_func; int spectrogram_pos; /* row or column - only used by one at a time */ union { struct { bool orientation : 1; bool mode : 1; bool amp_scale : 1; bool freq_scale : 1; bool window_func : 1; bool do_clear : 1; }; bool clear_all; /* Write 'false' to clear all above */ } changed; } graph_settings SHAREDDATA_ATTR = { /* Defaults */ .orientation_vertical = true, .mode = FFT_DM_LINES, .logarithmic_amp = true, .logarithmic_freq = true, .window_func = FFT_WF_HAMMING, .spectrogram_pos = 0, .changed = { .clear_all = false }, }; #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 globals *************************/ /************************* Math functions *************************/ /* 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) /* Apply window function to input */ static void apply_window_func(enum fft_window_func mode) { int i; switch(mode) { case FFT_WF_HAMMING: for(i = 0; i < ARRAYLEN_IN; ++i) { input[i].r = (input[i].r * HAMMING_COEFF[i] + 16384) >> 15; } break; case FFT_WF_HANN: for(i = 0; i < ARRAYLEN_IN; ++i) { input[i].r = (input[i].r * HANN_COEFF[i] + 16384) >> 15; } break; } } /* Calculates the magnitudes from complex numbers and returns the maximum */ static int32_t calc_magnitudes(bool logarithmic_amp) { /* A major assumption made when calculating the Q*MAX constants * is that the maximum magnitude is 29 bits long. */ uint32_t max = 0; kiss_fft_cpx *this_output = output[output_head] + 1; /* skip DC */ int i; /* Calculate the magnitude, discarding the phase. */ for(i = 0; i < ARRAYLEN_PLOT; ++i) { int32_t re = this_output[i].r; int32_t im = this_output[i].i; uint32_t tmp = re*re + im*im; if(tmp > 0) { if(tmp > 0x7FFFFFFF) /* clip */ { tmp = 0x7FFFFFFF; /* if our assumptions are correct, this should never happen. It's just a safeguard. */ } if(logarithmic_amp) { if(tmp < 0x8000) /* be more precise */ { /* ln(x ^ .5) = .5*ln(x) */ tmp = fp16_log(tmp << 16) >> 1; } else { tmp = isqrt(tmp); /* linear scaling, nothing bad should happen */ tmp = fp16_log(tmp << 16); /* the log function expects s15.16 values */ } } else { tmp = isqrt(tmp); /* linear scaling, nothing bad should happen */ } } /* Length 2 moving average - last transform and this one */ tmp = (plot_history[i] + tmp) >> 1; plot[i] = tmp; plot_history[i] = tmp; if(tmp > max) max = tmp; } return 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 logarithmic_plot_translate(void) { int i; for(i = ARRAYLEN_PLOT-1; i > 0; --i) { int bin; int s = binlog[i].bin; int e = binlog[i-1].bin; int frac = binlog[i].frac; bin = plot[s]; if(frac) { /* slope < 1, Interpolate stretched bins (linear for now) */ int diff = plot[s+1] - bin; do { plot[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 = plot[s]; if (val > bin) bin = val; } } plot[i] = bin; } } /* Calculates the translation for logarithmic plot bins */ static void logarithmic_plot_init(void) { int i, j; /* * log: y = round(n * ln(x) / ln(n)) * anti: y = round(exp(x * ln(n) / n)) */ j = fp16_log((ARRAYLEN_PLOT - 1) << 16); for(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(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(void); static void draw_lines_horizontal(void); static void draw_bars_vertical(void); static void draw_bars_horizontal(void); static void draw_spectrogram_vertical(void); static void draw_spectrogram_horizontal(void); #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 POPUP_HPADDING 3 /* 3 px of horizontal padding and */ #define POPUP_VPADDING 2 /* 2 px of vertical padding */ static void draw_message_string(const unsigned char *message, bool active) { int x, y; mylcd_getstringsize(message, &x, &y); /* x and y give the size of the box for the popup */ x += POPUP_HPADDING*2; y += POPUP_VPADDING*2; /* In vertical spectrogram mode, leave space for the popup * before actually drawing it (if space is needed) */ if(active && graph_settings.mode == FFT_DM_SPECTROGRAPH && graph_settings.orientation_vertical && graph_settings.spectrogram_pos >= LCD_WIDTH - x) { mylcd_scroll_left(graph_settings.spectrogram_pos - LCD_WIDTH + x); graph_settings.spectrogram_pos = LCD_WIDTH - x - 1; } mylcd_set_foreground(COLOR_MESSAGE_FRAME); mylcd_fillrect(LCD_WIDTH - x, 0, LCD_WIDTH - 1, y); mylcd_set_foreground(COLOR_MESSAGE_FG); mylcd_set_background(COLOR_MESSAGE_BG); mylcd_putsxy(LCD_WIDTH - x + POPUP_HPADDING, POPUP_VPADDING, message); mylcd_set_foreground(COLOR_DEFAULT_FG); mylcd_set_background(COLOR_DEFAULT_BG); } static void draw(const unsigned char* message) { static long show_message_tick = 0; static const unsigned char* last_message = 0; if(message != NULL) { last_message = message; show_message_tick = (*rb->current_tick + HZ) | 1; } /* maybe take additional actions depending upon the changed setting */ if(graph_settings.changed.orientation) { graph_settings.changed.amp_scale = true; graph_settings.changed.do_clear = true; } if(graph_settings.changed.mode) { graph_settings.changed.amp_scale = true; graph_settings.changed.do_clear = true; } if(graph_settings.changed.amp_scale) memset(plot_history, 0, sizeof (plot_history)); if(graph_settings.changed.freq_scale) graph_settings.changed.freq_scale = true; mylcd_set_foreground(COLOR_DEFAULT_FG); mylcd_set_background(COLOR_DEFAULT_BG); switch (graph_settings.mode) { default: case FFT_DM_LINES: { mylcd_clear_display(); if (graph_settings.orientation_vertical) draw_lines_vertical(); else draw_lines_horizontal(); break; } case FFT_DM_BARS: { mylcd_clear_display(); if(graph_settings.orientation_vertical) draw_bars_vertical(); else draw_bars_horizontal(); break; } case FFT_DM_SPECTROGRAPH: { if(graph_settings.changed.do_clear) { graph_settings.spectrogram_pos = 0; mylcd_clear_display(); } if(graph_settings.orientation_vertical) draw_spectrogram_vertical(); else draw_spectrogram_horizontal(); break; } } if(show_message_tick != 0) { if(TIME_BEFORE(*rb->current_tick, show_message_tick)) { /* We have a message to show */ draw_message_string(last_message, true); } else { /* Stop drawing message */ show_message_tick = 0; } } else if(last_message != NULL) { if(graph_settings.mode == FFT_DM_SPECTROGRAPH) { /* Spectrogram mode - need to erase the popup */ int x, y; mylcd_getstringsize(last_message, &x, &y); /* Recalculate the size */ x += POPUP_HPADDING*2; y += POPUP_VPADDING*2; if(!graph_settings.orientation_vertical) { /* In horizontal spectrogram mode, just scroll up by Y lines */ mylcd_scroll_up(y); graph_settings.spectrogram_pos -= y; if(graph_settings.spectrogram_pos < 0) graph_settings.spectrogram_pos = 0; } else { /* In vertical spectrogram mode, erase the popup */ mylcd_set_foreground(COLOR_DEFAULT_BG); mylcd_fillrect(graph_settings.spectrogram_pos + 1, 0, LCD_WIDTH, y); mylcd_set_foreground(COLOR_DEFAULT_FG); } } /* else These modes clear the screen themselves */ last_message = NULL; } mylcd_update(); graph_settings.changed.clear_all = false; } static void draw_lines_vertical(void) { static int max = 0; #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 int this_max; int i, x; if(graph_settings.changed.amp_scale) max = 0; /* reset the graph on scaling mode change */ this_max = calc_magnitudes(graph_settings.logarithmic_amp); if(this_max == 0) { mylcd_hline(0, LCD_WIDTH - 1, LCD_HEIGHT - 1); /* Draw all "zero" */ return; } if(graph_settings.logarithmic_freq) logarithmic_plot_translate(); /* take the maximum of neighboring bins if we have to scale the graph * horizontally */ if(LCD_WIDTH < ARRAYLEN_PLOT) /* graph compression */ { int bins_acc = LCD_WIDTH / 2; int bins_max = 0; i = 0, x = 0; for(;;) { int bin = plot[i++]; if(bin > bins_max) bins_max = bin; bins_acc += LCD_WIDTH; if(bins_acc >= ARRAYLEN_PLOT) { plot[x] = bins_max; if(bins_max > max) max = bins_max; if(++x >= LCD_WIDTH) break; bins_acc -= ARRAYLEN_PLOT; bins_max = 0; } } } else { if(this_max > max) max = this_max; } for(x = 0; x < plotwidth; ++x) { int h = LCD_HEIGHT*plot[x] / max; mylcd_vline(x + offset, LCD_HEIGHT - h, LCD_HEIGHT-1); } } static void draw_lines_horizontal(void) { static int max = 0; #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 int this_max; int y; if(graph_settings.changed.amp_scale) max = 0; /* reset the graph on scaling mode change */ this_max = calc_magnitudes(graph_settings.logarithmic_amp); if(this_max == 0) { mylcd_vline(0, 0, LCD_HEIGHT-1); /* Draw all "zero" */ return; } if(graph_settings.logarithmic_freq) logarithmic_plot_translate(); /* 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; int bins_max = 0; int i = 0; y = 0; for(;;) { int bin = plot[i++]; if (bin > bins_max) bins_max = bin; bins_acc += LCD_HEIGHT; if(bins_acc >= ARRAYLEN_PLOT) { plot[y] = bins_max; if(bins_max > max) max = bins_max; if(++y >= LCD_HEIGHT) break; bins_acc -= ARRAYLEN_PLOT; bins_max = 0; } } } else { if(this_max > max) max = this_max; } for(y = 0; y < plotwidth; ++y) { int w = LCD_WIDTH*plot[y] / max; mylcd_hline(0, w - 1, y + offset); } } static void draw_bars_vertical(void) { static int max = 0; #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) / 2; if(graph_settings.changed.amp_scale) max = 0; /* reset the graph on scaling mode change */ mylcd_hline(0, LCD_WIDTH-1, LCD_HEIGHT-1); /* Draw baseline */ if(calc_magnitudes(graph_settings.logarithmic_amp) == 0) return; /* nothing more to draw */ if(graph_settings.logarithmic_freq) logarithmic_plot_translate(); int bins_acc = bars / 2; int bins_max = 0; int x = 0, i = 0; for(;;) { int bin = plot[i++]; if(bin > bins_max) bins_max = bin; bins_acc += bars; if(bins_acc >= ARRAYLEN_PLOT) { plot[x] = bins_max; if(bins_max > max) max = bins_max; if(++x >= bars) break; bins_acc -= ARRAYLEN_PLOT; bins_max = 0; } } for(i = 0, x = offset; i < bars; ++i, x += barwidth) { int h = LCD_HEIGHT * plot[i] / max; mylcd_fillrect(x, LCD_HEIGHT - h, width, h - 1); } } static void draw_bars_horizontal(void) { static int max = 0; #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) / 2; if(graph_settings.changed.amp_scale) max = 0; /* reset the graph on scaling mode change */ mylcd_vline(0, 0, LCD_HEIGHT-1); /* Draw baseline */ if(calc_magnitudes(graph_settings.logarithmic_amp) == 0) return; /* nothing more to draw */ if(graph_settings.logarithmic_freq) logarithmic_plot_translate(); int bins_acc = bars / 2; int bins_max = 0; int y = 0, i = 0; for(;;) { int bin = plot[i++]; if (bin > bins_max) bins_max = bin; bins_acc += bars; if(bins_acc >= ARRAYLEN_PLOT) { plot[y] = bins_max; if(bins_max > max) max = bins_max; if(++y >= bars) break; bins_acc -= ARRAYLEN_PLOT; bins_max = 0; } } for(i = 0, y = offset; i < bars; ++i, y += barwidth) { int w = LCD_WIDTH * plot[i] / max; mylcd_fillrect(1, y, w, height); } } static void draw_spectrogram_vertical(void) { const int32_t scale_factor = MIN(LCD_HEIGHT, ARRAYLEN_PLOT); calc_magnitudes(graph_settings.logarithmic_amp); if(graph_settings.logarithmic_freq) logarithmic_plot_translate(); int bins_acc = scale_factor / 2; int bins_max = 0; int y = 0, i = 0; for(;;) { int bin = plot[i++]; if(bin > bins_max) bins_max = bin; bins_acc += scale_factor; if(bins_acc >= ARRAYLEN_PLOT) { unsigned index; if(graph_settings.logarithmic_amp) index = (SHADES-1)*bins_max / QLOG_MAX; else index = (SHADES-1)*bins_max / QLIN_MAX; /* These happen because we exaggerate the graph a little for * linear mode */ if(index >= SHADES) index = SHADES-1; mylcd_set_foreground(SPECTROGRAPH_PALETTE(index)); mylcd_drawpixel(graph_settings.spectrogram_pos, scale_factor-1 - y); if(++y >= scale_factor) break; bins_acc -= ARRAYLEN_PLOT; bins_max = 0; } } if(graph_settings.spectrogram_pos < LCD_WIDTH-1) graph_settings.spectrogram_pos++; else mylcd_scroll_left(1); } static void draw_spectrogram_horizontal(void) { const int32_t scale_factor = MIN(LCD_WIDTH, ARRAYLEN_PLOT); calc_magnitudes(graph_settings.logarithmic_amp); if(graph_settings.logarithmic_freq) logarithmic_plot_translate(); int bins_acc = scale_factor / 2; int bins_max = 0; int x = 0, i = 0; for(;;) { int bin = plot[i++]; if(bin > bins_max) bins_max = bin; bins_acc += scale_factor; if(bins_acc >= ARRAYLEN_PLOT) { unsigned index; if(graph_settings.logarithmic_amp) index = (SHADES-1)*bins_max / QLOG_MAX; else index = (SHADES-1)*bins_max / QLIN_MAX; /* These happen because we exaggerate the graph a little for * linear mode */ if(index >= SHADES) index = SHADES-1; mylcd_set_foreground(SPECTROGRAPH_PALETTE(index)); mylcd_drawpixel(x, graph_settings.spectrogram_pos); if(++x >= scale_factor) break; bins_acc -= ARRAYLEN_PLOT; bins_max = 0; } } if(graph_settings.spectrogram_pos < LCD_HEIGHT-1) graph_settings.spectrogram_pos++; else mylcd_scroll_up(1); } /********************* 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(graph_settings.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; 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 ****************************/ enum plugin_status plugin_start(const void* parameter) { /* Defaults */ bool run = true; bool showing_warning = false; if (!fft_init_fft()) return PLUGIN_ERROR; #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"); fft_close_fft(); return PLUGIN_ERROR; } grey_show(true); #endif logarithmic_plot_init(); #if LCD_DEPTH > 1 rb->lcd_set_backdrop(NULL); mylcd_clear_display(); mylcd_update(); #endif backlight_ignore_timeout(); #ifdef HAVE_ADJUSTABLE_CPU_FREQ rb->cpu_boost(true); #endif while (run) { /* Unless otherwise specified, HZ/50 is around the window length * and quite fast. We want to be done with drawing by this time. */ long next_frame_tick = *rb->current_tick + HZ/50; int button; while (!fft_have_fft()) { int timeout; if(!is_playing()) { showing_warning = true; mylcd_clear_display(); draw_message_string("No audio playing", false); mylcd_update(); timeout = HZ/5; } else { if(showing_warning) { showing_warning = false; mylcd_clear_display(); mylcd_update(); } timeout = HZ/100; /* 'till end of curent tick, don't use 100% CPU */ } /* Make sure the FFT has produced something before doing anything * but watching for buttons. Music might not be playing or things * just aren't going well for picking up buffers so keys are * scanned to avoid lockup. */ button = rb->button_get_w_tmo(timeout); if (button != BUTTON_NONE) goto read_button; } draw(NULL); fft_free_fft_output(); /* COP only */ long tick = *rb->current_tick; if(TIME_BEFORE(tick, next_frame_tick)) { tick = next_frame_tick - tick; } else { rb->yield(); /* tmo = 0 won't yield */ tick = 0; } button = rb->button_get_w_tmo(tick); read_button: switch (button) { case FFT_QUIT: run = false; break; case FFT_PREV_GRAPH: { if (graph_settings.mode-- <= FFT_DM_FIRST) graph_settings.mode = FFT_DM_COUNT-1; graph_settings.changed.mode = true; draw(modes_text[graph_settings.mode]); break; } case FFT_NEXT_GRAPH: { if (++graph_settings.mode >= FFT_DM_COUNT) graph_settings.mode = FFT_DM_FIRST; graph_settings.changed.mode = true; draw(modes_text[graph_settings.mode]); break; } case FFT_WINDOW: { if(++graph_settings.window_func >= FFT_WF_COUNT) graph_settings.window_func = FFT_WF_FIRST; graph_settings.changed.window_func = true; draw(window_text[graph_settings.window_func]); break; } case FFT_AMP_SCALE: { graph_settings.logarithmic_amp = !graph_settings.logarithmic_amp; graph_settings.changed.amp_scale = true; draw(amp_scales_text[graph_settings.logarithmic_amp ? 1 : 0]); break; } #ifdef FFT_FREQ_SCALE /* 'Till all keymaps are defined */ case FFT_FREQ_SCALE: { graph_settings.logarithmic_freq = !graph_settings.logarithmic_freq; graph_settings.changed.freq_scale = true; draw(freq_scales_text[graph_settings.logarithmic_freq ? 1 : 0]); break; } #endif case FFT_ORIENTATION: { graph_settings.orientation_vertical = !graph_settings.orientation_vertical; graph_settings.changed.orientation = true; draw(NULL); break; } default: { if (rb->default_event_handler(button) == SYS_USB_CONNECTED) return PLUGIN_USB_CONNECTED; } } } fft_close_fft(); #ifdef HAVE_ADJUSTABLE_CPU_FREQ rb->cpu_boost(false); #endif #ifndef HAVE_LCD_COLOR grey_release(); #endif backlight_use_settings(); return PLUGIN_OK; (void)parameter; }