├── Makefile ├── PKGBUILD ├── README.md ├── fastftoi.h ├── spectrogram.c ├── userinstall.sh └── userremove.sh /Makefile: -------------------------------------------------------------------------------- 1 | # Spectrogram plugin for the DeaDBeeF audio player 2 | # 3 | # Copyright (C) 2014 Christian Boxdörfer 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | 19 | OUT_GTK2?=ddb_vis_spectrogram_GTK2.so 20 | OUT_GTK3?=ddb_vis_spectrogram_GTK3.so 21 | 22 | GTK2_CFLAGS?=`pkg-config --cflags gtk+-2.0` 23 | GTK3_CFLAGS?=`pkg-config --cflags gtk+-3.0` 24 | 25 | GTK2_LIBS?=`pkg-config --libs gtk+-2.0` 26 | GTK3_LIBS?=`pkg-config --libs gtk+-3.0` 27 | 28 | FFTW_LIBS?=-lfftw3 29 | 30 | CC?=gcc 31 | CFLAGS+=-Wall -g -fPIC -std=c99 -D_GNU_SOURCE 32 | LDFLAGS+=-shared 33 | 34 | GTK2_DIR?=gtk2 35 | GTK3_DIR?=gtk3 36 | 37 | SOURCES?=$(wildcard *.c) 38 | OBJ_GTK2?=$(patsubst %.c, $(GTK2_DIR)/%.o, $(SOURCES)) 39 | OBJ_GTK3?=$(patsubst %.c, $(GTK3_DIR)/%.o, $(SOURCES)) 40 | 41 | define compile 42 | $(CC) $(CFLAGS) $1 $2 $< -c -o $@ 43 | endef 44 | 45 | define link 46 | $(CC) $(LDFLAGS) $1 $2 $3 -o $@ 47 | endef 48 | 49 | # Builds both GTK+2 and GTK+3 versions of the plugin. 50 | all: gtk2 gtk3 51 | 52 | # Builds GTK+2 version of the plugin. 53 | gtk2: mkdir_gtk2 $(SOURCES) $(GTK2_DIR)/$(OUT_GTK2) 54 | 55 | # Builds GTK+3 version of the plugin. 56 | gtk3: mkdir_gtk3 $(SOURCES) $(GTK3_DIR)/$(OUT_GTK3) 57 | 58 | mkdir_gtk2: 59 | @echo "Creating build directory for GTK+2 version" 60 | @mkdir -p $(GTK2_DIR) 61 | 62 | mkdir_gtk3: 63 | @echo "Creating build directory for GTK+3 version" 64 | @mkdir -p $(GTK3_DIR) 65 | 66 | $(GTK2_DIR)/$(OUT_GTK2): $(OBJ_GTK2) 67 | @echo "Linking GTK+2 version" 68 | @$(call link, $(OBJ_GTK2), $(GTK2_LIBS), $(FFTW_LIBS)) 69 | @echo "Done!" 70 | 71 | $(GTK3_DIR)/$(OUT_GTK3): $(OBJ_GTK3) 72 | @echo "Linking GTK+3 version" 73 | @$(call link, $(OBJ_GTK3), $(GTK3_LIBS), $(FFTW_LIBS)) 74 | @echo "Done!" 75 | 76 | $(GTK2_DIR)/%.o: %.c 77 | @echo "Compiling $(subst $(GTK2_DIR)/,,$@)" 78 | @$(call compile, $(GTK2_CFLAGS)) 79 | 80 | $(GTK3_DIR)/%.o: %.c 81 | @echo "Compiling $(subst $(GTK3_DIR)/,,$@)" 82 | @$(call compile, $(GTK3_CFLAGS)) 83 | 84 | clean: 85 | @echo "Cleaning files from previous build..." 86 | @rm -r -f $(GTK2_DIR) $(GTK3_DIR) 87 | -------------------------------------------------------------------------------- /PKGBUILD: -------------------------------------------------------------------------------- 1 | pkgname=deadbeef-plugin-spectrogram-git 2 | pkgver=20140327 3 | pkgrel=1 4 | pkgdesc="Spectrogram Plugin for the DeaDBeeF audio player (development version)" 5 | url="https://github.com/cboxdoerfer/ddb_spectrogram" 6 | arch=('i686' 'x86_64') 7 | license='GPL2' 8 | depends=('deadbeef' 'fftw') 9 | makedepends=('git') 10 | 11 | _gitname=ddb_spectrogram 12 | _gitroot=https://github.com/cboxdoerfer/${_gitname} 13 | 14 | build() { 15 | cd $srcdir 16 | msg "Connecting to GIT server..." 17 | rm -rf $srcdir/$_gitname-build 18 | 19 | if [ -d $_gitname ]; then 20 | cd $_gitname 21 | git pull origin master 22 | else 23 | git clone $_gitroot 24 | fi 25 | 26 | msg "GIT checkout done or server timeout" 27 | msg "Starting make..." 28 | 29 | cd $srcdir 30 | cp -r $_gitname $_gitname-build 31 | 32 | cd $_gitname-build 33 | 34 | touch AUTHORS 35 | touch ChangeLog 36 | 37 | make 38 | } 39 | 40 | package() { 41 | install -D -v -c $srcdir/$_gitname-build/gtk2/ddb_vis_spectrogram_GTK2.so $pkgdir/usr/lib/deadbeef/ddb_vis_spectrogram_GTK2.so 42 | install -D -v -c $srcdir/$_gitname-build/gtk3/ddb_vis_spectrogram_GTK3.so $pkgdir/usr/lib/deadbeef/ddb_vis_spectrogram_GTK3.so 43 | } 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://drone.io/github.com/cboxdoerfer/ddb_spectrogram/status.png)](https://drone.io/github.com/cboxdoerfer/ddb_spectrogram/latest) 2 | 3 | Spectrogram plugin for DeaDBeeF audio player 4 | ==================== 5 | 6 | ## Installation 7 | 8 | ### Arch Linux 9 | See the [AUR](https://aur.archlinux.org/packages/deadbeef-plugin-spectrogram-git/) 10 | 11 | ### Gentoo 12 | See ebuilds [here](https://github.com/megabaks/stuff/tree/master/media-plugins/deadbeef-spectrogram) 13 | 14 | ### Binaries 15 | 16 | #### Dev 17 | [x86_64](https://drone.io/github.com/cboxdoerfer/ddb_spectrogram/files/deadbeef-plugin-builder/ddb_spectrogram_x86_64.tar.gz) 18 | 19 | [i686](https://drone.io/github.com/cboxdoerfer/ddb_spectrogram/files/deadbeef-plugin-builder/ddb_spectrogram_i686.tar.gz) 20 | 21 | Install them as follows: 22 | 23 | x86_64: ```tar -xvf ddb_spectrogram_x86_64.tar.gz -C ~/.local/lib/deadbeef``` 24 | 25 | i686: ```tar -xvf ddb_spectrogram_i686.tar.gz -C ~/.local/lib/deadbeef``` 26 | 27 | ### Other distributions 28 | #### Build from sources 29 | First install DeaDBeeF (>=0.6) and fftw3 30 | ```bash 31 | make 32 | ./userinstall.sh 33 | ``` 34 | 35 | ## Screenshot 36 | 37 | ![](http://i.imgur.com/UTEVqr3.png) 38 | -------------------------------------------------------------------------------- /fastftoi.h: -------------------------------------------------------------------------------- 1 | // most of the code below was taken from libvorbis/vorbis/lib/os.h 2 | // under the conditions below 3 | /******************************************************************** 4 | * * 5 | * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * 6 | * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * 7 | * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * 8 | * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * 9 | * * 10 | * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2009 * 11 | * by the Xiph.Org Foundation http://www.xiph.org/ * 12 | * * 13 | ******************************************************************** 14 | 15 | contents of the libvorbis COPYING: 16 | 17 | Copyright (c) 2002-2008 Xiph.org Foundation 18 | 19 | Redistribution and use in source and binary forms, with or without 20 | modification, are permitted provided that the following conditions 21 | are met: 22 | 23 | - Redistributions of source code must retain the above copyright 24 | notice, this list of conditions and the following disclaimer. 25 | 26 | - Redistributions in binary form must reproduce the above copyright 27 | notice, this list of conditions and the following disclaimer in the 28 | documentation and/or other materials provided with the distribution. 29 | 30 | - Neither the name of the Xiph.org Foundation nor the names of its 31 | contributors may be used to endorse or promote products derived from 32 | this software without specific prior written permission. 33 | 34 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 35 | ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 36 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 37 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION 38 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 39 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 40 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 41 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 42 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 43 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 44 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 45 | */ 46 | #ifndef __OPTMATH_H 47 | #define __OPTMATH_H 48 | 49 | #include 50 | 51 | #ifdef __SSE2__ // that comes from -msse2 52 | #define __FORCE_SSE2__ 53 | #endif 54 | 55 | /* Special i386 GCC implementation */ 56 | #if defined(__i386__) && defined(__GNUC__) && !defined(__BEOS__) && !defined(__FORCE_SSE2__) 57 | # define FPU_CONTROL 58 | /* both GCC and MSVC are kinda stupid about rounding/casting to int. 59 | Because of encapsulation constraints (GCC can't see inside the asm 60 | block and so we end up doing stupid things like a store/load that 61 | is collectively a noop), we do it this way */ 62 | 63 | /* we must set up the fpu before this works!! */ 64 | 65 | typedef int16_t fpu_control; 66 | 67 | static inline void fpu_setround(fpu_control *fpu){ 68 | int16_t ret; 69 | int16_t temp; 70 | __asm__ __volatile__("fnstcw %0\n\t" 71 | "movw %0,%%dx\n\t" 72 | "orw $62463,%%dx\n\t" 73 | "movw %%dx,%1\n\t" 74 | "fldcw %1\n\t":"=m"(ret):"m"(temp): "dx"); 75 | *fpu=ret; 76 | } 77 | 78 | static inline void fpu_restore(fpu_control fpu){ 79 | __asm__ __volatile__("fldcw %0":: "m"(fpu)); 80 | } 81 | 82 | /* assumes the FPU is in round mode! */ 83 | static inline int ftoi(double f){ /* yes, double! Otherwise, 84 | we get extra fst/fld to 85 | truncate precision */ 86 | int i; 87 | __asm__("fistl %0": "=m"(i) : "t"(f)); 88 | return(i); 89 | } 90 | #endif /* Special i386 GCC implementation */ 91 | 92 | 93 | /* MSVC inline assembly. 32 bit only; inline ASM isn't implemented in the 94 | * 64 bit compiler */ 95 | #if defined(_MSC_VER) && !defined(_WIN64) 96 | # define FPU_CONTROL 97 | 98 | typedef int16_t fpu_control; 99 | 100 | static __inline int ftoi(double f){ 101 | int i; 102 | __asm{ 103 | fld f 104 | fistp i 105 | } 106 | return i; 107 | } 108 | 109 | static __inline void fpu_setround(fpu_control *fpu){ 110 | } 111 | 112 | static __inline void fpu_restore(fpu_control fpu){ 113 | } 114 | 115 | #endif /* Special MSVC 32 bit implementation */ 116 | 117 | 118 | /* Optimized code path for x86_64 builds. Uses SSE2 intrinsics. This can be 119 | done safely because all x86_64 CPUs supports SSE2. */ 120 | #if (defined(__FORCE_SSE2__)) || (defined(_MSC_VER) && defined(_WIN64)) || (defined(__GNUC__) && defined (__x86_64__)) 121 | #pragma warning "using sse2 for ftoi" 122 | # define FPU_CONTROL 123 | 124 | typedef int16_t fpu_control; 125 | 126 | #include 127 | static __inline int ftoi(double f){ 128 | return _mm_cvtsd_si32(_mm_load_sd(&f)); 129 | } 130 | 131 | static __inline void fpu_setround(fpu_control *fpu){ 132 | } 133 | 134 | static __inline void fpu_restore(fpu_control fpu){ 135 | } 136 | 137 | #endif /* Special MSVC x64 implementation */ 138 | 139 | 140 | /* If no special implementation was found for the current compiler / platform, 141 | use the default implementation here: */ 142 | #ifndef FPU_CONTROL 143 | 144 | typedef int fpu_control; 145 | 146 | static int ftoi(double f){ 147 | /* Note: MSVC and GCC (at least on some systems) round towards zero, thus, 148 | the floor() call is required to ensure correct roudning of 149 | negative numbers */ 150 | return (int)floor(f+.5); 151 | } 152 | 153 | /* We don't have special code for this compiler/arch, so do it the slow way */ 154 | # define fpu_setround(fpu_control) {} 155 | # define fpu_restore(fpu_control) {} 156 | 157 | #endif /* default implementation */ 158 | 159 | #endif // __OPTMATH_H 160 | -------------------------------------------------------------------------------- /spectrogram.c: -------------------------------------------------------------------------------- 1 | /* 2 | Spectrogram plugin for the DeaDBeeF audio player 3 | 4 | Copyright (C) 2014 Christian Boxdörfer 5 | 6 | This program is free software; you can redistribute it and/or 7 | modify it under the terms of the GNU General Public License 8 | as published by the Free Software Foundation; either version 2 9 | of the License, or (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program; if not, write to the Free Software 18 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | #include 33 | 34 | #include "fastftoi.h" 35 | 36 | #define GRADIENT_TABLE_SIZE 2048 37 | #define FFT_SIZE 8192 38 | #define MAX_HEIGHT 4096 39 | 40 | #define CONFSTR_SP_LOG_SCALE "spectrogram.log_scale" 41 | #define CONFSTR_SP_REFRESH_INTERVAL "spectrogram.refresh_interval" 42 | #define CONFSTR_SP_DB_RANGE "spectrogram.db_range" 43 | #define CONFSTR_SP_NUM_COLORS "spectrogram.num_colors" 44 | #define CONFSTR_SP_COLOR_GRADIENT_00 "spectrogram.color.gradient_00" 45 | #define CONFSTR_SP_COLOR_GRADIENT_01 "spectrogram.color.gradient_01" 46 | #define CONFSTR_SP_COLOR_GRADIENT_02 "spectrogram.color.gradient_02" 47 | #define CONFSTR_SP_COLOR_GRADIENT_03 "spectrogram.color.gradient_03" 48 | #define CONFSTR_SP_COLOR_GRADIENT_04 "spectrogram.color.gradient_04" 49 | #define CONFSTR_SP_COLOR_GRADIENT_05 "spectrogram.color.gradient_05" 50 | #define CONFSTR_SP_COLOR_GRADIENT_06 "spectrogram.color.gradient_06" 51 | 52 | /* Global variables */ 53 | static DB_misc_t plugin; 54 | static DB_functions_t * deadbeef = NULL; 55 | static ddb_gtkui_t * gtkui_plugin = NULL; 56 | 57 | typedef struct { 58 | ddb_gtkui_widget_t base; 59 | GtkWidget *drawarea; 60 | GtkWidget *popup; 61 | GtkWidget *popup_item; 62 | guint drawtimer; 63 | double *data; 64 | double window[FFT_SIZE]; 65 | double *in; 66 | //double *out_real; 67 | fftw_complex *out_complex; 68 | fftw_plan p_r2c; 69 | //fftw_plan p_r2r; 70 | uint32_t colors[GRADIENT_TABLE_SIZE]; 71 | double *samples; 72 | int *log_index; 73 | float samplerate; 74 | int height; 75 | int low_res_end; 76 | int resized; 77 | int buffered; 78 | intptr_t mutex; 79 | cairo_surface_t *surf; 80 | } w_spectrogram_t; 81 | 82 | 83 | static int CONFIG_LOG_SCALE = 1; 84 | static int CONFIG_DB_RANGE = 70; 85 | static int CONFIG_NUM_COLORS = 7; 86 | static int CONFIG_REFRESH_INTERVAL = 25; 87 | static GdkColor CONFIG_GRADIENT_COLORS[7]; 88 | 89 | static void 90 | save_config (void) 91 | { 92 | deadbeef->conf_set_int (CONFSTR_SP_LOG_SCALE, CONFIG_LOG_SCALE); 93 | deadbeef->conf_set_int (CONFSTR_SP_DB_RANGE, CONFIG_DB_RANGE); 94 | deadbeef->conf_set_int (CONFSTR_SP_NUM_COLORS, CONFIG_NUM_COLORS); 95 | deadbeef->conf_set_int (CONFSTR_SP_REFRESH_INTERVAL, CONFIG_REFRESH_INTERVAL); 96 | char color[100]; 97 | snprintf (color, sizeof (color), "%d %d %d", CONFIG_GRADIENT_COLORS[0].red, CONFIG_GRADIENT_COLORS[0].green, CONFIG_GRADIENT_COLORS[0].blue); 98 | deadbeef->conf_set_str (CONFSTR_SP_COLOR_GRADIENT_00, color); 99 | snprintf (color, sizeof (color), "%d %d %d", CONFIG_GRADIENT_COLORS[1].red, CONFIG_GRADIENT_COLORS[1].green, CONFIG_GRADIENT_COLORS[1].blue); 100 | deadbeef->conf_set_str (CONFSTR_SP_COLOR_GRADIENT_01, color); 101 | snprintf (color, sizeof (color), "%d %d %d", CONFIG_GRADIENT_COLORS[2].red, CONFIG_GRADIENT_COLORS[2].green, CONFIG_GRADIENT_COLORS[2].blue); 102 | deadbeef->conf_set_str (CONFSTR_SP_COLOR_GRADIENT_02, color); 103 | snprintf (color, sizeof (color), "%d %d %d", CONFIG_GRADIENT_COLORS[3].red, CONFIG_GRADIENT_COLORS[3].green, CONFIG_GRADIENT_COLORS[3].blue); 104 | deadbeef->conf_set_str (CONFSTR_SP_COLOR_GRADIENT_03, color); 105 | snprintf (color, sizeof (color), "%d %d %d", CONFIG_GRADIENT_COLORS[4].red, CONFIG_GRADIENT_COLORS[4].green, CONFIG_GRADIENT_COLORS[4].blue); 106 | deadbeef->conf_set_str (CONFSTR_SP_COLOR_GRADIENT_04, color); 107 | snprintf (color, sizeof (color), "%d %d %d", CONFIG_GRADIENT_COLORS[5].red, CONFIG_GRADIENT_COLORS[5].green, CONFIG_GRADIENT_COLORS[5].blue); 108 | deadbeef->conf_set_str (CONFSTR_SP_COLOR_GRADIENT_05, color); 109 | snprintf (color, sizeof (color), "%d %d %d", CONFIG_GRADIENT_COLORS[6].red, CONFIG_GRADIENT_COLORS[6].green, CONFIG_GRADIENT_COLORS[6].blue); 110 | deadbeef->conf_set_str (CONFSTR_SP_COLOR_GRADIENT_06, color); 111 | } 112 | 113 | static void 114 | load_config (void) 115 | { 116 | deadbeef->conf_lock (); 117 | CONFIG_LOG_SCALE = deadbeef->conf_get_int (CONFSTR_SP_LOG_SCALE, 1); 118 | CONFIG_DB_RANGE = deadbeef->conf_get_int (CONFSTR_SP_DB_RANGE, 70); 119 | CONFIG_NUM_COLORS = deadbeef->conf_get_int (CONFSTR_SP_NUM_COLORS, 7); 120 | CONFIG_REFRESH_INTERVAL = deadbeef->conf_get_int (CONFSTR_SP_REFRESH_INTERVAL, 25); 121 | const char *color; 122 | color = deadbeef->conf_get_str_fast (CONFSTR_SP_COLOR_GRADIENT_00, "65535 0 0"); 123 | sscanf (color, "%hd %hd %hd", &(CONFIG_GRADIENT_COLORS[0].red), &(CONFIG_GRADIENT_COLORS[0].green), &(CONFIG_GRADIENT_COLORS[0].blue)); 124 | color = deadbeef->conf_get_str_fast (CONFSTR_SP_COLOR_GRADIENT_01, "65535 32896 0"); 125 | sscanf (color, "%hd %hd %hd", &(CONFIG_GRADIENT_COLORS[1].red), &(CONFIG_GRADIENT_COLORS[1].green), &(CONFIG_GRADIENT_COLORS[1].blue)); 126 | color = deadbeef->conf_get_str_fast (CONFSTR_SP_COLOR_GRADIENT_02, "65535 65535 0"); 127 | sscanf (color, "%hd %hd %hd", &(CONFIG_GRADIENT_COLORS[2].red), &(CONFIG_GRADIENT_COLORS[2].green), &(CONFIG_GRADIENT_COLORS[2].blue)); 128 | color = deadbeef->conf_get_str_fast (CONFSTR_SP_COLOR_GRADIENT_03, "32896 65535 30840"); 129 | sscanf (color, "%hd %hd %hd", &(CONFIG_GRADIENT_COLORS[3].red), &(CONFIG_GRADIENT_COLORS[3].green), &(CONFIG_GRADIENT_COLORS[3].blue)); 130 | color = deadbeef->conf_get_str_fast (CONFSTR_SP_COLOR_GRADIENT_04, "0 38036 41120"); 131 | sscanf (color, "%hd %hd %hd", &(CONFIG_GRADIENT_COLORS[4].red), &(CONFIG_GRADIENT_COLORS[4].green), &(CONFIG_GRADIENT_COLORS[4].blue)); 132 | color = deadbeef->conf_get_str_fast (CONFSTR_SP_COLOR_GRADIENT_05, "0 8224 25700"); 133 | sscanf (color, "%hd %hd %hd", &(CONFIG_GRADIENT_COLORS[5].red), &(CONFIG_GRADIENT_COLORS[5].green), &(CONFIG_GRADIENT_COLORS[5].blue)); 134 | color = deadbeef->conf_get_str_fast (CONFSTR_SP_COLOR_GRADIENT_06, "0 0 0"); 135 | sscanf (color, "%hd %hd %hd", &(CONFIG_GRADIENT_COLORS[6].red), &(CONFIG_GRADIENT_COLORS[6].green), &(CONFIG_GRADIENT_COLORS[6].blue)); 136 | deadbeef->conf_unlock (); 137 | } 138 | 139 | void 140 | do_fft (w_spectrogram_t *w) 141 | { 142 | if (!w->samples || w->buffered < FFT_SIZE/2) { 143 | return; 144 | } 145 | deadbeef->mutex_lock (w->mutex); 146 | double real,imag; 147 | 148 | for (int i = 0; i < FFT_SIZE; i++) { 149 | w->in[i] = w->samples[i] * w->window[i]; 150 | } 151 | deadbeef->mutex_unlock (w->mutex); 152 | //fftw_execute (w->p_r2r); 153 | fftw_execute (w->p_r2c); 154 | for (int i = 0; i < FFT_SIZE/2; i++) 155 | { 156 | real = w->out_complex[i][0]; 157 | imag = w->out_complex[i][1]; 158 | w->data[i] = (real*real + imag*imag); 159 | //w->data[i] = w->out_real[i]*w->out_real[i] + w->out_real[FFT_SIZE/2+i]*w->out_real[FFT_SIZE/2+i]; 160 | } 161 | } 162 | 163 | static inline void 164 | _draw_point (uint8_t *data, int stride, int x0, int y0, uint32_t color) { 165 | uint32_t *ptr = (uint32_t*)&data[y0*stride+x0*4]; 166 | *ptr = color; 167 | } 168 | 169 | /* based on Delphi function by Witold J.Janik */ 170 | void 171 | create_gradient_table (gpointer user_data, GdkColor *colors, int num_colors) 172 | { 173 | w_spectrogram_t *w = user_data; 174 | 175 | num_colors -= 1; 176 | 177 | for (int i = 0; i < GRADIENT_TABLE_SIZE; i++) { 178 | double position = (double)i/GRADIENT_TABLE_SIZE; 179 | /* if position > 1 then we have repetition of colors it maybe useful */ 180 | if (position > 1.0) { 181 | if (position - ftoi (position) == 0.0) { 182 | position = 1.0; 183 | } 184 | else { 185 | position = position - ftoi (position); 186 | } 187 | } 188 | 189 | double m= num_colors * position; 190 | int n=(int)m; // integer of m 191 | double f=m-n; // fraction of m 192 | 193 | w->colors[i] = 0xFF000000; 194 | float scale = 255/65535.f; 195 | if (num_colors == 0) { 196 | w->colors[i] = ((uint32_t)(colors[0].red*scale) & 0xFF) << 16 | 197 | ((uint32_t)(colors[0].green*scale) & 0xFF) << 8 | 198 | ((uint32_t)(colors[0].blue*scale) & 0xFF) << 0; 199 | } 200 | else if (n < num_colors) { 201 | w->colors[i] = ((uint32_t)((colors[n].red*scale) + f * ((colors[n+1].red*scale)-(colors[n].red*scale))) & 0xFF) << 16 | 202 | ((uint32_t)((colors[n].green*scale) + f * ((colors[n+1].green*scale)-(colors[n].green*scale))) & 0xFF) << 8 | 203 | ((uint32_t)((colors[n].blue*scale) + f * ((colors[n+1].blue*scale)-(colors[n].blue*scale))) & 0xFF) << 0; 204 | } 205 | else if (n == num_colors) { 206 | w->colors[i] = ((uint32_t)(colors[n].red*scale) & 0xFF) << 16 | 207 | ((uint32_t)(colors[n].green*scale) & 0xFF) << 8 | 208 | ((uint32_t)(colors[n].blue*scale) & 0xFF) << 0; 209 | } 210 | else { 211 | w->colors[i] = 0xFFFFFFFF; 212 | } 213 | } 214 | } 215 | 216 | static int 217 | on_config_changed (gpointer user_data, uintptr_t ctx) 218 | { 219 | create_gradient_table (user_data, CONFIG_GRADIENT_COLORS, CONFIG_NUM_COLORS); 220 | load_config (); 221 | return 0; 222 | } 223 | 224 | #if !GTK_CHECK_VERSION(2,12,0) 225 | #define gtk_widget_get_window(widget) ((widget)->window) 226 | #define gtk_dialog_get_content_area(dialog) (dialog->vbox) 227 | #define gtk_dialog_get_action_area(dialog) (dialog->action_area) 228 | #endif 229 | 230 | #if !GTK_CHECK_VERSION(2,18,0) 231 | void 232 | gtk_widget_get_allocation (GtkWidget *widget, GtkAllocation *allocation) { 233 | (allocation)->x = widget->allocation.x; 234 | (allocation)->y = widget->allocation.y; 235 | (allocation)->width = widget->allocation.width; 236 | (allocation)->height = widget->allocation.height; 237 | } 238 | #define gtk_widget_set_can_default(widget, candefault) {if (candefault) GTK_WIDGET_SET_FLAGS (widget, GTK_CAN_DEFAULT); else GTK_WIDGET_UNSET_FLAGS(widget, GTK_CAN_DEFAULT);} 239 | #endif 240 | 241 | static void 242 | on_button_config (GtkMenuItem *menuitem, gpointer user_data) 243 | { 244 | GtkWidget *spectrogram_properties; 245 | GtkWidget *config_dialog; 246 | GtkWidget *vbox01; 247 | GtkWidget *vbox02; 248 | GtkWidget *hbox01; 249 | //GtkWidget *hbox02; 250 | GtkWidget *hbox03; 251 | GtkWidget *color_label; 252 | GtkWidget *color_frame; 253 | GtkWidget *color_gradient_00; 254 | GtkWidget *color_gradient_01; 255 | GtkWidget *color_gradient_02; 256 | GtkWidget *color_gradient_03; 257 | GtkWidget *color_gradient_04; 258 | GtkWidget *color_gradient_05; 259 | GtkWidget *color_gradient_06; 260 | GtkWidget *num_colors_label; 261 | GtkWidget *num_colors; 262 | GtkWidget *log_scale; 263 | GtkWidget *db_range_label0; 264 | GtkWidget *db_range; 265 | GtkWidget *dialog_action_area13; 266 | GtkWidget *applybutton1; 267 | GtkWidget *cancelbutton1; 268 | GtkWidget *okbutton1; 269 | #pragma GCC diagnostic push 270 | #pragma GCC diagnostic ignored "-Wdeprecated-declarations" 271 | spectrogram_properties = gtk_dialog_new (); 272 | gtk_window_set_title (GTK_WINDOW (spectrogram_properties), "Spectrogram Properties"); 273 | gtk_window_set_type_hint (GTK_WINDOW (spectrogram_properties), GDK_WINDOW_TYPE_HINT_DIALOG); 274 | 275 | config_dialog = gtk_dialog_get_content_area (GTK_DIALOG (spectrogram_properties)); 276 | gtk_widget_show (config_dialog); 277 | 278 | hbox01 = gtk_hbox_new (FALSE, 8); 279 | gtk_widget_show (hbox01); 280 | gtk_box_pack_start (GTK_BOX (config_dialog), hbox01, FALSE, FALSE, 0); 281 | gtk_container_set_border_width (GTK_CONTAINER (hbox01), 12); 282 | 283 | color_label = gtk_label_new (NULL); 284 | gtk_label_set_markup (GTK_LABEL (color_label),"Colors"); 285 | gtk_widget_show (color_label); 286 | 287 | color_frame = gtk_frame_new ("Colors"); 288 | gtk_frame_set_label_widget ((GtkFrame *)color_frame, color_label); 289 | gtk_frame_set_shadow_type ((GtkFrame *)color_frame, GTK_SHADOW_IN); 290 | gtk_widget_show (color_frame); 291 | gtk_box_pack_start (GTK_BOX (hbox01), color_frame, TRUE, FALSE, 0); 292 | 293 | vbox02 = gtk_vbox_new (FALSE, 8); 294 | gtk_widget_show (vbox02); 295 | gtk_container_add (GTK_CONTAINER (color_frame), vbox02); 296 | gtk_container_set_border_width (GTK_CONTAINER (vbox02), 12); 297 | 298 | num_colors_label = gtk_label_new (NULL); 299 | gtk_label_set_markup (GTK_LABEL (num_colors_label),"Number of colors:"); 300 | gtk_widget_show (num_colors_label); 301 | gtk_box_pack_start (GTK_BOX (vbox02), num_colors_label, FALSE, FALSE, 0); 302 | 303 | num_colors = gtk_spin_button_new_with_range (1,7,1); 304 | gtk_widget_show (num_colors); 305 | gtk_box_pack_start (GTK_BOX (vbox02), num_colors, FALSE, FALSE, 0); 306 | 307 | color_gradient_00 = gtk_color_button_new (); 308 | gtk_color_button_set_use_alpha ((GtkColorButton *)color_gradient_00, TRUE); 309 | gtk_widget_show (color_gradient_00); 310 | gtk_box_pack_start (GTK_BOX (vbox02), color_gradient_00, TRUE, FALSE, 0); 311 | gtk_widget_set_size_request (color_gradient_00, -1, 30); 312 | 313 | color_gradient_01 = gtk_color_button_new (); 314 | gtk_color_button_set_use_alpha ((GtkColorButton *)color_gradient_01, TRUE); 315 | gtk_widget_show (color_gradient_01); 316 | gtk_box_pack_start (GTK_BOX (vbox02), color_gradient_01, TRUE, FALSE, 0); 317 | gtk_widget_set_size_request (color_gradient_01, -1, 30); 318 | 319 | color_gradient_02 = gtk_color_button_new (); 320 | gtk_color_button_set_use_alpha ((GtkColorButton *)color_gradient_02, TRUE); 321 | gtk_widget_show (color_gradient_02); 322 | gtk_box_pack_start (GTK_BOX (vbox02), color_gradient_02, TRUE, FALSE, 0); 323 | gtk_widget_set_size_request (color_gradient_02, -1, 30); 324 | 325 | color_gradient_03 = gtk_color_button_new (); 326 | gtk_color_button_set_use_alpha ((GtkColorButton *)color_gradient_03, TRUE); 327 | gtk_widget_show (color_gradient_03); 328 | gtk_box_pack_start (GTK_BOX (vbox02), color_gradient_03, TRUE, FALSE, 0); 329 | gtk_widget_set_size_request (color_gradient_03, -1, 30); 330 | 331 | color_gradient_04 = gtk_color_button_new (); 332 | gtk_color_button_set_use_alpha ((GtkColorButton *)color_gradient_04, TRUE); 333 | gtk_widget_show (color_gradient_04); 334 | gtk_box_pack_start (GTK_BOX (vbox02), color_gradient_04, TRUE, FALSE, 0); 335 | gtk_widget_set_size_request (color_gradient_04, -1, 30); 336 | 337 | color_gradient_05 = gtk_color_button_new (); 338 | gtk_color_button_set_use_alpha ((GtkColorButton *)color_gradient_05, TRUE); 339 | gtk_widget_show (color_gradient_05); 340 | gtk_box_pack_start (GTK_BOX (vbox02), color_gradient_05, TRUE, FALSE, 0); 341 | gtk_widget_set_size_request (color_gradient_05, -1, 30); 342 | 343 | color_gradient_06 = gtk_color_button_new (); 344 | gtk_color_button_set_use_alpha ((GtkColorButton *)color_gradient_06, TRUE); 345 | gtk_widget_show (color_gradient_06); 346 | gtk_box_pack_start (GTK_BOX (vbox02), color_gradient_06, TRUE, FALSE, 0); 347 | gtk_widget_set_size_request (color_gradient_06, -1, 30); 348 | 349 | vbox01 = gtk_vbox_new (FALSE, 8); 350 | gtk_widget_show (vbox01); 351 | gtk_box_pack_start (GTK_BOX (hbox01), vbox01, FALSE, FALSE, 0); 352 | gtk_container_set_border_width (GTK_CONTAINER (vbox01), 12); 353 | 354 | //hbox02 = gtk_hbox_new (FALSE, 8); 355 | //gtk_widget_show (hbox02); 356 | //gtk_box_pack_start (GTK_BOX (vbox01), hbox02, FALSE, FALSE, 0); 357 | //gtk_container_set_border_width (GTK_CONTAINER (hbox01), 12); 358 | 359 | hbox03 = gtk_hbox_new (FALSE, 8); 360 | gtk_widget_show (hbox03); 361 | gtk_box_pack_start (GTK_BOX (vbox01), hbox03, FALSE, FALSE, 0); 362 | 363 | db_range_label0 = gtk_label_new (NULL); 364 | gtk_label_set_markup (GTK_LABEL (db_range_label0),"dB range:"); 365 | gtk_widget_show (db_range_label0); 366 | gtk_box_pack_start (GTK_BOX (hbox03), db_range_label0, FALSE, TRUE, 0); 367 | 368 | db_range = gtk_spin_button_new_with_range (50,120,10); 369 | gtk_widget_show (db_range); 370 | gtk_box_pack_start (GTK_BOX (hbox03), db_range, TRUE, TRUE, 0); 371 | 372 | log_scale = gtk_check_button_new_with_label ("Log scale"); 373 | gtk_widget_show (log_scale); 374 | gtk_box_pack_start (GTK_BOX (vbox01), log_scale, FALSE, FALSE, 0); 375 | 376 | dialog_action_area13 = gtk_dialog_get_action_area (GTK_DIALOG (spectrogram_properties)); 377 | gtk_widget_show (dialog_action_area13); 378 | gtk_button_box_set_layout (GTK_BUTTON_BOX (dialog_action_area13), GTK_BUTTONBOX_END); 379 | 380 | applybutton1 = gtk_button_new_from_stock ("gtk-apply"); 381 | gtk_widget_show (applybutton1); 382 | gtk_dialog_add_action_widget (GTK_DIALOG (spectrogram_properties), applybutton1, GTK_RESPONSE_APPLY); 383 | gtk_widget_set_can_default (applybutton1, TRUE); 384 | 385 | cancelbutton1 = gtk_button_new_from_stock ("gtk-cancel"); 386 | gtk_widget_show (cancelbutton1); 387 | gtk_dialog_add_action_widget (GTK_DIALOG (spectrogram_properties), cancelbutton1, GTK_RESPONSE_CANCEL); 388 | gtk_widget_set_can_default (cancelbutton1, TRUE); 389 | 390 | okbutton1 = gtk_button_new_from_stock ("gtk-ok"); 391 | gtk_widget_show (okbutton1); 392 | gtk_dialog_add_action_widget (GTK_DIALOG (spectrogram_properties), okbutton1, GTK_RESPONSE_OK); 393 | gtk_widget_set_can_default (okbutton1, TRUE); 394 | 395 | gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (log_scale), CONFIG_LOG_SCALE); 396 | gtk_spin_button_set_value (GTK_SPIN_BUTTON (num_colors), CONFIG_NUM_COLORS); 397 | gtk_spin_button_set_value (GTK_SPIN_BUTTON (db_range), CONFIG_DB_RANGE); 398 | gtk_color_button_set_color (GTK_COLOR_BUTTON (color_gradient_00), &(CONFIG_GRADIENT_COLORS[0])); 399 | gtk_color_button_set_color (GTK_COLOR_BUTTON (color_gradient_01), &(CONFIG_GRADIENT_COLORS[1])); 400 | gtk_color_button_set_color (GTK_COLOR_BUTTON (color_gradient_02), &(CONFIG_GRADIENT_COLORS[2])); 401 | gtk_color_button_set_color (GTK_COLOR_BUTTON (color_gradient_03), &(CONFIG_GRADIENT_COLORS[3])); 402 | gtk_color_button_set_color (GTK_COLOR_BUTTON (color_gradient_04), &(CONFIG_GRADIENT_COLORS[4])); 403 | gtk_color_button_set_color (GTK_COLOR_BUTTON (color_gradient_05), &(CONFIG_GRADIENT_COLORS[5])); 404 | gtk_color_button_set_color (GTK_COLOR_BUTTON (color_gradient_06), &(CONFIG_GRADIENT_COLORS[6])); 405 | 406 | switch (CONFIG_NUM_COLORS) { 407 | case 1: 408 | gtk_widget_hide (color_gradient_01); 409 | gtk_widget_hide (color_gradient_02); 410 | gtk_widget_hide (color_gradient_03); 411 | gtk_widget_hide (color_gradient_04); 412 | gtk_widget_hide (color_gradient_05); 413 | gtk_widget_hide (color_gradient_06); 414 | break; 415 | case 2: 416 | gtk_widget_show (color_gradient_01); 417 | gtk_widget_hide (color_gradient_02); 418 | gtk_widget_hide (color_gradient_03); 419 | gtk_widget_hide (color_gradient_04); 420 | gtk_widget_hide (color_gradient_05); 421 | gtk_widget_hide (color_gradient_06); 422 | break; 423 | case 3: 424 | gtk_widget_show (color_gradient_01); 425 | gtk_widget_show (color_gradient_02); 426 | gtk_widget_hide (color_gradient_03); 427 | gtk_widget_hide (color_gradient_04); 428 | gtk_widget_hide (color_gradient_05); 429 | gtk_widget_hide (color_gradient_06); 430 | break; 431 | case 4: 432 | gtk_widget_show (color_gradient_01); 433 | gtk_widget_show (color_gradient_02); 434 | gtk_widget_show (color_gradient_03); 435 | gtk_widget_hide (color_gradient_04); 436 | gtk_widget_hide (color_gradient_05); 437 | gtk_widget_hide (color_gradient_06); 438 | break; 439 | case 5: 440 | gtk_widget_show (color_gradient_01); 441 | gtk_widget_show (color_gradient_02); 442 | gtk_widget_show (color_gradient_03); 443 | gtk_widget_show (color_gradient_04); 444 | gtk_widget_hide (color_gradient_05); 445 | gtk_widget_hide (color_gradient_06); 446 | break; 447 | case 6: 448 | gtk_widget_show (color_gradient_01); 449 | gtk_widget_show (color_gradient_02); 450 | gtk_widget_show (color_gradient_03); 451 | gtk_widget_show (color_gradient_04); 452 | gtk_widget_show (color_gradient_05); 453 | gtk_widget_hide (color_gradient_06); 454 | break; 455 | case 7: 456 | gtk_widget_show (color_gradient_01); 457 | gtk_widget_show (color_gradient_02); 458 | gtk_widget_show (color_gradient_03); 459 | gtk_widget_show (color_gradient_04); 460 | gtk_widget_show (color_gradient_05); 461 | gtk_widget_show (color_gradient_06); 462 | break; 463 | } 464 | 465 | for (;;) { 466 | int response = gtk_dialog_run (GTK_DIALOG (spectrogram_properties)); 467 | if (response == GTK_RESPONSE_OK || response == GTK_RESPONSE_APPLY) { 468 | gtk_color_button_get_color (GTK_COLOR_BUTTON (color_gradient_00), &CONFIG_GRADIENT_COLORS[0]); 469 | gtk_color_button_get_color (GTK_COLOR_BUTTON (color_gradient_01), &CONFIG_GRADIENT_COLORS[1]); 470 | gtk_color_button_get_color (GTK_COLOR_BUTTON (color_gradient_02), &CONFIG_GRADIENT_COLORS[2]); 471 | gtk_color_button_get_color (GTK_COLOR_BUTTON (color_gradient_03), &CONFIG_GRADIENT_COLORS[3]); 472 | gtk_color_button_get_color (GTK_COLOR_BUTTON (color_gradient_04), &CONFIG_GRADIENT_COLORS[4]); 473 | gtk_color_button_get_color (GTK_COLOR_BUTTON (color_gradient_05), &CONFIG_GRADIENT_COLORS[5]); 474 | gtk_color_button_get_color (GTK_COLOR_BUTTON (color_gradient_06), &CONFIG_GRADIENT_COLORS[6]); 475 | 476 | CONFIG_LOG_SCALE = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (log_scale)); 477 | CONFIG_DB_RANGE = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (db_range)); 478 | CONFIG_NUM_COLORS = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (num_colors)); 479 | switch (CONFIG_NUM_COLORS) { 480 | case 1: 481 | gtk_widget_hide (color_gradient_01); 482 | gtk_widget_hide (color_gradient_02); 483 | gtk_widget_hide (color_gradient_03); 484 | gtk_widget_hide (color_gradient_04); 485 | gtk_widget_hide (color_gradient_05); 486 | gtk_widget_hide (color_gradient_06); 487 | break; 488 | case 2: 489 | gtk_widget_show (color_gradient_01); 490 | gtk_widget_hide (color_gradient_02); 491 | gtk_widget_hide (color_gradient_03); 492 | gtk_widget_hide (color_gradient_04); 493 | gtk_widget_hide (color_gradient_05); 494 | gtk_widget_hide (color_gradient_06); 495 | break; 496 | case 3: 497 | gtk_widget_show (color_gradient_01); 498 | gtk_widget_show (color_gradient_02); 499 | gtk_widget_hide (color_gradient_03); 500 | gtk_widget_hide (color_gradient_04); 501 | gtk_widget_hide (color_gradient_05); 502 | gtk_widget_hide (color_gradient_06); 503 | break; 504 | case 4: 505 | gtk_widget_show (color_gradient_01); 506 | gtk_widget_show (color_gradient_02); 507 | gtk_widget_show (color_gradient_03); 508 | gtk_widget_hide (color_gradient_04); 509 | gtk_widget_hide (color_gradient_05); 510 | gtk_widget_hide (color_gradient_06); 511 | break; 512 | case 5: 513 | gtk_widget_show (color_gradient_01); 514 | gtk_widget_show (color_gradient_02); 515 | gtk_widget_show (color_gradient_03); 516 | gtk_widget_show (color_gradient_04); 517 | gtk_widget_hide (color_gradient_05); 518 | gtk_widget_hide (color_gradient_06); 519 | break; 520 | case 6: 521 | gtk_widget_show (color_gradient_01); 522 | gtk_widget_show (color_gradient_02); 523 | gtk_widget_show (color_gradient_03); 524 | gtk_widget_show (color_gradient_04); 525 | gtk_widget_show (color_gradient_05); 526 | gtk_widget_hide (color_gradient_06); 527 | break; 528 | case 7: 529 | gtk_widget_show (color_gradient_01); 530 | gtk_widget_show (color_gradient_02); 531 | gtk_widget_show (color_gradient_03); 532 | gtk_widget_show (color_gradient_04); 533 | gtk_widget_show (color_gradient_05); 534 | gtk_widget_show (color_gradient_06); 535 | break; 536 | } 537 | save_config (); 538 | deadbeef->sendmessage (DB_EV_CONFIGCHANGED, 0, 0, 0); 539 | } 540 | if (response == GTK_RESPONSE_APPLY) { 541 | continue; 542 | } 543 | break; 544 | } 545 | gtk_widget_destroy (spectrogram_properties); 546 | #pragma GCC diagnostic pop 547 | return; 548 | } 549 | 550 | void 551 | w_spectrogram_destroy (ddb_gtkui_widget_t *w) { 552 | w_spectrogram_t *s = (w_spectrogram_t *)w; 553 | deadbeef->vis_waveform_unlisten (w); 554 | if (s->data) { 555 | free (s->data); 556 | s->data = NULL; 557 | } 558 | if (s->samples) { 559 | free (s->samples); 560 | s->samples = NULL; 561 | } 562 | if (s->log_index) { 563 | free (s->log_index); 564 | s->log_index = NULL; 565 | } 566 | //if (s->p_r2r) { 567 | // fftw_destroy_plan (s->p_r2r); 568 | //} 569 | if (s->p_r2c) { 570 | fftw_destroy_plan (s->p_r2c); 571 | } 572 | if (s->in) { 573 | fftw_free (s->in); 574 | s->in = NULL; 575 | } 576 | //if (s->out_real) { 577 | // fftw_free (s->out_real); 578 | // s->out_real = NULL; 579 | //} 580 | if (s->out_complex) { 581 | fftw_free (s->out_complex); 582 | s->out_complex = NULL; 583 | } 584 | if (s->drawtimer) { 585 | g_source_remove (s->drawtimer); 586 | s->drawtimer = 0; 587 | } 588 | if (s->surf) { 589 | cairo_surface_destroy (s->surf); 590 | s->surf = NULL; 591 | } 592 | if (s->mutex) { 593 | deadbeef->mutex_free (s->mutex); 594 | s->mutex = 0; 595 | } 596 | } 597 | 598 | gboolean 599 | w_spectrogram_draw_cb (void *data) { 600 | w_spectrogram_t *s = data; 601 | gtk_widget_queue_draw (s->drawarea); 602 | return TRUE; 603 | } 604 | 605 | static void 606 | spectrogram_wavedata_listener (void *ctx, ddb_audio_data_t *data) { 607 | w_spectrogram_t *w = ctx; 608 | if (!w->samples) { 609 | return; 610 | } 611 | deadbeef->mutex_lock (w->mutex); 612 | w->samplerate = (float)data->fmt->samplerate; 613 | int nsamples = data->nframes; 614 | int sz = MIN (FFT_SIZE, nsamples); 615 | int n = FFT_SIZE - sz; 616 | memmove (w->samples, w->samples + sz, (FFT_SIZE - sz)*sizeof (double)); 617 | 618 | float pos = 0; 619 | for (int i = 0; i < sz && pos < nsamples; i++, pos ++) { 620 | w->samples[n+i] = -1000.0; 621 | for (int j = 0; j < data->fmt->channels; j++) { 622 | w->samples[n + i] = MAX (w->samples[n + i], data->data[ftoi (pos * data->fmt->channels) + j]); 623 | } 624 | } 625 | deadbeef->mutex_unlock (w->mutex); 626 | if (w->buffered < FFT_SIZE) { 627 | w->buffered += sz; 628 | } 629 | } 630 | 631 | static inline float 632 | spectrogram_get_value (gpointer user_data, int start, int end) 633 | { 634 | w_spectrogram_t *w = user_data; 635 | if (start >= end) { 636 | return w->data[end]; 637 | } 638 | float value = 0.0; 639 | for (int i = start; i < end; i++) { 640 | value = MAX (w->data[i],value); 641 | } 642 | return value; 643 | } 644 | 645 | static inline float 646 | linear_interpolate (float y1, float y2, float mu) 647 | { 648 | return (y1 * (1 - mu) + y2 * mu); 649 | } 650 | 651 | static gboolean 652 | spectrogram_draw (GtkWidget *widget, cairo_t *cr, gpointer user_data) { 653 | w_spectrogram_t *w = user_data; 654 | GtkAllocation a; 655 | gtk_widget_get_allocation (widget, &a); 656 | if (!w->samples || a.height < 1) { 657 | return FALSE; 658 | } 659 | 660 | int width, height; 661 | width = a.width; 662 | height = a.height; 663 | int ratio = ftoi (FFT_SIZE/(a.height*2)); 664 | ratio = CLAMP (ratio,0,1023); 665 | 666 | if (deadbeef->get_output ()->state () == OUTPUT_STATE_PLAYING) { 667 | do_fft (w); 668 | float log_scale = (log2f(w->samplerate/2)-log2f(25.))/(a.height); 669 | float freq_res = w->samplerate / FFT_SIZE; 670 | 671 | if (a.height != w->height) { 672 | w->height = MIN (a.height, MAX_HEIGHT); 673 | for (int i = 0; i < w->height; i++) { 674 | w->log_index[i] = ftoi (powf(2.,((float)i) * log_scale + log2f(25.)) / freq_res); 675 | if (i > 0 && w->log_index[i-1] == w->log_index [i]) { 676 | w->low_res_end = i; 677 | } 678 | } 679 | } 680 | } 681 | 682 | // start drawing 683 | if (!w->surf || cairo_image_surface_get_width (w->surf) != a.width || cairo_image_surface_get_height (w->surf) != a.height) { 684 | if (w->surf) { 685 | cairo_surface_destroy (w->surf); 686 | w->surf = NULL; 687 | } 688 | w->surf = cairo_image_surface_create (CAIRO_FORMAT_RGB24, a.width, a.height); 689 | } 690 | 691 | cairo_surface_flush (w->surf); 692 | 693 | unsigned char *data = cairo_image_surface_get_data (w->surf); 694 | if (!data) { 695 | return FALSE; 696 | } 697 | int stride = cairo_image_surface_get_stride (w->surf); 698 | 699 | if (deadbeef->get_output ()->state () == OUTPUT_STATE_PLAYING) { 700 | for (int i = 0; i < a.height; i++) { 701 | // scrolling: move line i 1px to the left 702 | memmove (data + (i*stride), data + sizeof (uint32_t) + (i*stride), stride - sizeof (uint32_t)); 703 | } 704 | 705 | for (int i = 0; i < a.height; i++) 706 | { 707 | float f = 1.0; 708 | int index0, index1; 709 | int bin0, bin1, bin2; 710 | if (CONFIG_LOG_SCALE) { 711 | bin0 = w->log_index[CLAMP (i-1,0,height-1)]; 712 | bin1 = w->log_index[i]; 713 | bin2 = w->log_index[CLAMP (i+1,0,height-1)]; 714 | } 715 | else { 716 | bin0 = (i-1) * ratio; 717 | bin1 = i * ratio; 718 | bin2 = (i+1) * ratio; 719 | } 720 | 721 | index0 = bin0 + ftoi ((bin1 - bin0)/2.f); 722 | if (index0 == bin0) index0 = bin1; 723 | index1 = bin1 + ftoi ((bin2 - bin1)/2.f); 724 | if (index1 == bin2) index1 = bin1; 725 | 726 | index0 = CLAMP (index0,0,FFT_SIZE/2-1); 727 | index1 = CLAMP (index1,0,FFT_SIZE/2-1); 728 | 729 | f = spectrogram_get_value (w, index0, index1); 730 | float x = 10 * log10f (f); 731 | 732 | // interpolate 733 | if (i <= w->low_res_end && CONFIG_LOG_SCALE) { 734 | int j = 0; 735 | // find index of next value 736 | while (i+j < height && w->log_index[i+j] == w->log_index[i]) { 737 | j++; 738 | } 739 | float v0 = x; 740 | float v1 = w->data[w->log_index[i+j]]; 741 | if (v1 != 0) { 742 | v1 = 10 * log10f (v1); 743 | } 744 | 745 | int k = 0; 746 | while ((k+i) >= 0 && w->log_index[k+i] == w->log_index[i]) { 747 | j++; 748 | k--; 749 | } 750 | x = linear_interpolate (v0,v1,(1.0/(j-1)) * ((-1 * k) - 1)); 751 | } 752 | 753 | // TODO: get rid of hardcoding 754 | x += CONFIG_DB_RANGE - 63; 755 | x = CLAMP (x, 0, CONFIG_DB_RANGE); 756 | int color_index = GRADIENT_TABLE_SIZE - ftoi (GRADIENT_TABLE_SIZE/(float)CONFIG_DB_RANGE * x); 757 | color_index = CLAMP (color_index, 0, GRADIENT_TABLE_SIZE-1); 758 | _draw_point (data, stride, width-1, height-1-i, w->colors[color_index]); 759 | } 760 | } 761 | cairo_surface_mark_dirty (w->surf); 762 | 763 | cairo_save (cr); 764 | cairo_set_source_surface (cr, w->surf, 0, 0); 765 | cairo_rectangle (cr, 0, 0, a.width, a.height); 766 | cairo_fill (cr); 767 | cairo_restore (cr); 768 | 769 | return FALSE; 770 | } 771 | 772 | 773 | gboolean 774 | spectrogram_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer user_data) { 775 | cairo_t *cr = gdk_cairo_create (gtk_widget_get_window (widget)); 776 | gboolean res = spectrogram_draw (widget, cr, user_data); 777 | cairo_destroy (cr); 778 | return res; 779 | } 780 | 781 | 782 | gboolean 783 | spectrogram_button_press_event (GtkWidget *widget, GdkEventButton *event, gpointer user_data) 784 | { 785 | //w_spectrogram_t *w = user_data; 786 | if (event->button == 3) { 787 | return TRUE; 788 | } 789 | return TRUE; 790 | } 791 | 792 | gboolean 793 | spectrogram_button_release_event (GtkWidget *widget, GdkEventButton *event, gpointer user_data) 794 | { 795 | w_spectrogram_t *w = user_data; 796 | if (event->button == 3) { 797 | gtk_menu_popup (GTK_MENU (w->popup), NULL, NULL, NULL, w->drawarea, 0, gtk_get_current_event_time ()); 798 | return TRUE; 799 | } 800 | return TRUE; 801 | } 802 | 803 | static gboolean 804 | spectrogram_set_refresh_interval (gpointer user_data, int interval) 805 | { 806 | w_spectrogram_t *w = user_data; 807 | if (!w || interval <= 0) { 808 | return FALSE; 809 | } 810 | if (w->drawtimer) { 811 | g_source_remove (w->drawtimer); 812 | w->drawtimer = 0; 813 | } 814 | w->drawtimer = g_timeout_add (interval, w_spectrogram_draw_cb, w); 815 | return TRUE; 816 | } 817 | 818 | static int 819 | spectrogram_message (ddb_gtkui_widget_t *widget, uint32_t id, uintptr_t ctx, uint32_t p1, uint32_t p2) 820 | { 821 | w_spectrogram_t *w = (w_spectrogram_t *)widget; 822 | 823 | switch (id) { 824 | case DB_EV_CONFIGCHANGED: 825 | on_config_changed (w, ctx); 826 | spectrogram_set_refresh_interval (w, CONFIG_REFRESH_INTERVAL); 827 | break; 828 | case DB_EV_SONGSTARTED: 829 | spectrogram_set_refresh_interval (w, CONFIG_REFRESH_INTERVAL); 830 | break; 831 | case DB_EV_PAUSED: 832 | if (deadbeef->get_output ()->state () == OUTPUT_STATE_PLAYING) { 833 | spectrogram_set_refresh_interval (w, CONFIG_REFRESH_INTERVAL); 834 | } 835 | else { 836 | if (w->drawtimer) { 837 | g_source_remove (w->drawtimer); 838 | w->drawtimer = 0; 839 | } 840 | } 841 | break; 842 | case DB_EV_STOP: 843 | if (w->drawtimer) { 844 | g_source_remove (w->drawtimer); 845 | w->drawtimer = 0; 846 | } 847 | break; 848 | } 849 | return 0; 850 | } 851 | 852 | void 853 | w_spectrogram_init (ddb_gtkui_widget_t *w) { 854 | w_spectrogram_t *s = (w_spectrogram_t *)w; 855 | load_config (); 856 | deadbeef->mutex_lock (s->mutex); 857 | s->samples = malloc (sizeof (double) * FFT_SIZE); 858 | memset (s->samples, 0, sizeof (double) * FFT_SIZE); 859 | s->data = malloc (sizeof (double) * FFT_SIZE); 860 | memset (s->data, 0, sizeof (double) * FFT_SIZE); 861 | if (s->drawtimer) { 862 | g_source_remove (s->drawtimer); 863 | s->drawtimer = 0; 864 | } 865 | s->samplerate = 44100.0; 866 | s->height = 0; 867 | s->low_res_end = 0; 868 | s->log_index = (int *)malloc (sizeof (int) * MAX_HEIGHT); 869 | memset (s->log_index, 0, sizeof (int) * MAX_HEIGHT); 870 | 871 | for (int i = 0; i < FFT_SIZE; i++) { 872 | // Hanning 873 | //s->window[i] = (0.5 * (1 - cos (2 * M_PI * i/(FFT_SIZE-1)))); 874 | // Blackman-Harris 875 | s->window[i] = 0.35875 - 0.48829 * cos(2 * M_PI * i /(FFT_SIZE)) + 0.14128 * cos(4 * M_PI * i/(FFT_SIZE)) - 0.01168 * cos(6 * M_PI * i/(FFT_SIZE));; 876 | } 877 | create_gradient_table (s, CONFIG_GRADIENT_COLORS, CONFIG_NUM_COLORS); 878 | s->in = fftw_malloc (sizeof (double) * FFT_SIZE); 879 | memset (s->in, 0, sizeof (double) * FFT_SIZE); 880 | //s->out_real = fftw_malloc (sizeof (double) * FFT_SIZE); 881 | s->out_complex = fftw_malloc (sizeof (fftw_complex) * FFT_SIZE); 882 | //s->p_r2r = fftw_plan_r2r_1d (FFT_SIZE, s->in, s->out_real, FFTW_R2HC, FFTW_ESTIMATE); 883 | s->p_r2c = fftw_plan_dft_r2c_1d (FFT_SIZE, s->in, s->out_complex, FFTW_ESTIMATE); 884 | spectrogram_set_refresh_interval (s, CONFIG_REFRESH_INTERVAL); 885 | deadbeef->mutex_unlock (s->mutex); 886 | } 887 | 888 | ddb_gtkui_widget_t * 889 | w_spectrogram_create (void) { 890 | w_spectrogram_t *w = malloc (sizeof (w_spectrogram_t)); 891 | memset (w, 0, sizeof (w_spectrogram_t)); 892 | 893 | w->base.widget = gtk_event_box_new (); 894 | w->base.init = w_spectrogram_init; 895 | w->base.destroy = w_spectrogram_destroy; 896 | w->base.message = spectrogram_message; 897 | w->drawarea = gtk_drawing_area_new (); 898 | w->popup = gtk_menu_new (); 899 | w->popup_item = gtk_menu_item_new_with_mnemonic ("Configure"); 900 | w->mutex = deadbeef->mutex_create (); 901 | gtk_widget_show (w->drawarea); 902 | gtk_container_add (GTK_CONTAINER (w->base.widget), w->drawarea); 903 | gtk_widget_show (w->popup); 904 | //gtk_container_add (GTK_CONTAINER (w->drawarea), w->popup); 905 | gtk_widget_show (w->popup_item); 906 | gtk_container_add (GTK_CONTAINER (w->popup), w->popup_item); 907 | #if !GTK_CHECK_VERSION(3,0,0) 908 | g_signal_connect_after ((gpointer) w->drawarea, "expose_event", G_CALLBACK (spectrogram_expose_event), w); 909 | #else 910 | g_signal_connect_after ((gpointer) w->drawarea, "draw", G_CALLBACK (spectrogram_draw), w); 911 | #endif 912 | g_signal_connect_after ((gpointer) w->base.widget, "button_press_event", G_CALLBACK (spectrogram_button_press_event), w); 913 | g_signal_connect_after ((gpointer) w->base.widget, "button_release_event", G_CALLBACK (spectrogram_button_release_event), w); 914 | g_signal_connect_after ((gpointer) w->popup_item, "activate", G_CALLBACK (on_button_config), w); 915 | gtkui_plugin->w_override_signals (w->base.widget, w); 916 | deadbeef->vis_waveform_listen (w, spectrogram_wavedata_listener); 917 | return (ddb_gtkui_widget_t *)w; 918 | } 919 | 920 | int 921 | spectrogram_connect (void) 922 | { 923 | gtkui_plugin = (ddb_gtkui_t *) deadbeef->plug_get_for_id (DDB_GTKUI_PLUGIN_ID); 924 | if (gtkui_plugin) { 925 | //trace("using '%s' plugin %d.%d\n", DDB_GTKUI_PLUGIN_ID, gtkui_plugin->gui.plugin.version_major, gtkui_plugin->gui.plugin.version_minor ); 926 | if (gtkui_plugin->gui.plugin.version_major == 2) { 927 | //printf ("fb api2\n"); 928 | // 0.6+, use the new widget API 929 | gtkui_plugin->w_reg_widget ("Spectrogram", 0, w_spectrogram_create, "spectrogram", NULL); 930 | return 0; 931 | } 932 | } 933 | return -1; 934 | } 935 | 936 | int 937 | spectrogram_start (void) 938 | { 939 | load_config (); 940 | return 0; 941 | } 942 | 943 | int 944 | spectrogram_stop (void) 945 | { 946 | save_config (); 947 | return 0; 948 | } 949 | 950 | int 951 | spectrogram_startup (GtkWidget *cont) 952 | { 953 | return 0; 954 | } 955 | 956 | int 957 | spectrogram_shutdown (GtkWidget *cont) 958 | { 959 | return 0; 960 | } 961 | int 962 | spectrogram_disconnect (void) 963 | { 964 | gtkui_plugin = NULL; 965 | return 0; 966 | } 967 | 968 | static const char settings_dlg[] = 969 | "property \"Refresh interval (ms): \" spinbtn[10,1000,1] " CONFSTR_SP_REFRESH_INTERVAL " 25 ;\n" 970 | ; 971 | 972 | static DB_misc_t plugin = { 973 | //DB_PLUGIN_SET_API_VERSION 974 | .plugin.type = DB_PLUGIN_MISC, 975 | .plugin.api_vmajor = 1, 976 | .plugin.api_vminor = 5, 977 | .plugin.version_major = 0, 978 | .plugin.version_minor = 1, 979 | #if GTK_CHECK_VERSION(3,0,0) 980 | .plugin.id = "spectrogram-gtk3", 981 | #else 982 | .plugin.id = "spectrogram", 983 | #endif 984 | .plugin.name = "Spectrogram", 985 | .plugin.descr = "Spectrogram", 986 | .plugin.copyright = 987 | "Copyright (C) 2013 Christian Boxdörfer \n" 988 | "\n" 989 | "This program is free software; you can redistribute it and/or\n" 990 | "modify it under the terms of the GNU General Public License\n" 991 | "as published by the Free Software Foundation; either version 2\n" 992 | "of the License, or (at your option) any later version.\n" 993 | "\n" 994 | "This program is distributed in the hope that it will be useful,\n" 995 | "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" 996 | "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" 997 | "GNU General Public License for more details.\n" 998 | "\n" 999 | "You should have received a copy of the GNU General Public License\n" 1000 | "along with this program; if not, write to the Free Software\n" 1001 | "Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n" 1002 | , 1003 | .plugin.website = "https://github.com/cboxdoerfer/ddb_spectrogram", 1004 | .plugin.start = spectrogram_start, 1005 | .plugin.stop = spectrogram_stop, 1006 | .plugin.connect = spectrogram_connect, 1007 | .plugin.disconnect = spectrogram_disconnect, 1008 | .plugin.configdialog = settings_dlg, 1009 | }; 1010 | 1011 | #if !GTK_CHECK_VERSION(3,0,0) 1012 | DB_plugin_t * 1013 | ddb_vis_spectrogram_GTK2_load (DB_functions_t *ddb) { 1014 | deadbeef = ddb; 1015 | return &plugin.plugin; 1016 | } 1017 | #else 1018 | DB_plugin_t * 1019 | ddb_vis_spectrogram_GTK3_load (DB_functions_t *ddb) { 1020 | deadbeef = ddb; 1021 | return &plugin.plugin; 1022 | } 1023 | #endif 1024 | -------------------------------------------------------------------------------- /userinstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | /bin/mkdir -pv ${HOME}/.local/lib/deadbeef 3 | ## GTK2 version 4 | if [ -f ./gtk2/ddb_vis_spectrogram_GTK2.so ]; then 5 | /usr/bin/install -v -c -m 644 ./gtk2/ddb_vis_spectrogram_GTK2.so ${HOME}/.local/lib/deadbeef/ 6 | else 7 | /usr/bin/install -v -c -m 644 ./ddb_vis_spectrogram_GTK2.so ${HOME}/.local/lib/deadbeef/ 8 | fi 9 | ## GTK3 version 10 | if [ -f ./gtk3/ddb_vis_spectrogram_GTK3.so ]; then 11 | /usr/bin/install -v -c -m 644 ./gtk3/ddb_vis_spectrogram_GTK3.so ${HOME}/.local/lib/deadbeef/ 12 | else 13 | /usr/bin/install -v -c -m 644 ./ddb_vis_spectrogram_GTK3.so ${HOME}/.local/lib/deadbeef/ 14 | fi 15 | CHECK_PATHS="/usr/local/lib/deadbeef /usr/lib/deadbeef" 16 | for path in $CHECK_PATHS; do 17 | if [ -d $path ]; then 18 | if [ -f $path/ddb_vis_spectrogram_GTK2.so -o -f $path/ddb_vis_spectrogram_GTK3.so ]; then 19 | echo "Warning: Some version of the spectrogram plugin is present in $path, you should remove it to avoid conflicts!" 20 | fi 21 | fi 22 | done 23 | -------------------------------------------------------------------------------- /userremove.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | rm -fv ${HOME}/.local/lib/deadbeef/ddb_vis_spectrogram.so 4 | rm -fv ${HOME}/.local/lib/deadbeef/ddb_vis_spectrogram_GTK2.so 5 | rm -fv ${HOME}/.local/lib/deadbeef/ddb_vis_spectrogram_GTK3.so 6 | --------------------------------------------------------------------------------