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