├── userremove.sh ├── .gitignore ├── .github └── FUNDING.yml ├── userinstall.sh ├── ruler.h ├── PKGBUILD ├── PKGBUILD_release ├── draw_utils.c ├── draw_utils.h ├── config_dialog.h ├── utils.h ├── support.c ├── support.h ├── cache.h ├── render.h ├── waveform.h ├── README.md ├── utils.c ├── Makefile ├── config.h ├── cache.c ├── config.c ├── ruler.c ├── config_dialog.c ├── render.c └── waveform.c /userremove.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | rm -fv ${HOME}/.local/lib/deadbeef/ddb_misc_waveform.so 4 | rm -fv ${HOME}/.local/lib/deadbeef/ddb_misc_waveform_GTK2.so 5 | rm -fv ${HOME}/.local/lib/deadbeef/ddb_misc_waveform_GTK3.so -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.config 2 | *.creator 3 | *.files 4 | *.user 5 | *.vim 6 | *.py 7 | *.pyc 8 | *.includes 9 | *.o 10 | *.bak 11 | *~ 12 | *.tar.gz 13 | *.swp 14 | configure 15 | Makefile.in 16 | *.zip 17 | *.log 18 | *.guess 19 | tags 20 | .vimrc 21 | *.tar.bz2 22 | *.tar.gz 23 | *.so 24 | valgrind.out 25 | *.diff 26 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: cboxdoerfer # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /userinstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | /bin/mkdir -pv ${HOME}/.local/lib/deadbeef 3 | ## GTK2 version 4 | if [ -f ./gtk2/ddb_misc_waveform_GTK2.so ]; then 5 | /usr/bin/install -v -c -m 644 ./gtk2/ddb_misc_waveform_GTK2.so ${HOME}/.local/lib/deadbeef/ 6 | else 7 | /usr/bin/install -v -c -m 644 ./ddb_misc_waveform_GTK2.so ${HOME}/.local/lib/deadbeef/ 8 | fi 9 | ## GTK3 version 10 | if [ -f ./gtk3/ddb_misc_waveform_GTK3.so ]; then 11 | /usr/bin/install -v -c -m 644 ./gtk3/ddb_misc_waveform_GTK3.so ${HOME}/.local/lib/deadbeef/ 12 | else 13 | /usr/bin/install -v -c -m 644 ./ddb_misc_waveform_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_misc_waveform_GTK2.so -o -f $path/ddb_misc_waveform_GTK3.so ]; then 19 | echo "Warning: Some version of the waveform plugin is already present in $path. You should remove it to avoid conflicts!" 20 | fi 21 | fi 22 | done 23 | -------------------------------------------------------------------------------- /ruler.h: -------------------------------------------------------------------------------- 1 | /* 2 | Waveform seekbar plugin for the DeaDBeeF audio player 3 | 4 | Copyright (C) 2017 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 | #pragma once 22 | 23 | #include 24 | #include 25 | 26 | #include "waveform.h" 27 | 28 | void 29 | waveform_render_ruler (cairo_t *cr_ctx, waveform_colors_t *color, float duration, waveform_rect_t *rect); 30 | 31 | -------------------------------------------------------------------------------- /PKGBUILD: -------------------------------------------------------------------------------- 1 | pkgname=deadbeef-plugin-waveform-git 2 | pkgver=20140803 3 | pkgrel=1 4 | pkgdesc="Waveform Seekbar Plugin for the DeaDBeeF audio player (development version)" 5 | url="https://github.com/cboxdoerfer/ddb_waveform_seekbar" 6 | arch=('i686' 'x86_64') 7 | license='GPL2' 8 | depends=('deadbeef' 'sqlite' 'gtk2') 9 | makedepends=('git' 'pkg-config') 10 | conflicts=('deadbeef-plugin-waveform') 11 | 12 | _gitname=ddb_waveform_seekbar 13 | _gitroot=https://github.com/cboxdoerfer/${_gitname} 14 | 15 | build() { 16 | cd $srcdir 17 | msg "Connecting to GIT server..." 18 | rm -rf $srcdir/$_gitname-build 19 | 20 | if [ -d $_gitname ]; then 21 | cd $_gitname 22 | git pull origin master 23 | else 24 | git clone $_gitroot 25 | cd $_gitname 26 | fi 27 | 28 | msg "GIT checkout done or server timeout" 29 | msg "Starting make..." 30 | 31 | cd $srcdir 32 | cp -r $_gitname $_gitname-build 33 | 34 | cd $_gitname-build 35 | 36 | touch AUTHORS 37 | touch ChangeLog 38 | 39 | make 40 | } 41 | 42 | package() { 43 | install -D -v -c $srcdir/$_gitname-build/gtk2/ddb_misc_waveform_GTK2.so $pkgdir/usr/lib/deadbeef/ddb_misc_waveform_GTK2.so 44 | install -D -v -c $srcdir/$_gitname-build/gtk3/ddb_misc_waveform_GTK3.so $pkgdir/usr/lib/deadbeef/ddb_misc_waveform_GTK3.so 45 | } 46 | 47 | -------------------------------------------------------------------------------- /PKGBUILD_release: -------------------------------------------------------------------------------- 1 | pkgname=deadbeef-plugin-waveform 2 | pkgver=0.5 3 | pkgrel=3 4 | pkgdesc="Waveform Seekbar Plugin for the DeaDBeeF audio player" 5 | url="https://github.com/cboxdoerfer/ddb_waveform_seekbar" 6 | arch=('i686' 'x86_64') 7 | license='GPL2' 8 | depends=('deadbeef' 'sqlite' 'gtk2') 9 | makedepends=('git' 'pkg-config') 10 | conflicts=('deadbeef-plugin-waveform-git') 11 | 12 | _gitname=ddb_waveform_seekbar 13 | _gitroot=https://github.com/cboxdoerfer/${_gitname} 14 | 15 | build() { 16 | cd $srcdir 17 | msg "Connecting to GIT server..." 18 | rm -rf $srcdir/$_gitname-build 19 | 20 | if [ -d $_gitname ]; then 21 | cd $_gitname 22 | git pull origin v$pkgver 23 | else 24 | git clone $_gitroot 25 | cd $_gitname 26 | fi 27 | 28 | git checkout v$pkgver 29 | 30 | msg "GIT checkout done or server timeout" 31 | msg "Starting make..." 32 | 33 | cd $srcdir 34 | cp -r $_gitname $_gitname-build 35 | 36 | cd $_gitname-build 37 | 38 | touch AUTHORS 39 | touch ChangeLog 40 | 41 | make 42 | } 43 | 44 | package() { 45 | install -D -v -c $srcdir/$_gitname-build/gtk2/ddb_misc_waveform_GTK2.so $pkgdir/usr/lib/deadbeef/ddb_misc_waveform_GTK2.so 46 | install -D -v -c $srcdir/$_gitname-build/gtk3/ddb_misc_waveform_GTK3.so $pkgdir/usr/lib/deadbeef/ddb_misc_waveform_GTK3.so 47 | } 48 | 49 | 50 | -------------------------------------------------------------------------------- /draw_utils.c: -------------------------------------------------------------------------------- 1 | /* 2 | Waveform seekbar plugin for the DeaDBeeF audio player 3 | 4 | Copyright (C) 2014 Christian Boxdörfer 5 | 6 | Based on sndfile-tools waveform by Erik de Castro Lopo. 7 | waveform.c - v1.04 8 | Copyright (C) 2007-2012 Erik de Castro Lopo 9 | Copyright (C) 2012 Robin Gareus 10 | Copyright (C) 2013 driedfruit 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 program is distributed in the hope that it will be useful, 18 | but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | GNU General Public License for more details. 21 | 22 | You should have received a copy of the GNU General Public License 23 | along with this program; if not, write to the Free Software 24 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | -------------------------------------------------------------------------------- /draw_utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | Waveform seekbar plugin for the DeaDBeeF audio player 3 | 4 | Copyright (C) 2014 Christian Boxdörfer 5 | 6 | Based on sndfile-tools waveform by Erik de Castro Lopo. 7 | waveform.c - v1.04 8 | Copyright (C) 2007-2012 Erik de Castro Lopo 9 | Copyright (C) 2012 Robin Gareus 10 | Copyright (C) 2013 driedfruit 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 program is distributed in the hope that it will be useful, 18 | but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | GNU General Public License for more details. 21 | 22 | You should have received a copy of the GNU General Public License 23 | along with this program; if not, write to the Free Software 24 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | 37 | -------------------------------------------------------------------------------- /config_dialog.h: -------------------------------------------------------------------------------- 1 | /* 2 | Waveform seekbar plugin for the DeaDBeeF audio player 3 | 4 | Copyright (C) 2014 Christian Boxdörfer 5 | 6 | Based on sndfile-tools waveform by Erik de Castro Lopo. 7 | waveform.c - v1.04 8 | Copyright (C) 2007-2012 Erik de Castro Lopo 9 | Copyright (C) 2012 Robin Gareus 10 | Copyright (C) 2013 driedfruit 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 program is distributed in the hope that it will be useful, 18 | but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | GNU General Public License for more details. 21 | 22 | You should have received a copy of the GNU General Public License 23 | along with this program; if not, write to the Free Software 24 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 25 | */ 26 | 27 | #ifndef CONFIG_DIALOG_HEADER 28 | #define CONFIG_DIALOG_HEADER 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | 36 | void 37 | on_button_config (GtkMenuItem *menuitem, gpointer user_data); 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | Waveform seekbar plugin for the DeaDBeeF audio player 3 | 4 | Copyright (C) 2014 Christian Boxdörfer 5 | 6 | Based on sndfile-tools waveform by Erik de Castro Lopo. 7 | waveform.c - v1.04 8 | Copyright (C) 2007-2012 Erik de Castro Lopo 9 | Copyright (C) 2012 Robin Gareus 10 | Copyright (C) 2013 driedfruit 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 program is distributed in the hope that it will be useful, 18 | but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | GNU General Public License for more details. 21 | 22 | You should have received a copy of the GNU General Public License 23 | along with this program; if not, write to the Free Software 24 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 25 | */ 26 | 27 | #ifndef UTILS_HEADER 28 | #define UTILS_HEADER 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | int 39 | queue_add (const char *fname); 40 | 41 | void 42 | queue_pop (const char *fname); 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /support.c: -------------------------------------------------------------------------------- 1 | /* 2 | Waveform seekbar plugin for the DeaDBeeF audio player 3 | 4 | Copyright (C) 2017 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 "support.h" 22 | 23 | #if !GTK_CHECK_VERSION(2,18,0) 24 | void 25 | gtk_widget_set_allocation (GtkWidget *widget, const GtkAllocation *allocation) { 26 | widget->allocation.x = (allocation)->x; 27 | widget->allocation.y = (allocation)->y; 28 | widget->allocation.width = (allocation)->width; 29 | widget->allocation.height = (allocation)->height; 30 | } 31 | 32 | void 33 | gtk_widget_get_allocation (GtkWidget *widget, GtkAllocation *allocation) { 34 | (allocation)->x = widget->allocation.x; 35 | (allocation)->y = widget->allocation.y; 36 | (allocation)->width = widget->allocation.width; 37 | (allocation)->height = widget->allocation.height; 38 | } 39 | #endif 40 | -------------------------------------------------------------------------------- /support.h: -------------------------------------------------------------------------------- 1 | /* 2 | Waveform seekbar plugin for the DeaDBeeF audio player 3 | 4 | Copyright (C) 2017 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 | #pragma once 22 | 23 | #include 24 | 25 | #if !GTK_CHECK_VERSION(2,14,0) 26 | #define gtk_widget_get_window(widget) ((widget)->window) 27 | #define gtk_dialog_get_content_area(dialog) (dialog->vbox) 28 | #define gtk_dialog_get_action_area(dialog) (dialog->action_area) 29 | #endif 30 | 31 | #if !GTK_CHECK_VERSION(2,18,0) 32 | void gtk_widget_set_allocation (GtkWidget *widget, 33 | const GtkAllocation *allocation); 34 | 35 | void gtk_widget_get_allocation (GtkWidget *widget, 36 | GtkAllocation *allocation); 37 | 38 | #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);} 39 | #endif 40 | -------------------------------------------------------------------------------- /cache.h: -------------------------------------------------------------------------------- 1 | /* 2 | Waveform seekbar plugin for the DeaDBeeF audio player 3 | 4 | Copyright (C) 2014 Christian Boxdörfer 5 | 6 | Based on sndfile-tools waveform by Erik de Castro Lopo. 7 | waveform.c - v1.04 8 | Copyright (C) 2007-2012 Erik de Castro Lopo 9 | Copyright (C) 2012 Robin Gareus 10 | Copyright (C) 2013 driedfruit 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 program is distributed in the hope that it will be useful, 18 | but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | GNU General Public License for more details. 21 | 22 | You should have received a copy of the GNU General Public License 23 | along with this program; if not, write to the Free Software 24 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | void 37 | waveform_db_open (const char *fname); 38 | 39 | void 40 | waveform_db_close (); 41 | 42 | void 43 | waveform_db_init (char const *fname); 44 | 45 | int 46 | waveform_db_cached (char const *fname); 47 | 48 | int 49 | waveform_db_delete (char const *fname); 50 | 51 | int 52 | waveform_db_read (char const *fname, short *buffer, int buffer_len, int *channels); 53 | 54 | void 55 | waveform_db_write (char const *fname, short *buffer, int buffer_len, int channels, int compression); 56 | -------------------------------------------------------------------------------- /render.h: -------------------------------------------------------------------------------- 1 | /* 2 | Waveform seekbar plugin for the DeaDBeeF audio player 3 | 4 | Copyright (C) 2017 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 | #pragma once 22 | 23 | #include 24 | #include "waveform.h" 25 | 26 | typedef struct { 27 | float max; 28 | float min; 29 | float rms; 30 | } waveform_sample_t; 31 | 32 | typedef struct { 33 | waveform_sample_t **samples; 34 | int num_channels; 35 | // samples per channel 36 | int num_samples; 37 | } waveform_data_render_t; 38 | 39 | void 40 | waveform_data_render_free (waveform_data_render_t *w_render_ctx); 41 | 42 | waveform_data_render_t * 43 | waveform_render_data_build (wavedata_t *wave_data, int width, bool downmix_mono); 44 | 45 | void 46 | waveform_draw_wave_default (waveform_sample_t *samples, 47 | waveform_colors_t *colors, 48 | cairo_t *cr_ctx, 49 | waveform_rect_t *rect); 50 | 51 | void 52 | waveform_draw_wave_bars (waveform_sample_t *samples, 53 | waveform_colors_t *colors, 54 | cairo_t *cr_ctx, 55 | waveform_rect_t *rect); 56 | -------------------------------------------------------------------------------- /waveform.h: -------------------------------------------------------------------------------- 1 | /* 2 | Waveform seekbar plugin for the DeaDBeeF audio player 3 | 4 | Copyright (C) 2014 Christian Boxdörfer 5 | 6 | Based on sndfile-tools waveform by Erik de Castro Lopo. 7 | waveform.c - v1.04 8 | Copyright (C) 2007-2012 Erik de Castro Lopo 9 | Copyright (C) 2012 Robin Gareus 10 | Copyright (C) 2013 driedfruit 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 program is distributed in the hope that it will be useful, 18 | but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | GNU General Public License for more details. 21 | 22 | You should have received a copy of the GNU General Public License 23 | along with this program; if not, write to the Free Software 24 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 25 | */ 26 | 27 | #pragma once 28 | 29 | //#define trace(...) { fprintf(stderr, __VA_ARGS__); } 30 | #define trace(fmt,...) 31 | 32 | #include 33 | 34 | extern DB_functions_t *deadbeef; 35 | 36 | typedef struct wavedata_s 37 | { 38 | char *fname; 39 | short *data; 40 | size_t data_len; 41 | int channels; 42 | } wavedata_t; 43 | 44 | typedef struct color_s 45 | { 46 | double r; 47 | double g; 48 | double b; 49 | double a; 50 | } color_t; 51 | 52 | typedef struct 53 | { 54 | double x; 55 | double y; 56 | double width; 57 | double height; 58 | } waveform_rect_t; 59 | 60 | typedef struct waveform_colors_s 61 | { 62 | color_t fg, rms, bg, pb, rlr, font, font_pb; 63 | } waveform_colors_t; 64 | 65 | enum STYLE { BARS = 1, SPIKES = 2 }; 66 | 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://drone.io/github.com/cboxdoerfer/ddb_waveform_seekbar/status.png)](https://drone.io/github.com/cboxdoerfer/ddb_waveform_seekbar/latest) 2 | 3 | Waveform Seekbar plugin for DeaDBeeF audio player 4 | ==================== 5 | 6 | ## Table of Contents 7 | 8 | * [Installation](#installation) 9 | * [Arch Linux](#arch-linux) 10 | * [Gentoo](#gentoo) 11 | * [Binaries](#binaries) 12 | * [Stable](#stable) 13 | * [Dev](#dev) 14 | * [Compilation](#compilation) 15 | * [Usage](#usage) 16 | * [Screenshots](%screenshots) 17 | 18 | ## Installation 19 | ### Arch Linux 20 | See the [AUR](https://aur.archlinux.org/packages/deadbeef-plugin-waveform-git/). 21 | ### Gentoo 22 | See ebuilds [here](https://github.com/megabaks/stuff/tree/master/media-plugins/deadbeef-waveform-seekbar). 23 | ### Binaries 24 | Install them as follows: 25 | 26 | x86_64: ```tar -xvf ddb_waveform_seekbar_x86_64.tar.gz -C ~/.local/lib/deadbeef``` 27 | 28 | i686: ```tar -xvf ddb_waveform_seekbar_i686.tar.gz -C ~/.local/lib/deadbeef``` 29 | #### Stable 30 | [x86_64](https://github.com/cboxdoerfer/ddb_waveform_seekbar/releases/download/v0.4/ddb_waveform_seekbar_x86_64.tar.gz) 31 | 32 | [i686](https://github.com/cboxdoerfer/ddb_waveform_seekbar/releases/download/v0.4/ddb_waveform_seekbar_i686.tar.gz) 33 | #### Dev 34 | [x86_64](https://drone.io/github.com/cboxdoerfer/ddb_waveform_seekbar/files/deadbeef-plugin-builder/ddb_waveform_seekbar_x86_64.tar.gz) 35 | 36 | [i686](https://drone.io/github.com/cboxdoerfer/ddb_waveform_seekbar/files/deadbeef-plugin-builder/ddb_waveform_seekbar_i686.tar.gz) 37 | 38 | ### Compilation 39 | You need DeaDBeeF (>=0.6) and sqlite3 and their development files 40 | ```bash 41 | make 42 | ./userinstall.sh 43 | ``` 44 | ## Usage 45 | Add it to your Layout with Design Mode (Edit -> Design Mode -> right click in player UI). 46 | 47 | There are two settings dialogs: 48 | 49 | Right click on waveform and select Configure 50 | 51 | and 52 | 53 | Edit -> Preferences -> Plugins -> Waveform Seekbar -> Configure 54 | 55 | ## Screenshots 56 | ### Waveform 57 | ![](http://i.imgur.com/V4n2kQC.png) 58 | -------------------------------------------------------------------------------- /utils.c: -------------------------------------------------------------------------------- 1 | /* 2 | Waveform seekbar plugin for the DeaDBeeF audio player 3 | 4 | Copyright (C) 2014 Christian Boxdörfer 5 | 6 | Based on sndfile-tools waveform by Erik de Castro Lopo. 7 | waveform.c - v1.04 8 | Copyright (C) 2007-2012 Erik de Castro Lopo 9 | Copyright (C) 2012 Robin Gareus 10 | Copyright (C) 2013 driedfruit 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 program is distributed in the hope that it will be useful, 18 | but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | GNU General Public License for more details. 21 | 22 | You should have received a copy of the GNU General Public License 23 | along with this program; if not, write to the Free Software 24 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include 36 | 37 | #include "waveform.h" 38 | #include "utils.h" 39 | 40 | typedef struct cache_query_s 41 | { 42 | char *fname; 43 | struct cache_query_s *next; 44 | } cache_query_t; 45 | 46 | static uintptr_t mutex = 0; 47 | static cache_query_t *queue; 48 | static cache_query_t *queue_tail; 49 | 50 | int 51 | queue_add (const char *fname) 52 | { 53 | if (!mutex) { 54 | mutex = deadbeef->mutex_create (); 55 | } 56 | deadbeef->mutex_lock (mutex); 57 | for (cache_query_t *q = queue; q; q = q->next) { 58 | if (!strcmp (fname, q->fname)) { 59 | // already queued 60 | trace ("waveform: already queued. (%s)\n",fname); 61 | deadbeef->mutex_unlock (mutex); 62 | return 0; 63 | } 64 | } 65 | cache_query_t *q = malloc (sizeof (cache_query_t)); 66 | memset (q, 0, sizeof (cache_query_t)); 67 | q->fname = strdup (fname); 68 | if (queue_tail) { 69 | queue_tail->next = q; 70 | queue_tail = q; 71 | } 72 | else { 73 | queue = queue_tail = q; 74 | } 75 | trace ("waveform: queued. (%s)\n",fname); 76 | deadbeef->mutex_unlock (mutex); 77 | return 1; 78 | } 79 | 80 | void 81 | queue_pop (const char *fname) 82 | { 83 | deadbeef->mutex_lock (mutex); 84 | cache_query_t *next = NULL; 85 | for (cache_query_t *q = queue; q; q = q->next) { 86 | if (!strcmp (fname, q->fname)) { 87 | next = q->next; 88 | if (q->fname) { 89 | trace ("waveform: removed from queue. (%s)\n",q->fname); 90 | free (q->fname); 91 | } 92 | free (q); 93 | break; 94 | } 95 | } 96 | queue = next; 97 | if (!queue) { 98 | queue_tail = NULL; 99 | } 100 | deadbeef->mutex_unlock (mutex); 101 | } 102 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | # Waveform seekbar plugin for the DeaDBeeF audio player 3 | # 4 | # Copyright (C) 2013 Christian Boxdörfer 5 | # 6 | # Based on sndfile-tools waveform by Erik de Castro Lopo. 7 | # waveform.c - v1.04 8 | # Copyright (C) 2007-2012 Erik de Castro Lopo 9 | # Copyright (C) 2012 Robin Gareus 10 | # Copyright (C) 2013 driedfruit 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 program is distributed in the hope that it will be useful, 18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | # GNU General Public License for more details. 21 | # 22 | # You should have received a copy of the GNU General Public License 23 | # along with this program; if not, write to the Free Software 24 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 25 | 26 | 27 | OUT_GTK2?=ddb_misc_waveform_GTK2.so 28 | OUT_GTK3?=ddb_misc_waveform_GTK3.so 29 | 30 | GTK2_CFLAGS?=`pkg-config --cflags gtk+-2.0` 31 | GTK3_CFLAGS?=`pkg-config --cflags gtk+-3.0` 32 | 33 | GTK2_LIBS?=`pkg-config --libs gtk+-2.0` 34 | GTK3_LIBS?=`pkg-config --libs gtk+-3.0` 35 | 36 | SQLITE_LIBS?=-lsqlite3 37 | 38 | CC?=gcc 39 | CFLAGS+=-Wall -O2 -g -fPIC -std=c99 -D_GNU_SOURCE 40 | LDFLAGS+=-shared 41 | 42 | GTK2_DIR?=gtk2 43 | GTK3_DIR?=gtk3 44 | 45 | SOURCES?=$(wildcard *.c) 46 | OBJ_GTK2?=$(patsubst %.c, $(GTK2_DIR)/%.o, $(SOURCES)) 47 | OBJ_GTK3?=$(patsubst %.c, $(GTK3_DIR)/%.o, $(SOURCES)) 48 | 49 | define compile 50 | $(CC) $(CFLAGS) $1 $2 $< -c -o $@ 51 | endef 52 | 53 | define compile_debug 54 | echo $(CC) $(CFLAGS) $1 $2 $< -c -o $@ 55 | $(CC) $(CFLAGS) $1 $2 $< -c -o $@ 56 | endef 57 | 58 | define link 59 | $(CC) $(LDFLAGS) $1 $2 $3 -o $@ 60 | endef 61 | 62 | define link_debug 63 | echo $(CC) $(LDFLAGS) $1 $2 $3 -o $@ 64 | $(CC) $(LDFLAGS) $1 $2 $3 -o $@ 65 | endef 66 | 67 | # Builds both GTK+2 and GTK+3 versions of the plugin. 68 | all: gtk2 gtk3 69 | 70 | # Builds GTK+2 version of the plugin. 71 | gtk2: mkdir_gtk2 $(SOURCES) $(GTK2_DIR)/$(OUT_GTK2) 72 | 73 | # Builds GTK+3 version of the plugin. 74 | gtk3: mkdir_gtk3 $(SOURCES) $(GTK3_DIR)/$(OUT_GTK3) 75 | 76 | mkdir_gtk2: 77 | @echo "Creating build directory for GTK+2 version" 78 | @mkdir -p $(GTK2_DIR) 79 | 80 | mkdir_gtk3: 81 | @echo "Creating build directory for GTK+3 version" 82 | @mkdir -p $(GTK3_DIR) 83 | 84 | $(GTK2_DIR)/$(OUT_GTK2): $(OBJ_GTK2) 85 | @echo "Linking GTK+2 version" 86 | @$(call link, $(OBJ_GTK2), $(GTK2_LIBS), $(SQLITE_LIBS)) 87 | @echo "Done!" 88 | 89 | $(GTK3_DIR)/$(OUT_GTK3): $(OBJ_GTK3) 90 | @echo "Linking GTK+3 version" 91 | @$(call link, $(OBJ_GTK3), $(GTK3_LIBS), $(SQLITE_LIBS)) 92 | @echo "Done!" 93 | 94 | $(GTK2_DIR)/%.o: %.c 95 | @echo "Compiling $(subst $(GTK2_DIR)/,,$@)" 96 | @$(call compile, $(GTK2_CFLAGS)) 97 | 98 | $(GTK3_DIR)/%.o: %.c 99 | @echo "Compiling $(subst $(GTK3_DIR)/,,$@)" 100 | @$(call compile, $(GTK3_CFLAGS)) 101 | 102 | clean: 103 | @echo "Cleaning files from previous build..." 104 | @rm -r -f $(GTK2_DIR) $(GTK3_DIR) 105 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | /* 2 | Waveform seekbar plugin for the DeaDBeeF audio player 3 | 4 | Copyright (C) 2014 Christian Boxdörfer 5 | 6 | Based on sndfile-tools waveform by Erik de Castro Lopo. 7 | waveform.c - v1.04 8 | Copyright (C) 2007-2012 Erik de Castro Lopo 9 | Copyright (C) 2012 Robin Gareus 10 | Copyright (C) 2013 driedfruit 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 program is distributed in the hope that it will be useful, 18 | but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | GNU General Public License for more details. 21 | 22 | You should have received a copy of the GNU General Public License 23 | along with this program; if not, write to the Free Software 24 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 25 | */ 26 | 27 | #pragma once 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | #include 39 | #include "cache.h" 40 | 41 | #define CONFSTR_WF_LOG_ENABLED "waveform.log_enabled" 42 | #define CONFSTR_WF_MIX_TO_MONO "waveform.mix_to_mono" 43 | #define CONFSTR_WF_DISPLAY_RMS "waveform.display_rms" 44 | #define CONFSTR_WF_DISPLAY_RULER "waveform.display_ruler" 45 | #define CONFSTR_WF_RENDER_METHOD "waveform.render_method" 46 | #define CONFSTR_WF_FILL_WAVEFORM "waveform.fill_waveform" 47 | #define CONFSTR_WF_SOUNDCLOUD_STYLE "waveform.soundcloud_style" 48 | #define CONFSTR_WF_SHADE_WAVEFORM "waveform.shade_waveform" 49 | #define CONFSTR_WF_BG_COLOR_R "waveform.bg_color_r" 50 | #define CONFSTR_WF_BG_COLOR_G "waveform.bg_color_g" 51 | #define CONFSTR_WF_BG_COLOR_B "waveform.bg_color_b" 52 | #define CONFSTR_WF_BG_ALPHA "waveform.bg_alpha" 53 | #define CONFSTR_WF_FG_COLOR_R "waveform.fg_color_r" 54 | #define CONFSTR_WF_FG_COLOR_G "waveform.fg_color_g" 55 | #define CONFSTR_WF_FG_COLOR_B "waveform.fg_color_b" 56 | #define CONFSTR_WF_FG_ALPHA "waveform.fg_alpha" 57 | #define CONFSTR_WF_PB_COLOR_R "waveform.pb_color_r" 58 | #define CONFSTR_WF_PB_COLOR_G "waveform.pb_color_g" 59 | #define CONFSTR_WF_PB_COLOR_B "waveform.pb_color_b" 60 | #define CONFSTR_WF_PB_ALPHA "waveform.pb_alpha" 61 | #define CONFSTR_WF_RLR_COLOR_R "waveform.rlr_color_r" 62 | #define CONFSTR_WF_RLR_COLOR_G "waveform.rlr_color_g" 63 | #define CONFSTR_WF_RLR_COLOR_B "waveform.rlr_color_b" 64 | #define CONFSTR_WF_RLR_ALPHA "waveform.rlr_alpha" 65 | #define CONFSTR_WF_FG_RMS_COLOR_R "waveform.fg_rms_color_r" 66 | #define CONFSTR_WF_FG_RMS_COLOR_G "waveform.fg_rms_color_g" 67 | #define CONFSTR_WF_FG_RMS_COLOR_B "waveform.fg_rms_color_b" 68 | #define CONFSTR_WF_FG_RMS_ALPHA "waveform.fg_rms_alpha" 69 | 70 | #define CONFSTR_WF_REFRESH_INTERVAL "waveform.refresh_interval" 71 | #define CONFSTR_WF_BORDER_WIDTH "waveform.border_width" 72 | #define CONFSTR_WF_CURSOR_WIDTH "waveform.cursor_width" 73 | #define CONFSTR_WF_FONT_SIZE "waveform.font_size" 74 | #define CONFSTR_WF_MAX_FILE_LENGTH "waveform.max_file_length" 75 | #define CONFSTR_WF_CACHE_ENABLED "waveform.cache_enabled" 76 | #define CONFSTR_WF_SCROLL_ENABLED "waveform.scroll_enabled" 77 | #define CONFSTR_WF_NUM_SAMPLES "waveform.num_samples" 78 | 79 | extern gboolean CONFIG_LOG_ENABLED; 80 | extern gboolean CONFIG_MIX_TO_MONO; 81 | extern gboolean CONFIG_CACHE_ENABLED; 82 | extern gboolean CONFIG_SCROLL_ENABLED; 83 | extern gboolean CONFIG_DISPLAY_RMS; 84 | extern gboolean CONFIG_DISPLAY_RULER; 85 | extern gboolean CONFIG_SHADE_WAVEFORM; 86 | extern gboolean CONFIG_SOUNDCLOUD_STYLE; 87 | extern GdkColor CONFIG_BG_COLOR; 88 | extern GdkColor CONFIG_FG_COLOR; 89 | extern GdkColor CONFIG_PB_COLOR; 90 | extern GdkColor CONFIG_RLR_COLOR; 91 | extern GdkColor CONFIG_FG_RMS_COLOR; 92 | extern guint16 CONFIG_BG_ALPHA; 93 | extern guint16 CONFIG_FG_ALPHA; 94 | extern guint16 CONFIG_PB_ALPHA; 95 | extern guint16 CONFIG_RLR_ALPHA; 96 | extern guint16 CONFIG_FG_RMS_ALPHA; 97 | extern gint CONFIG_RENDER_METHOD; 98 | extern gint CONFIG_FILL_WAVEFORM; 99 | extern gint CONFIG_BORDER_WIDTH; 100 | extern gint CONFIG_CURSOR_WIDTH; 101 | extern gint CONFIG_FONT_SIZE; 102 | extern gint CONFIG_MAX_FILE_LENGTH; 103 | extern gint CONFIG_NUM_SAMPLES; 104 | extern gint CONFIG_REFRESH_INTERVAL; 105 | 106 | 107 | void 108 | save_config (void); 109 | 110 | void 111 | load_config (void); 112 | 113 | -------------------------------------------------------------------------------- /cache.c: -------------------------------------------------------------------------------- 1 | /* 2 | Waveform seekbar plugin for the DeaDBeeF audio player 3 | 4 | Copyright (C) 2014 Christian Boxdörfer 5 | 6 | Based on sndfile-tools waveform by Erik de Castro Lopo. 7 | waveform.c - v1.04 8 | Copyright (C) 2007-2012 Erik de Castro Lopo 9 | Copyright (C) 2012 Robin Gareus 10 | Copyright (C) 2013 driedfruit 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 program is distributed in the hope that it will be useful, 18 | but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | GNU General Public License for more details. 21 | 22 | You should have received a copy of the GNU General Public License 23 | along with this program; if not, write to the Free Software 24 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 25 | */ 26 | 27 | #include "cache.h" 28 | 29 | static sqlite3 *db; 30 | 31 | void 32 | waveform_db_open (const char* path) 33 | { 34 | sqlite3_close(db); 35 | char db_path[1024] = ""; 36 | snprintf (db_path, sizeof(db_path)/sizeof (char), "%s/%s", path, "wavecache.db"); 37 | int rc = sqlite3_open(db_path, &db); 38 | if (rc) { 39 | fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db)); 40 | sqlite3_close(db); 41 | return; 42 | } 43 | } 44 | 45 | void 46 | waveform_db_close () 47 | { 48 | sqlite3_close(db); 49 | } 50 | 51 | void 52 | waveform_db_init (char const *fname) 53 | { 54 | char *zErrMsg = 0; 55 | int rc; 56 | 57 | char *query = "CREATE TABLE IF NOT EXISTS wave ( path TEXT PRIMARY KEY NOT NULL, channels INTEGER NOT NULL, compression INTEGER, data BLOB)"; 58 | rc = sqlite3_exec(db, query, NULL, 0, &zErrMsg); 59 | if (rc != SQLITE_OK) { 60 | fprintf(stderr, "SQL error: %s\n", zErrMsg); 61 | } 62 | sqlite3_free(zErrMsg); 63 | } 64 | 65 | int 66 | waveform_db_cached (char const *fname) 67 | { 68 | int rc; 69 | sqlite3_stmt* p = 0; 70 | 71 | char* query = sqlite3_mprintf ("SELECT * FROM wave WHERE path = '%q'", fname); 72 | rc = sqlite3_prepare_v2 (db, query, strlen(query), &p, NULL); 73 | if (rc != SQLITE_OK) { 74 | fprintf(stderr, "cached_perpare: SQL error: %d\n", rc); 75 | } 76 | rc = sqlite3_step (p); 77 | if (rc == SQLITE_ROW) { 78 | sqlite3_finalize (p); 79 | return 1; 80 | } 81 | sqlite3_finalize (p); 82 | return 0; 83 | } 84 | 85 | int 86 | waveform_db_delete (char const *fname) 87 | { 88 | int rc; 89 | sqlite3_stmt* p = 0; 90 | 91 | char* query = sqlite3_mprintf ("DELETE FROM wave WHERE path = '%q'", fname); 92 | rc = sqlite3_prepare_v2 (db, query, strlen(query), &p, NULL); 93 | if (rc != SQLITE_OK) { 94 | fprintf(stderr, "delete_perpare: SQL error: %d\n", rc); 95 | } 96 | rc = sqlite3_step (p); 97 | if (rc != SQLITE_DONE) { 98 | fprintf(stderr, "delete_exec: SQL error: %d\n", rc); 99 | } 100 | sqlite3_finalize (p); 101 | return 1; 102 | } 103 | 104 | int 105 | waveform_db_read (char const *fname, short *buffer, int buffer_len, int *channels) 106 | { 107 | int rc; 108 | sqlite3_stmt* p = 0; 109 | 110 | char* query = sqlite3_mprintf("SELECT channels, data FROM wave WHERE path = '%q'", fname); 111 | rc = sqlite3_prepare_v2 (db, query, strlen(query), &p, NULL); 112 | if (rc != SQLITE_OK) { 113 | fprintf(stderr, "read_perpare: SQL error: %d\n", rc); 114 | } 115 | rc = sqlite3_step (p); 116 | if (rc == SQLITE_DONE) { 117 | sqlite3_finalize (p); 118 | return 0; 119 | } 120 | else if (rc != SQLITE_ROW) { 121 | fprintf(stderr, "read_exec: SQL error: %d\n", rc); 122 | sqlite3_finalize (p); 123 | return 0; 124 | } 125 | 126 | *channels = sqlite3_column_int (p,0); 127 | short *data = (short *)sqlite3_column_blob (p,1); 128 | 129 | int bytes = sqlite3_column_bytes (p,1); 130 | if (bytes > buffer_len * sizeof(short)) { 131 | bytes = buffer_len; 132 | } 133 | memcpy (buffer,data,bytes); 134 | 135 | sqlite3_finalize (p); 136 | return bytes / sizeof(short); 137 | } 138 | 139 | void 140 | waveform_db_write (char const *fname, short *buffer, int buffer_len, int channels, int compression) 141 | { 142 | int rc; 143 | sqlite3_stmt* p = 0; 144 | 145 | char* query = "INSERT INTO wave (path, channels, compression, data) VALUES (?, ?, ?, ?);"; 146 | rc = sqlite3_prepare_v2 (db, query, strlen(query), &p, NULL); 147 | if (rc != SQLITE_OK) { 148 | fprintf(stderr, "write_perpare: SQL error: %d\n", rc); 149 | } 150 | rc = sqlite3_bind_text (p, 1, fname, -1, SQLITE_STATIC); 151 | if (rc != SQLITE_OK) { 152 | fprintf(stderr, "write_fname: SQL error: %d\n", rc); 153 | } 154 | rc = sqlite3_bind_int (p, 2, channels); 155 | if (rc != SQLITE_OK) { 156 | fprintf(stderr, "write_channels: SQL error: %d\n", rc); 157 | } 158 | rc = sqlite3_bind_int (p, 3, compression); 159 | if (rc != SQLITE_OK) { 160 | fprintf(stderr, "write_compression: SQL error: %d\n", rc); 161 | } 162 | rc = sqlite3_bind_blob (p, 4, buffer, buffer_len, SQLITE_STATIC); 163 | if (rc != SQLITE_OK) { 164 | fprintf(stderr, "write_data: SQL error: %d\n", rc); 165 | } 166 | rc = sqlite3_step (p); 167 | if (rc != SQLITE_DONE) { 168 | fprintf(stderr, "write_exec: SQL error: %d\n", rc); 169 | } 170 | sqlite3_finalize (p); 171 | } 172 | -------------------------------------------------------------------------------- /config.c: -------------------------------------------------------------------------------- 1 | /* 2 | Waveform seekbar plugin for the DeaDBeeF audio player 3 | 4 | Copyright (C) 2014 Christian Boxdörfer 5 | 6 | Based on sndfile-tools waveform by Erik de Castro Lopo. 7 | waveform.c - v1.04 8 | Copyright (C) 2007-2012 Erik de Castro Lopo 9 | Copyright (C) 2012 Robin Gareus 10 | Copyright (C) 2013 driedfruit 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 program is distributed in the hope that it will be useful, 18 | but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | GNU General Public License for more details. 21 | 22 | You should have received a copy of the GNU General Public License 23 | along with this program; if not, write to the Free Software 24 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #include 37 | 38 | #include "config.h" 39 | #include "waveform.h" 40 | 41 | gboolean CONFIG_LOG_ENABLED = FALSE; 42 | gboolean CONFIG_MIX_TO_MONO = FALSE; 43 | gboolean CONFIG_CACHE_ENABLED = TRUE; 44 | gboolean CONFIG_SCROLL_ENABLED = TRUE; 45 | gboolean CONFIG_DISPLAY_RMS = TRUE; 46 | gboolean CONFIG_DISPLAY_RULER = FALSE; 47 | gboolean CONFIG_SHADE_WAVEFORM = FALSE; 48 | gboolean CONFIG_SOUNDCLOUD_STYLE = FALSE; 49 | GdkColor CONFIG_BG_COLOR; 50 | GdkColor CONFIG_FG_COLOR; 51 | GdkColor CONFIG_PB_COLOR; 52 | GdkColor CONFIG_RLR_COLOR; 53 | GdkColor CONFIG_FG_RMS_COLOR; 54 | guint16 CONFIG_BG_ALPHA; 55 | guint16 CONFIG_FG_ALPHA; 56 | guint16 CONFIG_PB_ALPHA; 57 | guint16 CONFIG_RLR_ALPHA; 58 | guint16 CONFIG_FG_RMS_ALPHA; 59 | gint CONFIG_RENDER_METHOD = SPIKES; 60 | gint CONFIG_FILL_WAVEFORM = 1; 61 | gint CONFIG_BORDER_WIDTH = 1; 62 | gint CONFIG_CURSOR_WIDTH = 3; 63 | gint CONFIG_FONT_SIZE = 18; 64 | gint CONFIG_MAX_FILE_LENGTH = 180; 65 | gint CONFIG_NUM_SAMPLES = 2048; 66 | gint CONFIG_REFRESH_INTERVAL = 33; 67 | 68 | void 69 | save_config (void) 70 | { 71 | deadbeef->conf_set_int (CONFSTR_WF_LOG_ENABLED, CONFIG_LOG_ENABLED); 72 | deadbeef->conf_set_int (CONFSTR_WF_MIX_TO_MONO, CONFIG_MIX_TO_MONO); 73 | deadbeef->conf_set_int (CONFSTR_WF_DISPLAY_RMS, CONFIG_DISPLAY_RMS); 74 | deadbeef->conf_set_int (CONFSTR_WF_DISPLAY_RULER, CONFIG_DISPLAY_RULER); 75 | deadbeef->conf_set_int (CONFSTR_WF_SHADE_WAVEFORM, CONFIG_SHADE_WAVEFORM); 76 | deadbeef->conf_set_int (CONFSTR_WF_SOUNDCLOUD_STYLE, CONFIG_SOUNDCLOUD_STYLE); 77 | deadbeef->conf_set_int (CONFSTR_WF_RENDER_METHOD, CONFIG_RENDER_METHOD); 78 | deadbeef->conf_set_int (CONFSTR_WF_FILL_WAVEFORM, CONFIG_FILL_WAVEFORM); 79 | deadbeef->conf_set_int (CONFSTR_WF_BORDER_WIDTH, CONFIG_BORDER_WIDTH); 80 | deadbeef->conf_set_int (CONFSTR_WF_CURSOR_WIDTH, CONFIG_CURSOR_WIDTH); 81 | deadbeef->conf_set_int (CONFSTR_WF_FONT_SIZE, CONFIG_FONT_SIZE); 82 | deadbeef->conf_set_int (CONFSTR_WF_MAX_FILE_LENGTH, CONFIG_MAX_FILE_LENGTH); 83 | deadbeef->conf_set_int (CONFSTR_WF_REFRESH_INTERVAL, CONFIG_REFRESH_INTERVAL); 84 | deadbeef->conf_set_int (CONFSTR_WF_NUM_SAMPLES, CONFIG_NUM_SAMPLES); 85 | deadbeef->conf_set_int (CONFSTR_WF_CACHE_ENABLED, CONFIG_CACHE_ENABLED); 86 | deadbeef->conf_set_int (CONFSTR_WF_SCROLL_ENABLED, CONFIG_SCROLL_ENABLED); 87 | deadbeef->conf_set_int (CONFSTR_WF_BG_COLOR_R, CONFIG_BG_COLOR.red); 88 | deadbeef->conf_set_int (CONFSTR_WF_BG_COLOR_G, CONFIG_BG_COLOR.green); 89 | deadbeef->conf_set_int (CONFSTR_WF_BG_COLOR_B, CONFIG_BG_COLOR.blue); 90 | deadbeef->conf_set_int (CONFSTR_WF_BG_ALPHA, CONFIG_BG_ALPHA); 91 | deadbeef->conf_set_int (CONFSTR_WF_FG_COLOR_R, CONFIG_FG_COLOR.red); 92 | deadbeef->conf_set_int (CONFSTR_WF_FG_COLOR_G, CONFIG_FG_COLOR.green); 93 | deadbeef->conf_set_int (CONFSTR_WF_FG_COLOR_B, CONFIG_FG_COLOR.blue); 94 | deadbeef->conf_set_int (CONFSTR_WF_FG_ALPHA, CONFIG_FG_ALPHA); 95 | deadbeef->conf_set_int (CONFSTR_WF_PB_COLOR_R, CONFIG_PB_COLOR.red); 96 | deadbeef->conf_set_int (CONFSTR_WF_PB_COLOR_G, CONFIG_PB_COLOR.green); 97 | deadbeef->conf_set_int (CONFSTR_WF_PB_COLOR_B, CONFIG_PB_COLOR.blue); 98 | deadbeef->conf_set_int (CONFSTR_WF_PB_ALPHA, CONFIG_PB_ALPHA); 99 | deadbeef->conf_set_int (CONFSTR_WF_RLR_COLOR_R, CONFIG_RLR_COLOR.red); 100 | deadbeef->conf_set_int (CONFSTR_WF_RLR_COLOR_G, CONFIG_RLR_COLOR.green); 101 | deadbeef->conf_set_int (CONFSTR_WF_RLR_COLOR_B, CONFIG_RLR_COLOR.blue); 102 | deadbeef->conf_set_int (CONFSTR_WF_RLR_ALPHA, CONFIG_RLR_ALPHA); 103 | deadbeef->conf_set_int (CONFSTR_WF_FG_RMS_COLOR_R, CONFIG_FG_RMS_COLOR.red); 104 | deadbeef->conf_set_int (CONFSTR_WF_FG_RMS_COLOR_G, CONFIG_FG_RMS_COLOR.green); 105 | deadbeef->conf_set_int (CONFSTR_WF_FG_RMS_COLOR_B, CONFIG_FG_RMS_COLOR.blue); 106 | deadbeef->conf_set_int (CONFSTR_WF_FG_RMS_ALPHA, CONFIG_FG_RMS_ALPHA); 107 | } 108 | 109 | void 110 | load_config (void) 111 | { 112 | deadbeef->conf_lock (); 113 | CONFIG_LOG_ENABLED = deadbeef->conf_get_int (CONFSTR_WF_LOG_ENABLED, FALSE); 114 | CONFIG_MIX_TO_MONO = deadbeef->conf_get_int (CONFSTR_WF_MIX_TO_MONO, FALSE); 115 | CONFIG_DISPLAY_RMS = deadbeef->conf_get_int (CONFSTR_WF_DISPLAY_RMS, TRUE); 116 | CONFIG_DISPLAY_RULER = deadbeef->conf_get_int (CONFSTR_WF_DISPLAY_RULER, FALSE); 117 | CONFIG_SHADE_WAVEFORM = deadbeef->conf_get_int (CONFSTR_WF_SHADE_WAVEFORM, FALSE); 118 | CONFIG_SOUNDCLOUD_STYLE = deadbeef->conf_get_int (CONFSTR_WF_SOUNDCLOUD_STYLE, FALSE); 119 | CONFIG_RENDER_METHOD = deadbeef->conf_get_int (CONFSTR_WF_RENDER_METHOD, SPIKES); 120 | CONFIG_FILL_WAVEFORM = deadbeef->conf_get_int (CONFSTR_WF_FILL_WAVEFORM, 1); 121 | CONFIG_BORDER_WIDTH = deadbeef->conf_get_int (CONFSTR_WF_BORDER_WIDTH, 1); 122 | CONFIG_CURSOR_WIDTH = deadbeef->conf_get_int (CONFSTR_WF_CURSOR_WIDTH, 3); 123 | CONFIG_FONT_SIZE = deadbeef->conf_get_int (CONFSTR_WF_FONT_SIZE, 18); 124 | CONFIG_REFRESH_INTERVAL = deadbeef->conf_get_int (CONFSTR_WF_REFRESH_INTERVAL, 33); 125 | CONFIG_MAX_FILE_LENGTH = deadbeef->conf_get_int (CONFSTR_WF_MAX_FILE_LENGTH, 180); 126 | CONFIG_NUM_SAMPLES = deadbeef->conf_get_int (CONFSTR_WF_NUM_SAMPLES, 2048); 127 | CONFIG_CACHE_ENABLED = deadbeef->conf_get_int (CONFSTR_WF_CACHE_ENABLED, TRUE); 128 | CONFIG_SCROLL_ENABLED = deadbeef->conf_get_int (CONFSTR_WF_SCROLL_ENABLED, TRUE); 129 | 130 | CONFIG_BG_COLOR.red = deadbeef->conf_get_int (CONFSTR_WF_BG_COLOR_R, 50000); 131 | CONFIG_BG_COLOR.green = deadbeef->conf_get_int (CONFSTR_WF_BG_COLOR_G, 50000); 132 | CONFIG_BG_COLOR.blue = deadbeef->conf_get_int (CONFSTR_WF_BG_COLOR_B, 50000); 133 | CONFIG_BG_ALPHA = deadbeef->conf_get_int (CONFSTR_WF_BG_ALPHA, 65535); 134 | 135 | CONFIG_FG_COLOR.red = deadbeef->conf_get_int (CONFSTR_WF_FG_COLOR_R, 20000); 136 | CONFIG_FG_COLOR.green = deadbeef->conf_get_int (CONFSTR_WF_FG_COLOR_G, 20000); 137 | CONFIG_FG_COLOR.blue = deadbeef->conf_get_int (CONFSTR_WF_FG_COLOR_B, 20000); 138 | CONFIG_FG_ALPHA = deadbeef->conf_get_int (CONFSTR_WF_FG_ALPHA, 65535); 139 | 140 | CONFIG_PB_COLOR.red = deadbeef->conf_get_int (CONFSTR_WF_PB_COLOR_R, 0); 141 | CONFIG_PB_COLOR.green = deadbeef->conf_get_int (CONFSTR_WF_PB_COLOR_G, 65535); 142 | CONFIG_PB_COLOR.blue = deadbeef->conf_get_int (CONFSTR_WF_PB_COLOR_B, 0); 143 | CONFIG_PB_ALPHA = deadbeef->conf_get_int (CONFSTR_WF_PB_ALPHA, 20000); 144 | 145 | CONFIG_RLR_COLOR.red = deadbeef->conf_get_int (CONFSTR_WF_RLR_COLOR_R, 0); 146 | CONFIG_RLR_COLOR.green = deadbeef->conf_get_int (CONFSTR_WF_RLR_COLOR_G, 0); 147 | CONFIG_RLR_COLOR.blue = deadbeef->conf_get_int (CONFSTR_WF_RLR_COLOR_B, 0); 148 | CONFIG_RLR_ALPHA = deadbeef->conf_get_int (CONFSTR_WF_RLR_ALPHA, 65535); 149 | 150 | CONFIG_FG_RMS_COLOR.red = deadbeef->conf_get_int (CONFSTR_WF_FG_RMS_COLOR_R, 5000); 151 | CONFIG_FG_RMS_COLOR.green = deadbeef->conf_get_int (CONFSTR_WF_FG_RMS_COLOR_G, 5000); 152 | CONFIG_FG_RMS_COLOR.blue = deadbeef->conf_get_int (CONFSTR_WF_FG_RMS_COLOR_B, 5000); 153 | CONFIG_FG_RMS_ALPHA = deadbeef->conf_get_int (CONFSTR_WF_FG_RMS_ALPHA, 65535); 154 | 155 | deadbeef->conf_unlock (); 156 | } 157 | 158 | -------------------------------------------------------------------------------- /ruler.c: -------------------------------------------------------------------------------- 1 | /* 2 | Waveform seekbar plugin for the DeaDBeeF audio player 3 | 4 | Copyright (C) 2017 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 "ruler.h" 23 | #include "config.h" 24 | #include "waveform.h" 25 | 26 | #define TEXT_SPACING 40 27 | #define TEXT_MARKER_SPACING 3 28 | #define RULER_MAX_LABELS 30 29 | #define RULER_LINE_WIDTH 1.0 30 | #define RULER_FONT_SIZE 8.0 31 | #define RULER_SUB_MARKER_SPACING_MIN 3.0 32 | 33 | typedef enum 34 | { 35 | TIME_ID_6H, 36 | TIME_ID_1H, 37 | TIME_ID_30M, 38 | TIME_ID_15M, 39 | TIME_ID_10M, 40 | TIME_ID_5M, 41 | TIME_ID_2M, 42 | TIME_ID_1M, 43 | TIME_ID_30S, 44 | TIME_ID_10S, 45 | TIME_ID_5S, 46 | TIME_ID_1S, 47 | TIME_ID_500MS, 48 | TIME_ID_100MS, 49 | TIME_ID_50MS, 50 | TIME_ID_10MS, 51 | N_TIME_IDS, 52 | } TimeValueID; 53 | 54 | typedef struct 55 | { 56 | TimeValueID id; 57 | float value; 58 | // a logical division for values in between 59 | // e.g. given 10s steps it makes more sense to divide each step by 2 than by 3 60 | // to highlight the 5s instead of the 3.333s steps in between 61 | // 62 | // | 5s 63 | // V 64 | // | 10s | | 20s 65 | // |---------------------------------... 66 | int sub_div; 67 | } ruler_time_value_t; 68 | 69 | static ruler_time_value_t time_scale[] = { 70 | { TIME_ID_6H, 71 | 6 * 3600.f, // 6 Hours 72 | 3, 73 | }, 74 | { TIME_ID_1H, 75 | 3600.f, // 1 hour 76 | 2, 77 | }, 78 | { TIME_ID_30M, 79 | 1800.f, // 30 min 80 | 2, 81 | }, 82 | { TIME_ID_15M, 83 | 900.f, // 15 min 84 | 3, 85 | }, 86 | { TIME_ID_10M, 87 | 600.f, // 10 min 88 | 2, 89 | }, 90 | { TIME_ID_5M, 91 | 300.f, // 5 min 92 | 5, 93 | }, 94 | { TIME_ID_2M, 95 | 120.f, // 2 min 96 | 2, 97 | }, 98 | { TIME_ID_1M, 99 | 60.f, // 1 min 100 | 2, 101 | }, 102 | { TIME_ID_30S, 103 | 30.f, // 30 sec 104 | 3, 105 | }, 106 | { TIME_ID_10S, 107 | 10.f, // 10 sec 108 | 2, 109 | }, 110 | { TIME_ID_5S, 111 | 5.f, // 5 sec 112 | 2, 113 | }, 114 | { TIME_ID_1S, 115 | 1.f, // 1 sec 116 | 5, 117 | }, 118 | { TIME_ID_500MS, 119 | 0.5f, // 0.5 sec 120 | 5, 121 | }, 122 | { TIME_ID_100MS, 123 | 0.1f, // 0.1 sec 124 | 5, 125 | }, 126 | { TIME_ID_50MS, 127 | 0.05f, // 0.05 sec 128 | 5, 129 | }, 130 | { TIME_ID_10MS, 131 | 0.01f, // 0.01 sec 132 | 5, 133 | }, 134 | }; 135 | 136 | typedef struct 137 | { 138 | ruler_time_value_t value; 139 | // number of times this resolution fits into a given duration 140 | int n; 141 | } ruler_time_resolution_t; 142 | 143 | static double 144 | ruler_text_height_get (cairo_t *cr) 145 | { 146 | cairo_text_extents_t text_dim; 147 | cairo_text_extents (cr, "Test", &text_dim); 148 | return text_dim.height; 149 | } 150 | 151 | static void 152 | ruler_format_time (char *dest, size_t dest_size, ruler_time_value_t *time_val, int n) 153 | { 154 | const double time_in_seconds = n * time_val->value; 155 | 156 | const int hours = (int)time_in_seconds/3600; 157 | const int minutes = (int)time_in_seconds/60; 158 | const int seconds = (int)time_in_seconds; 159 | 160 | int time_remaining = 0; 161 | switch (time_val->id) { 162 | case TIME_ID_6H: 163 | case TIME_ID_1H: 164 | snprintf (dest, dest_size, "%d:00:00", hours); 165 | break; 166 | case TIME_ID_30M: 167 | case TIME_ID_15M: 168 | case TIME_ID_10M: 169 | case TIME_ID_5M: 170 | case TIME_ID_2M: 171 | case TIME_ID_1M: 172 | if (time_in_seconds >= 3600) { 173 | time_remaining = seconds % 3600 / 60; 174 | snprintf (dest, dest_size, "%d:%02d:00", hours, time_remaining); 175 | } 176 | else { 177 | snprintf (dest, dest_size, "%d:00", minutes); 178 | } 179 | break; 180 | case TIME_ID_30S: 181 | case TIME_ID_10S: 182 | case TIME_ID_5S: 183 | case TIME_ID_1S: 184 | if (time_in_seconds >= 60) { 185 | time_remaining = seconds % 60; 186 | snprintf (dest, dest_size, "%d:%02d", minutes, time_remaining); 187 | } 188 | else { 189 | snprintf (dest, dest_size, "0:%02d", seconds); 190 | } 191 | break; 192 | case TIME_ID_500MS: 193 | case TIME_ID_100MS: 194 | case TIME_ID_50MS: 195 | case TIME_ID_10MS: 196 | snprintf (dest, dest_size, "%.2f", time_in_seconds); 197 | break; 198 | default: 199 | return; 200 | } 201 | } 202 | 203 | static void 204 | ruler_time_resolution_build (ruler_time_resolution_t *res, float duration) 205 | { 206 | for (int i = 0; i < N_TIME_IDS; i++) { 207 | ruler_time_resolution_t *r = &res[i]; 208 | r->value = time_scale[i]; 209 | // Determine the number of times each time step fits into the given track duration 210 | // e.g. given a 2min10s track a 30s time step will fit 4 times 211 | r->n = floorf (duration / time_scale[i].value); 212 | } 213 | } 214 | 215 | static bool 216 | ruler_time_fits_width (cairo_t *cr, 217 | char *dest, 218 | size_t dest_size, 219 | ruler_time_resolution_t *res, 220 | float duration, 221 | double width) 222 | { 223 | if (res->n > RULER_MAX_LABELS) { 224 | return false; 225 | } 226 | 227 | ruler_time_value_t *time_val = &res->value; 228 | double text_width = 0; 229 | const double x_start = time_val->value/duration * width; 230 | 231 | for (int i = 1; i <= res->n; i++) { 232 | ruler_format_time (dest, dest_size, time_val, i); 233 | cairo_text_extents_t text_dim; 234 | cairo_text_extents (cr, dest, &text_dim); 235 | text_width += text_dim.width + TEXT_SPACING; 236 | } 237 | return floor ((width-x_start)/text_width) >= 1 ? true : false; 238 | } 239 | 240 | // Determine the best time resolution for a given duration and width. 241 | // Where "best" means: 242 | // 1) high resolution (e.g. given a 2 min duration: four 30s steps are better than two 1min steps) 243 | // 244 | // | 0:30 1:00 1:30 | <-- better 245 | // 246 | // | 1:00 | <-- worse 247 | // 248 | // 2) given a resolution the resp. time labels must fit to the available width 249 | // 250 | // E.g. given a 2 min duration and the following width 251 | // | | <- max width 252 | // 253 | // the following 10s resolution obviously doesn't work 254 | // 255 | // | max width 256 | // V 257 | // |0:10 0:20 0:30 0:40 0:50 1:00 1:10 1:20 1:30 1:40 1:50 2:00| 258 | // 259 | // since displaying all values up to 2min uses more space than available 260 | // 261 | static ruler_time_resolution_t * 262 | ruler_time_find_resolution (cairo_t *cr, 263 | ruler_time_resolution_t *resolutions, 264 | float duration, 265 | double width) 266 | { 267 | ruler_time_resolution_t *res = NULL; 268 | 269 | int n_max = 0; 270 | 271 | for (int i = 0; i < N_TIME_IDS; i++) { 272 | ruler_time_resolution_t *res_tmp = &resolutions[i]; 273 | if (res_tmp->n <= 0) { 274 | // resolution too low, e.g. duration: 3min, resolution: 1h 275 | continue; 276 | } 277 | char time_text[100] = ""; 278 | // determine how many labels fit in to the width for the given resolution 279 | bool fits = ruler_time_fits_width (cr, 280 | time_text, 281 | sizeof (time_text)/sizeof (char), 282 | res_tmp, 283 | duration, 284 | width); 285 | // only consider resolutions which are able to display one or more labels 286 | // and prefer higher to lower resolutions 287 | if (fits && res_tmp->n > n_max) { 288 | res = res_tmp; 289 | n_max = res_tmp->n; 290 | } 291 | } 292 | return res; 293 | } 294 | 295 | static void 296 | ruler_sub_marker_draw (cairo_t *cr_ctx, 297 | ruler_time_resolution_t *res, 298 | double x, 299 | double y, 300 | double width, 301 | double height) 302 | { 303 | const int sub_div = res->value.sub_div; 304 | const double marker_dist = width / sub_div; 305 | 306 | if (marker_dist >= RULER_SUB_MARKER_SPACING_MIN) { 307 | const double m_height = floor (height/3); 308 | double x_start = x + marker_dist; 309 | for (int i = 1; i < sub_div; i++) { 310 | cairo_move_to (cr_ctx, x_start, y); 311 | cairo_line_to (cr_ctx, x_start, y - m_height); 312 | cairo_stroke (cr_ctx); 313 | x_start += marker_dist; 314 | } 315 | } 316 | } 317 | 318 | void 319 | waveform_render_ruler (cairo_t *cr_ctx, 320 | waveform_colors_t *color, 321 | float duration, 322 | waveform_rect_t *rect) 323 | { 324 | // Draw background 325 | cairo_set_source_rgba (cr_ctx, color->bg.r, color->bg.g, color->bg.b, 1.0); 326 | cairo_rectangle (cr_ctx, rect->x, rect->y, rect->width, rect->height); 327 | cairo_fill (cr_ctx); 328 | 329 | cairo_set_antialias (cr_ctx, CAIRO_ANTIALIAS_NONE); 330 | cairo_set_line_width (cr_ctx, RULER_LINE_WIDTH); 331 | cairo_set_font_size (cr_ctx, RULER_FONT_SIZE); 332 | cairo_set_source_rgba (cr_ctx, color->rlr.r, color->rlr.g, color->rlr.b, color->rlr.a); 333 | 334 | // Draw separator 335 | // 0:30 1:00 1:30 2:00 336 | // -> ------------------------------------ 337 | // 338 | // Waveform 339 | // 340 | cairo_move_to (cr_ctx, rect->x, rect->height); 341 | cairo_line_to (cr_ctx, rect->width, rect->height); 342 | cairo_stroke (cr_ctx); 343 | 344 | if (duration <= 0.f) { 345 | // no duration, no ruler. 346 | return; 347 | } 348 | 349 | ruler_time_resolution_t resolutions[N_TIME_IDS]; 350 | ruler_time_resolution_build (resolutions, duration); 351 | 352 | 353 | ruler_time_resolution_t *res = ruler_time_find_resolution (cr_ctx, 354 | resolutions, 355 | duration, 356 | rect->width); 357 | if (!res) { 358 | return; 359 | } 360 | 361 | const double x_start = res->value.value/duration * rect->width; 362 | const double center = (rect->height - RULER_LINE_WIDTH)/2.0; 363 | const double center_abs = rect->height/2.0; 364 | const double y = center + ruler_text_height_get (cr_ctx)/2.0; 365 | 366 | double x = rect->x; 367 | for (int i = 1; i <= res->n; i++) { 368 | // Draw sub time markers 369 | // 370 | // | 371 | // v 372 | // . | 0:30 . | 1:00 . | 1:30 . | 2:00 373 | // --------------------------------------------------- 374 | // 375 | // Waveform 376 | // 377 | ruler_sub_marker_draw (cr_ctx, res, x, rect->height, x_start, center_abs); 378 | 379 | x += x_start; 380 | 381 | // Draw time markers 382 | // 383 | // | 384 | // v 385 | // | 0:30 | 1:00 | 1:30 | 2:00 386 | // ---------------------------------------------- 387 | // 388 | // Waveform 389 | // 390 | cairo_move_to (cr_ctx, x, center_abs); 391 | cairo_line_to (cr_ctx, x, rect->height); 392 | cairo_stroke (cr_ctx); 393 | 394 | 395 | // Draw time labels 396 | // 397 | // | 398 | // v 399 | // | 0:30 | 1:00 | 1:30 | 2:00 400 | // ---------------------------------------------- 401 | // 402 | // Waveform 403 | // 404 | cairo_move_to (cr_ctx, x + TEXT_MARKER_SPACING, y); 405 | char time_text[100] = ""; 406 | ruler_format_time (time_text, sizeof (time_text)/sizeof (char), &res->value, i); 407 | cairo_show_text (cr_ctx, time_text); 408 | } 409 | 410 | // Draw sub markers after the last label 411 | ruler_sub_marker_draw (cr_ctx, res, x, rect->height, x_start, center_abs); 412 | } 413 | 414 | -------------------------------------------------------------------------------- /config_dialog.c: -------------------------------------------------------------------------------- 1 | /* 2 | Waveform seekbar plugin for the DeaDBeeF audio player 3 | 4 | Copyright (C) 2014 Christian Boxdörfer 5 | 6 | Based on sndfile-tools waveform by Erik de Castro Lopo. 7 | waveform.c - v1.04 8 | Copyright (C) 2007-2012 Erik de Castro Lopo 9 | Copyright (C) 2012 Robin Gareus 10 | Copyright (C) 2013 driedfruit 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 program is distributed in the hope that it will be useful, 18 | but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | GNU General Public License for more details. 21 | 22 | You should have received a copy of the GNU General Public License 23 | along with this program; if not, write to the Free Software 24 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include 34 | 35 | #include "support.h" 36 | #include "config.h" 37 | #include "waveform.h" 38 | #include "config_dialog.h" 39 | 40 | void 41 | on_button_config (GtkMenuItem *menuitem, gpointer user_data) 42 | { 43 | GtkWidget *waveform_properties; 44 | GtkWidget *config_dialog; 45 | GtkWidget *vbox01; 46 | GtkWidget *hbox01; 47 | GtkWidget *color_label; 48 | GtkWidget *color_frame; 49 | GtkWidget *color_table; 50 | GtkWidget *color_background_label; 51 | GtkWidget *background_color; 52 | GtkWidget *color_waveform_label; 53 | GtkWidget *foreground_color; 54 | GtkWidget *color_rms_label; 55 | GtkWidget *foreground_rms_color; 56 | GtkWidget *color_progressbar_label; 57 | GtkWidget *color_ruler_label; 58 | GtkWidget *progressbar_color; 59 | GtkWidget *ruler_color; 60 | GtkWidget *downmix_to_mono; 61 | GtkWidget *log_scale; 62 | GtkWidget *display_rms; 63 | GtkWidget *display_ruler; 64 | GtkWidget *style_label; 65 | GtkWidget *style_frame; 66 | GtkWidget *options_label; 67 | GtkWidget *options_frame; 68 | GtkWidget *vbox02; 69 | GtkWidget *vbox03; 70 | GtkWidget *render_method_spikes; 71 | GtkWidget *render_method_bars; 72 | GtkWidget *shade_waveform; 73 | GtkWidget *fill_waveform; 74 | GtkWidget *soundcloud_style; 75 | GtkWidget *dialog_action_area13; 76 | GtkWidget *applybutton1; 77 | GtkWidget *cancelbutton1; 78 | GtkWidget *okbutton1; 79 | #pragma GCC diagnostic push 80 | #pragma GCC diagnostic ignored "-Wdeprecated-declarations" 81 | waveform_properties = gtk_dialog_new (); 82 | gtk_window_set_transient_for (GTK_WINDOW (waveform_properties), gtk_widget_get_toplevel(menuitem)); 83 | gtk_window_set_title (GTK_WINDOW (waveform_properties), "Waveform Properties"); 84 | gtk_window_set_type_hint (GTK_WINDOW (waveform_properties), GDK_WINDOW_TYPE_HINT_DIALOG); 85 | 86 | config_dialog = gtk_dialog_get_content_area (GTK_DIALOG (waveform_properties)); 87 | gtk_widget_show (config_dialog); 88 | 89 | vbox01 = gtk_vbox_new (FALSE, 8); 90 | gtk_widget_show (vbox01); 91 | gtk_box_pack_start (GTK_BOX (config_dialog), vbox01, FALSE, FALSE, 0); 92 | gtk_container_set_border_width (GTK_CONTAINER (vbox01), 12); 93 | 94 | hbox01 = gtk_hbox_new (FALSE, 8); 95 | gtk_widget_show (hbox01); 96 | gtk_box_pack_start (GTK_BOX (vbox01), hbox01, FALSE, FALSE, 0); 97 | gtk_container_set_border_width (GTK_CONTAINER (hbox01), 0); 98 | 99 | color_label = gtk_label_new (NULL); 100 | gtk_label_set_markup (GTK_LABEL (color_label),"Colors"); 101 | gtk_widget_show (color_label); 102 | 103 | color_frame = gtk_frame_new ("Colors"); 104 | gtk_frame_set_label_widget ((GtkFrame *)color_frame, color_label); 105 | gtk_frame_set_shadow_type ((GtkFrame *)color_frame, GTK_SHADOW_IN); 106 | gtk_widget_show (color_frame); 107 | gtk_box_pack_start (GTK_BOX (hbox01), color_frame, TRUE, FALSE, 0); 108 | 109 | color_table = gtk_table_new (5, 2, TRUE); 110 | gtk_widget_show (color_table); 111 | gtk_container_add (GTK_CONTAINER (color_frame), color_table); 112 | gtk_table_set_col_spacings ((GtkTable *) color_table, 8); 113 | gtk_table_set_row_spacings ((GtkTable *) color_table, 4); 114 | gtk_container_set_border_width (GTK_CONTAINER (color_table), 6); 115 | 116 | color_background_label = gtk_label_new ("Background"); 117 | gtk_misc_set_alignment(GTK_MISC(color_background_label), 0, .5); 118 | gtk_widget_show (color_background_label); 119 | gtk_table_attach_defaults ((GtkTable *) color_table, color_background_label, 1,2,0,1); 120 | 121 | color_waveform_label = gtk_label_new ("Waveform"); 122 | gtk_misc_set_alignment(GTK_MISC(color_waveform_label), 0, .5); 123 | gtk_widget_show (color_waveform_label); 124 | gtk_table_attach_defaults ((GtkTable *) color_table, color_waveform_label, 1,2,1,2); 125 | 126 | color_rms_label = gtk_label_new ("RMS"); 127 | gtk_misc_set_alignment(GTK_MISC(color_rms_label), 0, .5); 128 | gtk_widget_show (color_rms_label); 129 | gtk_table_attach_defaults ((GtkTable *) color_table, color_rms_label, 1,2,2,3); 130 | 131 | color_progressbar_label = gtk_label_new ("Progressbar"); 132 | gtk_misc_set_alignment(GTK_MISC(color_progressbar_label), 0, .5); 133 | gtk_widget_show (color_progressbar_label); 134 | gtk_table_attach_defaults ((GtkTable *) color_table, color_progressbar_label, 1,2,3,4); 135 | 136 | color_ruler_label = gtk_label_new ("Ruler"); 137 | gtk_misc_set_alignment(GTK_MISC(color_ruler_label), 0, .5); 138 | gtk_widget_show (color_ruler_label); 139 | gtk_table_attach_defaults ((GtkTable *) color_table, color_ruler_label, 1,2,4,5); 140 | 141 | background_color = gtk_color_button_new (); 142 | gtk_color_button_set_use_alpha ((GtkColorButton *)background_color, TRUE); 143 | gtk_widget_show (background_color); 144 | gtk_table_attach_defaults ((GtkTable *) color_table, background_color, 0,1,0,1); 145 | 146 | foreground_color = gtk_color_button_new (); 147 | gtk_color_button_set_use_alpha ((GtkColorButton *)foreground_color, TRUE); 148 | gtk_widget_show (foreground_color); 149 | gtk_table_attach_defaults ((GtkTable *) color_table, foreground_color, 0,1,1,2); 150 | 151 | foreground_rms_color = gtk_color_button_new (); 152 | gtk_color_button_set_use_alpha ((GtkColorButton *)foreground_rms_color, TRUE); 153 | gtk_widget_show (foreground_rms_color); 154 | gtk_table_attach_defaults ((GtkTable *) color_table, foreground_rms_color, 0,1,2,3); 155 | 156 | progressbar_color = gtk_color_button_new (); 157 | gtk_color_button_set_use_alpha ((GtkColorButton *)progressbar_color, TRUE); 158 | gtk_widget_show (progressbar_color); 159 | gtk_table_attach_defaults ((GtkTable *) color_table, progressbar_color, 0,1,3,4); 160 | 161 | ruler_color = gtk_color_button_new (); 162 | gtk_color_button_set_use_alpha ((GtkColorButton *)ruler_color, TRUE); 163 | gtk_widget_show (ruler_color); 164 | gtk_table_attach_defaults ((GtkTable *) color_table, ruler_color, 0,1,4,5); 165 | 166 | style_label = gtk_label_new (NULL); 167 | gtk_label_set_markup (GTK_LABEL (style_label),"Style"); 168 | gtk_widget_show (style_label); 169 | 170 | style_frame = gtk_frame_new ("Style"); 171 | gtk_frame_set_label_widget ((GtkFrame *)style_frame, style_label); 172 | gtk_frame_set_shadow_type ((GtkFrame *)style_frame, GTK_SHADOW_IN); 173 | gtk_widget_show (style_frame); 174 | gtk_box_pack_start (GTK_BOX (hbox01), style_frame, FALSE, FALSE, 0); 175 | 176 | vbox02 = gtk_vbox_new (FALSE, 6); 177 | gtk_widget_show (vbox02); 178 | gtk_container_add (GTK_CONTAINER (style_frame), vbox02); 179 | 180 | render_method_spikes = gtk_radio_button_new_with_label (NULL, "Spikes"); 181 | gtk_widget_show (render_method_spikes); 182 | gtk_box_pack_start (GTK_BOX (vbox02), render_method_spikes, TRUE, TRUE, 0); 183 | 184 | render_method_bars = gtk_radio_button_new_with_label_from_widget ((GtkRadioButton *)render_method_spikes, "Bars"); 185 | gtk_widget_show (render_method_bars); 186 | gtk_box_pack_start (GTK_BOX (vbox02), render_method_bars, TRUE, TRUE, 0); 187 | 188 | fill_waveform = gtk_check_button_new_with_label ("Fill waveform"); 189 | gtk_widget_show (fill_waveform); 190 | gtk_box_pack_start (GTK_BOX (vbox02), fill_waveform, TRUE, TRUE, 0); 191 | 192 | soundcloud_style = gtk_check_button_new_with_label ("Soundcloud style"); 193 | gtk_widget_show (soundcloud_style); 194 | gtk_box_pack_start (GTK_BOX (vbox02), soundcloud_style, TRUE, TRUE, 0); 195 | 196 | shade_waveform = gtk_check_button_new_with_label ("Shade waveform"); 197 | gtk_widget_show (shade_waveform); 198 | gtk_box_pack_start (GTK_BOX (vbox02), shade_waveform, TRUE, TRUE, 0); 199 | 200 | options_label = gtk_label_new (NULL); 201 | gtk_widget_show (options_label); 202 | 203 | options_frame = gtk_frame_new ("Options"); 204 | gtk_frame_set_label_widget ((GtkFrame *)options_frame, options_label); 205 | gtk_frame_set_shadow_type ((GtkFrame *)options_frame, GTK_SHADOW_IN); 206 | gtk_widget_show (options_frame); 207 | gtk_box_pack_start (GTK_BOX (vbox01), options_frame, FALSE, FALSE, 0); 208 | 209 | vbox03 = gtk_vbox_new (FALSE, 6); 210 | gtk_widget_show (vbox03); 211 | gtk_container_add (GTK_CONTAINER (options_frame), vbox03); 212 | 213 | downmix_to_mono = gtk_check_button_new_with_label ("Downmix to mono"); 214 | gtk_widget_show (downmix_to_mono); 215 | gtk_box_pack_start (GTK_BOX (vbox03), downmix_to_mono, FALSE, FALSE, 0); 216 | 217 | log_scale = gtk_check_button_new_with_label ("Logarithmic scale"); 218 | gtk_widget_show (log_scale); 219 | gtk_box_pack_start (GTK_BOX (vbox03), log_scale, FALSE, FALSE, 0); 220 | 221 | display_rms = gtk_check_button_new_with_label ("Display RMS"); 222 | gtk_widget_show (display_rms); 223 | gtk_box_pack_start (GTK_BOX (vbox03), display_rms, FALSE, FALSE, 0); 224 | 225 | display_ruler = gtk_check_button_new_with_label ("Display Ruler"); 226 | gtk_widget_show (display_ruler); 227 | gtk_box_pack_start (GTK_BOX (vbox03), display_ruler, FALSE, FALSE, 0); 228 | 229 | dialog_action_area13 = gtk_dialog_get_action_area (GTK_DIALOG (waveform_properties)); 230 | gtk_widget_show (dialog_action_area13); 231 | gtk_button_box_set_layout (GTK_BUTTON_BOX (dialog_action_area13), GTK_BUTTONBOX_END); 232 | 233 | applybutton1 = gtk_button_new_from_stock ("gtk-apply"); 234 | gtk_widget_show (applybutton1); 235 | gtk_dialog_add_action_widget (GTK_DIALOG (waveform_properties), applybutton1, GTK_RESPONSE_APPLY); 236 | gtk_widget_set_can_default (applybutton1, TRUE); 237 | 238 | cancelbutton1 = gtk_button_new_from_stock ("gtk-cancel"); 239 | gtk_widget_show (cancelbutton1); 240 | gtk_dialog_add_action_widget (GTK_DIALOG (waveform_properties), cancelbutton1, GTK_RESPONSE_CANCEL); 241 | gtk_widget_set_can_default (cancelbutton1, TRUE); 242 | 243 | okbutton1 = gtk_button_new_from_stock ("gtk-ok"); 244 | gtk_widget_show (okbutton1); 245 | gtk_dialog_add_action_widget (GTK_DIALOG (waveform_properties), okbutton1, GTK_RESPONSE_OK); 246 | gtk_widget_set_can_default (okbutton1, TRUE); 247 | 248 | gtk_color_button_set_color (GTK_COLOR_BUTTON (background_color), &CONFIG_BG_COLOR); 249 | gtk_color_button_set_color (GTK_COLOR_BUTTON (foreground_color), &CONFIG_FG_COLOR); 250 | gtk_color_button_set_color (GTK_COLOR_BUTTON (progressbar_color), &CONFIG_PB_COLOR); 251 | gtk_color_button_set_color (GTK_COLOR_BUTTON (ruler_color), &CONFIG_RLR_COLOR); 252 | gtk_color_button_set_color (GTK_COLOR_BUTTON (foreground_rms_color), &CONFIG_FG_RMS_COLOR); 253 | gtk_color_button_set_alpha (GTK_COLOR_BUTTON (background_color), CONFIG_BG_ALPHA); 254 | gtk_color_button_set_alpha (GTK_COLOR_BUTTON (foreground_color), CONFIG_FG_ALPHA); 255 | gtk_color_button_set_alpha (GTK_COLOR_BUTTON (progressbar_color), CONFIG_PB_ALPHA); 256 | gtk_color_button_set_alpha (GTK_COLOR_BUTTON (ruler_color), CONFIG_RLR_ALPHA); 257 | gtk_color_button_set_alpha (GTK_COLOR_BUTTON (foreground_rms_color), CONFIG_FG_RMS_ALPHA); 258 | 259 | gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (downmix_to_mono), CONFIG_MIX_TO_MONO); 260 | gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (log_scale), CONFIG_LOG_ENABLED); 261 | gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (display_rms), CONFIG_DISPLAY_RMS); 262 | gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (display_ruler), CONFIG_DISPLAY_RULER); 263 | gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (shade_waveform), CONFIG_SHADE_WAVEFORM); 264 | gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (soundcloud_style), CONFIG_SOUNDCLOUD_STYLE); 265 | gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (fill_waveform), CONFIG_FILL_WAVEFORM); 266 | 267 | gtk_widget_set_sensitive (display_rms, !CONFIG_SOUNDCLOUD_STYLE); 268 | 269 | switch (CONFIG_RENDER_METHOD) { 270 | case SPIKES: 271 | gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (render_method_spikes), TRUE); 272 | break; 273 | case BARS: 274 | gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (render_method_bars), TRUE); 275 | break; 276 | } 277 | 278 | for (;;) { 279 | int response = gtk_dialog_run (GTK_DIALOG (waveform_properties)); 280 | if (response == GTK_RESPONSE_OK || response == GTK_RESPONSE_APPLY) { 281 | gtk_color_button_get_color (GTK_COLOR_BUTTON (background_color), &CONFIG_BG_COLOR); 282 | gtk_color_button_get_color (GTK_COLOR_BUTTON (foreground_color), &CONFIG_FG_COLOR); 283 | gtk_color_button_get_color (GTK_COLOR_BUTTON (progressbar_color), &CONFIG_PB_COLOR); 284 | gtk_color_button_get_color (GTK_COLOR_BUTTON (ruler_color), &CONFIG_RLR_COLOR); 285 | gtk_color_button_get_color (GTK_COLOR_BUTTON (foreground_rms_color), &CONFIG_FG_RMS_COLOR); 286 | CONFIG_BG_ALPHA = gtk_color_button_get_alpha (GTK_COLOR_BUTTON (background_color)); 287 | CONFIG_FG_ALPHA = gtk_color_button_get_alpha (GTK_COLOR_BUTTON (foreground_color)); 288 | CONFIG_PB_ALPHA = gtk_color_button_get_alpha (GTK_COLOR_BUTTON (progressbar_color)); 289 | CONFIG_RLR_ALPHA = gtk_color_button_get_alpha (GTK_COLOR_BUTTON (ruler_color)); 290 | CONFIG_FG_RMS_ALPHA = gtk_color_button_get_alpha (GTK_COLOR_BUTTON (foreground_rms_color)); 291 | CONFIG_MIX_TO_MONO = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (downmix_to_mono)); 292 | CONFIG_LOG_ENABLED = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (log_scale)); 293 | CONFIG_DISPLAY_RMS = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (display_rms)); 294 | CONFIG_DISPLAY_RULER = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (display_ruler)); 295 | CONFIG_SHADE_WAVEFORM = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (shade_waveform)); 296 | CONFIG_SOUNDCLOUD_STYLE = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (soundcloud_style)); 297 | CONFIG_FILL_WAVEFORM = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (fill_waveform)); 298 | gtk_widget_set_sensitive (display_rms, !CONFIG_SOUNDCLOUD_STYLE); 299 | if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (render_method_spikes)) == TRUE) { 300 | CONFIG_RENDER_METHOD = SPIKES; 301 | } 302 | else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (render_method_bars)) == TRUE) { 303 | CONFIG_RENDER_METHOD = BARS; 304 | } 305 | save_config (); 306 | deadbeef->sendmessage (DB_EV_CONFIGCHANGED, 0, 0, 0); 307 | } 308 | if (response == GTK_RESPONSE_APPLY) { 309 | continue; 310 | } 311 | break; 312 | } 313 | gtk_widget_destroy (waveform_properties); 314 | #pragma GCC diagnostic pop 315 | return; 316 | } 317 | 318 | -------------------------------------------------------------------------------- /render.c: -------------------------------------------------------------------------------- 1 | /* 2 | Waveform seekbar plugin for the DeaDBeeF audio player 3 | 4 | Copyright (C) 2017 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 | 28 | #include "render.h" 29 | #include "waveform.h" 30 | #include "config.h" 31 | 32 | #define LINE_WIDTH_DEFAULT (1.0) 33 | #define LINE_WIDTH_BARS (1.0) 34 | #define VALUES_PER_SAMPLE (3) 35 | #define W_COLOR(X) (X)->r, (X)->g, (X)->b, (X)->a 36 | 37 | typedef struct 38 | { 39 | double x; 40 | double y; 41 | } waveform_point_t; 42 | 43 | typedef struct 44 | { 45 | double x1, y1; 46 | double x2, y2; 47 | } waveform_line_t; 48 | 49 | void 50 | waveform_data_render_free (waveform_data_render_t *w_render_ctx) 51 | { 52 | if (!w_render_ctx) { 53 | return; 54 | } 55 | 56 | if (w_render_ctx->samples) { 57 | for (int ch = 0; ch < w_render_ctx->num_channels; ch++) { 58 | waveform_sample_t *samples = w_render_ctx->samples[ch]; 59 | if (samples) { 60 | free (samples); 61 | w_render_ctx->samples[ch] = NULL; 62 | } 63 | } 64 | free (w_render_ctx->samples); 65 | w_render_ctx->samples = NULL; 66 | } 67 | free (w_render_ctx); 68 | w_render_ctx = NULL; 69 | 70 | return; 71 | } 72 | 73 | waveform_data_render_t * 74 | waveform_data_render_new (int channels, int width) 75 | { 76 | if (channels <= 0) { 77 | return NULL; 78 | } 79 | 80 | waveform_data_render_t *w_render_ctx = calloc (1, sizeof (waveform_data_render_t)); 81 | assert (w_render_ctx != NULL); 82 | 83 | w_render_ctx->samples = calloc (channels, sizeof (waveform_sample_t *)); 84 | assert (w_render_ctx->samples != NULL); 85 | 86 | for (int ch = 0; ch < channels; ch++) { 87 | w_render_ctx->samples[ch] = calloc (width, sizeof (waveform_sample_t)); 88 | assert (w_render_ctx->samples[ch] != NULL); 89 | } 90 | 91 | w_render_ctx->num_channels = channels; 92 | w_render_ctx->num_samples = width; 93 | 94 | return w_render_ctx; 95 | } 96 | 97 | static int 98 | waveform_data_render_build_sample (wavedata_t *wave_data, 99 | waveform_sample_t *sample, 100 | int sample_size, 101 | int channel, 102 | double start, 103 | double end) 104 | { 105 | const int ch_offset = channel * VALUES_PER_SAMPLE; 106 | 107 | float min = 1.0; 108 | float max = -1.0; 109 | float rms = 0.0; 110 | 111 | const int s_end = floorf (sample_size * end); 112 | 113 | int counter = 0; 114 | for (int i = floor(start); i < ceil(end); i++) { 115 | for (int pos = i * sample_size; pos < s_end; pos += sample_size, counter++) { 116 | int index = pos + ch_offset; 117 | float s_max = (float)wave_data->data[index]/1000; 118 | float s_min = (float)wave_data->data[index+1]/1000; 119 | float s_rms = (float)wave_data->data[index+2]/1000; 120 | max = MAX (max, s_max); 121 | min = MIN (min, s_min); 122 | rms += s_rms * s_rms; 123 | } 124 | } 125 | 126 | sample->max = max; 127 | sample->min = min; 128 | sample->rms = rms; 129 | 130 | return counter; 131 | } 132 | 133 | waveform_data_render_t * 134 | waveform_render_data_build (wavedata_t *wave_data, int width, bool downmix_mono) 135 | { 136 | const int channels_data = wave_data->channels; 137 | if (channels_data <= 0) { 138 | return NULL; 139 | } 140 | 141 | const int channels_render = CONFIG_MIX_TO_MONO ? 1 : channels_data; 142 | const int sample_size = VALUES_PER_SAMPLE * channels_data; 143 | const double num_samples_per_x = wave_data->data_len / (double)(width * sample_size); 144 | 145 | waveform_data_render_t *w_render_ctx = waveform_data_render_new (channels_render, width); 146 | 147 | for (int ch = 0; ch < w_render_ctx->num_channels; ch++) { 148 | waveform_sample_t *samples = w_render_ctx->samples[ch]; 149 | 150 | double d_start = 0.; 151 | 152 | for (int x = 0; x < width; x++) { 153 | const double d_end = MAX ((x + 1) * num_samples_per_x, 1.); 154 | waveform_sample_t *sample = &samples[x]; 155 | 156 | int counter = 0; 157 | if (CONFIG_MIX_TO_MONO) { 158 | for (int ch_data = 0; ch_data < channels_data; ch_data++) { 159 | counter += waveform_data_render_build_sample (wave_data, 160 | sample, 161 | sample_size, 162 | ch_data, 163 | d_start, 164 | d_end); 165 | } 166 | } 167 | else { 168 | counter += waveform_data_render_build_sample (wave_data, 169 | sample, 170 | sample_size, 171 | ch, 172 | d_start, 173 | d_end); 174 | } 175 | 176 | sample->rms /= counter; 177 | sample->rms = sqrt (sample->rms); 178 | 179 | d_start = d_end; 180 | } 181 | } 182 | 183 | return w_render_ctx; 184 | } 185 | 186 | enum SAMPLE_TYPE { 187 | SAMPLE_MAX, 188 | SAMPLE_MIN, 189 | SAMPLE_RMS_MAX, 190 | SAMPLE_RMS_MIN, 191 | N_SAMPLE_TYPES 192 | }; 193 | 194 | typedef void (*waveform_render_sample_func)(cairo_t *cr_ctx, 195 | waveform_sample_t *sample, 196 | waveform_point_t *point, 197 | double y_scale_1, 198 | double y_scale_2); 199 | 200 | /* copied from ardour3 */ 201 | static inline float 202 | _log_meter (float power, double lower_db, double upper_db, double non_linearity) 203 | { 204 | return (power < lower_db ? 0.0 : pow ((power - lower_db) / (upper_db - lower_db), non_linearity)); 205 | } 206 | 207 | static inline float 208 | alt_log_meter (float power) 209 | { 210 | return _log_meter (power, -192.0, 0.0, 8.0); 211 | } 212 | 213 | static inline float 214 | coefficient_to_dB (float coeff) 215 | { 216 | return 20.0f * log10 (coeff); 217 | } 218 | /* end of ardour copy */ 219 | 220 | static inline float 221 | sample_log_scale (float sample) 222 | { 223 | float sample_log = 0.0; 224 | if (sample > 0.0) { 225 | sample_log = alt_log_meter (coefficient_to_dB (sample)); 226 | } 227 | else { 228 | sample_log = -alt_log_meter (coefficient_to_dB (-sample)); 229 | } 230 | 231 | return sample_log; 232 | } 233 | 234 | static float 235 | sample_value_scale (float value, float scale, bool log_scale) 236 | { 237 | if (log_scale) { 238 | value = sample_log_scale (value); 239 | } 240 | 241 | return value * scale; 242 | } 243 | 244 | static void 245 | waveform_render_samples_loop_reverse (cairo_t *cr_ctx, 246 | waveform_sample_t *samples, 247 | waveform_render_sample_func *render_sample, 248 | double y_scale_1, 249 | double y_scale_2, 250 | double x_start, 251 | double y_start, 252 | double width) 253 | { 254 | if (!render_sample) { 255 | return; 256 | } 257 | 258 | const int width_i = floor (width) - 1; 259 | 260 | for (int x = width_i; x >= x_start; x--) { 261 | waveform_sample_t *sample = &samples[x]; 262 | waveform_point_t point = {x, y_start}; 263 | 264 | (*render_sample)(cr_ctx, sample, &point, y_scale_1, y_scale_2); 265 | } 266 | } 267 | 268 | static void 269 | waveform_render_samples_loop (cairo_t *cr_ctx, 270 | waveform_sample_t *samples, 271 | waveform_render_sample_func *render_sample, 272 | double y_scale_1, 273 | double y_scale_2, 274 | double x_start, 275 | double y_start, 276 | double width) 277 | { 278 | if (!render_sample) { 279 | return; 280 | } 281 | 282 | const int width_i = floor (width); 283 | 284 | for (int x = 0; x < width_i; x++) { 285 | waveform_sample_t *sample = &samples[x]; 286 | waveform_point_t point = {x_start + x, y_start}; 287 | 288 | (*render_sample)(cr_ctx, sample, &point, y_scale_1, y_scale_2); 289 | } 290 | } 291 | 292 | static void inline 293 | waveform_render_bars_sample_generic (cairo_t *cr_ctx, 294 | double s1, 295 | double s2, 296 | waveform_point_t *point, 297 | double y_scale_1, 298 | double y_scale_2) 299 | { 300 | const double x = point->x; 301 | const double y = point->y; 302 | const double y_1 = y - sample_value_scale (s1, y_scale_1, CONFIG_LOG_ENABLED); 303 | const double y_2 = y - sample_value_scale (s2, y_scale_2, CONFIG_LOG_ENABLED); 304 | 305 | cairo_move_to (cr_ctx, x, y_1); 306 | cairo_line_to (cr_ctx, x, y_2); 307 | } 308 | 309 | static void 310 | waveform_render_bars_sample_minmax (cairo_t *cr_ctx, 311 | waveform_sample_t *sample, 312 | waveform_point_t *point, 313 | double y_scale_1, 314 | double y_scale_2) 315 | { 316 | waveform_render_bars_sample_generic (cr_ctx, 317 | sample->max, 318 | sample->min, 319 | point, 320 | y_scale_1, 321 | y_scale_2); 322 | } 323 | 324 | static void 325 | waveform_render_bars_sample_rms (cairo_t *cr_ctx, 326 | waveform_sample_t *sample, 327 | waveform_point_t *point, 328 | double y_scale_1, 329 | double y_scale_2) 330 | { 331 | waveform_render_bars_sample_generic (cr_ctx, 332 | sample->rms, 333 | -sample->rms, 334 | point, 335 | y_scale_1, 336 | y_scale_2); 337 | 338 | } 339 | 340 | static cairo_pattern_t * 341 | waveform_render_soundcloud_pattern_get (cairo_t *cr_ctx, 342 | waveform_colors_t *color, 343 | waveform_line_t *vec_pat) 344 | { 345 | cairo_pattern_t *lin_pat = cairo_pattern_create_linear (vec_pat->x1, 346 | vec_pat->y1, 347 | vec_pat->x2, 348 | vec_pat->y2); 349 | cairo_pattern_add_color_stop_rgba (lin_pat, 0.0, color->fg.r, color->fg.g, color->fg.b, 0.7); 350 | cairo_pattern_add_color_stop_rgba (lin_pat, 0.7, color->fg.r, color->fg.g, color->fg.b, 1.0); 351 | cairo_pattern_add_color_stop_rgba (lin_pat, 0.7, color->fg.r, color->fg.g, color->fg.b, 0.5); 352 | cairo_pattern_add_color_stop_rgba (lin_pat, 1.0, color->fg.r, color->fg.g, color->fg.b, 0.5); 353 | cairo_set_source (cr_ctx, lin_pat); 354 | 355 | return lin_pat; 356 | } 357 | 358 | static void 359 | waveform_render_wave_bar_values (cairo_t *cr_ctx, 360 | waveform_sample_t *samples, 361 | waveform_colors_t *color, 362 | int type, 363 | waveform_rect_t *rect) 364 | { 365 | double x = rect->x; 366 | double y = rect->y; 367 | double width = rect->width; 368 | double height = rect->height; 369 | 370 | double y_scale_1 = 0.5 * height; 371 | if (CONFIG_SOUNDCLOUD_STYLE) { 372 | y_scale_1 = 0.7 * height; 373 | } 374 | double y_scale_2 = height - y_scale_1; 375 | 376 | double y_center = y_scale_1 + y; 377 | 378 | cairo_move_to (cr_ctx, x, y_center); 379 | 380 | waveform_render_sample_func render_func = NULL; 381 | switch (type) { 382 | case SAMPLE_RMS_MAX: 383 | case SAMPLE_RMS_MIN: 384 | render_func = &waveform_render_bars_sample_rms; 385 | break; 386 | case SAMPLE_MAX: 387 | case SAMPLE_MIN: 388 | render_func = &waveform_render_bars_sample_minmax; 389 | break; 390 | } 391 | 392 | cairo_pattern_t *lin_pat = NULL; 393 | if (CONFIG_SOUNDCLOUD_STYLE) { 394 | waveform_line_t vec_pat = { 395 | .x1 = x, 396 | .y1 = y, 397 | .x2 = x, 398 | .y2 = y + height, 399 | }; 400 | lin_pat = waveform_render_soundcloud_pattern_get (cr_ctx, 401 | color, 402 | &vec_pat); 403 | } 404 | 405 | waveform_render_samples_loop (cr_ctx, 406 | samples, 407 | &render_func, 408 | y_scale_1, 409 | y_scale_2, 410 | x, 411 | y_center, 412 | width); 413 | cairo_stroke (cr_ctx); 414 | 415 | if (lin_pat) { 416 | cairo_pattern_destroy (lin_pat); 417 | lin_pat = NULL; 418 | } 419 | } 420 | 421 | void 422 | waveform_draw_wave_bars (waveform_sample_t *samples, 423 | waveform_colors_t *colors, 424 | cairo_t *cr_ctx, 425 | waveform_rect_t *rect) 426 | { 427 | cairo_set_line_width (cr_ctx, LINE_WIDTH_BARS); 428 | cairo_set_antialias (cr_ctx, CAIRO_ANTIALIAS_NONE); 429 | cairo_set_source_rgba (cr_ctx, W_COLOR (&colors->fg)); 430 | 431 | // draw min/max values 432 | waveform_render_wave_bar_values (cr_ctx, 433 | samples, 434 | colors, 435 | SAMPLE_MAX, 436 | rect); 437 | 438 | if (CONFIG_DISPLAY_RMS) { 439 | // draw rms values 440 | cairo_set_source_rgba (cr_ctx, W_COLOR (&colors->rms)); 441 | waveform_render_wave_bar_values (cr_ctx, 442 | samples, 443 | colors, 444 | SAMPLE_RMS_MAX, 445 | rect); 446 | } 447 | 448 | return; 449 | } 450 | 451 | static void 452 | waveform_render_default_sample_generic (cairo_t *cr_ctx, 453 | double sample, 454 | waveform_point_t *point, 455 | double y_scale) 456 | { 457 | const double x = point->x; 458 | const double y = point->y - sample_value_scale (sample, y_scale, CONFIG_LOG_ENABLED); 459 | cairo_line_to (cr_ctx, x, y); 460 | } 461 | 462 | static void 463 | waveform_render_default_sample_max (cairo_t *cr_ctx, 464 | waveform_sample_t *sample, 465 | waveform_point_t *point, 466 | double y_scale_1, 467 | double y_scale_2) 468 | { 469 | waveform_render_default_sample_generic (cr_ctx, 470 | sample->max, 471 | point, 472 | y_scale_1); 473 | } 474 | 475 | static void 476 | waveform_render_default_sample_min (cairo_t *cr_ctx, 477 | waveform_sample_t *sample, 478 | waveform_point_t *point, 479 | double y_scale_1, 480 | double y_scale_2) 481 | { 482 | waveform_render_default_sample_generic (cr_ctx, 483 | sample->min, 484 | point, 485 | y_scale_1); 486 | } 487 | 488 | static void 489 | waveform_render_default_sample_rms1 (cairo_t *cr_ctx, 490 | waveform_sample_t *sample, 491 | waveform_point_t *point, 492 | double y_scale_1, 493 | double y_scale_2) 494 | { 495 | waveform_render_default_sample_generic (cr_ctx, 496 | sample->rms, 497 | point, 498 | y_scale_1); 499 | } 500 | 501 | static void 502 | waveform_render_default_sample_rms2 (cairo_t *cr_ctx, 503 | waveform_sample_t *sample, 504 | waveform_point_t *point, 505 | double y_scale_1, 506 | double y_scale_2) 507 | { 508 | waveform_render_default_sample_generic (cr_ctx, 509 | -sample->rms, 510 | point, 511 | y_scale_1); 512 | } 513 | 514 | enum SAMPLE_GROUPS { 515 | SAMPLE_MIN_MAX, 516 | SAMPLE_RMS_MIN_MAX, 517 | N_SAMPLE_GROUPS, 518 | }; 519 | 520 | static void 521 | waveform_render_wave_default_values (cairo_t *cr_ctx, 522 | waveform_sample_t *samples, 523 | waveform_colors_t *color, 524 | int type, 525 | waveform_rect_t *rect) 526 | { 527 | double x = rect->x; 528 | double y = rect->y; 529 | double width = rect->width; 530 | double height = rect->height; 531 | 532 | waveform_render_sample_func render_func_1; 533 | waveform_render_sample_func render_func_2; 534 | 535 | switch (type) { 536 | case SAMPLE_MIN_MAX: 537 | render_func_1 = waveform_render_default_sample_max; 538 | render_func_2= waveform_render_default_sample_min; 539 | break; 540 | case SAMPLE_RMS_MIN_MAX: 541 | render_func_1= waveform_render_default_sample_rms1; 542 | render_func_2= waveform_render_default_sample_rms2; 543 | break; 544 | default: 545 | return; 546 | } 547 | 548 | double y_scale = 0.5 * height; 549 | double y_center = y_scale + y; 550 | if (CONFIG_SOUNDCLOUD_STYLE) { 551 | y_scale = 0.7 * height; 552 | y_center = y_scale + y; 553 | } 554 | 555 | cairo_pattern_t *lin_pat = NULL; 556 | if (CONFIG_SOUNDCLOUD_STYLE) { 557 | waveform_line_t vec_pat = { 558 | .x1 = x, 559 | .y1 = y, 560 | .x2 = x, 561 | .y2 = y + height, 562 | }; 563 | lin_pat = waveform_render_soundcloud_pattern_get (cr_ctx, color, &vec_pat); 564 | } 565 | 566 | cairo_move_to (cr_ctx, x, y_center); 567 | waveform_render_samples_loop (cr_ctx, 568 | samples, 569 | &render_func_1, 570 | y_scale, 571 | y_scale, 572 | x, 573 | y_center, 574 | width); 575 | 576 | y_scale = height - y_scale; 577 | 578 | waveform_render_samples_loop_reverse (cr_ctx, 579 | samples, 580 | &render_func_2, 581 | y_scale, 582 | y_scale, 583 | x, 584 | y_center, 585 | width); 586 | if (!CONFIG_FILL_WAVEFORM) { 587 | cairo_stroke (cr_ctx); 588 | } 589 | else { 590 | cairo_line_to (cr_ctx, x, y); 591 | cairo_close_path (cr_ctx); 592 | cairo_fill (cr_ctx); 593 | } 594 | 595 | if (lin_pat) { 596 | cairo_pattern_destroy (lin_pat); 597 | lin_pat = NULL; 598 | } 599 | } 600 | 601 | void 602 | waveform_draw_wave_default (waveform_sample_t *samples, 603 | waveform_colors_t *colors, 604 | cairo_t *cr_ctx, 605 | waveform_rect_t *rect) 606 | { 607 | cairo_set_line_width (cr_ctx, LINE_WIDTH_DEFAULT); 608 | cairo_set_antialias (cr_ctx, CAIRO_ANTIALIAS_DEFAULT); 609 | cairo_set_source_rgba (cr_ctx, W_COLOR (&colors->fg)); 610 | 611 | waveform_render_wave_default_values (cr_ctx, 612 | samples, 613 | colors, 614 | SAMPLE_MIN_MAX, 615 | rect); 616 | 617 | if (CONFIG_DISPLAY_RMS) { 618 | cairo_set_source_rgba (cr_ctx, W_COLOR (&colors->rms)); 619 | 620 | waveform_render_wave_default_values (cr_ctx, 621 | samples, 622 | colors, 623 | SAMPLE_RMS_MIN_MAX, 624 | rect); 625 | } 626 | 627 | return; 628 | } 629 | 630 | -------------------------------------------------------------------------------- /waveform.c: -------------------------------------------------------------------------------- 1 | /* 2 | Waveform seekbar plugin for the DeaDBeeF audio player 3 | 4 | Copyright (C) 2014 Christian Boxdörfer 5 | 6 | Based on sndfile-tools waveform by Erik de Castro Lopo. 7 | waveform.c - v1.04 8 | Copyright (C) 2007-2012 Erik de Castro Lopo 9 | Copyright (C) 2012 Robin Gareus 10 | Copyright (C) 2013 driedfruit 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 program is distributed in the hope that it will be useful, 18 | but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | GNU General Public License for more details. 21 | 22 | You should have received a copy of the GNU General Public License 23 | along with this program; if not, write to the Free Software 24 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | #include "support.h" 40 | #include "cache.h" 41 | #include "config.h" 42 | #include "config_dialog.h" 43 | #include "utils.h" 44 | #include "waveform.h" 45 | #include "render.h" 46 | #include "ruler.h" 47 | 48 | #define W_COLOR(X) (X)->r, (X)->g, (X)->b, (X)->a 49 | 50 | //#define M_PI (3.1415926535897932384626433832795029) 51 | // min, max, rms 52 | #define VALUES_PER_SAMPLE (3) 53 | #define MAX_CHANNELS (6) 54 | #define MAX_SAMPLES (4096) 55 | #define DISTANCE_THRESHOLD (100) 56 | 57 | 58 | /* Global variables */ 59 | DB_functions_t *deadbeef = NULL; 60 | static DB_misc_t plugin; 61 | static ddb_gtkui_t *gtkui_plugin = NULL; 62 | 63 | static char cache_path[PATH_MAX]; 64 | 65 | enum PLAYBACK_STATUS { STOPPED = 0, PLAYING = 1, PAUSED = 2 }; 66 | static int playback_status = STOPPED; 67 | static int waveform_instancecount; 68 | 69 | typedef struct 70 | { 71 | ddb_gtkui_widget_t base; 72 | GtkWidget *popup; 73 | GtkWidget *popup_item; 74 | GtkWidget *drawarea; 75 | GtkWidget *ruler; 76 | GtkWidget *frame; 77 | guint drawtimer; 78 | guint resizetimer; 79 | wavedata_t *wave; 80 | 81 | waveform_colors_t colors; 82 | waveform_colors_t colors_shaded; 83 | 84 | size_t max_buffer_len; 85 | int seekbar_moving; 86 | float seekbar_move_x; 87 | float seekbar_move_x_clicked; 88 | float height; 89 | float width; 90 | float pos_last; 91 | intptr_t mutex; 92 | cairo_surface_t *surf; 93 | cairo_surface_t *surf_shaded; 94 | } waveform_t; 95 | 96 | typedef struct 97 | { 98 | double x; 99 | double y; 100 | } waveform_point_t; 101 | 102 | typedef struct 103 | { 104 | double x1, y1; 105 | double x2, y2; 106 | } waveform_line_t; 107 | 108 | static gboolean 109 | waveform_draw_cb (void *user_data); 110 | 111 | static gboolean 112 | waveform_redraw_cb (void *user_data); 113 | 114 | static gboolean 115 | ruler_redraw_cb (void *user_data); 116 | 117 | static void 118 | waveform_draw (void *user_data, int shaded); 119 | 120 | static gboolean 121 | waveform_set_refresh_interval (void *user_data, int interval); 122 | 123 | static color_t 124 | waveform_color_contrast (color_t *color) 125 | { 126 | // Counting the perceptive luminance - human eye favors green color... 127 | double a = 1.0 - ( 2.0 * color->r + 3.0 * color->g + color->b) / 6.0; 128 | if (a < 0.5) 129 | a = 0.0; // bright colors - black font 130 | else 131 | a = 1.0; // dark colors - white font 132 | 133 | color_t color_cont = { 134 | .r = a, 135 | .g = a, 136 | .b = a, 137 | .a = 1.0, 138 | }; 139 | return color_cont; 140 | } 141 | 142 | static void 143 | waveform_colors_update (waveform_t *w) 144 | { 145 | w->colors.fg = (color_t) { 146 | CONFIG_FG_COLOR.red/65535.f, 147 | CONFIG_FG_COLOR.green/65535.f, 148 | CONFIG_FG_COLOR.blue/65535.f, 149 | 1.0 150 | }; 151 | w->colors.bg = (color_t) { 152 | CONFIG_BG_COLOR.red/65535.f, 153 | CONFIG_BG_COLOR.green/65535.f, 154 | CONFIG_BG_COLOR.blue/65535.f, 155 | 1.0 156 | }; 157 | w->colors.rms = (color_t) { 158 | CONFIG_FG_RMS_COLOR.red/65535.f, 159 | CONFIG_FG_RMS_COLOR.green/65535.f, 160 | CONFIG_FG_RMS_COLOR.blue/65535.f, 161 | 1.0 162 | }; 163 | 164 | w->colors.pb = (color_t) { 165 | CONFIG_PB_COLOR.red/65535.f, 166 | CONFIG_PB_COLOR.green/65535.f, 167 | CONFIG_PB_COLOR.blue/65535.f, 168 | 1.0 169 | }; 170 | 171 | w->colors.rlr = (color_t) { 172 | CONFIG_RLR_COLOR.red/65535.f, 173 | CONFIG_RLR_COLOR.green/65535.f, 174 | CONFIG_RLR_COLOR.blue/65535.f, 175 | CONFIG_RLR_ALPHA/65535.f 176 | }; 177 | 178 | w->colors.font = waveform_color_contrast (&w->colors.bg); 179 | w->colors.font_pb = waveform_color_contrast (&w->colors.pb); 180 | 181 | w->colors_shaded.fg = w->colors.pb; 182 | w->colors_shaded.bg = w->colors.bg; 183 | w->colors_shaded.pb = w->colors.pb; 184 | w->colors_shaded.pb.a = CONFIG_PB_ALPHA/65535.f; 185 | 186 | w->colors_shaded.rms = (color_t) { 187 | 0.8 * w->colors.pb.r, 188 | 0.8 * w->colors.pb.g, 189 | 0.8 * w->colors.pb.b, 190 | 1.0 191 | }; 192 | } 193 | 194 | static int 195 | on_config_changed (void *widget) 196 | { 197 | waveform_t *w = (waveform_t *) widget; 198 | load_config (); 199 | waveform_colors_update (w); 200 | // enable/disable border 201 | switch (CONFIG_BORDER_WIDTH) { 202 | case 0: 203 | gtk_frame_set_shadow_type ((GtkFrame *)w->frame, GTK_SHADOW_NONE); 204 | break; 205 | case 1: 206 | gtk_frame_set_shadow_type ((GtkFrame *)w->frame, GTK_SHADOW_IN); 207 | break; 208 | } 209 | switch (CONFIG_DISPLAY_RULER) { 210 | case 0: 211 | gtk_widget_hide (w->ruler); 212 | break; 213 | case 1: 214 | gtk_widget_show (w->ruler); 215 | break; 216 | } 217 | 218 | waveform_set_refresh_interval (w, CONFIG_REFRESH_INTERVAL); 219 | g_idle_add (waveform_redraw_cb, w); 220 | g_idle_add (ruler_redraw_cb, w); 221 | return 0; 222 | } 223 | 224 | static void 225 | make_cache_dir (char *path, int size) 226 | { 227 | const char *cache_dir = g_get_user_cache_dir (); 228 | if (cache_dir) { 229 | snprintf (path, size, "%s/deadbeef/waveform_seekbar", cache_dir); 230 | g_mkdir_with_parents (path, 0755); 231 | } 232 | } 233 | 234 | static char * 235 | waveform_format_uri (DB_playItem_t *it, const char *uri) 236 | { 237 | if (!it || !uri) { 238 | return NULL; 239 | } 240 | const int key_len = strlen (uri) + 10; 241 | char *key = malloc (key_len); 242 | if (deadbeef->pl_get_item_flags (it) & DDB_IS_SUBTRACK) { 243 | int subtrack = deadbeef->pl_find_meta_int (it, ":TRACKNUM", 0); 244 | snprintf (key, key_len, "%d%s", subtrack, uri); 245 | } 246 | else { 247 | snprintf (key, key_len, "%s", uri); 248 | } 249 | return key; 250 | } 251 | 252 | enum BORDERS 253 | { 254 | CORNER_NONE = 0, 255 | CORNER_TOPLEFT = 1, 256 | CORNER_TOPRIGHT = 2, 257 | CORNER_BOTTOMLEFT = 4, 258 | CORNER_BOTTOMRIGHT = 8, 259 | CORNER_ALL = 15 260 | }; 261 | 262 | static void 263 | clearlooks_rounded_rectangle (cairo_t * cr, 264 | double x, 265 | double y, 266 | double w, 267 | double h, 268 | double radius, 269 | uint8_t corners) 270 | { 271 | if (radius < 0.01 || (corners == CORNER_NONE)) { 272 | cairo_rectangle (cr, x, y, w, h); 273 | return; 274 | } 275 | 276 | if (corners & CORNER_TOPLEFT) 277 | cairo_move_to (cr, x + radius, y); 278 | else 279 | cairo_move_to (cr, x, y); 280 | 281 | if (corners & CORNER_TOPRIGHT) 282 | cairo_arc (cr, x + w - radius, y + radius, radius, M_PI * 1.5, M_PI * 2); 283 | else 284 | cairo_line_to (cr, x + w, y); 285 | 286 | if (corners & CORNER_BOTTOMRIGHT) 287 | cairo_arc (cr, x + w - radius, y + h - radius, radius, 0, M_PI * 0.5); 288 | else 289 | cairo_line_to (cr, x + w, y + h); 290 | 291 | if (corners & CORNER_BOTTOMLEFT) 292 | cairo_arc (cr, x + radius, y + h - radius, radius, M_PI * 0.5, M_PI); 293 | else 294 | cairo_line_to (cr, x, y + h); 295 | 296 | if (corners & CORNER_TOPLEFT) 297 | cairo_arc (cr, x + radius, y + radius, radius, M_PI, M_PI * 1.5); 298 | else 299 | cairo_line_to (cr, x, y); 300 | 301 | } 302 | 303 | static inline void 304 | waveform_draw_cairo_rectangle (cairo_t *cr, color_t *clr, waveform_rect_t *rect) 305 | { 306 | cairo_set_source_rgba (cr, W_COLOR (clr)); 307 | cairo_rectangle (cr, rect->x, rect->y, rect->width, rect->height); 308 | cairo_fill (cr); 309 | } 310 | 311 | static gboolean 312 | ruler_redraw_cb (void *user_data) 313 | { 314 | waveform_t *w = user_data; 315 | gtk_widget_queue_draw (w->ruler); 316 | return FALSE; 317 | } 318 | 319 | static gboolean 320 | waveform_draw_cb (void *user_data) 321 | { 322 | waveform_t *w = user_data; 323 | 324 | DB_playItem_t *trk = deadbeef->streamer_get_playing_track (); 325 | if (!trk) { 326 | return FALSE; 327 | } 328 | 329 | GtkAllocation a; 330 | gtk_widget_get_allocation (w->drawarea, &a); 331 | const int width = a.width; 332 | const int height = a.height; 333 | const float dur = deadbeef->pl_get_item_duration (trk); 334 | deadbeef->pl_item_unref (trk); 335 | 336 | const float pos = (deadbeef->streamer_get_playpos () * width)/ dur; 337 | if (pos < w->pos_last) { 338 | w->pos_last = 0; 339 | } 340 | 341 | const float x = floorf (w->pos_last - CONFIG_CURSOR_WIDTH); 342 | const float dx = ceilf (pos - w->pos_last + CONFIG_CURSOR_WIDTH); 343 | gtk_widget_queue_draw_area (w->drawarea, x, 0, dx, height); 344 | 345 | w->pos_last = pos; 346 | 347 | return TRUE; 348 | } 349 | 350 | static gboolean 351 | waveform_redraw_cb (void *user_data) 352 | { 353 | waveform_t *w = user_data; 354 | if (w->resizetimer) { 355 | g_source_remove (w->resizetimer); 356 | w->resizetimer = 0; 357 | } 358 | waveform_draw (w, 0); 359 | waveform_draw (w, 1); 360 | gtk_widget_queue_draw (w->drawarea); 361 | return FALSE; 362 | } 363 | 364 | static void 365 | waveform_draw_text (cairo_t *cr, waveform_colors_t *color, const char *text, double x, double y) 366 | { 367 | cairo_set_source_rgba (cr, W_COLOR (&color->font)); 368 | cairo_set_font_size (cr, CONFIG_FONT_SIZE); 369 | 370 | cairo_text_extents_t ex; 371 | cairo_text_extents (cr, text, &ex); 372 | const double text_x = x - ex.width/2; 373 | const double text_y = y + ex.height/2; 374 | cairo_move_to (cr, text_x, text_y); 375 | cairo_show_text (cr, text); 376 | } 377 | 378 | static void 379 | waveform_draw_seeking_cursor (waveform_t *w, cairo_t *cr, float duration, waveform_rect_t *rect) 380 | { 381 | float seek_pos = floor(CLAMP (w->seekbar_move_x, rect->x, rect->x + rect->width)); 382 | 383 | waveform_rect_t cursor_rect = { 384 | .x = seek_pos - CONFIG_CURSOR_WIDTH, 385 | .y = rect->y, 386 | .width = CONFIG_CURSOR_WIDTH, 387 | .height = rect->height, 388 | }; 389 | waveform_draw_cairo_rectangle (cr, &w->colors.pb, &cursor_rect); 390 | 391 | if (w->seekbar_move_x != w->seekbar_move_x_clicked || w->seekbar_move_x_clicked == -1) { 392 | w->seekbar_move_x_clicked = -1; 393 | 394 | const float cur_time = CLAMP (w->seekbar_move_x * duration / rect->width, 0, duration); 395 | const int hr = cur_time / 3600; 396 | const int mn = (cur_time - hr * 3600)/60; 397 | const int sc = cur_time - hr * 3600 - mn * 60; 398 | 399 | char s[100] = ""; 400 | snprintf (s, sizeof (s), "%02d:%02d:%02d", hr, mn, sc); 401 | 402 | cairo_set_source_rgba (cr, W_COLOR (&w->colors.pb)); 403 | cairo_set_font_size (cr, CONFIG_FONT_SIZE); 404 | cairo_select_font_face (cr, 405 | "monospace", 406 | CAIRO_FONT_SLANT_NORMAL, 407 | CAIRO_FONT_WEIGHT_NORMAL); 408 | 409 | 410 | cairo_text_extents_t text_size; 411 | cairo_text_extents (cr, s, &text_size); 412 | 413 | const double rec_padding = 5; 414 | const double rec_padding_total = 2 * rec_padding; 415 | const double rec_width = ceil (text_size.x_advance) + rec_padding_total; 416 | const double rec_height = ceil (text_size.height) + rec_padding_total; 417 | double rec_pos = ceil (seek_pos - rec_width); 418 | double text_pos = rec_pos + rec_padding; 419 | 420 | //uint8_t corners = CORNER_TOPLEFT | CORNER_BOTTOMLEFT; 421 | uint8_t corners = CORNER_NONE; 422 | if (seek_pos < rec_width) { 423 | rec_pos = 0; 424 | text_pos = rec_pos + rec_padding; 425 | //corners = CORNER_TOPRIGHT | CORNER_BOTTOMRIGHT; 426 | } 427 | 428 | 429 | clearlooks_rounded_rectangle (cr, rec_pos, (rect->height - text_size.height - rec_padding_total)/2, rec_width, rec_height, 3, corners); 430 | cairo_fill (cr); 431 | cairo_move_to (cr, text_pos, (rect->height + text_size.height)/2); 432 | cairo_set_source_rgba (cr, W_COLOR (&w->colors.font_pb)); 433 | cairo_show_text (cr, s); 434 | } 435 | } 436 | 437 | static void 438 | waveform_seekbar_draw (gpointer user_data, cairo_t *cr, waveform_rect_t *rect) 439 | { 440 | waveform_t *w = user_data; 441 | if (playback_status == STOPPED) { 442 | return; 443 | } 444 | DB_playItem_t *trk = deadbeef->streamer_get_playing_track (); 445 | if (!trk) { 446 | return; 447 | } 448 | 449 | const double left = rect->x; 450 | const double top = rect->y; 451 | const double width = rect->width; 452 | const double height = rect->height; 453 | 454 | const float dur = deadbeef->pl_get_item_duration (trk); 455 | const float pos = (deadbeef->streamer_get_playpos () * width)/ dur + left; 456 | int cursor_width = CONFIG_CURSOR_WIDTH; 457 | 458 | if (!deadbeef->is_local_file (deadbeef->pl_find_meta_raw (trk, ":URI"))) { 459 | if (w->drawtimer) { 460 | g_source_remove (w->drawtimer); 461 | w->drawtimer = 0; 462 | } 463 | waveform_draw_cairo_rectangle (cr, &w->colors.bg, rect); 464 | waveform_draw_text (cr, &w->colors, "Streaming...", width/2,height/2); 465 | } 466 | else { 467 | if (height != w->height || width != w->width) { 468 | cairo_save (cr); 469 | cairo_translate (cr, 0, 0); 470 | cairo_scale (cr, width/w->width, height/w->height); 471 | cairo_set_source_surface (cr, w->surf_shaded, 0, 0); 472 | cairo_rectangle (cr, left, top, (pos - cursor_width) / (width/w->width), height / (height/w->height)); 473 | cairo_fill (cr); 474 | cairo_restore (cr); 475 | } 476 | else { 477 | cairo_set_source_surface (cr, w->surf_shaded, 0, 0); 478 | cairo_rectangle (cr, left, top, pos - cursor_width, height); 479 | cairo_fill (cr); 480 | } 481 | 482 | waveform_rect_t cursor_rect = { 483 | .x = pos - cursor_width, 484 | .y = top, 485 | .width = cursor_width, 486 | .height = height, 487 | }; 488 | waveform_draw_cairo_rectangle (cr, &w->colors.pb, &cursor_rect); 489 | 490 | if (w->seekbar_moving && dur >= 0) { 491 | waveform_draw_seeking_cursor (w, cr, dur, rect); 492 | } 493 | } 494 | 495 | deadbeef->pl_item_unref (trk); 496 | } 497 | 498 | static cairo_surface_t * 499 | waveform_draw_surface_update (cairo_surface_t *surface, double width, double height) 500 | { 501 | if (!surface || cairo_image_surface_get_width (surface) != width || cairo_image_surface_get_height (surface) != height) { 502 | if (surface) { 503 | cairo_surface_destroy (surface); 504 | surface = NULL; 505 | } 506 | surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height); 507 | } 508 | return surface; 509 | } 510 | 511 | static void 512 | waveform_draw (void *user_data, int shaded) 513 | { 514 | waveform_t *w = user_data; 515 | GtkAllocation a; 516 | gtk_widget_get_allocation (w->drawarea, &a); 517 | 518 | const int width = a.width; 519 | const int height = a.height; 520 | 521 | w->width = width; 522 | w->height = height; 523 | 524 | cairo_surface_t *surface; 525 | if (!shaded) { 526 | w->surf = waveform_draw_surface_update (w->surf, width, height); 527 | surface = w->surf; 528 | } 529 | else { 530 | w->surf_shaded = waveform_draw_surface_update (w->surf_shaded, width, height); 531 | surface = w->surf_shaded; 532 | } 533 | 534 | cairo_surface_flush (surface); 535 | cairo_t *cr = cairo_create (surface); 536 | assert (cr != NULL); 537 | 538 | waveform_data_render_t *w_render_ctx = waveform_render_data_build (w->wave, width, CONFIG_MIX_TO_MONO); 539 | 540 | // Draw background 541 | waveform_rect_t bg_rect = { 542 | .x = 0, 543 | .y = 0, 544 | .width = width, 545 | .height = height, 546 | }; 547 | waveform_draw_cairo_rectangle (cr, &w->colors.bg, &bg_rect); 548 | 549 | if (w_render_ctx) { 550 | 551 | const int channels = w_render_ctx->num_channels; 552 | const double channel_height = height/channels; 553 | const double waveform_height = 0.9 * channel_height; 554 | const double x = 0.0; 555 | double y = (channel_height - waveform_height)/2; 556 | 557 | waveform_colors_t *colors = &w->colors; 558 | if (CONFIG_SHADE_WAVEFORM && shaded) { 559 | colors = &w->colors_shaded; 560 | } 561 | 562 | for (int ch = 0; ch < channels; ch++, y += channel_height) { 563 | waveform_sample_t *samples = w_render_ctx->samples[ch]; 564 | waveform_rect_t rect = { 565 | .x = x, 566 | .y = y, 567 | .width = width, 568 | .height = waveform_height, 569 | }; 570 | switch (CONFIG_RENDER_METHOD) { 571 | case SPIKES: 572 | waveform_draw_wave_default (samples, colors, cr, &rect); 573 | break; 574 | case BARS: 575 | waveform_draw_wave_bars (samples, colors, cr, &rect); 576 | break; 577 | default: 578 | waveform_draw_wave_default (samples, colors, cr, &rect); 579 | break; 580 | } 581 | } 582 | if (!CONFIG_SHADE_WAVEFORM && shaded == 1) { 583 | waveform_draw_cairo_rectangle (cr, &w->colors_shaded.pb, &bg_rect); 584 | } 585 | 586 | waveform_data_render_free (w_render_ctx); 587 | } 588 | 589 | cairo_destroy (cr); 590 | return; 591 | } 592 | 593 | static void 594 | waveform_scale (void *user_data, cairo_t *cr, waveform_rect_t *rect) 595 | { 596 | waveform_t *w = user_data; 597 | 598 | if (rect->height != w->height || rect->width != w->width) { 599 | cairo_save (cr); 600 | cairo_translate (cr, rect->x, rect->y); 601 | cairo_scale (cr, rect->width/w->width, rect->height/w->height); 602 | cairo_set_source_surface (cr, w->surf, rect->x, rect->y); 603 | cairo_paint (cr); 604 | cairo_restore (cr); 605 | } 606 | else { 607 | cairo_set_source_surface (cr, w->surf, rect->x, rect->y); 608 | cairo_paint (cr); 609 | } 610 | } 611 | 612 | static gboolean 613 | waveform_generate_wavedata (gpointer user_data, DB_playItem_t *it, const char *uri, wavedata_t *wavedata) 614 | { 615 | waveform_t *w = user_data; 616 | const double width = CONFIG_NUM_SAMPLES; 617 | 618 | DB_fileinfo_t *fileinfo = NULL; 619 | 620 | deadbeef->pl_lock (); 621 | const char *dec_meta = deadbeef->pl_find_meta_raw (it, ":DECODER"); 622 | char decoder_id[100]; 623 | if (dec_meta) { 624 | strncpy (decoder_id, dec_meta, sizeof (decoder_id)); 625 | } 626 | DB_decoder_t *dec = NULL; 627 | DB_decoder_t **decoders = deadbeef->plug_get_decoder_list (); 628 | for (int i = 0; decoders[i]; i++) { 629 | if (!strcmp (decoders[i]->plugin.id, decoder_id)) { 630 | dec = decoders[i]; 631 | break; 632 | } 633 | } 634 | deadbeef->pl_unlock (); 635 | 636 | wavedata->data_len = 0; 637 | wavedata->channels = 0; 638 | 639 | if (dec && dec->open) { 640 | fileinfo = dec->open (0); 641 | if (fileinfo && dec->init (fileinfo, DB_PLAYITEM (it)) != 0) { 642 | deadbeef->pl_lock (); 643 | fprintf (stderr, "waveform: failed to decode file %s\n", deadbeef->pl_find_meta (it, ":URI")); 644 | deadbeef->pl_unlock (); 645 | goto out; 646 | } 647 | float *data; 648 | float *buffer; 649 | 650 | if (fileinfo) { 651 | const float duration = deadbeef->pl_get_item_duration (it); 652 | const int num_updates = MAX (1, floorf (duration)/30); 653 | const int update_after_nsamples = width/num_updates; 654 | if (duration <= 0) { 655 | goto out; 656 | } 657 | const int bytes_per_sample = fileinfo->fmt.bps / 8; 658 | const int samplesize = fileinfo->fmt.channels * bytes_per_sample; 659 | const int nsamples_per_channel = floorf (duration * (float)fileinfo->fmt.samplerate); 660 | const int samples_per_buf = ceilf ((float) nsamples_per_channel / (float) width); 661 | const int max_samples_per_buf = 1 + samples_per_buf; 662 | 663 | w->wave->channels = fileinfo->fmt.channels; 664 | w->wave->data_len = w->wave->channels * 3 * CONFIG_NUM_SAMPLES; 665 | deadbeef->mutex_lock (w->mutex); 666 | memset (w->wave->data, 0, sizeof (short) * w->max_buffer_len); 667 | deadbeef->mutex_unlock (w->mutex); 668 | 669 | data = malloc (sizeof (float) * max_samples_per_buf * samplesize); 670 | if (!data) { 671 | trace ("waveform: out of memory.\n"); 672 | goto out; 673 | } 674 | memset (data, 0, sizeof (float) * max_samples_per_buf * samplesize); 675 | 676 | buffer = malloc (sizeof (float) * max_samples_per_buf * samplesize); 677 | if (!buffer) { 678 | trace ("waveform: out of memory.\n"); 679 | goto out; 680 | } 681 | memset (buffer, 0, sizeof (float) * max_samples_per_buf * samplesize); 682 | 683 | 684 | ddb_waveformat_t out_fmt = { 685 | .bps = 32, 686 | .channels = fileinfo->fmt.channels, 687 | .samplerate = fileinfo->fmt.samplerate, 688 | .channelmask = fileinfo->fmt.channelmask, 689 | .is_float = 1, 690 | .is_bigendian = 0 691 | }; 692 | 693 | int update_counter = 0; 694 | int eof = 0; 695 | int counter = 0; 696 | const long buffer_len = samples_per_buf * samplesize; 697 | while (!eof) { 698 | int sz = dec->read (fileinfo, (char *)buffer, buffer_len); 699 | if (sz != buffer_len) { 700 | eof = 1; 701 | } 702 | else if (sz == 0) { 703 | break; 704 | } 705 | 706 | deadbeef->pcm_convert (&fileinfo->fmt, (char *)buffer, &out_fmt, (char *)data, sz); 707 | 708 | int sample; 709 | float min, max, rms; 710 | 711 | for (int ch = 0; ch < fileinfo->fmt.channels; ch++) { 712 | min = 1.0; max = -1.0; rms = 0.0; 713 | for (sample = 0; sample < sz/samplesize; sample++) { 714 | if (sample * fileinfo->fmt.channels > buffer_len) { 715 | fprintf (stderr, "index error!\n"); 716 | break; 717 | } 718 | const float sample_val = data [sample * fileinfo->fmt.channels + ch]; 719 | max = MAX (max, sample_val); 720 | min = MIN (min, sample_val); 721 | rms += (sample_val * sample_val); 722 | } 723 | rms /= sample; 724 | rms = sqrt (rms); 725 | wavedata->data[counter] = (short)(max*1000); 726 | wavedata->data[counter+1] = (short)(min*1000); 727 | wavedata->data[counter+2] = (short)(rms*1000); 728 | counter += 3; 729 | } 730 | if (update_counter == update_after_nsamples) { 731 | DB_playItem_t *playing = deadbeef->streamer_get_playing_track (); 732 | if (playing) { 733 | if (playing == it) { 734 | deadbeef->mutex_lock (w->mutex); 735 | w->wave->channels = fileinfo->fmt.channels; 736 | w->wave->data_len = w->wave->channels * VALUES_PER_SAMPLE * CONFIG_NUM_SAMPLES; 737 | memset (w->wave->data, 0, sizeof (short) * w->max_buffer_len); 738 | memcpy (w->wave->data, wavedata->data, counter * sizeof (short)); 739 | deadbeef->mutex_unlock (w->mutex); 740 | g_idle_add (waveform_redraw_cb, w); 741 | } 742 | deadbeef->pl_item_unref (playing); 743 | } 744 | update_counter = 0; 745 | } 746 | update_counter++; 747 | } 748 | wavedata->fname = strdup (deadbeef->pl_find_meta_raw (it, ":URI")); 749 | wavedata->data_len = counter; 750 | wavedata->channels = fileinfo->fmt.channels; 751 | 752 | 753 | if (data) { 754 | free (data); 755 | data = NULL; 756 | } 757 | if (buffer) { 758 | free (buffer); 759 | buffer = NULL; 760 | } 761 | } 762 | } 763 | out: 764 | if (dec && fileinfo) { 765 | dec->free (fileinfo); 766 | fileinfo = NULL; 767 | } 768 | 769 | return TRUE; 770 | } 771 | 772 | static void 773 | waveform_db_cache (gpointer user_data, DB_playItem_t *it, wavedata_t *wavedata) 774 | { 775 | waveform_t *w = user_data; 776 | char *key = waveform_format_uri (it, wavedata->fname); 777 | if (!key) { 778 | return; 779 | } 780 | deadbeef->mutex_lock (w->mutex); 781 | waveform_db_write (key, wavedata->data, wavedata->data_len * sizeof (short), wavedata->channels, 0); 782 | deadbeef->mutex_unlock (w->mutex); 783 | if (key) { 784 | free (key); 785 | key = NULL; 786 | } 787 | } 788 | 789 | static int 790 | waveform_valid_track (DB_playItem_t *it, const char *uri) 791 | { 792 | if (!deadbeef->is_local_file (uri)) { 793 | return 0; 794 | } 795 | if (deadbeef->pl_get_item_duration (it)/60 >= CONFIG_MAX_FILE_LENGTH && CONFIG_MAX_FILE_LENGTH != -1) { 796 | return 0; 797 | } 798 | 799 | deadbeef->pl_lock (); 800 | const char *file_meta = deadbeef->pl_find_meta_raw (it, ":FILETYPE"); 801 | if (file_meta && strcmp (file_meta,"cdda") == 0) { 802 | deadbeef->pl_unlock (); 803 | return 0; 804 | } 805 | deadbeef->pl_unlock (); 806 | return 1; 807 | } 808 | 809 | static int 810 | waveform_delete (DB_playItem_t *it, const char *uri) 811 | { 812 | char *key = waveform_format_uri (it, uri); 813 | if (!key) { 814 | return 0; 815 | } 816 | int result = waveform_db_delete (key); 817 | if (key) { 818 | free (key); 819 | key = NULL; 820 | } 821 | return result; 822 | } 823 | 824 | static int 825 | waveform_is_cached (DB_playItem_t *it, const char *uri) 826 | { 827 | char *key = waveform_format_uri (it, uri); 828 | if (!key) { 829 | return 0; 830 | } 831 | int result = waveform_db_cached (key); 832 | if (key) { 833 | free (key); 834 | key = NULL; 835 | } 836 | return result; 837 | } 838 | 839 | static void 840 | waveform_get_from_cache (gpointer user_data, DB_playItem_t *it, const char *uri) 841 | { 842 | waveform_t *w = user_data; 843 | char *key = waveform_format_uri (it, uri); 844 | if (!key) { 845 | return; 846 | } 847 | deadbeef->mutex_lock (w->mutex); 848 | w->wave->data_len = waveform_db_read (key, w->wave->data, w->max_buffer_len, &w->wave->channels); 849 | deadbeef->mutex_unlock (w->mutex); 850 | if (key) { 851 | free (key); 852 | key = NULL; 853 | } 854 | } 855 | 856 | static void 857 | waveform_get_wavedata (gpointer user_data) 858 | { 859 | waveform_t *w = user_data; 860 | DB_playItem_t *it = deadbeef->streamer_get_playing_track (); 861 | if (!it) { 862 | return; 863 | } 864 | 865 | char *uri = strdup (deadbeef->pl_find_meta_raw (it, ":URI")); 866 | if (!uri) { 867 | return; 868 | } 869 | if (!waveform_valid_track (it, uri)) { 870 | return; 871 | } 872 | 873 | deadbeef->background_job_increment (); 874 | if (CONFIG_CACHE_ENABLED && waveform_is_cached (it, uri)) { 875 | waveform_get_from_cache (w, it, uri); 876 | g_idle_add (waveform_redraw_cb, w); 877 | } 878 | else if (queue_add (uri)) { 879 | wavedata_t *wavedata = malloc (sizeof (wavedata_t)); 880 | wavedata->data = malloc (sizeof (short) * w->max_buffer_len); 881 | memset (wavedata->data, 0, sizeof (short) * w->max_buffer_len); 882 | wavedata->fname = NULL; 883 | 884 | waveform_generate_wavedata (w, it, uri, wavedata); 885 | if (CONFIG_CACHE_ENABLED) { 886 | waveform_db_cache (w, it, wavedata); 887 | } 888 | queue_pop (uri); 889 | 890 | DB_playItem_t *playing = deadbeef->streamer_get_playing_track (); 891 | if (playing && it && it == playing) { 892 | deadbeef->mutex_lock (w->mutex); 893 | memcpy (w->wave->data, wavedata->data, wavedata->data_len * sizeof (short)); 894 | w->wave->data_len = wavedata->data_len; 895 | w->wave->channels = wavedata->channels; 896 | deadbeef->mutex_unlock (w->mutex); 897 | g_idle_add (waveform_redraw_cb, w); 898 | 899 | } 900 | if (playing) { 901 | deadbeef->pl_item_unref (playing); 902 | } 903 | 904 | if (wavedata->data) { 905 | free (wavedata->data); 906 | wavedata->data = NULL; 907 | } 908 | if (wavedata->fname) { 909 | free (wavedata->fname); 910 | wavedata->fname = NULL; 911 | } 912 | if (wavedata) { 913 | free (wavedata); 914 | wavedata = NULL; 915 | } 916 | } 917 | 918 | free (uri); 919 | uri = NULL; 920 | 921 | deadbeef->pl_item_unref (it); 922 | deadbeef->background_job_decrement (); 923 | } 924 | 925 | static gboolean 926 | waveform_set_refresh_interval (gpointer user_data, int interval) 927 | { 928 | waveform_t *w = user_data; 929 | if (!w || interval <= 0) { 930 | return FALSE; 931 | } 932 | if (w->drawtimer) { 933 | g_source_remove (w->drawtimer); 934 | w->drawtimer = 0; 935 | } 936 | w->drawtimer = g_timeout_add (interval, waveform_draw_cb, w); 937 | return TRUE; 938 | } 939 | 940 | static void 941 | ruler_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer user_data) 942 | { 943 | waveform_t *w = user_data; 944 | GtkAllocation a; 945 | gtk_widget_get_allocation (w->ruler, &a); 946 | #pragma GCC diagnostic push 947 | #pragma GCC diagnostic ignored "-Wdeprecated-declarations" 948 | cairo_t *cr = gdk_cairo_create (gtk_widget_get_window (w->ruler)); 949 | #pragma GCC diagnostic pop 950 | if (!cr) { 951 | return; 952 | } 953 | 954 | waveform_rect_t rect = { 955 | .x = 0.0, 956 | .y = 0.0, 957 | .width = a.width, 958 | .height = a.height, 959 | }; 960 | 961 | float duration = 0.f; 962 | 963 | DB_playItem_t *trk = deadbeef->streamer_get_playing_track (); 964 | if (trk) { 965 | duration = deadbeef->pl_get_item_duration (trk); 966 | deadbeef->pl_item_unref (trk); 967 | } 968 | 969 | waveform_render_ruler (cr, &w->colors, duration, &rect); 970 | 971 | cairo_destroy (cr); 972 | } 973 | 974 | static void 975 | waveform_draw_generic_event (waveform_t *w, cairo_t *cr) 976 | { 977 | if (playback_status != PLAYING) { 978 | if (w->drawtimer) { 979 | g_source_remove (w->drawtimer); 980 | w->drawtimer = 0; 981 | } 982 | } 983 | GtkAllocation a; 984 | gtk_widget_get_allocation (w->drawarea, &a); 985 | 986 | waveform_rect_t rect = { 987 | .x = 0, 988 | .y = 0, 989 | .width = a.width, 990 | .height = a.height, 991 | }; 992 | 993 | waveform_scale (w, cr, &rect); 994 | waveform_seekbar_draw (w, cr, &rect); 995 | } 996 | 997 | #if !GTK_CHECK_VERSION(3,0,0) 998 | static gboolean 999 | waveform_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer user_data) 1000 | { 1001 | waveform_t *w = user_data; 1002 | cairo_t *cr = gdk_cairo_create (gtk_widget_get_window (w->drawarea)); 1003 | waveform_draw_generic_event (w, cr); 1004 | cairo_destroy (cr); 1005 | 1006 | return TRUE; 1007 | } 1008 | #else 1009 | static gboolean 1010 | waveform_draw_event (GtkWidget *widget, cairo_t *cr, gpointer user_data) 1011 | { 1012 | waveform_t *w = user_data; 1013 | waveform_draw_generic_event (w, cr); 1014 | 1015 | return TRUE; 1016 | } 1017 | #endif 1018 | 1019 | static gboolean 1020 | waveform_configure_event (GtkWidget *widget, GdkEvent *event, gpointer user_data) 1021 | { 1022 | waveform_t *w = user_data; 1023 | if (!w) { 1024 | return FALSE; 1025 | } 1026 | if (w->resizetimer) { 1027 | g_source_remove (w->resizetimer); 1028 | w->resizetimer = 0; 1029 | } 1030 | w->resizetimer = g_timeout_add (100, waveform_redraw_cb, w); 1031 | return FALSE; 1032 | } 1033 | 1034 | static gboolean 1035 | waveform_motion_notify_event (GtkWidget *widget, GdkEventButton *event, gpointer user_data) 1036 | { 1037 | waveform_t *w = user_data; 1038 | GtkAllocation a; 1039 | gtk_widget_get_allocation (w->drawarea, &a); 1040 | 1041 | if (w->seekbar_moving || w->seekbar_move_x_clicked) { 1042 | if (event->x < -DISTANCE_THRESHOLD 1043 | || event->x > a.width + DISTANCE_THRESHOLD 1044 | || event->y < -DISTANCE_THRESHOLD 1045 | || event->y > a.height + DISTANCE_THRESHOLD) { 1046 | w->seekbar_moving = 0; 1047 | } 1048 | else { 1049 | w->seekbar_moving = 1; 1050 | w->seekbar_move_x = event->x - a.x; 1051 | } 1052 | gtk_widget_queue_draw (w->drawarea); 1053 | } 1054 | return TRUE; 1055 | } 1056 | 1057 | static gboolean 1058 | waveform_scroll_event (GtkWidget *widget, GdkEvent *event, gpointer user_data) 1059 | { 1060 | GdkEventScroll *ev = (GdkEventScroll *)event; 1061 | if (!CONFIG_SCROLL_ENABLED) { 1062 | return TRUE; 1063 | } 1064 | 1065 | DB_playItem_t *trk = deadbeef->streamer_get_playing_track (); 1066 | if (trk) { 1067 | const int duration = (int)(deadbeef->pl_get_item_duration (trk) * 1000); 1068 | const int time = (int)(deadbeef->streamer_get_playpos () * 1000); 1069 | const int step = CLAMP (duration / 30, 1000, 3600000); 1070 | 1071 | switch (ev->direction) { 1072 | case GDK_SCROLL_UP: 1073 | deadbeef->sendmessage (DB_EV_SEEK, 0, MIN (duration, time + step), 0); 1074 | break; 1075 | case GDK_SCROLL_DOWN: 1076 | deadbeef->sendmessage (DB_EV_SEEK, 0, MAX (0, time - step), 0); 1077 | break; 1078 | default: 1079 | break; 1080 | } 1081 | deadbeef->pl_item_unref (trk); 1082 | } 1083 | return TRUE; 1084 | } 1085 | 1086 | static gboolean 1087 | waveform_button_press_event (GtkWidget *widget, GdkEventButton *event, gpointer user_data) 1088 | { 1089 | waveform_t *w = user_data; 1090 | if (event->button == 3 || event->button == 2) { 1091 | return TRUE; 1092 | } 1093 | GtkAllocation a; 1094 | gtk_widget_get_allocation (w->drawarea, &a); 1095 | 1096 | w->seekbar_moving = 1; 1097 | w->seekbar_move_x = event->x - a.x; 1098 | w->seekbar_move_x_clicked = event->x - a.x; 1099 | return TRUE; 1100 | } 1101 | 1102 | static gboolean 1103 | waveform_button_release_event (GtkWidget *widget, GdkEventButton *event, gpointer user_data) 1104 | { 1105 | waveform_t *w = user_data; 1106 | if (event->button == 3) { 1107 | #pragma GCC diagnostic push 1108 | #pragma GCC diagnostic ignored "-Wdeprecated-declarations" 1109 | gtk_menu_popup (GTK_MENU (w->popup), NULL, NULL, NULL, w->drawarea, 0, gtk_get_current_event_time ()); 1110 | #pragma GCC diagnostic pop 1111 | return TRUE; 1112 | } 1113 | if (event->button == 2) { 1114 | deadbeef->sendmessage (DB_EV_TOGGLE_PAUSE, 0, 0, 0); 1115 | return TRUE; 1116 | } 1117 | w->seekbar_move_x_clicked = 0; 1118 | if (w->seekbar_moving) { 1119 | DB_playItem_t *trk = deadbeef->streamer_get_playing_track (); 1120 | if (trk) { 1121 | GtkAllocation a; 1122 | gtk_widget_get_allocation (w->drawarea, &a); 1123 | const float time = MAX (0, (event->x - a.x) * deadbeef->pl_get_item_duration (trk) / (a.width) * 1000.f); 1124 | deadbeef->sendmessage (DB_EV_SEEK, 0, time, 0); 1125 | deadbeef->pl_item_unref (trk); 1126 | } 1127 | gtk_widget_queue_draw (widget); 1128 | } 1129 | w->seekbar_moving = 0; 1130 | return TRUE; 1131 | } 1132 | 1133 | static int 1134 | waveform_message (ddb_gtkui_widget_t *widget, uint32_t id, uintptr_t ctx, uint32_t p1, uint32_t p2) 1135 | { 1136 | waveform_t *w = (waveform_t *)widget; 1137 | intptr_t tid; 1138 | 1139 | switch (id) { 1140 | case DB_EV_SONGSTARTED: 1141 | playback_status = PLAYING; 1142 | waveform_set_refresh_interval (w, CONFIG_REFRESH_INTERVAL); 1143 | g_idle_add (waveform_redraw_cb, w); 1144 | g_idle_add (ruler_redraw_cb, w); 1145 | tid = deadbeef->thread_start_low_priority (waveform_get_wavedata, w); 1146 | if (tid) { 1147 | deadbeef->thread_detach (tid); 1148 | } 1149 | break; 1150 | case DB_EV_STOP: 1151 | playback_status = STOPPED; 1152 | deadbeef->mutex_lock (w->mutex); 1153 | memset (w->wave->data, 0, sizeof (short) * w->max_buffer_len); 1154 | w->wave->data_len = 0; 1155 | w->wave->channels = 0; 1156 | deadbeef->mutex_unlock (w->mutex); 1157 | g_idle_add (waveform_redraw_cb, w); 1158 | g_idle_add (ruler_redraw_cb, w); 1159 | break; 1160 | case DB_EV_CONFIGCHANGED: 1161 | on_config_changed (w); 1162 | break; 1163 | case DB_EV_PAUSED: 1164 | if (p1) { 1165 | playback_status = PAUSED; 1166 | } 1167 | else { 1168 | playback_status = PLAYING; 1169 | waveform_set_refresh_interval (w, CONFIG_REFRESH_INTERVAL); 1170 | } 1171 | } 1172 | return 0; 1173 | } 1174 | 1175 | static void 1176 | waveform_destroy (ddb_gtkui_widget_t *widget) 1177 | { 1178 | waveform_t *w = (waveform_t *)widget; 1179 | deadbeef->mutex_lock (w->mutex); 1180 | waveform_db_close (); 1181 | if (w->drawtimer) { 1182 | g_source_remove (w->drawtimer); 1183 | w->drawtimer = 0; 1184 | } 1185 | if (w->resizetimer) { 1186 | g_source_remove (w->resizetimer); 1187 | w->resizetimer = 0; 1188 | } 1189 | if (w->surf) { 1190 | cairo_surface_destroy (w->surf); 1191 | w->surf = NULL; 1192 | } 1193 | if (w->surf_shaded) { 1194 | cairo_surface_destroy (w->surf_shaded); 1195 | w->surf_shaded = NULL; 1196 | } 1197 | if (w->wave->data) { 1198 | free (w->wave->data); 1199 | w->wave->data = NULL; 1200 | } 1201 | if (w->wave->fname) { 1202 | free (w->wave->fname); 1203 | w->wave->fname = NULL; 1204 | } 1205 | if (w->wave) { 1206 | free (w->wave); 1207 | w->wave = NULL; 1208 | } 1209 | deadbeef->mutex_unlock (w->mutex); 1210 | if (w->mutex) { 1211 | deadbeef->mutex_free (w->mutex); 1212 | w->mutex = 0; 1213 | } 1214 | 1215 | if (waveform_instancecount > 0) { 1216 | waveform_instancecount--; 1217 | } 1218 | } 1219 | 1220 | static void 1221 | waveform_init (ddb_gtkui_widget_t *w) 1222 | { 1223 | waveform_t *wf = (waveform_t *)w; 1224 | GtkAllocation a; 1225 | gtk_widget_get_allocation (wf->drawarea, &a); 1226 | load_config (); 1227 | waveform_colors_update (wf); 1228 | 1229 | wf->max_buffer_len = MAX_SAMPLES * VALUES_PER_SAMPLE * MAX_CHANNELS * sizeof (short); 1230 | deadbeef->mutex_lock (wf->mutex); 1231 | wf->wave = malloc (sizeof (wavedata_t)); 1232 | wf->wave->data = malloc (sizeof (short) * wf->max_buffer_len); 1233 | memset (wf->wave->data, 0, sizeof (short) * wf->max_buffer_len); 1234 | wf->wave->fname = NULL; 1235 | wf->wave->data_len = 0; 1236 | wf->wave->channels = 0; 1237 | wf->surf = cairo_image_surface_create (CAIRO_FORMAT_RGB24, 1238 | a.width, 1239 | a.height); 1240 | wf->surf_shaded = cairo_image_surface_create (CAIRO_FORMAT_RGB24, 1241 | a.width, 1242 | a.height); 1243 | deadbeef->mutex_unlock (wf->mutex); 1244 | wf->seekbar_moving = 0; 1245 | wf->height = a.height; 1246 | wf->width = a.width; 1247 | wf->pos_last = 0; 1248 | 1249 | make_cache_dir (cache_path, sizeof (cache_path)/sizeof (char)); 1250 | 1251 | deadbeef->mutex_lock (wf->mutex); 1252 | waveform_db_open (cache_path); 1253 | waveform_db_init (NULL); 1254 | deadbeef->mutex_unlock (wf->mutex); 1255 | 1256 | DB_playItem_t *it = deadbeef->streamer_get_playing_track (); 1257 | if (it) { 1258 | playback_status = PLAYING; 1259 | intptr_t tid = deadbeef->thread_start_low_priority (waveform_get_wavedata, w); 1260 | if (tid) { 1261 | deadbeef->thread_detach (tid); 1262 | } 1263 | deadbeef->pl_item_unref (it); 1264 | } 1265 | wf->resizetimer = 0; 1266 | 1267 | on_config_changed (w); 1268 | } 1269 | 1270 | static ddb_gtkui_widget_t * 1271 | waveform_create (void) 1272 | { 1273 | waveform_t *w = malloc (sizeof (waveform_t)); 1274 | memset (w, 0, sizeof (waveform_t)); 1275 | 1276 | w->base.widget = gtk_event_box_new (); 1277 | w->base.init = waveform_init; 1278 | w->base.destroy = waveform_destroy; 1279 | w->base.message = waveform_message; 1280 | w->drawarea = gtk_drawing_area_new (); 1281 | w->ruler = gtk_drawing_area_new (); 1282 | #if !GTK_CHECK_VERSION(3,0,0) 1283 | GtkWidget *vbox = gtk_vbox_new (FALSE, 0); 1284 | #else 1285 | GtkWidget *vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); 1286 | #endif 1287 | w->frame = gtk_frame_new (NULL); 1288 | w->popup = gtk_menu_new (); 1289 | gtk_menu_attach_to_widget (GTK_MENU (w->popup), w->base.widget, NULL); 1290 | w->popup_item = gtk_menu_item_new_with_mnemonic ("Configure"); 1291 | w->mutex = deadbeef->mutex_create (); 1292 | gtk_widget_set_size_request (w->base.widget, 300, 96); 1293 | gtk_widget_set_size_request (w->ruler, -1, 20); 1294 | gtk_widget_set_size_request (w->drawarea, -1, -1); 1295 | gtk_widget_add_events (w->base.widget, GDK_SCROLL_MASK); 1296 | gtk_container_add (GTK_CONTAINER (w->base.widget), w->frame); 1297 | gtk_container_add (GTK_CONTAINER (w->frame), vbox); 1298 | gtk_container_add (GTK_CONTAINER (vbox), w->ruler); 1299 | gtk_container_add (GTK_CONTAINER (vbox), w->drawarea); 1300 | gtk_container_add (GTK_CONTAINER (w->popup), w->popup_item); 1301 | gtk_box_set_child_packing (GTK_BOX (vbox), w->drawarea, TRUE, TRUE, 0, 0); 1302 | gtk_box_set_child_packing (GTK_BOX (vbox), w->ruler, FALSE, TRUE, 0, 0); 1303 | gtk_widget_show (w->drawarea); 1304 | gtk_widget_show (vbox); 1305 | gtk_widget_show (w->frame); 1306 | gtk_widget_show (w->popup); 1307 | gtk_widget_show (w->ruler); 1308 | gtk_widget_show (w->popup_item); 1309 | 1310 | #if !GTK_CHECK_VERSION(3,0,0) 1311 | g_signal_connect_after ((gpointer) w->drawarea, "expose_event", G_CALLBACK (waveform_expose_event), w); 1312 | #else 1313 | g_signal_connect_after ((gpointer) w->drawarea, "draw", G_CALLBACK (waveform_draw_event), w); 1314 | #endif 1315 | #if !GTK_CHECK_VERSION(3,0,0) 1316 | g_signal_connect_after ((gpointer) w->ruler, "expose_event", G_CALLBACK (ruler_expose_event), w); 1317 | #else 1318 | g_signal_connect_after ((gpointer) w->ruler, "draw", G_CALLBACK (ruler_expose_event), w); 1319 | #endif 1320 | g_signal_connect_after ((gpointer) w->drawarea, "configure_event", G_CALLBACK (waveform_configure_event), w); 1321 | g_signal_connect_after ((gpointer) w->base.widget, "button_press_event", G_CALLBACK (waveform_button_press_event), w); 1322 | g_signal_connect_after ((gpointer) w->base.widget, "button_release_event", G_CALLBACK (waveform_button_release_event), w); 1323 | g_signal_connect_after ((gpointer) w->base.widget, "scroll-event", G_CALLBACK (waveform_scroll_event), w); 1324 | g_signal_connect_after ((gpointer) w->base.widget, "motion_notify_event", G_CALLBACK (waveform_motion_notify_event), w); 1325 | g_signal_connect_after ((gpointer) w->popup_item, "activate", G_CALLBACK (on_button_config), w); 1326 | gtkui_plugin->w_override_signals (w->base.widget, w); 1327 | 1328 | waveform_instancecount++; 1329 | 1330 | return (ddb_gtkui_widget_t *)w; 1331 | } 1332 | 1333 | static int 1334 | waveform_connect (void) 1335 | { 1336 | gtkui_plugin = (ddb_gtkui_t *) deadbeef->plug_get_for_id (DDB_GTKUI_PLUGIN_ID); 1337 | if (gtkui_plugin) { 1338 | trace ("using '%s' plugin %d.%d\n", DDB_GTKUI_PLUGIN_ID, gtkui_plugin->gui.plugin.version_major, gtkui_plugin->gui.plugin.version_minor ); 1339 | if (gtkui_plugin->gui.plugin.version_major == 2) { 1340 | gtkui_plugin->w_reg_widget ("Waveform Seekbar", DDB_WF_SINGLE_INSTANCE, waveform_create, "waveform_seekbar", NULL); 1341 | return 0; 1342 | } 1343 | } 1344 | return -1; 1345 | } 1346 | 1347 | static int 1348 | waveform_start (void) 1349 | { 1350 | load_config (); 1351 | return 0; 1352 | } 1353 | 1354 | static int 1355 | waveform_stop (void) 1356 | { 1357 | save_config (); 1358 | return 0; 1359 | } 1360 | 1361 | static int 1362 | waveform_disconnect (void) 1363 | { 1364 | if (gtkui_plugin) { 1365 | gtkui_plugin->w_unreg_widget ("waveform_seekbar"); 1366 | } 1367 | gtkui_plugin = NULL; 1368 | return 0; 1369 | } 1370 | 1371 | static int 1372 | waveform_action_lookup (DB_plugin_action_t *action, int ctx) 1373 | { 1374 | DB_playItem_t *it = NULL; 1375 | deadbeef->pl_lock (); 1376 | if (ctx == DDB_ACTION_CTX_SELECTION) { 1377 | ddb_playlist_t *plt = deadbeef->plt_get_curr (); 1378 | if (plt) { 1379 | it = deadbeef->plt_get_first (plt, PL_MAIN); 1380 | while (it) { 1381 | if (deadbeef->pl_is_selected (it)) { 1382 | const char *uri = deadbeef->pl_find_meta_raw (it, ":URI"); 1383 | if (waveform_is_cached (it, uri)) { 1384 | waveform_delete (it, uri); 1385 | } 1386 | } 1387 | DB_playItem_t *next = deadbeef->pl_get_next (it, PL_MAIN); 1388 | deadbeef->pl_item_unref (it); 1389 | it = next; 1390 | } 1391 | deadbeef->plt_unref (plt); 1392 | } 1393 | } 1394 | if (it) { 1395 | deadbeef->pl_item_unref (it); 1396 | } 1397 | deadbeef->pl_unlock (); 1398 | return 0; 1399 | } 1400 | 1401 | static DB_plugin_action_t lookup_action = { 1402 | .title = "Remove Waveform From Cache", 1403 | .name = "waveform_lookup", 1404 | .flags = DB_ACTION_MULTIPLE_TRACKS | DB_ACTION_ADD_MENU, 1405 | .callback2 = waveform_action_lookup, 1406 | .next = NULL 1407 | }; 1408 | 1409 | static DB_plugin_action_t * 1410 | waveform_get_actions (DB_playItem_t *it) 1411 | { 1412 | if (!waveform_instancecount) { 1413 | return NULL; 1414 | } 1415 | deadbeef->pl_lock (); 1416 | lookup_action.flags |= DB_ACTION_DISABLED; 1417 | DB_playItem_t *current = deadbeef->pl_get_first (PL_MAIN); 1418 | while (current) { 1419 | if (deadbeef->pl_is_selected (current) && waveform_is_cached (current, deadbeef->pl_find_meta_raw (current, ":URI"))) { 1420 | lookup_action.flags &= ~DB_ACTION_DISABLED; 1421 | deadbeef->pl_item_unref (current); 1422 | break; 1423 | } 1424 | DB_playItem_t *next = deadbeef->pl_get_next (current, PL_MAIN); 1425 | deadbeef->pl_item_unref (current); 1426 | current = next; 1427 | } 1428 | deadbeef->pl_unlock (); 1429 | return &lookup_action; 1430 | } 1431 | 1432 | 1433 | static const char settings_dlg[] = 1434 | "property \"Refresh interval (ms): \" spinbtn[10,1000,1] " CONFSTR_WF_REFRESH_INTERVAL " 33 ;\n" 1435 | "property \"Border width: \" spinbtn[0,1,1] " CONFSTR_WF_BORDER_WIDTH " 1 ;\n" 1436 | "property \"Cursor width: \" spinbtn[0,3,1] " CONFSTR_WF_CURSOR_WIDTH " 3 ;\n" 1437 | "property \"Font size: \" spinbtn[8,20,1] " CONFSTR_WF_FONT_SIZE " 18 ;\n" 1438 | "property \"Ignore files longer than x minutes " 1439 | "(-1 scans every file): \" spinbtn[-1,9999,1] " CONFSTR_WF_MAX_FILE_LENGTH " 180 ;\n" 1440 | "property \"Use cache \" checkbox " CONFSTR_WF_CACHE_ENABLED " 1 ;\n" 1441 | "property \"Scroll wheel to seek \" checkbox " CONFSTR_WF_SCROLL_ENABLED " 1 ;\n" 1442 | "property \"Number of samples (per channel): \" spinbtn[2048,4092,2048] " CONFSTR_WF_NUM_SAMPLES " 2048 ;\n" 1443 | ; 1444 | 1445 | static DB_misc_t plugin = { 1446 | //DB_PLUGIN_SET_API_VERSION 1447 | .plugin.type = DB_PLUGIN_MISC, 1448 | .plugin.api_vmajor = 1, 1449 | .plugin.api_vminor = 5, 1450 | .plugin.version_major = 0, 1451 | .plugin.version_minor = 5, 1452 | #if GTK_CHECK_VERSION(3,0,0) 1453 | .plugin.id = "waveform_seekbar-gtk3", 1454 | #else 1455 | .plugin.id = "waveform_seekbar", 1456 | #endif 1457 | .plugin.name = "Waveform Seekbar", 1458 | .plugin.descr = "Waveform Seekbar", 1459 | .plugin.copyright = 1460 | "Copyright (C) 2014 Christian Boxdörfer \n" 1461 | "\n" 1462 | "Based on sndfile-tools waveform by Erik de Castro Lopo.\n" 1463 | "\n" 1464 | "This program is free software; you can redistribute it and/or\n" 1465 | "modify it under the terms of the GNU General Public License\n" 1466 | "as published by the Free Software Foundation; either version 2\n" 1467 | "of the License, or (at your option) any later version.\n" 1468 | "\n" 1469 | "This program is distributed in the hope that it will be useful,\n" 1470 | "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" 1471 | "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" 1472 | "GNU General Public License for more details.\n" 1473 | "\n" 1474 | "You should have received a copy of the GNU General Public License\n" 1475 | "along with this program; if not, write to the Free Software\n" 1476 | "Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n" 1477 | , 1478 | .plugin.website = "https://github.com/cboxdoerfer/ddb_waveform_seekbar", 1479 | .plugin.start = waveform_start, 1480 | .plugin.stop = waveform_stop, 1481 | .plugin.connect = waveform_connect, 1482 | .plugin.disconnect = waveform_disconnect, 1483 | .plugin.configdialog = settings_dlg, 1484 | .plugin.get_actions = waveform_get_actions, 1485 | }; 1486 | 1487 | #if !GTK_CHECK_VERSION(3,0,0) 1488 | DB_plugin_t * 1489 | ddb_misc_waveform_GTK2_load (DB_functions_t *ddb) 1490 | { 1491 | deadbeef = ddb; 1492 | return &plugin.plugin; 1493 | } 1494 | #else 1495 | DB_plugin_t * 1496 | ddb_misc_waveform_GTK3_load (DB_functions_t *ddb) 1497 | { 1498 | deadbeef = ddb; 1499 | return &plugin.plugin; 1500 | } 1501 | #endif 1502 | --------------------------------------------------------------------------------