1 /*
2     DeaDBeeF -- the music player
3     Copyright (C) 2009-2015 Alexey Yakovenko and other contributors
4 
5     This software is provided 'as-is', without any express or implied
6     warranty.  In no event will the authors be held liable for any damages
7     arising from the use of this software.
8 
9     Permission is granted to anyone to use this software for any purpose,
10     including commercial applications, and to alter it and redistribute it
11     freely, subject to the following restrictions:
12 
13     1. The origin of this software must not be misrepresented; you must not
14      claim that you wrote the original software. If you use this software
15      in a product, an acknowledgment in the product documentation would be
16      appreciated but is not required.
17 
18     2. Altered source versions must be plainly marked as such, and must not be
19      misrepresented as being the original software.
20 
21     3. This notice may not be removed or altered from any source distribution.
22 */
23 
24 #ifdef HAVE_CONFIG_H
25 #  include <config.h>
26 #endif
27 
28 #include <gtk/gtk.h>
29 #include <math.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <unistd.h>
33 #include <assert.h>
34 #include <dirent.h>
35 #include <sys/stat.h>
36 #include "callbacks.h"
37 #include "interface.h"
38 #include "support.h"
39 #include "../../deadbeef.h"
40 #include "gtkui.h"
41 #include "pluginconf.h"
42 
43 static ddb_dsp_context_t *chain;
44 static GtkWidget *prefwin;
45 
46 static ddb_dsp_context_t *
dsp_clone(ddb_dsp_context_t * from)47 dsp_clone (ddb_dsp_context_t *from) {
48     ddb_dsp_context_t *dsp = from->plugin->open ();
49     char param[2000];
50     if (from->plugin->num_params) {
51         int n = from->plugin->num_params ();
52         for (int i = 0; i < n; i++) {
53             from->plugin->get_param (from, i, param, sizeof (param));
54             dsp->plugin->set_param (dsp, i, param);
55         }
56     }
57     dsp->enabled = from->enabled;
58     return dsp;
59 }
60 
61 static void
fill_dsp_chain(GtkListStore * mdl)62 fill_dsp_chain (GtkListStore *mdl) {
63     ddb_dsp_context_t *dsp = chain;
64     while (dsp) {
65         GtkTreeIter iter;
66         gtk_list_store_append (mdl, &iter);
67         gtk_list_store_set (mdl, &iter, 0, dsp->plugin->plugin.name, -1);
68         dsp = dsp->next;
69     }
70 }
71 
dirent_alphasort(const struct dirent ** a,const struct dirent ** b)72 static int dirent_alphasort (const struct dirent **a, const struct dirent **b) {
73     return strcmp ((*a)->d_name, (*b)->d_name);
74 }
75 
76 static int
scandir_preset_filter(const struct dirent * ent)77 scandir_preset_filter (const struct dirent *ent) {
78     char *ext = strrchr (ent->d_name, '.');
79     if (ext && !strcasecmp (ext, ".txt")) {
80         return 1;
81     }
82     return 0;
83 }
84 
85 static void
dsp_fill_preset_list(GtkWidget * combobox)86 dsp_fill_preset_list (GtkWidget *combobox) {
87     // fill list of presets
88     GtkListStore *mdl;
89     mdl = GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (combobox)));
90     gtk_list_store_clear (mdl);
91     struct dirent **namelist = NULL;
92     char path[1024];
93     if (snprintf (path, sizeof (path), "%s/presets/dsp", deadbeef->get_config_dir ()) > 0) {
94         int n = scandir (path, &namelist, scandir_preset_filter, dirent_alphasort);
95         int i;
96         for (i = 0; i < n; i++) {
97             char title[100];
98             strcpy (title, namelist[i]->d_name);
99             char *e = strrchr (title, '.');
100             if (e) {
101                 *e = 0;
102             }
103             GtkTreeIter iter;
104             gtk_list_store_append (mdl, &iter);
105             gtk_list_store_set (mdl, &iter, 0, title, -1);
106             free (namelist[i]);
107         }
108         free (namelist);
109     }
110 
111     // set last preset name
112     GtkWidget *entry = gtk_bin_get_child (GTK_BIN (combobox));
113     if (entry) {
114         deadbeef->conf_lock ();
115         gtk_entry_set_text (GTK_ENTRY (entry), deadbeef->conf_get_str_fast ("gtkui.conf_dsp_preset", ""));
116         deadbeef->conf_unlock ();
117     }
118 }
119 
120 void
dsp_setup_init(GtkWidget * _prefwin)121 dsp_setup_init (GtkWidget *_prefwin) {
122     prefwin = _prefwin;
123     // copy current dsp chain
124     ddb_dsp_context_t *streamer_chain = deadbeef->streamer_get_dsp_chain ();
125 
126     ddb_dsp_context_t *tail = NULL;
127     while (streamer_chain) {
128         ddb_dsp_context_t *new = dsp_clone (streamer_chain);
129         if (tail) {
130             tail->next = new;
131             tail = new;
132         }
133         else {
134             chain = tail = new;
135         }
136         streamer_chain = streamer_chain->next;
137     }
138 
139     // fill dsp_listview
140     GtkWidget *listview = lookup_widget (prefwin, "dsp_listview");
141 
142 
143     GtkCellRenderer *title_cell = gtk_cell_renderer_text_new ();
144     GtkTreeViewColumn *col = gtk_tree_view_column_new_with_attributes (_("Plugin"), title_cell, "text", 0, NULL);
145     gtk_tree_view_append_column (GTK_TREE_VIEW (listview), GTK_TREE_VIEW_COLUMN (col));
146     GtkListStore *mdl = gtk_list_store_new (1, G_TYPE_STRING);
147     gtk_tree_view_set_model (GTK_TREE_VIEW (listview), GTK_TREE_MODEL (mdl));
148 
149     fill_dsp_chain (mdl);
150     GtkTreePath *path = gtk_tree_path_new_from_indices (0, -1);
151     gtk_tree_view_set_cursor (GTK_TREE_VIEW (listview), path, NULL, FALSE);
152     gtk_tree_path_free (path);
153 
154     GtkWidget *combobox = lookup_widget (prefwin, "dsp_preset");
155     dsp_fill_preset_list (combobox);
156 }
157 
158 void
dsp_setup_free(void)159 dsp_setup_free (void) {
160     while (chain) {
161         ddb_dsp_context_t *next = chain->next;
162         chain->plugin->close (chain);
163         chain = next;
164     }
165     prefwin = NULL;
166 }
167 
168 static void
fill_dsp_plugin_list(GtkListStore * mdl)169 fill_dsp_plugin_list (GtkListStore *mdl) {
170     struct DB_dsp_s **dsp = deadbeef->plug_get_dsp_list ();
171     int i;
172     for (i = 0; dsp[i]; i++) {
173         GtkTreeIter iter;
174         gtk_list_store_append (mdl, &iter);
175         gtk_list_store_set (mdl, &iter, 0, dsp[i]->plugin.name, -1);
176     }
177 }
178 
179 static void
update_streamer(void)180 update_streamer (void) {
181     deadbeef->streamer_set_dsp_chain (chain);
182 }
183 
184 void
on_dsp_add_clicked(GtkButton * button,gpointer user_data)185 on_dsp_add_clicked                     (GtkButton       *button,
186                                         gpointer         user_data)
187 {
188     GtkWidget *dlg = create_select_dsp_plugin ();
189     gtk_dialog_set_default_response (GTK_DIALOG (dlg), GTK_RESPONSE_OK);
190     gtk_window_set_transient_for (GTK_WINDOW (dlg), GTK_WINDOW (prefwin));
191     gtk_window_set_title (GTK_WINDOW (dlg), _("Add plugin to DSP chain"));
192 
193     GtkComboBox *combo;
194     // fill encoder presets
195     combo = GTK_COMBO_BOX (lookup_widget (dlg, "plugin"));
196     GtkListStore *mdl = GTK_LIST_STORE (gtk_combo_box_get_model (combo));
197     fill_dsp_plugin_list (mdl);
198     gtk_combo_box_set_active (combo, deadbeef->conf_get_int ("converter.last_selected_dsp", 0));
199 
200     int r = gtk_dialog_run (GTK_DIALOG (dlg));
201     if (r == GTK_RESPONSE_OK) {
202         // create new instance of the selected plugin
203         int idx = gtk_combo_box_get_active (combo);
204         struct DB_dsp_s **dsp = deadbeef->plug_get_dsp_list ();
205         int i;
206         ddb_dsp_context_t *inst = NULL;
207         for (i = 0; dsp[i]; i++) {
208             if (i == idx) {
209                 inst = dsp[i]->open ();
210                 break;
211             }
212         }
213         if (inst) {
214             // append to DSP chain
215             ddb_dsp_context_t *tail = chain;
216             while (tail && tail->next) {
217                 tail = tail->next;
218             }
219             if (tail) {
220                 tail->next = inst;
221             }
222             else {
223                 chain = inst;
224             }
225 
226             // reinit list of instances
227             GtkWidget *list = lookup_widget (prefwin, "dsp_listview");
228             GtkListStore *mdl = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW(list)));
229             gtk_list_store_clear (mdl);
230             fill_dsp_chain (mdl);
231             update_streamer ();
232         }
233         else {
234             fprintf (stderr, "prefwin: failed to add DSP plugin to chain\n");
235         }
236     }
237     gtk_widget_destroy (dlg);
238 }
239 
240 static int
listview_get_index(GtkWidget * list)241 listview_get_index (GtkWidget *list) {
242     GtkTreePath *path;
243     GtkTreeViewColumn *col;
244     gtk_tree_view_get_cursor (GTK_TREE_VIEW (list), &path, &col);
245     if (!path) {
246         // nothing selected
247         return -1;
248     }
249     int *indices = gtk_tree_path_get_indices (path);
250     int idx = *indices;
251     g_free (indices);
252     return idx;
253 }
254 
255 void
on_dsp_remove_clicked(GtkButton * button,gpointer user_data)256 on_dsp_remove_clicked                  (GtkButton       *button,
257                                         gpointer         user_data)
258 {
259     GtkWidget *list = lookup_widget (prefwin, "dsp_listview");
260     int idx = listview_get_index (list);
261     if (idx == -1) {
262         return;
263     }
264 
265     ddb_dsp_context_t *p = chain;
266     ddb_dsp_context_t *prev = NULL;
267     int i = idx;
268     while (p && i--) {
269         prev = p;
270         p = p->next;
271     }
272     if (p) {
273         if (prev) {
274             prev->next = p->next;
275         }
276         else {
277             chain = p->next;
278         }
279         p->plugin->close (p);
280         GtkListStore *mdl = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW(list)));
281         gtk_list_store_clear (mdl);
282         fill_dsp_chain (mdl);
283         GtkTreePath *path = gtk_tree_path_new_from_indices (idx, -1);
284         gtk_tree_view_set_cursor (GTK_TREE_VIEW (list), path, NULL, FALSE);
285         gtk_tree_path_free (path);
286         update_streamer ();
287     }
288 }
289 
290 static ddb_dsp_context_t *current_dsp_context = NULL;
291 
292 void
dsp_ctx_set_param(const char * key,const char * value)293 dsp_ctx_set_param (const char *key, const char *value) {
294     current_dsp_context->plugin->set_param (current_dsp_context, atoi (key), value);
295 }
296 
297 void
dsp_ctx_get_param(const char * key,char * value,int len,const char * def)298 dsp_ctx_get_param (const char *key, char *value, int len, const char *def) {
299     strncpy (value, def, len);
300     current_dsp_context->plugin->get_param (current_dsp_context, atoi (key), value, len);
301 }
302 
303 int
button_cb(int btn,void * ctx)304 button_cb (int btn, void *ctx) {
305     if (btn == ddb_button_apply) {
306         update_streamer ();
307     }
308     return 1;
309 }
310 
311 void
on_dsp_configure_clicked(GtkButton * button,gpointer user_data)312 on_dsp_configure_clicked               (GtkButton       *button,
313                                         gpointer         user_data)
314 {
315     GtkWidget *list = lookup_widget (prefwin, "dsp_listview");
316     int idx = listview_get_index (list);
317     if (idx == -1) {
318         return;
319     }
320     ddb_dsp_context_t *p = chain;
321     int i = idx;
322     while (p && i--) {
323         p = p->next;
324     }
325     if (!p || !p->plugin->configdialog) {
326         return;
327     }
328     current_dsp_context = p;
329     ddb_dialog_t conf = {
330         .title = p->plugin->plugin.name,
331         .layout = p->plugin->configdialog,
332         .set_param = dsp_ctx_set_param,
333         .get_param = dsp_ctx_get_param,
334     };
335     int response = gtkui_run_dialog (prefwin, &conf, 0, button_cb, NULL);
336     if (response == ddb_button_ok) {
337         update_streamer ();
338     }
339     current_dsp_context = NULL;
340 }
341 
342 static int
swap_items(GtkWidget * list,int idx)343 swap_items (GtkWidget *list, int idx) {
344     ddb_dsp_context_t *prev = NULL;
345     ddb_dsp_context_t *p = chain;
346 
347     int n = idx;
348     while (n > 0 && p) {
349         prev = p;
350         p = p->next;
351         n--;
352     }
353 
354     if (!p || !p->next) {
355         return -1;
356     }
357 
358     ddb_dsp_context_t *moved = p->next;
359 
360     if (!moved) {
361         return -1;
362     }
363 
364     ddb_dsp_context_t *last = moved ? moved->next : NULL;
365 
366     if (prev) {
367         p->next = last;
368         prev->next = moved;
369         moved->next = p;
370     }
371     else {
372         p->next = last;
373         chain = moved;
374         moved->next = p;
375     }
376     GtkListStore *mdl = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW(list)));
377     gtk_list_store_clear (mdl);
378     fill_dsp_chain (mdl);
379     return 0;
380 }
381 
382 
383 void
on_dsp_up_clicked(GtkButton * button,gpointer user_data)384 on_dsp_up_clicked                      (GtkButton       *button,
385                                         gpointer         user_data)
386 {
387     GtkWidget *list = lookup_widget (prefwin, "dsp_listview");
388     int idx = listview_get_index (list);
389     if (idx <= 0) {
390         return;
391     }
392 
393     if (-1 == swap_items (list, idx-1)) {
394         return;
395     }
396     GtkTreePath *path = gtk_tree_path_new_from_indices (idx-1, -1);
397     gtk_tree_view_set_cursor (GTK_TREE_VIEW (list), path, NULL, FALSE);
398     gtk_tree_path_free (path);
399     update_streamer ();
400 }
401 
402 
403 void
on_dsp_down_clicked(GtkButton * button,gpointer user_data)404 on_dsp_down_clicked                    (GtkButton       *button,
405                                         gpointer         user_data)
406 {
407     GtkWidget *list = lookup_widget (prefwin, "dsp_listview");
408     int idx = listview_get_index (list);
409     if (idx == -1) {
410         return;
411     }
412 
413     if (-1 == swap_items (list, idx)) {
414         return;
415     }
416     GtkTreePath *path = gtk_tree_path_new_from_indices (idx+1, -1);
417     gtk_tree_view_set_cursor (GTK_TREE_VIEW (list), path, NULL, FALSE);
418     gtk_tree_path_free (path);
419     update_streamer ();
420 }
421 
422 void
on_dsp_preset_changed(GtkComboBox * combobox,gpointer user_data)423 on_dsp_preset_changed                  (GtkComboBox     *combobox,
424                                         gpointer         user_data)
425 {
426     GtkWidget *entry = gtk_bin_get_child (GTK_BIN (combobox));
427     if (entry) {
428         deadbeef->conf_set_str ("gtkui.conf_dsp_preset", gtk_entry_get_text (GTK_ENTRY (entry)));
429     }
430 }
431 
432 
433 void
on_dsp_preset_save_clicked(GtkButton * button,gpointer user_data)434 on_dsp_preset_save_clicked             (GtkButton       *button,
435                                         gpointer         user_data)
436 {
437     const char *confdir = deadbeef->get_config_dir ();
438     char path[1024];
439     if (snprintf (path, sizeof (path), "%s/presets", confdir) < 0) {
440         return;
441     }
442     mkdir (path, 0755);
443     if (snprintf (path, sizeof (path), "%s/presets/dsp", confdir) < 0) {
444         return;
445     }
446     GtkWidget *combobox = lookup_widget (prefwin, "dsp_preset");
447     GtkWidget *entry = gtk_bin_get_child (GTK_BIN (combobox));
448     if (!entry) {
449         return;
450     }
451 
452     const char *text = gtk_entry_get_text (GTK_ENTRY (entry));
453     mkdir (path, 0755);
454     if (snprintf (path, sizeof (path), "%s/presets/dsp/%s.txt", confdir, text) < 0) {
455         return;
456     }
457     deadbeef->dsp_preset_save (path, chain);
458 
459     dsp_fill_preset_list (combobox);
460 }
461 
462 
463 void
on_dsp_preset_load_clicked(GtkButton * button,gpointer user_data)464 on_dsp_preset_load_clicked             (GtkButton       *button,
465                                         gpointer         user_data)
466 {
467     GtkWidget *combobox = lookup_widget (prefwin, "dsp_preset");
468     GtkWidget *entry = gtk_bin_get_child (GTK_BIN (combobox));
469     if (entry) {
470         const char *text = gtk_entry_get_text (GTK_ENTRY (entry));
471         char path[PATH_MAX];
472         if (snprintf (path, sizeof (path), "%s/presets/dsp/%s.txt", deadbeef->get_config_dir (), text) > 0) {
473             ddb_dsp_context_t *new_chain = NULL;
474             int res = deadbeef->dsp_preset_load (path, &new_chain);
475             if (!res) {
476                 deadbeef->dsp_preset_free (chain);
477                 chain = new_chain;
478                 GtkWidget *list = lookup_widget (prefwin, "dsp_listview");
479                 GtkListStore *mdl = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW(list)));
480                 gtk_list_store_clear (mdl);
481                 fill_dsp_chain (mdl);
482                 update_streamer ();
483             }
484         }
485     }
486 }
487 
488