diff -ruN audacious-1.0.0.org/Plugins/Output/Makefile.in audacious-1.0.0/Plugins/Output/Makefile.in --- audacious-1.0.0.org/Plugins/Output/Makefile.in 2006-04-17 03:34:59.000000000 +0900 +++ audacious-1.0.0/Plugins/Output/Makefile.in 2006-04-17 19:48:34.000000000 +0900 @@ -1,5 +1,5 @@ include ../../mk/rules.mk include ../../mk/objective.mk -ALL_PLUGINS = OSS alsa disk_writer esd jack arts +ALL_PLUGINS = OSS alsa disk_writer esd jack arts crossfade SUBDIRS = @OUTPUT_PLUGINS@ diff -ruN audacious-1.0.0.org/Plugins/Output/crossfade/Makefile.in audacious-1.0.0/Plugins/Output/crossfade/Makefile.in --- audacious-1.0.0.org/Plugins/Output/crossfade/Makefile.in 1970-01-01 09:00:00.000000000 +0900 +++ audacious-1.0.0/Plugins/Output/crossfade/Makefile.in 2006-04-17 19:48:34.000000000 +0900 @@ -0,0 +1,18 @@ +include ../../../mk/rules.mk +include ../../../mk/objective.mk + +OBJECTIVE_LIBS = libcrossfade.so + +noinst_HEADERS = callbacks.h crossfade.h format.h rate.h timing.h\ + configure.h debug.h interface-2.0.h support-2.0.h volume.h\ + convert.h effect.h monitor.h support.h + +LIBDIR = $(plugindir)/$(OUTPUT_PLUGIN_DIR) + +SOURCES = callbacks.c crossfade.c monitor.c support.c\ + configure.c debug.c format.c rate.c timing.c\ + convert.c effect.c interface-2.0.c volume.c + +OBJECTS = ${SOURCES:.c=.o} + +CFLAGS += -fPIC -DPIC $(GTK_CFLAGS) -I../../.. diff -ruN audacious-1.0.0.org/Plugins/Output/crossfade/callbacks.c audacious-1.0.0/Plugins/Output/crossfade/callbacks.c --- audacious-1.0.0.org/Plugins/Output/crossfade/callbacks.c 1970-01-01 09:00:00.000000000 +0900 +++ audacious-1.0.0/Plugins/Output/crossfade/callbacks.c 2006-04-17 19:48:34.000000000 +0900 @@ -0,0 +1,37 @@ +/* + * XMMS Crossfade Plugin + * Copyright (C) 2000-2004 Peter Eisenlohr + * + * based on the original OSS Output Plugin + * Copyright (C) 1998-2000 Peter Alm, Mikael Alm, Olle Hallnas, Thomas Nilsson and 4Front Technologies + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "crossfade.h" + +#include "callbacks.h" +#include "interface-2.0.h" +#include "support.h" + +void on_help_close_button_clicked(GtkButton *button, gpointer user_data) +{ + +} diff -ruN audacious-1.0.0.org/Plugins/Output/crossfade/callbacks.h audacious-1.0.0/Plugins/Output/crossfade/callbacks.h --- audacious-1.0.0.org/Plugins/Output/crossfade/callbacks.h 1970-01-01 09:00:00.000000000 +0900 +++ audacious-1.0.0/Plugins/Output/crossfade/callbacks.h 2006-04-17 19:48:34.000000000 +0900 @@ -0,0 +1,94 @@ +/* + * XMMS Crossfade Plugin + * Copyright (C) 2000-2004 Peter Eisenlohr + * + * based on the original OSS Output Plugin + * Copyright (C) 1998-2000 Peter Alm, Mikael Alm, Olle Hallnas, Thomas Nilsson and 4Front Technologies + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#ifndef _CALLBACKS_H_ +#define _CALLBACKS_H_ + +#include + +/* configure.c*/ +void on_config_ok_clicked(GtkButton *button, gpointer user_data); +void on_config_apply_clicked(GtkButton *button, gpointer user_data); +void on_output_plugin_radio_toggled(GtkToggleButton *togglebutton, gpointer user_data); +void on_output_none_radio_toggled(GtkToggleButton *togglebutton, gpointer user_data); +void on_config_adevice_alt_check_toggled(GtkToggleButton *togglebutton, gpointer user_data); +void on_config_mdevice_alt_check_toggled(GtkToggleButton *togglebutton,gpointer user_data); +void on_osshwb_maxbuf_check_toggled(GtkToggleButton *togglebutton, gpointer user_data); +void on_config_crossfade_check_toggled(GtkToggleButton *togglebutton, gpointer user_data); +void on_config_mixopt_enable_check_toggled(GtkToggleButton *togglebutton, gpointer user_data); +void on_output_plugin_configure_button_clicked (GtkButton *button, gpointer user_data); +void on_output_plugin_about_button_clicked(GtkButton *button, gpointer user_data); +void on_op_throttle_check_toggled(GtkToggleButton *togglebutton, gpointer user_data); +void on_op_maxblock_check_toggled(GtkToggleButton *togglebutton, gpointer user_data); +void on_op_maxblock_spin_changed(GtkEditable *editable, gpointer user_data); +void on_op_forcereopen_check_toggled(GtkToggleButton *togglebutton, gpointer user_data); +void on_ep_configure_button_clicked(GtkButton *button, gpointer user_data); +void on_ep_about_button_clicked(GtkButton *button, gpointer user_data); +void on_ep_enable_check_toggled(GtkToggleButton *togglebutton, gpointer user_data); +void on_volnorm_enable_check_toggled(GtkToggleButton *togglebutton, gpointer user_data); +void on_xftfp_enable_check_toggled(GtkToggleButton *togglebutton, gpointer user_data); +void on_xftfp_length_spin_changed(GtkEditable *editable, gpointer user_data); +void on_xftffi_enable_check_toggled(GtkToggleButton *togglebutton, gpointer user_data); +void on_xftffi_length_spin_changed(GtkEditable *editable, gpointer user_data); +void on_xftffi_volume_spin_changed(GtkEditable *editable, gpointer user_data); +void on_pause_length_spin_changed(GtkEditable *editable, gpointer user_data); +void on_simple_length_spin_changed(GtkEditable *editable,gpointer user_data); +void on_xf_buffer_spin_changed(GtkEditable *editable, gpointer user_data); +void on_xf_autobuf_check_toggled(GtkToggleButton *togglebutton, gpointer user_data); +void on_fadeout_enable_check_toggled(GtkToggleButton *togglebutton, gpointer user_data); +void on_xfofs_none_radiobutton_toggled(GtkToggleButton *togglebutton, gpointer user_data); +void on_xfofs_none_radiobutton_clicked(GtkButton *button, gpointer user_data); +void on_xfofs_lockout_radiobutton_toggled(GtkToggleButton *togglebutton, gpointer user_data); +void on_xfofs_lockout_radiobutton_clicked(GtkButton *button, gpointer user_data); +void on_xfofs_lockin_radiobutton_toggled(GtkToggleButton *togglebutton, gpointer user_data); +void on_xfofs_lockin_radiobutton_clicked(GtkButton *button, gpointer user_data); +void on_xfofs_custom_radiobutton_toggled(GtkToggleButton *togglebutton, gpointer user_data); +void on_xfofs_custom_radiobutton_clicked(GtkButton *button, gpointer user_data); +void on_xfofs_custom_spin_changed(GtkEditable *editable, gpointer user_data); +void on_fadein_enable_check_toggled(GtkToggleButton *togglebutton, gpointer user_data); +void on_fadein_lock_check_toggled(GtkToggleButton *togglebutton, gpointer user_data); +void on_lgap_enable_check_toggled(GtkToggleButton *togglebutton, gpointer user_data); +void on_tgap_enable_check_toggled(GtkToggleButton *togglebutton, gpointer user_data); +void on_tgap_lock_check_toggled(GtkToggleButton *togglebutton, gpointer user_data); +void on_fadeout_length_spin_changed (GtkEditable *editable, gpointer user_data); +void on_fadeout_volume_spin_changed(GtkEditable *editable, gpointer user_data); +void on_fadein_length_spin_changed(GtkEditable *editable, gpointer user_data); +void on_fadein_volume_spin_changed(GtkEditable *editable, gpointer user_data); +void on_lgap_length_spin_changed(GtkEditable *editable, gpointer user_data); +void on_lgap_level_spin_changed(GtkEditable *editable, gpointer user_data); +void on_tgap_length_spin_changed(GtkEditable *editable, gpointer user_data); +void on_tgap_level_spin_changed(GtkEditable *editable, gpointer user_data); +void on_gapkiller_default_button_clicked(GtkButton *button, gpointer user_data); +void on_moth_songchange_spin_changed(GtkEditable *editable, gpointer user_data); +void on_moth_opmaxused_check_toggled(GtkToggleButton *togglebutton, gpointer user_data); +void on_misc_default_button_clicked(GtkButton *button, gpointer user_data); +void on_presets_list_click_column(GtkCList *clist, gint column, gpointer user_data); + +/* monitor.c */ +gboolean on_monitor_display_drawingarea_expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer user_data); +gboolean on_monitor_win_delete_event(GtkWidget *widget, GdkEvent *event, gpointer user_data); + +/* help.c (not yet implemented) */ +void on_help_close_button_clicked(GtkButton *button, gpointer user_data); + +#endif /* _CALLBACKS_H_ */ diff -ruN audacious-1.0.0.org/Plugins/Output/crossfade/configure.c audacious-1.0.0/Plugins/Output/crossfade/configure.c --- audacious-1.0.0.org/Plugins/Output/crossfade/configure.c 1970-01-01 09:00:00.000000000 +0900 +++ audacious-1.0.0/Plugins/Output/crossfade/configure.c 2006-04-17 19:48:34.000000000 +0900 @@ -0,0 +1,1858 @@ + +/* + * XMMS Crossfade Plugin + * Copyright (C) 2000-2004 Peter Eisenlohr + * + * based on the original OSS Output Plugin + * Copyright (C) 1998-2000 Peter Alm, Mikael Alm, Olle Hallnas, Thomas Nilsson and 4Front Technologies + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#undef PRESET_SUPPORT + +#include "crossfade.h" +#include "configure.h" +#include "interface-2.0.h" +#include "monitor.h" +#include "support.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_LIBSAMPLERATE +# include +#endif + + +#define HIDE(name) \ +{ if((set_wgt = lookup_widget(config_win, name))) \ + gtk_widget_hide(set_wgt); } + +#define SHOW(name) \ +{ if((set_wgt = lookup_widget(config_win, name))) \ + gtk_widget_show(set_wgt); } + + +#define SETW_SENSITIVE(wgt, sensitive) \ + gtk_widget_set_sensitive(wgt, sensitive) + +#define SETW_TOGGLE(wgt, active) \ + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(wgt), active) + +#define SETW_SPIN(wgt, value) \ + gtk_spin_button_set_value(GTK_SPIN_BUTTON(wgt), value) + + +#define SET_SENSITIVE(name, sensitive) \ +{ if((set_wgt = lookup_widget(config_win, name))) \ + gtk_widget_set_sensitive(set_wgt, sensitive); } + +#define SET_TOGGLE(name, active) \ +{ if((set_wgt = lookup_widget(config_win, name))) \ + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(set_wgt), active); } + +#define SET_SPIN(name, value) \ +{ if((set_wgt = lookup_widget(config_win, name))) \ + gtk_spin_button_set_value(GTK_SPIN_BUTTON(set_wgt), value); } + +#define SET_PAGE(name, index) \ +{ if((set_wgt = lookup_widget(config_win, name))) \ + gtk_notebook_set_page(GTK_NOTEBOOK(set_wgt), index); } + +#define SET_HISTORY(name, index) \ +{ if((set_wgt = lookup_widget(config_win, name))) \ + gtk_option_menu_set_history(GTK_OPTION_MENU(set_wgt), index); } + + +#define GET_SENSITIVE(name) \ +((get_wgt = lookup_widget(config_win, name)) \ + && GTK_WIDGET_SENSITIVE(get_wgt)) \ + +#define GET_TOGGLE(name) \ +((get_wgt = lookup_widget(config_win, name)) \ + && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(get_wgt))) + +#define GET_SPIN(name) \ +((get_wgt = lookup_widget(config_win, name)) \ + ? gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(get_wgt)) : 0) + + +static GtkWidget *config_win = NULL; +static GtkWidget *set_wgt; +static GtkWidget *get_wgt; + +/* init with DEFAULT_CFG to make sure all string pointers are set to NULL */ +static config_t _cfg = CONFIG_DEFAULT; +static config_t *cfg = &_cfg; + +/* some helpers to keep track of the GUI's state */ +static gboolean checking = FALSE; +static gint op_index; +static plugin_config_t op_config; +static gint ep_index; + + +static void update_plugin_config(gchar **config_string, gchar *name, + plugin_config_t *pc, gboolean save); + +/*****************************************************************************/ + +void g_free_f(gpointer data, gpointer user_data) +{ + g_free(data); +} + +/*****************************************************************************/ + +#ifdef PRESET_SUPPORT +static void +scan_presets(gchar *filename) +{ + struct stat stats; + FILE *fh; + gchar *data, **lines, *tmp, *name; + int i; + + if(lstat(filename, &stats)) { + DEBUG(("[crossfade] scan_presets: \"%s\":\n", filename)); + PERROR("[crossfade] scan_presets: lstat"); + return; + } + if(stats.st_size <= 0) return; + + if(!(data = g_malloc(stats.st_size + 1))) { + DEBUG(("[crossfade] scan_presets: g_malloc(%ld) failed!\n", stats.st_size)); + return; + } + + if(!(fh = fopen(filename, "r"))) { + PERROR("[crossfade] scan_presets: fopen"); + g_free(data); + return; + } + + if(fread(data, stats.st_size, 1, fh) != 1) { + DEBUG(("[crossfade] scan_presets: fread() failed!\n")); + g_free(data); + fclose(fh); + return; + } + fclose(fh); + data[stats.st_size] = 0; + + lines = g_strsplit(data, "\n", 0); + g_free(data); + + if(!lines) { + DEBUG(("[crossfade] scan_presets: g_strsplit() failed!\n")); + return; + } + + g_list_foreach(config->presets, g_free_f, NULL); + g_list_free(config->presets); + config->presets = NULL; + + for(i=0; lines[i]; i++) { + if(lines[i][0] == '[') { + if((tmp = strchr(lines[i], ']'))) { + *tmp = 0; + if((name = g_strdup(lines[i]+1))) + config->presets = g_list_append(config->presets, name); + } + } + } + + g_strfreev(lines); +} +#endif + +static void +read_fade_config(ConfigDb *db, gchar *section, gchar *key, fade_config_t *fc) +{ + gchar *s = NULL; + gint n; + + if(!db || !section || !key || !fc) return; + + bmp_cfg_db_get_string(db, section, key, &s); + if(!s) return; + + n = sscanf(s, + "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", + &fc->type, + &fc->pause_len_ms, + &fc->simple_len_ms, + &fc->out_enable, + &fc->out_len_ms, + &fc->out_volume, + &fc->ofs_type, + &fc->ofs_type_wanted, + &fc->ofs_custom_ms, + &fc->in_locked, + &fc->in_enable, + &fc->in_len_ms, + &fc->in_volume, + &fc->flush_pause_enable, + &fc->flush_pause_len_ms, + &fc->flush_in_enable, + &fc->flush_in_len_ms, + &fc->flush_in_volume); + + g_free(s); +} + +static void +write_fade_config(ConfigDb *db, gchar *section, gchar *key, fade_config_t *fc) +{ + gchar *s; + + if(!db || !section || !key || !fc) return; + + s = g_strdup_printf("%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", + fc->type, + fc->pause_len_ms, + fc->simple_len_ms, + fc->out_enable, + fc->out_len_ms, + fc->out_volume, + fc->ofs_type, + fc->ofs_type_wanted, + fc->ofs_custom_ms, + fc->in_locked, + fc->in_enable, + fc->in_len_ms, + fc->in_volume, + fc->flush_pause_enable, + fc->flush_pause_len_ms, + fc->flush_in_enable, + fc->flush_in_len_ms, + fc->flush_in_volume); + + if(!s) return; + + bmp_cfg_db_set_string(db, section, key, s); + g_free(s); +} + +void +xfade_load_config() +{ +#ifdef PRESET_SUPPORT + gchar *filename; +#endif + gchar *section = "Crossfade"; + ConfigDb *db; + + db = bmp_cfg_db_open(); + + /* config items used in v0.1 */ + bmp_cfg_db_get_string(db, section, "output_plugin", &config->op_name); + bmp_cfg_db_get_string(db, section, "op_config_string", &config->op_config_string); + bmp_cfg_db_get_int(db, section, "buffer_size", &config->mix_size_ms); + bmp_cfg_db_get_int(db, section, "sync_size", &config->sync_size_ms); + bmp_cfg_db_get_int(db, section, "preload_size", &config->preload_size_ms); + bmp_cfg_db_get_int(db, section, "songchange_timeout", &config->songchange_timeout); + bmp_cfg_db_get_bool(db, section, "enable_mixer", &config->enable_mixer); + bmp_cfg_db_get_bool(db, section, "mixer_reverse", &config->mixer_reverse); + bmp_cfg_db_get_bool(db, section, "enable_debug", &config->enable_debug); + bmp_cfg_db_get_bool(db, section, "enable_monitor", &config->enable_monitor); + + /* config items introduced by v0.2 */ + bmp_cfg_db_get_bool(db, section, "gap_lead_enable", &config->gap_lead_enable); + bmp_cfg_db_get_int(db, section, "gap_lead_len_ms", &config->gap_lead_len_ms); + bmp_cfg_db_get_int(db, section, "gap_lead_level", &config->gap_lead_level); + bmp_cfg_db_get_bool(db, section, "gap_trail_enable", &config->gap_trail_enable); + bmp_cfg_db_get_int(db, section, "gap_trail_len_ms", &config->gap_trail_len_ms); + bmp_cfg_db_get_int(db, section, "gap_trail_level", &config->gap_trail_level); + bmp_cfg_db_get_int(db, section, "gap_trail_locked", &config->gap_trail_locked); + + /* config items introduced by v0.2.1 */ + bmp_cfg_db_get_bool(db, section, "buffer_size_auto", &config->mix_size_auto); + + /* config items introduced by v0.2.3 */ + bmp_cfg_db_get_bool(db, section, "album_detection", &config->album_detection); + + /* config items introduced by v0.2.4 */ + bmp_cfg_db_get_bool(db, section, "http_workaround", &config->enable_http_workaround); + bmp_cfg_db_get_bool(db, section, "enable_op_max_used", &config->enable_op_max_used); + bmp_cfg_db_get_int(db, section, "op_max_used_ms", &config->op_max_used_ms); + + /* config items introduced by v0.2.6 */ + bmp_cfg_db_get_string(db, section, "effect_plugin", &config->ep_name); + bmp_cfg_db_get_bool(db, section, "effect_enable", &config->ep_enable); + bmp_cfg_db_get_int(db, section, "output_rate", &config->output_rate); + + /* config items introduced by v0.3.0 */ + bmp_cfg_db_get_bool(db, section, "volnorm_enable", &config->volnorm_enable); + bmp_cfg_db_get_bool(db, section, "volnorm_use_qa", &config->volnorm_use_qa); + bmp_cfg_db_get_int(db, section, "volnorm_target", &config->volnorm_target); + bmp_cfg_db_get_bool(db, section, "output_keep_opened", &config->output_keep_opened); + bmp_cfg_db_get_bool(db, section, "mixer_software", &config->mixer_software); + bmp_cfg_db_get_int(db, section, "mixer_vol_left", &config->mixer_vol_left); + bmp_cfg_db_get_int(db, section, "mixer_vol_right", &config->mixer_vol_right); + + /* config items introduced by v0.3.2 */ + bmp_cfg_db_get_bool(db, section, "no_xfade_if_same_file", &config->no_xfade_if_same_file); + + /* config items introduced by v0.3.3 */ + bmp_cfg_db_get_bool(db, section, "gap_crossing", &config->gap_crossing); + + /* config items introduced by v0.3.6 */ + bmp_cfg_db_get_int(db, section, "output_quality", &config->output_quality); + + /* fade configs */ + read_fade_config(db, section, "fc_xfade", &config->fc[FADE_CONFIG_XFADE]); + read_fade_config(db, section, "fc_manual", &config->fc[FADE_CONFIG_MANUAL]); + read_fade_config(db, section, "fc_album", &config->fc[FADE_CONFIG_ALBUM]); + read_fade_config(db, section, "fc_start", &config->fc[FADE_CONFIG_START]); + read_fade_config(db, section, "fc_stop", &config->fc[FADE_CONFIG_STOP]); + read_fade_config(db, section, "fc_eop", &config->fc[FADE_CONFIG_EOP]); + read_fade_config(db, section, "fc_seek", &config->fc[FADE_CONFIG_SEEK]); + read_fade_config(db, section, "fc_pause", &config->fc[FADE_CONFIG_PAUSE]); + + bmp_cfg_db_close(db); + +#ifdef PRESET_SUPPORT + filename = g_strconcat(g_get_home_dir(), "/.audacious/xmms-crossfade-presets", NULL); + scan_presets(filename); + g_free(filename); +#endif +} + +void +xfade_save_config() +{ + gchar *section = "Crossfade"; + ConfigDb *db; + + db = bmp_cfg_db_open(); + + /* obsolete config items */ + bmp_cfg_db_unset_key(db, section, "underrun_pct"); + bmp_cfg_db_unset_key(db, section, "enable_crossfade"); + bmp_cfg_db_unset_key(db, section, "enable_gapkiller"); + bmp_cfg_db_unset_key(db, section, "mixer_use_master"); + bmp_cfg_db_unset_key(db, section, "late_effect"); + bmp_cfg_db_unset_key(db, section, "gap_lead_length"); + + /* config items used in v0.1 */ + bmp_cfg_db_set_string(db, section, "output_plugin", config->op_name ? config->op_name : DEFAULT_OP_NAME); + bmp_cfg_db_set_string(db, section, "op_config_string", config->op_config_string ? config->op_config_string : DEFAULT_OP_CONFIG_STRING); + bmp_cfg_db_set_int(db, section, "buffer_size", config->mix_size_ms); + bmp_cfg_db_set_int(db, section, "sync_size", config->sync_size_ms); + bmp_cfg_db_set_int(db, section, "preload_size", config->preload_size_ms); + bmp_cfg_db_set_int(db, section, "songchange_timeout", config->songchange_timeout); + bmp_cfg_db_set_bool(db, section, "enable_mixer", config->enable_mixer); + bmp_cfg_db_set_bool(db, section, "mixer_reverse", config->mixer_reverse); + bmp_cfg_db_set_bool(db, section, "enable_debug", config->enable_debug); + bmp_cfg_db_set_bool(db, section, "enable_monitor", config->enable_monitor); + + /* config items introduced by v0.2 */ + bmp_cfg_db_set_bool(db, section, "gap_lead_enable", config->gap_lead_enable); + bmp_cfg_db_set_int(db, section, "gap_lead_len_ms", config->gap_lead_len_ms); + bmp_cfg_db_set_int(db, section, "gap_lead_level", config->gap_lead_level); + bmp_cfg_db_set_bool(db, section, "gap_trail_enable", config->gap_trail_enable); + bmp_cfg_db_set_int(db, section, "gap_trail_len_ms", config->gap_trail_len_ms); + bmp_cfg_db_set_int(db, section, "gap_trail_level", config->gap_trail_level); + bmp_cfg_db_set_int(db, section, "gap_trail_locked", config->gap_trail_locked); + + /* config items introduced by v0.2.1 */ + bmp_cfg_db_set_bool(db, section, "buffer_size_auto", config->mix_size_auto); + + /* config items introduced by v0.2.3 */ + bmp_cfg_db_set_bool(db, section, "album_detection", config->album_detection); + + /* config items introduced by v0.2.4 */ + bmp_cfg_db_set_bool(db, section, "http_workaround", config->enable_http_workaround); + bmp_cfg_db_set_bool(db, section, "enable_op_max_used", config->enable_op_max_used); + bmp_cfg_db_set_int(db, section, "op_max_used_ms", config->op_max_used_ms); + + /* config items introduced by v0.2.6 */ + bmp_cfg_db_set_string(db, section, "effect_plugin", config->ep_name ? config->ep_name : DEFAULT_EP_NAME); + bmp_cfg_db_set_bool(db, section, "effect_enable", config->ep_enable); + bmp_cfg_db_set_int(db, section, "output_rate", config->output_rate); + + /* config items introduced by v0.3.0 */ +#ifdef VOLUME_NORMALIZER + bmp_cfg_db_set_bool(db, section, "volnorm_enable", config->volnorm_enable); + bmp_cfg_db_set_bool(db, section, "volnorm_use_qa", config->volnorm_use_qa); + bmp_cfg_db_set_int(db, section, "volnorm_target", config->volnorm_target); +#endif + bmp_cfg_db_set_bool(db, section, "output_keep_opened", config->output_keep_opened); + bmp_cfg_db_set_bool(db, section, "mixer_software", config->mixer_software); + bmp_cfg_db_set_int(db, section, "mixer_vol_left", config->mixer_vol_left); + bmp_cfg_db_set_int(db, section, "mixer_vol_right", config->mixer_vol_right); + + /* config items introduced by v0.3.2 */ + bmp_cfg_db_set_bool(db, section, "no_xfade_if_same_file",config->no_xfade_if_same_file); + + /* config items introduced by v0.3.2 */ + bmp_cfg_db_set_bool(db, section, "gap_crossing", config->gap_crossing); + + /* config items introduced by v0.3.6 */ + bmp_cfg_db_set_int(db, section, "output_quality", config->output_quality); + + /* fade configs */ + write_fade_config(db, section, "fc_xfade", &config->fc[FADE_CONFIG_XFADE]); + write_fade_config(db, section, "fc_manual", &config->fc[FADE_CONFIG_MANUAL]); + write_fade_config(db, section, "fc_album", &config->fc[FADE_CONFIG_ALBUM]); + write_fade_config(db, section, "fc_start", &config->fc[FADE_CONFIG_START]); + write_fade_config(db, section, "fc_stop", &config->fc[FADE_CONFIG_STOP]); + write_fade_config(db, section, "fc_eop", &config->fc[FADE_CONFIG_EOP]); + write_fade_config(db, section, "fc_seek", &config->fc[FADE_CONFIG_SEEK]); + write_fade_config(db, section, "fc_pause", &config->fc[FADE_CONFIG_PAUSE]); + + bmp_cfg_db_close(db); +} + +#define SAFE_FREE(x) if(x) { g_free(x); x = NULL; } +void +xfade_free_config() +{ + SAFE_FREE(cfg->op_config_string); + SAFE_FREE(cfg->op_name); + + g_list_foreach(config->presets, g_free_f, NULL); + g_list_free(config->presets); + config->presets = NULL; +} + +void +xfade_load_plugin_config(gchar *config_string, + gchar *plugin_name, + plugin_config_t *plugin_config) +{ + update_plugin_config(&config_string, plugin_name, plugin_config, FALSE); +} + +void +xfade_save_plugin_config(gchar **config_string, + gchar *plugin_name, + plugin_config_t *plugin_config) +{ + update_plugin_config(config_string, plugin_name, plugin_config, TRUE); +} + +/*** helpers *****************************************************************/ + +gint +xfade_cfg_fadeout_len(fade_config_t *fc) +{ + if(!fc) return 0; + switch(fc->type) { + case FADE_TYPE_SIMPLE_XF: + return fc->simple_len_ms; + case FADE_TYPE_ADVANCED_XF: + return fc->out_enable ? fc->out_len_ms : 0; + case FADE_TYPE_FADEOUT: + case FADE_TYPE_PAUSE_ADV: + return fc->out_len_ms; + } + return 0; +} + +gint +xfade_cfg_fadeout_volume(fade_config_t *fc) +{ + gint volume; + if(!fc) return 0; + switch(fc->type) { + case FADE_TYPE_ADVANCED_XF: + case FADE_TYPE_FADEOUT: + volume = fc->out_volume; + if(volume < 0) volume = 0; + if(volume > 100) volume = 100; + return volume; + } + return 0; +} + +gint +xfade_cfg_offset(fade_config_t *fc) +{ + if(!fc) return 0; + switch(fc->type) { + case FADE_TYPE_FLUSH: + return fc->flush_pause_enable ? fc->flush_pause_len_ms : 0; + case FADE_TYPE_PAUSE: + return fc->pause_len_ms; + case FADE_TYPE_SIMPLE_XF: + return -fc->simple_len_ms; + case FADE_TYPE_ADVANCED_XF: + switch(fc->ofs_type) { + case FC_OFFSET_LOCK_OUT: + return -fc->out_len_ms; + case FC_OFFSET_LOCK_IN: + return -fc->in_len_ms; + case FC_OFFSET_CUSTOM: + return fc->ofs_custom_ms; + } + return 0; + case FADE_TYPE_FADEOUT: + case FADE_TYPE_PAUSE_ADV: + return fc->ofs_custom_ms; + } + return 0; +} + +gint +xfade_cfg_fadein_len(fade_config_t *fc) +{ + if(!fc) return 0; + switch(fc->type) { + case FADE_TYPE_FLUSH: + return fc->flush_in_enable ? fc->flush_in_len_ms : 0; + case FADE_TYPE_SIMPLE_XF: + return fc->simple_len_ms; + case FADE_TYPE_ADVANCED_XF: + return + fc->in_locked + ? (fc->out_enable ? fc->out_len_ms : 0) + : (fc->in_enable ? fc->in_len_ms : 0); + case FADE_TYPE_FADEIN: + case FADE_TYPE_PAUSE_ADV: + return fc->in_len_ms; + } + return 0; +} + +gint +xfade_cfg_fadein_volume(fade_config_t *fc) +{ + gint volume; + if(!fc) return 0; + switch(fc->type) { + case FADE_TYPE_FLUSH: + volume = fc->flush_in_volume; + break; + case FADE_TYPE_ADVANCED_XF: + volume = fc->in_locked ? fc->out_volume : fc->in_volume; + break; + case FADE_TYPE_FADEIN: + volume = fc->in_volume; + break; + default: + volume = 0; + } + if(volume < 0) volume = 0; + if(volume > 100) volume = 100; + return volume; +} + +gboolean +xfade_cfg_gap_trail_enable(config_t *cfg) +{ + return cfg->gap_trail_locked + ? cfg->gap_lead_enable + : cfg->gap_trail_enable; +} + +gint +xfade_cfg_gap_trail_len(config_t *cfg) +{ + if(!xfade_cfg_gap_trail_enable(cfg)) return 0; + return cfg->gap_trail_locked + ? cfg->gap_lead_len_ms + : cfg->gap_trail_len_ms; +} + +gint +xfade_cfg_gap_trail_level(config_t *cfg) +{ + return cfg->gap_trail_locked + ? cfg->gap_lead_level + : cfg->gap_trail_level; +} + +gint +xfade_mix_size_ms(config_t *cfg) +{ + if(cfg->mix_size_auto) { + gint i, min_size = 0; + + for(i=0; ifc[i]); + gint offset = xfade_cfg_offset(&cfg->fc[i]); + + if(cfg->fc[i].type == FADE_TYPE_PAUSE_ADV) + size += xfade_cfg_fadein_len(&cfg->fc[i]); + + if(size < -offset) + size = -offset; + + if(size > min_size) + min_size = size; + } + return min_size += xfade_cfg_gap_trail_len(cfg) + + cfg->songchange_timeout; + } + else + return cfg->mix_size_ms; +} + +/*** internal helpers ********************************************************/ + +static void +add_menu_item(GtkWidget *menu, gchar *title, GtkSignalFunc func, gint index, gint **imap) +{ + GtkWidget *item; + if(!menu || !title || !func) return; + item = gtk_menu_item_new_with_label(title); + gtk_signal_connect(GTK_OBJECT(item), "activate", GTK_SIGNAL_FUNC(func), (gpointer)index); + gtk_widget_show(item); + gtk_menu_append(GTK_MENU(menu), item); + + if(imap) *((*imap)++) = index; +} + +/*** output method ***********************************************************/ + +/*-- callbacks --------------------------------------------------------------*/ + +static void resampling_rate_cb(GtkWidget *widget, gint index) +{ + cfg->output_rate = index; +} + +#ifdef HAVE_LIBSAMPLERATE +static void resampling_quality_cb(GtkWidget *widget, gint index) +{ + cfg->output_quality = index; +} +#endif + +/*** plugin output ***********************************************************/ + +static gchar * +strip(gchar *s) +{ + gchar *p; + if(!s) return NULL; + for(; *s == ' '; s++); + if(!*s) return s; + for(p = s+strlen(s)-1; *p == ' '; p--); + *++p = 0; + return s; +} + +static void +update_plugin_config(gchar **config_string, gchar *name, + plugin_config_t *pc, gboolean save) +{ + plugin_config_t default_pc = DEFAULT_OP_CONFIG; + + gchar *buffer = NULL; + gchar out[1024]; + + gboolean plugin_found = FALSE; + gchar *plugin, *next_plugin; + gchar *args; + + if(pc && !save) *pc = default_pc; + if(!config_string || !*config_string || !name || !pc) { + DEBUG(("[crossfade] update_plugin_config: missing arg!\n")); + return; + } + + buffer = g_strdup(*config_string); + out[0] = 0; + + for(plugin = buffer; plugin; plugin = next_plugin) { + if((next_plugin = strchr(plugin, ';'))) *next_plugin++ = 0; + if((args = strchr(plugin, '='))) *args++ = 0; + plugin = strip(plugin); + if(!*plugin || !args || !*args) continue; + + if(save) { + if(0 == strcmp(plugin, name)) continue; + if(*out) strcat(out, "; "); + strcat(out, plugin); + strcat(out, "="); + strcat(out, args); + continue; + } + else if(strcmp(plugin, name)) continue; + + args = strip(args); + sscanf(args, "%d,%d,%d,%d", + &pc->throttle_enable, + &pc->max_write_enable, + &pc->max_write_len, + &pc->force_reopen); + pc->max_write_len &= -4; + plugin_found = TRUE; + } + + if(save) { + /* only save if settings differ from defaults */ + if(( pc->throttle_enable != default_pc.throttle_enable) + ||(pc->max_write_enable != default_pc.max_write_enable) + ||(pc->max_write_len != default_pc.max_write_len) + ||(pc->force_reopen != default_pc.force_reopen)) { + if(*out) strcat(out, "; "); + sprintf(out + strlen(out), "%s=%d,%d,%d,%d", name, + pc->throttle_enable ? 1 : 0, + pc->max_write_enable ? 1 : 0, + pc->max_write_len, + pc->force_reopen); + } + if(*config_string) g_free(*config_string); + *config_string = g_strdup(out); + } + + g_free(buffer); +} + +static void +config_plugin_cb(GtkWidget *widget, gint index); + +static gint +scan_plugins(GtkWidget *option_menu, gchar *selected) +{ + GtkWidget *menu = gtk_menu_new(); + GList *list = g_list_first(get_output_list()); /* XMMS */ + gint index = 0; + gint sel_index = -1; + gint def_index = -1; + + /* sanity check */ + if(selected == NULL) selected = ""; + + /* parse module list */ + while(list) { + OutputPlugin *op = (OutputPlugin *)list->data; + GtkWidget *item = gtk_menu_item_new_with_label(op->description); + + if(op == get_crossfade_oplugin_info()) /* disable selecting ourselves */ + gtk_widget_set_sensitive(item, FALSE); + else { + if(def_index == -1) def_index = index; + if(selected && !strcmp(g_basename(op->filename), selected)) + sel_index = index; + } + + /* create menu item */ + gtk_signal_connect(GTK_OBJECT(item), "activate", + GTK_SIGNAL_FUNC(config_plugin_cb), (gpointer)index++); + gtk_widget_show(item); + gtk_menu_append(GTK_MENU(menu), item); + + /* advance to next module */ + list = g_list_next(list); + } + + /* attach menu */ + gtk_option_menu_set_menu(GTK_OPTION_MENU(option_menu), menu); + + if(sel_index == -1) { + DEBUG(("[crossfade] scan_plugins: plugin not found (\"%s\")\n", selected)); + return def_index; /* use default (first entry) */ + } + return sel_index; +} + +/*-- plugin output callbacks ------------------------------------------------*/ + +static void +config_plugin_cb(GtkWidget *widget, gint index) +{ + OutputPlugin *op = g_list_nth_data(get_output_list(), index); /* XMMS */ + + /* get plugin options from gui */ + op_config.throttle_enable = GET_TOGGLE("op_throttle_check"); + op_config.max_write_enable = GET_TOGGLE("op_maxblock_check"); + op_config.max_write_len = GET_SPIN ("op_maxblock_spin"); + op_config.force_reopen = GET_TOGGLE("op_forcereopen_check"); + + /* config -> string */ + xfade_save_plugin_config(&cfg->op_config_string, cfg->op_name, &op_config); + + /* select new plugin */ + op_index = index; + + /* get new plugin's name */ + if(cfg->op_name) g_free(cfg->op_name); + cfg->op_name = (op && op->filename) + ? g_strdup(g_basename(op->filename)) : NULL; + + /* string -> config */ + xfade_load_plugin_config(cfg->op_config_string, cfg->op_name, &op_config); + + /* update gui */ + SET_SENSITIVE("op_configure_button", op && (op->configure != NULL)); + SET_SENSITIVE("op_about_button", op && (op->about != NULL)); + SET_TOGGLE ("op_throttle_check", op_config.throttle_enable); + SET_TOGGLE ("op_maxblock_check", op_config.max_write_enable); + SET_SPIN ("op_maxblock_spin", op_config.max_write_len); + SET_SENSITIVE("op_maxblock_spin", op_config.max_write_enable); + SET_TOGGLE ("op_forcereopen_check", op_config.force_reopen); +} + +void on_output_plugin_configure_button_clicked (GtkButton *button, gpointer user_data) +{ + OutputPlugin *op = g_list_nth_data(get_output_list(), op_index); /* XMMS */ + if((op == NULL) || (op->configure == NULL)) return; + op->configure(); +} + +void on_output_plugin_about_button_clicked(GtkButton *button, gpointer user_data) +{ + OutputPlugin *op = g_list_nth_data(get_output_list(), op_index); /* XMMS */ + if((op == NULL) || (op->about == NULL)) return; + op->about(); +} + +void on_op_throttle_check_toggled(GtkToggleButton *togglebutton, gpointer user_data) +{ + op_config.throttle_enable = gtk_toggle_button_get_active(togglebutton); +} + +void on_op_maxblock_check_toggled(GtkToggleButton *togglebutton, gpointer user_data) +{ + op_config.max_write_enable = gtk_toggle_button_get_active(togglebutton); + SET_SENSITIVE("op_maxblock_spin", op_config.max_write_enable); +} + +void on_op_maxblock_spin_changed(GtkEditable *editable, gpointer user_data) +{ + op_config.max_write_len = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(editable)); +} + +void on_op_forcereopen_check_toggled(GtkToggleButton *togglebutton, gpointer user_data) +{ + op_config.max_write_enable = gtk_toggle_button_get_active(togglebutton); +} + +/*** effects *****************************************************************/ + +static void +config_effect_plugin_cb(GtkWidget *widget, gint index); + +static gint +scan_effect_plugins(GtkWidget *option_menu, gchar *selected) +{ + GtkWidget *menu = gtk_menu_new(); + GList *list = g_list_first(get_effect_list()); /* XMMS */ + gint index = 0; + gint sel_index = -1; + gint def_index = -1; + + /* sanity check */ + if(selected == NULL) selected = ""; + + /* parse module list */ + while(list) { + EffectPlugin *ep = (EffectPlugin *)list->data; + GtkWidget *item = gtk_menu_item_new_with_label(ep->description); + + if(def_index == -1) def_index = index; + if(selected && !strcmp(g_basename(ep->filename), selected)) + sel_index = index; + + /* create menu item */ + gtk_signal_connect(GTK_OBJECT(item), "activate", + GTK_SIGNAL_FUNC(config_effect_plugin_cb), (gpointer)index++); + gtk_widget_show(item); + gtk_menu_append(GTK_MENU(menu), item); + + /* advance to next module */ + list = g_list_next(list); + } + + /* attach menu */ + gtk_option_menu_set_menu(GTK_OPTION_MENU(option_menu), menu); + + if(sel_index == -1) { + DEBUG(("[crossfade] scan_effect_plugins: plugin not found (\"%s\")\n", selected)); + return def_index; /* use default (first entry) */ + } + return sel_index; +} + +/*-- plugin output callbacks ------------------------------------------------*/ + +static void +config_effect_plugin_cb(GtkWidget *widget, gint index) +{ + EffectPlugin *ep = g_list_nth_data(get_effect_list(), index); /* XMMS */ + + /* select new plugin */ + ep_index = index; + + /* get new plugin's name */ + if(cfg->ep_name) g_free(cfg->ep_name); + cfg->ep_name = (ep && ep->filename) + ? g_strdup(g_basename(ep->filename)) : NULL; + + /* update gui */ + SET_SENSITIVE("ep_configure_button", ep && (ep->configure != NULL)); + SET_SENSITIVE("ep_about_button", ep && (ep->about != NULL)); + + /* 0.3.5: apply effect config immediatelly */ + if(config->ep_name) g_free(config->ep_name); + config->ep_name = g_strdup(cfg->ep_name); + xfade_realize_ep_config(); +} + +void on_ep_configure_button_clicked(GtkButton *button, gpointer user_data) +{ + EffectPlugin *ep = g_list_nth_data(get_effect_list(), ep_index); /* XMMS */ + if((ep == NULL) || (ep->configure == NULL)) return; + ep->configure(); +} + +void on_ep_about_button_clicked(GtkButton *button, gpointer user_data) +{ + EffectPlugin *ep = g_list_nth_data(get_effect_list(), ep_index); /* XMMS */ + if((ep == NULL) || (ep->about == NULL)) return; + ep->about(); +} + +void on_ep_enable_check_toggled(GtkToggleButton *togglebutton, gpointer user_data) +{ + /* 0.3.5: apply effect config immediatelly */ + config->ep_enable = cfg->ep_enable = GET_TOGGLE("ep_enable_check"); + xfade_realize_ep_config(); +} + +/*-- volume normalizer ------------------------------------------------------*/ + +void check_effects_dependencies() +{ + if(checking) return; + checking = TRUE; + + SET_SENSITIVE("volnorm_target_spin", cfg->volnorm_enable); + SET_SENSITIVE("volnorm_target_label", cfg->volnorm_enable); + SET_SENSITIVE("volnorm_quantaudio_check", cfg->volnorm_enable); + SET_SENSITIVE("volnorm_target_spin", cfg->volnorm_enable); + + checking = FALSE; +} + +void on_volnorm_enable_check_toggled(GtkToggleButton *togglebutton, gpointer user_data) +{ + if(checking) return; + cfg->volnorm_enable = gtk_toggle_button_get_active(togglebutton); + check_effects_dependencies(); +} + +/*** crossfader **************************************************************/ + +static void xf_config_cb(GtkWidget *widget, gint index); +static void xf_type_cb (GtkWidget *widget, gint index); + +/* crude hack to keep track of menu items */ +static gint xf_config_index_map[MAX_FADE_CONFIGS]; +static gint xf_type_index_map [MAX_FADE_TYPES]; + +static void +create_crossfader_config_menu() +{ + GtkWidget *optionmenu, *menu; + gint i, *imap; + + if((optionmenu = lookup_widget(config_win, "xf_config_optionmenu"))) { + for(i=0; ifc[cfg->xf_index].type_mask; + if(mask & (1 << FADE_TYPE_REOPEN)) add_menu_item(menu, "Reopen output device", GTK_SIGNAL_FUNC(xf_type_cb), FADE_TYPE_REOPEN, &imap); + if(mask & (1 << FADE_TYPE_FLUSH)) add_menu_item(menu, "Flush output device", GTK_SIGNAL_FUNC(xf_type_cb), FADE_TYPE_FLUSH, &imap); + if(mask & (1 << FADE_TYPE_NONE)) add_menu_item(menu, "None (gapless/off)", GTK_SIGNAL_FUNC(xf_type_cb), FADE_TYPE_NONE, &imap); + if(mask & (1 << FADE_TYPE_PAUSE)) add_menu_item(menu, "Pause", GTK_SIGNAL_FUNC(xf_type_cb), FADE_TYPE_PAUSE, &imap); + if(mask & (1 << FADE_TYPE_SIMPLE_XF)) add_menu_item(menu, "Simple crossfade", GTK_SIGNAL_FUNC(xf_type_cb), FADE_TYPE_SIMPLE_XF, &imap); + if(mask & (1 << FADE_TYPE_ADVANCED_XF)) add_menu_item(menu, "Advanced crossfade", GTK_SIGNAL_FUNC(xf_type_cb), FADE_TYPE_ADVANCED_XF, &imap); + if(mask & (1 << FADE_TYPE_FADEIN)) add_menu_item(menu, "Fadein", GTK_SIGNAL_FUNC(xf_type_cb), FADE_TYPE_FADEIN, &imap); + if(mask & (1 << FADE_TYPE_FADEOUT)) add_menu_item(menu, "Fadeout", GTK_SIGNAL_FUNC(xf_type_cb), FADE_TYPE_FADEOUT, &imap); + if(mask & (1 << FADE_TYPE_PAUSE_NONE)) add_menu_item(menu, "None", GTK_SIGNAL_FUNC(xf_type_cb), FADE_TYPE_PAUSE_NONE, &imap); + if(mask & (1 << FADE_TYPE_PAUSE_ADV)) add_menu_item(menu, "Fadeout/Fadein", GTK_SIGNAL_FUNC(xf_type_cb), FADE_TYPE_PAUSE_ADV, &imap); + gtk_option_menu_set_menu(GTK_OPTION_MENU(optionmenu), menu); + } +} + +#define NONE 0x00000000L +#define XF_CONFIG 0x00000001L +#define XF_TYPE 0x00000002L +#define XF_MIX_SIZE 0x00000004L +#define XF_FADEOUT 0x00000008L +#define XF_OFFSET 0x00000010L +#define XF_FADEIN 0x00000020L +#define XF_PAGE 0x00000040L +#define XF_FLUSH 0x00000080L +#define ANY 0xffffffffL + +static void +check_crossfader_dependencies(guint32 mask) +{ + fade_config_t *fc = &cfg->fc[cfg->xf_index]; + gint i; + + /* HACK: avoid endless recursion */ + if(checking) return; + checking = TRUE; + + if(mask & XF_FLUSH) { + SET_TOGGLE ("xftfp_enable_check", fc->flush_pause_enable); + SET_SENSITIVE("xftfp_length_label", fc->flush_pause_enable); + SET_SENSITIVE("xftfp_length_spin", fc->flush_pause_enable); + SET_TOGGLE ("xftffi_enable_check", fc->flush_in_enable); + SET_SENSITIVE("xftffi_length_label", fc->flush_in_enable); + SET_SENSITIVE("xftffi_length_spin", fc->flush_in_enable); + SET_SENSITIVE("xftffi_volume_label", fc->flush_in_enable); + SET_SENSITIVE("xftffi_volume_spin", fc->flush_in_enable); + } + + if(mask & XF_MIX_SIZE) { + SET_TOGGLE ("xf_autobuf_check", cfg->mix_size_auto); + SET_SENSITIVE("xf_buffer_spin", !cfg->mix_size_auto); + SET_SPIN ("xf_buffer_spin", xfade_mix_size_ms(cfg)); + } + + if(mask & XF_CONFIG) { + for(i=0; ixf_index); i++); + if(i == MAX_FADE_CONFIGS) i=0; + SET_HISTORY("xf_config_optionmenu", i); + } + + if(mask & XF_TYPE) { + create_crossfader_type_menu(); + for(i=0; itype); i++); + if(i == MAX_FADE_TYPES) { + fc->type = FADE_TYPE_NONE; + for(i=0; itype); i++); + if(i == MAX_FADE_CONFIGS) i=0; + } + SET_HISTORY("xf_type_optionmenu", i); + } + + if(mask & XF_PAGE) { + SET_PAGE("xf_type_notebook", fc->type); + SET_SPIN("pause_length_spin", fc->pause_len_ms); + SET_SPIN("simple_length_spin", fc->simple_len_ms); + if(fc->config == FADE_CONFIG_SEEK) { + HIDE("xftf_pause_frame"); + HIDE("xftf_fadein_frame"); + } + else { + SHOW("xftf_pause_frame"); + SHOW("xftf_fadein_frame"); + } + } + + if(mask & XF_FADEOUT) { + SET_TOGGLE ("fadeout_enable_check", fc->out_enable); + SET_SENSITIVE("fadeout_length_label", fc->out_enable); + SET_SENSITIVE("fadeout_length_spin", fc->out_enable); + SET_SPIN ("fadeout_length_spin", fc->out_len_ms); + SET_SENSITIVE("fadeout_volume_label", fc->out_enable); + SET_SENSITIVE("fadeout_volume_spin", fc->out_enable); + SET_SPIN ("fadeout_volume_spin", fc->out_volume); + SET_SPIN ("xftfo_length_spin", fc->out_len_ms); + SET_SPIN ("xftfo_volume_spin", fc->out_volume); + SET_SPIN ("xftfoi_fadeout_spin", fc->out_len_ms); + } + + if(mask & XF_FADEIN) { + SET_TOGGLE ("fadein_lock_check", fc->in_locked); + SET_SENSITIVE("fadein_enable_check", !fc->in_locked); + SET_TOGGLE ("fadein_enable_check", fc->in_locked ? fc->out_enable : fc->in_enable); + SET_SENSITIVE("fadein_length_label", !fc->in_locked && fc->in_enable); + SET_SENSITIVE("fadein_length_spin", !fc->in_locked && fc->in_enable); + SET_SPIN ("fadein_length_spin", fc->in_locked ? fc->out_len_ms : fc->in_len_ms); + SET_SENSITIVE("fadein_volume_label", !fc->in_locked && fc->in_enable); + SET_SENSITIVE("fadein_volume_spin", !fc->in_locked && fc->in_enable); + SET_SPIN ("fadein_volume_spin", fc->in_locked ? fc->out_volume : fc->in_volume); + SET_SPIN ("xftfi_length_spin", fc->in_len_ms); + SET_SPIN ("xftfi_volume_spin", fc->in_volume); + SET_SPIN ("xftfoi_fadein_spin", fc->in_len_ms); + } + + if(mask & XF_OFFSET) { + if(fc->out_enable) + SET_SENSITIVE("xfofs_lockout_radiobutton", TRUE); + if(!fc->in_locked && fc->in_enable) + SET_SENSITIVE("xfofs_lockin_radiobutton", TRUE); + + switch(fc->ofs_type) { + case FC_OFFSET_LOCK_OUT: + if(!fc->out_enable) { + SET_TOGGLE("xfofs_none_radiobutton", TRUE); + fc->ofs_type = FC_OFFSET_NONE; + } + break; + case FC_OFFSET_LOCK_IN: + if(!(!fc->in_locked && fc->in_enable)) { + if((fc->in_locked && fc->out_enable)) { + SET_TOGGLE("xfofs_lockout_radiobutton", TRUE); + fc->ofs_type = FC_OFFSET_LOCK_OUT; + } else { + SET_TOGGLE("xfofs_none_radiobutton", TRUE); + fc->ofs_type = FC_OFFSET_NONE; + } + } + break; + } + + switch(fc->ofs_type_wanted) { + case FC_OFFSET_NONE: + SET_TOGGLE("xfofs_none_radiobutton", TRUE); + fc->ofs_type = FC_OFFSET_NONE; + break; + case FC_OFFSET_LOCK_OUT: + if(fc->out_enable) { + SET_TOGGLE("xfofs_lockout_radiobutton", TRUE); + fc->ofs_type = FC_OFFSET_LOCK_OUT; + } + break; + case FC_OFFSET_LOCK_IN: + if(!fc->in_locked && fc->in_enable) { + SET_TOGGLE("xfofs_lockin_radiobutton", TRUE); + fc->ofs_type = FC_OFFSET_LOCK_IN; + } + else if(fc->out_enable) { + SET_TOGGLE("xfofs_lockout_radiobutton", TRUE); + fc->ofs_type = FC_OFFSET_LOCK_OUT; + } + break; + case FC_OFFSET_CUSTOM: + SET_TOGGLE("xfofs_custom_radiobutton", TRUE); + fc->ofs_type = FC_OFFSET_CUSTOM; + break; + } + + if(!fc->out_enable) + SET_SENSITIVE("xfofs_lockout_radiobutton", FALSE); + if(!(!fc->in_locked && fc->in_enable)) + SET_SENSITIVE("xfofs_lockin_radiobutton", FALSE); + + SET_SENSITIVE("xfofs_custom_spin", fc->ofs_type == FC_OFFSET_CUSTOM); + SET_SPIN ("xfofs_custom_spin", xfade_cfg_offset(fc)); + SET_SPIN ("xftfo_silence_spin", xfade_cfg_offset(fc)); + SET_SPIN ("xftfoi_silence_spin", xfade_cfg_offset(fc)); + } + + checking = FALSE; +} + +/*-- crossfader callbacks ---------------------------------------------------*/ + +void on_xf_buffer_spin_changed(GtkEditable *editable, gpointer user_data) +{ + if(checking) return; + cfg->mix_size_ms = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(editable)); + check_crossfader_dependencies(NONE); +} + +void on_xf_autobuf_check_toggled(GtkToggleButton *togglebutton, gpointer user_data) +{ + cfg->mix_size_auto = gtk_toggle_button_get_active(togglebutton); + check_crossfader_dependencies(XF_MIX_SIZE); +} + +/* - config/type - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/ + +void xf_config_cb(GtkWidget *widget, gint index) +{ + if(checking) return; + cfg->xf_index = index; + check_crossfader_dependencies(ANY & ~XF_CONFIG); +} + +void xf_type_cb(GtkWidget *widget, gint index) +{ + if(checking) return; + cfg->fc[cfg->xf_index].type = index; + check_crossfader_dependencies(ANY & ~XF_CONFIG); +} + +/* - flush - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/ + +void on_xftfp_enable_check_toggled(GtkToggleButton *togglebutton, gpointer user_data) +{ + if(checking) return; + cfg->fc[cfg->xf_index].flush_pause_enable + = gtk_toggle_button_get_active(togglebutton); + check_crossfader_dependencies(XF_FLUSH|XF_MIX_SIZE); +} + +void on_xftfp_length_spin_changed(GtkEditable *editable, gpointer user_data) +{ + if(checking) return; + cfg->fc[cfg->xf_index].flush_pause_len_ms + = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(editable)); + check_crossfader_dependencies(XF_FLUSH); +} + +void on_xftffi_enable_check_toggled(GtkToggleButton *togglebutton, gpointer user_data) +{ + if(checking) return; + cfg->fc[cfg->xf_index].flush_in_enable + = gtk_toggle_button_get_active(togglebutton); + check_crossfader_dependencies(XF_FLUSH|XF_OFFSET|XF_FADEOUT|XF_FADEIN); +} + +void on_xftffi_length_spin_changed(GtkEditable *editable, gpointer user_data) +{ + if(checking) return; + cfg->fc[cfg->xf_index].flush_in_len_ms + = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(editable)); + check_crossfader_dependencies(XF_FLUSH); +} + +void on_xftffi_volume_spin_changed(GtkEditable *editable, gpointer user_data) +{ + if(checking) return; + cfg->fc[cfg->xf_index].flush_in_volume + = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(editable)); + check_crossfader_dependencies(XF_FLUSH); +} + +/* - pause - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/ + +void on_pause_length_spin_changed(GtkEditable *editable, gpointer user_data) +{ + if(checking) return; + cfg->fc[cfg->xf_index].pause_len_ms + = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(editable)); + check_crossfader_dependencies(XF_MIX_SIZE); +} + +/* - simple - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/ + +void on_simple_length_spin_changed(GtkEditable *editable, gpointer user_data) +{ + if(checking) return; + cfg->fc[cfg->xf_index].simple_len_ms + = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(editable)); + check_crossfader_dependencies(XF_MIX_SIZE); +} + +/* - crossfade-fadeout - - - - - - - - - - - - - - - - - - - - - - - - - - -*/ + +void on_fadeout_enable_check_toggled(GtkToggleButton *togglebutton, gpointer user_data) +{ + if(checking) return; + cfg->fc[cfg->xf_index].out_enable + = gtk_toggle_button_get_active(togglebutton); + check_crossfader_dependencies(XF_MIX_SIZE|XF_OFFSET|XF_FADEOUT|XF_FADEIN); +} + +void on_fadeout_length_spin_changed(GtkEditable *editable, gpointer user_data) +{ + if(checking) return; + cfg->fc[cfg->xf_index].out_len_ms + = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(editable)); + check_crossfader_dependencies(XF_MIX_SIZE|XF_OFFSET|XF_FADEIN); +} + +void on_fadeout_volume_spin_changed(GtkEditable *editable, gpointer user_data) +{ + cfg->fc[cfg->xf_index].out_volume + = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(editable)); + check_crossfader_dependencies(XF_OFFSET|XF_FADEIN); +} + +/* - crossfade-offset - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/ + +void on_xfofs_none_radiobutton_toggled(GtkToggleButton *togglebutton, gpointer user_data) +{ + if(checking || !gtk_toggle_button_get_active(togglebutton)) return; + cfg->fc[cfg->xf_index].ofs_type = FC_OFFSET_NONE; + cfg->fc[cfg->xf_index].ofs_type_wanted = FC_OFFSET_NONE; + check_crossfader_dependencies(XF_MIX_SIZE|XF_OFFSET); +} + +void on_xfofs_none_radiobutton_clicked(GtkButton *button, gpointer user_data) +{ + if(checking) return; + cfg->fc[cfg->xf_index].ofs_type_wanted = FC_OFFSET_NONE; +} + +void on_xfofs_lockout_radiobutton_toggled(GtkToggleButton *togglebutton, gpointer user_data) +{ + if(checking || !gtk_toggle_button_get_active(togglebutton)) return; + cfg->fc[cfg->xf_index].ofs_type = FC_OFFSET_LOCK_OUT; + cfg->fc[cfg->xf_index].ofs_type_wanted = FC_OFFSET_LOCK_OUT; + check_crossfader_dependencies(XF_MIX_SIZE|XF_OFFSET); +} + +void on_xfofs_lockout_radiobutton_clicked(GtkButton *button, gpointer user_data) +{ + if(checking) return; + cfg->fc[cfg->xf_index].ofs_type_wanted = FC_OFFSET_LOCK_OUT; +} + +void on_xfofs_lockin_radiobutton_toggled(GtkToggleButton *togglebutton, gpointer user_data) +{ + if(checking || !gtk_toggle_button_get_active(togglebutton)) return; + cfg->fc[cfg->xf_index].ofs_type = FC_OFFSET_LOCK_IN; + cfg->fc[cfg->xf_index].ofs_type_wanted = FC_OFFSET_LOCK_IN; + check_crossfader_dependencies(XF_MIX_SIZE|XF_OFFSET); +} + +void on_xfofs_lockin_radiobutton_clicked(GtkButton *button, gpointer user_data) +{ + if(checking) return; + cfg->fc[cfg->xf_index].ofs_type_wanted = FC_OFFSET_LOCK_IN; +} + +void on_xfofs_custom_radiobutton_toggled(GtkToggleButton *togglebutton, gpointer user_data) +{ + if(checking || !gtk_toggle_button_get_active(togglebutton)) return; + cfg->fc[cfg->xf_index].ofs_type = FC_OFFSET_CUSTOM; + cfg->fc[cfg->xf_index].ofs_type_wanted = FC_OFFSET_CUSTOM; + check_crossfader_dependencies(XF_MIX_SIZE|XF_OFFSET); +} + +void on_xfofs_custom_radiobutton_clicked(GtkButton *button, gpointer user_data) +{ + if(checking) return; + cfg->fc[cfg->xf_index].ofs_type_wanted = FC_OFFSET_CUSTOM; +} + +void on_xfofs_custom_spin_changed(GtkEditable *editable, gpointer user_data) +{ + if(checking) return; + cfg->fc[cfg->xf_index].ofs_custom_ms = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(editable)); + check_crossfader_dependencies(XF_MIX_SIZE); +} + +/* - crossfade-fadein - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/ + +void on_fadein_lock_check_toggled(GtkToggleButton *togglebutton, gpointer user_data) +{ + if(checking) return; + cfg->fc[cfg->xf_index].in_locked = gtk_toggle_button_get_active(togglebutton); + check_crossfader_dependencies(XF_OFFSET|XF_FADEIN); +} + +void on_fadein_enable_check_toggled(GtkToggleButton *togglebutton, gpointer user_data) +{ + if(checking) return; + cfg->fc[cfg->xf_index].in_enable = gtk_toggle_button_get_active(togglebutton); + check_crossfader_dependencies(XF_OFFSET|XF_FADEIN); +} + +void on_fadein_length_spin_changed(GtkEditable *editable, gpointer user_data) +{ + if(checking) return; + cfg->fc[cfg->xf_index].in_len_ms = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(editable)); + check_crossfader_dependencies(XF_MIX_SIZE|XF_OFFSET); +} + +void on_fadein_volume_spin_changed(GtkEditable *editable, gpointer user_data) +{ + if(checking) return; + cfg->fc[cfg->xf_index].in_volume = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(editable)); + check_crossfader_dependencies(NONE); +} + +/*-- fadein -----------------------------------------------------------------*/ + +/* signal set to on_fadein_length_spin_changed */ +/* signal set to on_fadein_volume_spin_changed */ + +/*-- fadeout ----------------------------------------------------------------*/ + +/* signal set to on_fadeout_length_spin_changed */ +/* signal set to on_fadeout_volume_spin_changed */ + +/*-- fadeout/fadein ---------------------------------------------------------*/ + +/* signal set to on_fadeout_length_spin_changed */ +/* signal set to on_xfofs_custom_spin_changed */ +/* signal set to on_fadeout_volume_spin_changed */ + +/*** gap killer **************************************************************/ + +void check_gapkiller_dependencies() +{ + if(checking) return; + checking = TRUE; + + SET_SENSITIVE("lgap_length_spin", cfg->gap_lead_enable); + SET_SENSITIVE("lgap_level_spin", cfg->gap_lead_enable); + SET_SENSITIVE("tgap_enable_check", !cfg->gap_trail_locked); + SET_SENSITIVE("tgap_length_spin", !cfg->gap_trail_locked && cfg->gap_trail_enable); + SET_SENSITIVE("tgap_level_spin", !cfg->gap_trail_locked && cfg->gap_trail_enable); + + if(cfg->gap_trail_locked) { + SET_TOGGLE("tgap_enable_check", cfg->gap_lead_enable); + SET_SPIN ("tgap_length_spin", cfg->gap_lead_len_ms); + SET_SPIN ("tgap_level_spin", cfg->gap_lead_level); + } + else { + SET_TOGGLE("tgap_enable_check", cfg->gap_trail_enable); + SET_SPIN ("tgap_length_spin", cfg->gap_trail_len_ms); + SET_SPIN ("tgap_level_spin", cfg->gap_trail_level); + } + + if(cfg->mix_size_auto) + SET_SPIN("xf_buffer_spin", xfade_mix_size_ms(cfg)); + + checking = FALSE; +} + +/*-- gapkiller callbacks ----------------------------------------------------*/ + +void on_lgap_enable_check_toggled(GtkToggleButton *togglebutton, gpointer user_data) +{ + if(checking) return; + cfg->gap_lead_enable = gtk_toggle_button_get_active(togglebutton); + check_gapkiller_dependencies(); +} + +void on_lgap_length_spin_changed(GtkEditable *editable, gpointer user_data) +{ + if(checking) return; + cfg->gap_lead_len_ms = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(editable)); + check_gapkiller_dependencies(); +} + +void on_lgap_level_spin_changed(GtkEditable *editable, gpointer user_data) +{ + if(checking) return; + cfg->gap_lead_level = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(editable)); + check_gapkiller_dependencies(); +} + +void on_tgap_lock_check_toggled(GtkToggleButton *togglebutton, gpointer user_data) +{ + if(checking) return; + cfg->gap_trail_locked = gtk_toggle_button_get_active(togglebutton); + check_gapkiller_dependencies(); +} + +void on_tgap_enable_check_toggled(GtkToggleButton *togglebutton, gpointer user_data) +{ + if(checking) return; + cfg->gap_trail_enable = gtk_toggle_button_get_active(togglebutton); + check_gapkiller_dependencies(); +} + +void on_tgap_length_spin_changed(GtkEditable *editable, gpointer user_data) +{ + if(checking) return; + cfg->gap_trail_len_ms = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(editable)); +} + +void on_tgap_level_spin_changed(GtkEditable *editable, gpointer user_data) +{ + if(checking) return; + cfg->gap_trail_level = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(editable)); +} + +/*** misc ********************************************************************/ + +void check_misc_dependencies() +{ + if(checking) return; + checking = TRUE; + + if(cfg->mix_size_auto) + SET_SPIN("xf_buffer_spin", xfade_mix_size_ms(cfg)); + + SET_SENSITIVE("moth_opmaxused_spin", cfg->enable_op_max_used); + + checking = FALSE; +} + +/*-- misc callbacks ---------------------------------------------------------*/ + +void on_config_mixopt_enable_check_toggled(GtkToggleButton *togglebutton, gpointer user_data) +{ + SET_SENSITIVE("mixopt_reverse_check", gtk_toggle_button_get_active(togglebutton)); + SET_SENSITIVE("mixopt_software_check", gtk_toggle_button_get_active(togglebutton)); +} + +void on_moth_songchange_spin_changed(GtkEditable *editable, gpointer user_data) +{ + if(checking) return; + cfg->songchange_timeout = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(editable)); + check_misc_dependencies(); +} + +void on_moth_opmaxused_check_toggled(GtkToggleButton *togglebutton, gpointer user_data) +{ + if(checking) return; + cfg->enable_op_max_used = gtk_toggle_button_get_active(togglebutton); + check_misc_dependencies(); +} + +/*** presets *****************************************************************/ + +void on_presets_list_click_column(GtkCList *clist, gint column, gpointer user_data) +{ + DEBUG(("*** column=%d\n", column)); +} + +/*** main config *************************************************************/ + +void on_config_apply_clicked(GtkButton *button, gpointer user_data) +{ + GtkWidget *widget; + + /* get current notebook page */ + if((widget = lookup_widget(config_win, "config_notebook"))) + cfg->page = gtk_notebook_get_current_page(GTK_NOTEBOOK(widget)); + + /* output method */ + + /* sample rate */ + + /* output method: plugin */ + op_config.throttle_enable = GET_TOGGLE("op_throttle_check"); + op_config.max_write_enable = GET_TOGGLE("op_maxblock_check"); + op_config.max_write_len = GET_SPIN ("op_maxblock_spin"); + op_config.force_reopen = GET_TOGGLE("op_forcereopen_check"); + + xfade_save_plugin_config(&cfg->op_config_string, cfg->op_name, &op_config); + + /* output method: none: */ + + /* effects: pre-mixing effect plugin */ + + /* effects: volume normalizer */ + cfg->volnorm_target = GET_SPIN ("volnorm_target_spin"); + cfg->volnorm_use_qa = GET_TOGGLE("volnorm_quantaudio_check"); + + /* crossfader */ + cfg->mix_size_auto = GET_TOGGLE("xf_autobuf_check"); + + /* gap killer */ + cfg->gap_lead_enable = GET_TOGGLE("lgap_enable_check"); + cfg->gap_lead_len_ms = GET_SPIN ("lgap_length_spin"); + cfg->gap_lead_level = GET_SPIN ("lgap_level_spin"); + + cfg->gap_trail_locked = GET_TOGGLE("tgap_lock_check"); + + cfg->gap_crossing = GET_TOGGLE("gadv_crossing_check"); + + /* misc */ + cfg->enable_debug = GET_TOGGLE("debug_stderr_check"); + cfg->enable_monitor = GET_TOGGLE("debug_monitor_check"); + cfg->enable_mixer = GET_TOGGLE("mixopt_enable_check"); + cfg->mixer_reverse = GET_TOGGLE("mixopt_reverse_check"); + cfg->mixer_software = GET_TOGGLE("mixopt_software_check"); + cfg->preload_size_ms = GET_SPIN ("moth_preload_spin"); + cfg->album_detection = GET_TOGGLE("noxf_album_check"); + cfg->no_xfade_if_same_file = GET_TOGGLE("noxf_samefile_check"); + cfg->enable_http_workaround = GET_TOGGLE("moth_httpworkaround_check"); + cfg->op_max_used_ms = GET_SPIN ("moth_opmaxused_spin"); + cfg->output_keep_opened = GET_TOGGLE("moth_outputkeepopened_check"); + + /* presets */ + + /* lock buffer */ + g_static_mutex_lock(&buffer_mutex); + + /* free existing strings */ + if(config->op_config_string) g_free(config->op_config_string); + if(config->op_name) g_free(config->op_name); + if(config->ep_name) g_free(config->ep_name); + + /* copy current settings (dupping the strings) */ + *config = *cfg; + config->op_config_string = g_strdup(cfg->op_config_string); + config->op_name = g_strdup(cfg->op_name); + config->ep_name = g_strdup(cfg->ep_name); + + /* tell the engine that the config has changed */ + xfade_realize_config(); + + /* unlock buffer */ + g_static_mutex_unlock(&buffer_mutex); + + /* save configuration */ + xfade_save_config(); + + /* show/hide monitor win depending on config->enable_monitor */ + xfade_check_monitor_win(); +} + +void on_config_ok_clicked(GtkButton *button, gpointer user_data) +{ + /* apply and save config */ + on_config_apply_clicked(button, user_data); + + /* close and destroy window */ + gtk_widget_destroy(config_win); +} + +void xfade_configure() +{ + GtkWidget *widget; + + if(!config_win) { + /* create */ + if(!(config_win = create_config_win())) { + DEBUG(("[crossfade] plugin_configure: error creating window!\n")); + return; + } + + /* update config_win when window is destroyed */ + gtk_signal_connect(GTK_OBJECT(config_win), "destroy", + GTK_SIGNAL_FUNC(gtk_widget_destroyed), &config_win); + + /* free any strings that might be left in our local copy of the config */ + if(cfg->op_config_string) g_free(cfg->op_config_string); + if(cfg->op_name) g_free(cfg->op_name); + if(cfg->ep_name) g_free(cfg->ep_name); + + /* copy current settings (dupping the strings) */ + *cfg = *config; + cfg->op_config_string = g_strdup(config->op_config_string); + cfg->op_name = g_strdup(config->op_name); + cfg->ep_name = g_strdup(config->ep_name); + + /* go to remembered notebook page */ + if((widget = lookup_widget(config_win, "config_notebook"))) + gtk_notebook_set_page(GTK_NOTEBOOK(widget), config->page); + + /* output: resampling rate */ + if((widget = lookup_widget(config_win, "resampling_rate_optionmenu"))) { + GtkWidget *menu = gtk_menu_new(); + GtkWidget *item; + + item = gtk_menu_item_new_with_label("44100 Hz"); + gtk_signal_connect(GTK_OBJECT(item), "activate", GTK_SIGNAL_FUNC(resampling_rate_cb), (gpointer)44100); + gtk_widget_show(item); + gtk_menu_append(GTK_MENU(menu), item); + + item = gtk_menu_item_new_with_label("48000 Hz"); + gtk_signal_connect(GTK_OBJECT(item), "activate", GTK_SIGNAL_FUNC(resampling_rate_cb), (gpointer)48000); + gtk_widget_show(item); + gtk_menu_append(GTK_MENU(menu), item); + + gtk_option_menu_set_menu(GTK_OPTION_MENU(widget), menu); + + switch(cfg->output_rate) { + default: + DEBUG(("[crossfade] plugin_configure: WARNING: invalid output sample rate (%d)!\n", cfg->output_rate)); + DEBUG(("[crossfade] plugin_configure: ... using default of 44100\n")); + cfg->output_rate = 44100; + case 44100: gtk_option_menu_set_history(GTK_OPTION_MENU(widget), 0); break; + case 48000: gtk_option_menu_set_history(GTK_OPTION_MENU(widget), 1); break; + } + } + + /* output: resampling quality (libsamplerate setting) */ +#ifdef HAVE_LIBSAMPLERATE + if((widget = lookup_widget(config_win, "resampling_quality_optionmenu"))) { + GtkWidget *menu = gtk_menu_new(); + GtkWidget *item; + + GtkTooltips *tooltips = (GtkTooltips *)gtk_object_get_data(GTK_OBJECT(config_win), "tooltips"); + + int converter_type; + const char *name, *description; + for(converter_type = 0; (name = src_get_name(converter_type)); + converter_type++) { + description = src_get_description(converter_type); + + item = gtk_menu_item_new_with_label(name); + gtk_tooltips_set_tip(tooltips, item, description, NULL); + + gtk_signal_connect(GTK_OBJECT(item), "activate", + GTK_SIGNAL_FUNC(resampling_quality_cb), (gpointer)converter_type); + gtk_widget_show(item); + gtk_menu_append(GTK_MENU(menu), item); + } + + gtk_option_menu_set_menu (GTK_OPTION_MENU(widget), menu); + gtk_option_menu_set_history(GTK_OPTION_MENU(widget), cfg->output_quality); + } +#else + HIDE("resampling_quality_hbox"); + HIDE("resampling_quality_optionmenu"); +#endif + + /* output method: plugin */ + xfade_load_plugin_config(cfg->op_config_string, cfg->op_name, &op_config); + SET_TOGGLE ("op_throttle_check", op_config.throttle_enable); + SET_TOGGLE ("op_maxblock_check", op_config.max_write_enable); + SET_SPIN ("op_maxblock_spin", op_config.max_write_len); + SET_SENSITIVE("op_maxblock_spin", op_config.max_write_enable); + SET_TOGGLE ("op_forcereopen_check", op_config.force_reopen); + + if((widget = lookup_widget(config_win, "op_plugin_optionmenu"))) { + OutputPlugin *op = NULL; + if((op_index = scan_plugins(widget, cfg->op_name)) >= 0) { + gtk_option_menu_set_history(GTK_OPTION_MENU(widget), op_index); + op = g_list_nth_data(get_output_list(), op_index); /* XMMS */ + } + SET_SENSITIVE("op_configure_button", op && (op->configure != NULL)); + SET_SENSITIVE("op_about_button", op && (op->about != NULL)); + } + + /* output method: none */ + + /* effects: pre-mixing effect plugin */ + if((widget = lookup_widget(config_win, "ep_plugin_optionmenu"))) { + EffectPlugin *ep = NULL; + if((ep_index = scan_effect_plugins(widget, cfg->ep_name)) >= 0) { + gtk_option_menu_set_history(GTK_OPTION_MENU(widget), ep_index); + ep = g_list_nth_data(get_effect_list(), ep_index); /* XMMS */ + } + SET_SENSITIVE("ep_configure_button", ep && (ep->configure != NULL)); + SET_SENSITIVE("ep_about_button", ep && (ep->about != NULL)); + SET_TOGGLE ("ep_enable_check", cfg->ep_enable); + } + + /* effects: volume normalizer */ + SET_TOGGLE("volnorm_enable_check", cfg->volnorm_enable); + SET_TOGGLE("volnorm_quantaudio_check", cfg->volnorm_use_qa); + SET_SPIN ("volnorm_target_spin", cfg->volnorm_target); + + check_effects_dependencies(); + + /* crossfader */ + create_crossfader_config_menu(); + + if((cfg->xf_index < 0) || (cfg->xf_index >= MAX_FADE_CONFIGS)) { + DEBUG(("[crossfade] plugin_configure: crossfade index out of range (%d)!\n", cfg->xf_index)); + cfg->xf_index = CLAMP(cfg->xf_index, 0, MAX_FADE_CONFIGS); + } + + check_crossfader_dependencies(ANY); + + /* gap killer */ + SET_TOGGLE ("lgap_enable_check", cfg->gap_lead_enable); + SET_SPIN ("lgap_length_spin", cfg->gap_lead_len_ms); + SET_SPIN ("lgap_level_spin", cfg->gap_lead_level); + SET_TOGGLE ("tgap_lock_check", cfg->gap_trail_locked); + SET_TOGGLE ("tgap_enable_check", cfg->gap_trail_enable); + SET_SPIN ("tgap_length_spin", cfg->gap_trail_len_ms); + SET_SPIN ("tgap_level_spin", cfg->gap_trail_level); + SET_TOGGLE ("gadv_crossing_check", cfg->gap_crossing); + + check_gapkiller_dependencies(); + + /* misc */ + SET_TOGGLE("debug_stderr_check", cfg->enable_debug); + SET_TOGGLE("debug_monitor_check", cfg->enable_monitor); + SET_TOGGLE("mixopt_enable_check", cfg->enable_mixer); + SET_TOGGLE("mixopt_reverse_check", cfg->mixer_reverse); + SET_TOGGLE("mixopt_software_check", cfg->mixer_software); + SET_SPIN ("moth_songchange_spin", cfg->songchange_timeout); + SET_SPIN ("moth_preload_spin", cfg->preload_size_ms); + SET_TOGGLE("noxf_album_check", cfg->album_detection); + SET_TOGGLE("noxf_samefile_check", cfg->album_detection); + SET_TOGGLE("moth_httpworkaround_check", cfg->enable_http_workaround); + SET_TOGGLE("moth_opmaxused_check", cfg->enable_op_max_used); + SET_SPIN ("moth_opmaxused_spin", cfg->op_max_used_ms); + SET_TOGGLE("moth_outputkeepopened_check", cfg->output_keep_opened); + + check_misc_dependencies(); + + /* presets */ + if((set_wgt = lookup_widget(config_win, "presets_list_list"))) { + GList *item; + + for(item = config->presets; item; item = g_list_next(item)) { + gchar *name = (gchar *)item->data; + gchar *text[] = {name, "Default", "No"}; + gtk_clist_append(GTK_CLIST(set_wgt), text); + } + } + + /* show window near mouse pointer */ + gtk_window_set_position(GTK_WINDOW(config_win), GTK_WIN_POS_MOUSE); + gtk_widget_show(config_win); + } + else + /* bring window to front */ + gdk_window_raise(config_win->window); +} + +void xfade_about() +{ + static GtkWidget *dialog; + + if (dialog != NULL) + return; + + dialog = xmms_show_message( + _("About crossfade"), + _("Audacious crossfading plugin\n" + "Code adapted for Audacious usage by Tony Vroon from:\n" + "XMMS Crossfade Plugin "VERSION"\n" + "Copyright (C) 2000-2004 Peter Eisenlohr \n" + "\n" + "based on the original OSS Output Plugin Copyright (C) 1998-2000\n" + "Peter Alm, Mikael Alm, Olle Hallnas, Thomas Nilsson and 4Front Technologies\n" + "\n" + "This program is free software; you can redistribute it and/or modify\n" + "it under the terms of the GNU General Public License as published by\n" + "the Free Software Foundation; either version 2 of the License, or\n" + "(at your option) any later version.\n" + "\n" + "This program is distributed in the hope that it will be useful,\n" + "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" + "GNU General Public License for more details.\n" + "\n" + "You should have received a copy of the GNU General Public License\n" + "along with this program; if not, write to the Free Software\n" + "Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,\n" + "USA."),_("OK"), FALSE, NULL, NULL); + + gtk_signal_connect(GTK_OBJECT(dialog), "destroy", + GTK_SIGNAL_FUNC(gtk_widget_destroyed), &dialog); +} diff -ruN audacious-1.0.0.org/Plugins/Output/crossfade/configure.h audacious-1.0.0/Plugins/Output/crossfade/configure.h --- audacious-1.0.0.org/Plugins/Output/crossfade/configure.h 1970-01-01 09:00:00.000000000 +0900 +++ audacious-1.0.0/Plugins/Output/crossfade/configure.h 2006-04-17 19:48:34.000000000 +0900 @@ -0,0 +1,55 @@ +/* + * XMMS Crossfade Plugin + * Copyright (C) 2000-2004 Peter Eisenlohr + * + * based on the original OSS Output Plugin + * Copyright (C) 1998-2000 Peter Alm, Mikael Alm, Olle Hallnas, Thomas Nilsson and 4Front Technologies + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#ifndef _CONFIGURE_H_ +#define _CONFIGURE_H_ + +#include "crossfade.h" + +void xfade_load_config(); +void xfade_save_config(); +void xfade_free_config(); + +void xfade_load_plugin_config(gchar *config_string, gchar *plugin_name, + plugin_config_t *plugin_config); +void xfade_save_plugin_config(gchar **config_string, gchar *plugin_name, + plugin_config_t *plugin_confg); + +/* some helper functions */ +gint xfade_mix_size_ms(config_t *cfg); + +gint xfade_cfg_fadeout_len (fade_config_t *fc); +gint xfade_cfg_fadeout_volume(fade_config_t *fc); +gint xfade_cfg_offset (fade_config_t *fc); +gint xfade_cfg_fadein_len (fade_config_t *fc); +gint xfade_cfg_fadein_volume (fade_config_t *fc); + +gboolean xfade_cfg_gap_trail_enable(config_t *cfg); +gint xfade_cfg_gap_trail_len (config_t *cfg); +gint xfade_cfg_gap_trail_level (config_t *cfg); + +/* xmms callback prototypes */ +void xfade_about (); +void xfade_configure(); + +#endif /* _CONFIGURE_H_ */ diff -ruN audacious-1.0.0.org/Plugins/Output/crossfade/convert.c audacious-1.0.0/Plugins/Output/crossfade/convert.c --- audacious-1.0.0.org/Plugins/Output/crossfade/convert.c 1970-01-01 09:00:00.000000000 +0900 +++ audacious-1.0.0/Plugins/Output/crossfade/convert.c 2006-04-17 19:48:34.000000000 +0900 @@ -0,0 +1,132 @@ +/* + * XMMS Crossfade Plugin + * Copyright (C) 2000-2004 Peter Eisenlohr + * + * based on the original OSS Output Plugin + * Copyright (C) 1998-2000 Peter Alm, Mikael Alm, Olle Hallnas, Thomas Nilsson and 4Front Technologies + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +/* + * Convert to standard (16bit-le stereo) + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include "convert.h" + +void +convert_init(convert_context_t *cc) +{ + memset(cc, 0, sizeof(*cc)); +} + +gint +convert_flow(convert_context_t *cc, + gpointer *buffer, + gint length, + format_t *format) +{ + gpointer data; + gint size; + gint sample_size, sample_count; + gint element_count; + gint16 *out, s; + + if(!cc) return 0; + if(length <= 0) return 0; + + /* calculate sample count */ + sample_size = format->is_8bit ? 1 : 2; + sample_count = length / sample_size; + if(sample_count == 0) return 0; + + /* calculate buffer size */ + size = sample_count * 2; + if(format->nch == 1) size *= 2; + + /* resize buffer if necessary */ + if(!cc->data || (size > cc->size)) { + if(!(data = g_realloc(cc->data, size))) { + DEBUG(("[crossfade] convert: g_realloc(%d) failed!\n", size)); + return 0; + } + cc->data = data; + cc->size = size; + } + + /* calculate number of stereo samples */ + element_count = sample_count; + if(format->nch == 2) element_count /= 2; + +#define CONVERT(x) \ + if(format->nch == 1) { \ + while(sample_count--) { s = x; *out++ = s; *out++ = s; } \ + } \ + else { \ + while(sample_count--) *out++ = x; \ + } + + out = cc->data; + if(format->is_8bit) { + if(format->is_unsigned) { + guint8 *in = *buffer; + CONVERT((gint16)(*in++ ^ 128) << 8); + } + else { + gint8 *in = *buffer; + CONVERT((gint16)*in++ << 8); + } + } + else { + if(format->is_unsigned) { + guint16 *in = *buffer; + if(format->is_swapped) { + CONVERT((gint16)(((*in & 0x00ff) << 8) | (*in >> 8)) ^ 32768); in++; + } else { + CONVERT((gint16)*in++ ^ 32768); + } + } + else { + gint16 *in = *buffer; + if(format->is_swapped) { + CONVERT(((*in & 0x00ff) << 8) | (*in >> 8)); in++; + } else { + if(format->nch == 1) { + CONVERT(*in++); + } + else memcpy(out, in, size); + } + } + } + *buffer = cc->data; + + return size; +} + +void +convert_free(convert_context_t *cc) +{ + if(cc->data) { + g_free(cc->data); + cc->data = NULL; + } +} + diff -ruN audacious-1.0.0.org/Plugins/Output/crossfade/convert.h audacious-1.0.0/Plugins/Output/crossfade/convert.h --- audacious-1.0.0.org/Plugins/Output/crossfade/convert.h 1970-01-01 09:00:00.000000000 +0900 +++ audacious-1.0.0/Plugins/Output/crossfade/convert.h 2006-04-17 19:48:34.000000000 +0900 @@ -0,0 +1,48 @@ +/* + * XMMS Crossfade Plugin + * Copyright (C) 2000-2004 Peter Eisenlohr + * + * based on the original OSS Output Plugin + * Copyright (C) 1998-2000 Peter Alm, Mikael Alm, Olle Hallnas, Thomas Nilsson and 4Front Technologies + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +/* + * Convert to standard (44100/16bit-le/stereo) + */ + +#ifndef __CONVERT_H__ +#define __CONVERT_H__ + +#include "crossfade.h" +#include "format.h" + +typedef struct +{ + gpointer data; + gint size; +} +convert_context_t; + +void convert_init(convert_context_t *cc); +gint convert_flow(convert_context_t *cc, + gpointer *buffer, + gint length, + format_t *format); +void convert_free(convert_context_t *cc); + +#endif /* _CONVERT_H_ */ diff -ruN audacious-1.0.0.org/Plugins/Output/crossfade/crossfade.c audacious-1.0.0/Plugins/Output/crossfade/crossfade.c --- audacious-1.0.0.org/Plugins/Output/crossfade/crossfade.c 1970-01-01 09:00:00.000000000 +0900 +++ audacious-1.0.0/Plugins/Output/crossfade/crossfade.c 2006-04-17 19:48:34.000000000 +0900 @@ -0,0 +1,2243 @@ +/* + * XMMS Crossfade Plugin + * Copyright (C) 2000-2004 Peter Eisenlohr + * + * based on the original OSS Output Plugin + * Copyright (C) 1998-2000 Peter Alm, Mikael Alm, Olle Hallnas, Thomas Nilsson and 4Front Technologies + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "crossfade.h" +#include "configure.h" +#include "interface-2.0.h" +#include "monitor.h" +#include "support.h" +#include "effect.h" +#include "format.h" +#include "convert.h" +#include "rate.h" +#include "volume.h" +#include "timing.h" + +#include +#include +#include +#include + +#ifdef HAVE_DLFCN_H +# include +#endif +#include +#include +#include +#include +#include + + +#undef DEBUG_HARDCORE + +/* output plugin callback prototypes */ +static void xfade_init (); +static void xfade_set_volume (int l, int r); +static void xfade_get_volume (int *l, int *r); +static gint xfade_open_audio (AFormat fmt, int rate, int nch); +static void xfade_write_audio (void *ptr, int length); +static void xfade_close_audio (); +static void xfade_flush (int time); +static void xfade_pause (short paused); +static gint xfade_buffer_free (); +static gint xfade_buffer_playing(); +static gint xfade_written_time (); +static gint xfade_output_time (); + +/* output plugin callback table */ +static OutputPlugin xfade_op = +{ + NULL, + NULL, + "Crossfade Plugin " VERSION, + xfade_init, + NULL, + xfade_about, + xfade_configure, + xfade_get_volume, + xfade_set_volume, + xfade_open_audio, + xfade_write_audio, + xfade_close_audio, + xfade_flush, + xfade_pause, + xfade_buffer_free, + xfade_buffer_playing, + xfade_output_time, + xfade_written_time, + NULL /* we do not support effects on crossfade, too fragile */ +}; + +/* internal prototypes */ +static void load_symbols (); +static void output_list_hack(); +static gint open_output (); +static void buffer_reset (buffer_t *buf, config_t *cfg); +static void *buffer_thread_f (void *arg); +static void sync_output (); + +/* special XMMS symbols (dynamically looked up, see xfade_init) */ +static gboolean (*input_stopped_for_restart)() = NULL; /* XMMS */ + +void fini() __attribute__((destructor)); + +/* local variables */ +static gint session_id; +static gboolean realtime; +static gboolean is_http; + +static gint64 streampos; /* position within current song (input bps) */ +static gboolean playing; + gboolean opened; /* TRUE between open_ and close_audio() */ +static gboolean paused; /* TRUE: no playback (but still filling buffer) */ +static gboolean stopped; /* TRUE: stop buffer thread ASAP */ +static gboolean eop; /* TRUE: wait until buffer is empty then sync() */ + +static plugin_config_t the_op_config = DEFAULT_OP_CONFIG; + OutputPlugin *the_op = NULL; + gint the_rate = 44100; + +static gboolean input_playing = FALSE; + + gboolean output_opened = FALSE; +static gint output_flush_time = 0; + gint output_offset = 0; +static gint64 output_written = 0; + gint64 output_streampos = 0; + +static gboolean finishing = FALSE; /* TRUE after fini() has been called */ + +static gchar zero_4k[4096]; + +#ifdef VOLUME_NORMALIZER +static quantaudio_t qa, last_qa; +static gboolean have_qa = FALSE, have_last_qa = FALSE; +#endif + + +/* + * Available fade configs: + * + * fc_start: First song, only in_len and in_level are used + * fc_xfade: Automatic crossfade at end of song + * fc_album: Like xfade but for consecutive songs of the same album + * fc_manual: Manual crossfade (triggered by Next or Prev) + * fc_stop: Last song, only out_len and out_level are used + * fc_eop: Last song, only out_len and out_level are used + * + * NOTE: As of version 0.2 of xmms-crossfade, + * only xfade and manual are implemented. + * + * With version 0.2.3, fc_album has been added. + * + * With 0.2.4, all configs are implemented: + * + * Available parameters: + * + * | start | xfade | manual | album | stop | eop + * ------------+-------+-------+--------+-------+------+------ + * in_len | yes | yes | yes | no | no | no + * in_volume | yes | yes | yes | no | no | no + * offset | no | yes | yes | no | +yes | +yes + * out_len | no | yes | yes | no | yes | yes + * out_volume | no | yes | yes | no | yes | yes + * flush (*) | no | no | yes | no | yes | no + * ------------+-------+-------+--------+-------+------+------ + * + * Parameters marked with (*) are not configureable by the user + * + * The offset parameters for 'stop' and 'eop' is used to store the + * length of the additional silence to be added. It may be >= 0 only. + * + */ + +static struct timeval last_close; +static struct timeval last_write; + +static gchar *last_filename = NULL; + +static format_t in_format; +static format_t out_format; + +static buffer_t the_buffer; + buffer_t *buffer = &the_buffer; + +GStaticMutex buffer_mutex = G_STATIC_MUTEX_INIT; +static GThread *buffer_thread; + +static effect_context_t effect_context; +static convert_context_t convert_context; +static rate_context_t rate_context; +static volume_context_t volume_context; + +static config_t the_config; + config_t *config = &the_config; + config_t config_default = CONFIG_DEFAULT; + +static fade_config_t *fade_config = NULL; + + +/* this is the entry point for XMMS */ +OutputPlugin *get_oplugin_info() +{ + return &xfade_op; +} + +OutputPlugin *get_crossfade_oplugin_info() +{ + return &xfade_op; +} + +static gint +effect_list_f(gconstpointer a, gconstpointer b) +{ + EffectPlugin *ep = (EffectPlugin *)a; + gchar *name = (gchar *)b; + + return strcmp(g_basename(ep->filename), name); +} + +static gboolean +open_output_f(gpointer data) +{ + DEBUG(("[crossfade] open_output_f: pid=%d\n", getpid())); + open_output(); + return FALSE; /* FALSE = 'do not call me again' */ +} + +void +xfade_realize_ep_config() +{ + GList *list, *element; + EffectPlugin *ep = NULL; + + /* find effect plugin */ + if(config->ep_enable && config->ep_name && (list = get_effect_list())) /* XMMS */ + if((element = g_list_find_custom(list, config->ep_name, effect_list_f))) + ep = element->data; + + effect_set_plugin(&effect_context, ep); +} + +void +xfade_realize_config() /* also called by xfade_init() */ +{ + /* realize effect plugin config */ + xfade_realize_ep_config(); + +#ifdef VOLUME_NORMALIZER + /* update volume normalizer target rms */ + volume_set_target_rms(&volume_context, config->volnorm_target); +#endif + + /* 0.3.0: keep device opened */ + if(config->output_keep_opened && !output_opened) { + DEBUG(("[crossfade] realize_config: keeping output opened...\n")); + + /* HACK: avoid rate converter complaing about illegal input rate */ + /* if(!rate_context.valid || !rate_context.in_rate) + rate_config(&rate_context, out_format.rate, out_format.rate); */ + + /* 0.3.1: HACK: this will make sure that we start playing silence after startup */ + gettimeofday(&last_close, NULL); + + /* 0.3.1: HACK: Somehow, if we open output here at XMMS startup, there + will be leftover filedescriptors later when closing output again. + Opening output in a timeout function seems to work around this... */ + DEBUG(("[crossfade] realize_config: adding timeout (pid=%d)\n", getpid())); + g_timeout_add(0, open_output_f, NULL); + } +} + +static gint +output_list_f(gconstpointer a, gconstpointer b) +{ + OutputPlugin *op = (OutputPlugin *)a; + gchar *name = (gchar *)b; + + return strcmp(g_basename(op->filename), name); +} + +static OutputPlugin * +find_output() +{ + GList *list, *element; + OutputPlugin *op = NULL; + + /* find output plugin */ + if(config->op_name && (list = get_output_list())) /* XMMS */ + if((element = g_list_find_custom(list, config->op_name, output_list_f))) + op = element->data; + + if(op == &xfade_op) { + DEBUG(("[crossfade] find_output: can't use myself as output plugin!\n")); + op = NULL; + } + else if(!op) { + DEBUG(("[crossfade] find_output: could not find output plugin \"%s\"\n", + config->op_name ? config->op_name : "#NULL#")); + } + else /* ok, we have a plugin. last, get its compatibility options */ + xfade_load_plugin_config(config->op_config_string, config->op_name, + &the_op_config); + + return op; +} + +static gint +open_output() +{ + /* sanity check */ + if(output_opened) + DEBUG(("[crossfade] open_output: WARNING: output_opened=TRUE!\n")); + + /* reset output_* */ + output_opened = FALSE; + output_flush_time = 0; + output_offset = 0; + output_written = 0; + output_streampos = 0; + + /* get output plugin (this will also init the_op_config) */ + if(!(the_op = find_output())) { + DEBUG(("[crossfade] open_output: could not find any output!\n")); + return -1; + } + + /* print output plugin info */ + DEBUG(("[crossfade] open_output: using \"%s\" for output", + the_op->description ? the_op->description : "#NULL#")); + + if(realtime) + DEBUG((" (RT)")); + + if(the_op_config.throttle_enable) + DEBUG((realtime + ? " (throttled (disabled with RT))" + : " (throttled)")); + + if(the_op_config.max_write_enable) + DEBUG((" (max_write=%d)", the_op_config.max_write_len)); + + DEBUG(("\n")); + + /* setup sample rate (note that OUTPUT_RATE is #defined as the_rate) */ + the_rate = config->output_rate; + + /* setup out_format. use host byte order for easy math */ + setup_format(FMT_S16_NE, OUTPUT_RATE, OUTPUT_NCH, &out_format); + + /* force re-config of rate converter */ + if(in_format.rate == 0) /* this happens with keep_output_opened */ + rate_config(&rate_context, out_format.rate, out_format.rate, config->output_quality); + else + rate_config(&rate_context, in_format.rate, out_format.rate, config->output_quality); + + /* open plugin */ + if(!the_op->open_audio(out_format.fmt, out_format.rate, out_format.nch)) { + DEBUG(("[crossfade] open_output: open_audio() failed!\n")); + the_op = NULL; + return -1; + } + + /* clear buffer struct */ + memset(buffer, 0, sizeof(*buffer)); + + /* calculate buffer size */ + buffer->mix_size = MS2B(xfade_mix_size_ms(config)) & -4; + buffer->sync_size = MS2B(config->sync_size_ms) & -4; + buffer->preload_size = MS2B(config->preload_size_ms) & -4; + + buffer->size = (buffer->mix_size + /* mixing area */ + buffer->sync_size + /* additional sync */ + buffer->preload_size); /* preload */ + + DEBUG(("[crossfade] open_output: buffer: size=%d (%d+%d+%d=%d ms) (%d Hz)\n", + buffer->size, + B2MS(buffer->mix_size), + B2MS(buffer->preload_size), + B2MS(buffer->sync_size), + B2MS(buffer->size), + the_rate)); + + /* allocate buffer */ + if(!(buffer->data = g_malloc0(buffer->size))) { + DEBUG(("[crossfade] open_output: error allocating buffer!\n")); + the_op->close_audio(); + the_op = NULL; + return -1; + } + + /* reset buffer */ + buffer_reset(buffer, config); + + /* make sure stopped is TRUE -- otherwise the buffer thread would + * stop again immediatelly after it has been started. */ + stopped = FALSE; + + /* create and run buffer thread */ + buffer_thread = g_thread_create((GThreadFunc)buffer_thread_f, NULL, FALSE, NULL); + if (buffer_thread == NULL) { + PERROR("[crossfade] open_output: g_thread_create()"); + g_free(buffer->data); + the_op->close_audio(); + the_op = NULL; + return -1; + } + + /* start updating monitor */ + xfade_start_monitor(); + + /* done */ + output_opened = TRUE; + return 0; +} + +static void +xfade_init() +{ + /* load config */ + memset(config, 0, sizeof(*config)); + *config = config_default; + xfade_load_config(); + + /* set default strings if there is no existing config */ + if(!config->op_config_string) + config->op_config_string = g_strdup(DEFAULT_OP_CONFIG_STRING); + if(!config->op_name) + config->op_name = g_strdup(DEFAULT_OP_NAME); + + /* check for realtime priority, it needs some special attention */ + realtime = xmms_check_realtime_priority(); + + /* show monitor win if enabled in config */ + xfade_check_monitor_win(); + + /* init contexts */ + effect_init(&effect_context, NULL); + convert_init(&convert_context); + rate_init(&rate_context); + volume_init(&volume_context); + + /* reset */ + stopped = FALSE; + + /* find current output plugin early so that volume control works + * even if playback has not started yet. */ + if(!(the_op = find_output())) + DEBUG(("[crossfade] init: could not find any output!\n")); + + /* get xmms controlsocket session id */ + session_id = ctrlsocket_get_session_id(); + + /* load any dynamic linked symbols */ + load_symbols(); + + /* HACK: make sure we are at the beginning of XMMS' output plugin list */ + output_list_hack(); + + /* realize config -- will also setup the pre-mixing effect plugin */ + xfade_realize_config(); +} + +static void load_symbols() +{ +#ifdef HAVE_DLFCN_H + void *handle; + char *error; + + /* open ourselves (that is, the XMMS binary) */ + handle = dlopen(NULL, RTLD_NOW); + if(!handle) { + DEBUG(("[crossfade] init: dlopen(NULL) failed!\n")); + return; + } + + /* check for XMMS patches */ + DEBUG(("[crossfade] load_symbols: input_stopped_for_restart:")); + input_stopped_for_restart = dlsym(handle, "input_stopped_for_restart"); + DEBUG((!(error = dlerror()) ? " found\n" : " not found\n")); + + DEBUG(("[crossfade] load_symbols: playlist_get_info_going:")); + xmms_playlist_get_info_going = dlsym(handle, "playlist_get_info_going"); + DEBUG((!(error = dlerror()) ? " found\n" : " not found\n")); + + DEBUG(("[crossfade] load_symbols: is_quitting:")); + xmms_is_quitting = dlsym(handle, "is_quitting"); + DEBUG((!(error = dlerror()) ? " found\n" : " not found\n")); + + dlclose(handle); +#endif +} + +/* + HACK: Try to move ourselves to the beginning of XMMS output plugin list, + so that we will be freed first when XMMS is quitting. This way, we + avoid the segfault when using ALSA as the output plugin. +*/ +static void output_list_hack() +{ + GList *output_list = get_output_list(); + + int i0 = g_list_index(output_list, &xfade_op), i1; + + GList *first = g_list_first(output_list); + GList *xfade = g_list_find (output_list, &xfade_op); + xfade->data = first->data; + first->data = &xfade_op; + + i1 = g_list_index(output_list, &xfade_op); + if (i0 != i1) + DEBUG(("[crossfade] output_list_hack: crossfade moved from index %d to %d\n", i0, i1)); +} + +void fini() +{ + DEBUG(("[crossfade]\n")); + DEBUG(("[crossfade] fini: cleanup:\n")); + + /* wait for buffer_thread to stop */ + g_static_mutex_lock(&buffer_mutex); + + /* reset last_close so that the buffer thread will + * not unnecessarily wait for a timeout. */ + memset(&last_close, 0, sizeof(last_close)); + + /* HACK: tell buffer thread not to close output by itself */ + finishing = TRUE; + paused = FALSE; + + /* wait for buffer thread to clean up and terminate */ + DEBUG(("[crossfade] fini: cleanup: waiting for buffer thread...\n")); + while(output_opened && finishing) { + g_static_mutex_unlock(&buffer_mutex); + xmms_usleep(10000); + g_static_mutex_lock(&buffer_mutex); + } + DEBUG(("[crossfade] fini: cleanup: waiting for buffer thread... done\n")); + + /* HACK: 0.3.5: close output plugin from THIS thread */ +#if 0 + if(output_opened) { + DEBUG(("[crossfade] fini: cleanup: closing audio...\n")); + if(the_op->close_audio) the_op->close_audio(); + DEBUG(("[crossfade] fini: cleanup: closing audio... done\n")); + g_free(buffer->data); + output_opened = FALSE; + } +#endif + DEBUG(("[crossfade] fini: cleanup: done\n")); + + g_static_mutex_unlock (&buffer_mutex); + + /* free contexts */ + volume_free(&volume_context); + rate_free(&rate_context); + convert_free(&convert_context); + effect_free(&effect_context); + + /* save configuration (required only for mixer_vol_*) */ + xfade_save_config(); + + /* free resources */ + if(config->op_name) g_free(config->op_name); + xfade_free_config(); + if(last_filename) g_free(last_filename); + + DEBUG(("[crossfade] fini: done.\n")); +} + +void +xfade_get_volume(int *l, int *r) +{ + if(config->mixer_software) { + if(config->mixer_reverse) { + *l = config->mixer_vol_right; + *r = config->mixer_vol_left; + } else { + *l = config->mixer_vol_left; + *r = config->mixer_vol_right; + } + } + else { + if(the_op && the_op->get_volume) { + if(config->mixer_reverse) + the_op->get_volume(r, l); + else + the_op->get_volume(l, r); + } + } +} + +void +xfade_set_volume(int l, int r) +{ + if(!config->enable_mixer) return; + if(config->mixer_software) { + if(config->mixer_reverse) { + config->mixer_vol_right = l; + config->mixer_vol_left = r; + } + else { + config->mixer_vol_right = r; + config->mixer_vol_left = l; + } + } + else { + if(the_op && the_op->set_volume) { + if(config->mixer_reverse) + the_op->set_volume(r, l); + else + the_op->set_volume(l, r); + } + } +} + +/*** buffer stuff ***********************************************************/ + +static void +buffer_mfg_reset(buffer_t *buf, config_t *cfg) +{ + buf->mix = 0; + buf->fade = 0; + buf->gap = (cfg->gap_lead_enable ? MS2B(cfg->gap_lead_len_ms) & -4 : 0); + buf->gap_len = buf->gap; + buf->gap_level = cfg->gap_lead_level; + buf->gap_killed = 0; +} + +static void +buffer_reset(buffer_t *buf, config_t *cfg) +{ + buffer_mfg_reset(buf, cfg); + + buf->rd_index = 0; + buf->used = 0; + buf->preload = buf->preload_size; + + buf->silence = 0; + buf->silence_len = 0; + buf->reopen = -1; + buf->pause = -1; +} + +/****************************************************************************/ + +static void +xfade_apply_fade_config(fade_config_t *fc) +{ + gint avail, out_len, in_len, offset, skip; + gint index, length, fade, n; + gfloat out_scale, in_scale; + gboolean out_len_clipped = FALSE, offset_clipped = FALSE; + + /* Overwrites mix and fade; may add silence */ + + /* + * Example 1: offset < 0 --> mix streams together + * Example 2: offset > 0 --> insert pause between streams + * + * |----- out_len -----| * |out_len| + * | | * | | + * ~~~~~-_ /T~~~~~~~T~~ * ~~~~~\ | /T~~ + * ~-_ / | | * \ | / | + * ~-_/ | | * \ | / | + * /~-_| | * \ | / | + * / T-_ | * \ | / | + * / | ~-_ | * \ | / | + * _________/______|_____~-|__ * ___________\__________/______|__ + * |in_len| | * | |in_len| + * |<-- offset ---| * |offset-->| + * + * a) avail: max(0, used - preload) + * b) out_len: 0 .. avail + * c) in_len: 0 .. # + * d) offset: -avail .. buffer->mix_size - out_size + * e) skip: min(used, preload) + * + */ + + out_scale = 1.0f - (gfloat)xfade_cfg_fadeout_volume(fc) / 100.0f; + in_scale = 1.0f - (gfloat)xfade_cfg_fadein_volume (fc) / 100.0f; + + /* rules (see above) */ + /* a */ + avail = buffer->used - buffer->preload_size; + if(avail < 0) avail = 0; + + /* b */ + out_len = MS2B(xfade_cfg_fadeout_len(fc)) & -4; + if(out_len < 0) out_len = 0; + if(out_len > avail) { + DEBUG(("[crossfade] apply_fade_config: WARNING: clipping out_len (%d -> %d)!\n", + B2MS(out_len), B2MS(avail))); + out_len = avail; + out_len_clipped = TRUE; + } + + /* c */ + in_len = MS2B(xfade_cfg_fadein_len(fc)) & -4; + if(in_len < 0) in_len = 0; + + /* d */ + offset = MS2B(xfade_cfg_offset(fc)) & -4; + if(offset < -avail) { + DEBUG(("[crossfade] apply_fade_config: WARNING: clipping offset (%d -> %d)!\n", + B2MS(offset), -B2MS(avail))); + offset = -avail; + offset_clipped = TRUE; + } + if(offset > (buffer->mix_size - out_len)) + offset = buffer->mix_size - out_len; + + /* e */ + skip = buffer->preload_size; + if(skip > buffer->used) skip = buffer->used; + + /* cut off rest of stream (decreases latency on manual songchange) */ + if(fc->flush) { + gint cutoff = avail - MAX(out_len, -offset); /* MAX() -> glib.h */ + if(cutoff > 0) { + DEBUG(("[crossfade] apply_fade_config: %d ms flushed\n", B2MS(cutoff))); + buffer->used -= cutoff; + avail -= cutoff; + } + + /* make sure there is no pending silence */ + buffer->silence = 0; + buffer->silence_len = 0; + } + + /* begin modifying buffer at index */ + index = (buffer->rd_index + buffer->used - out_len) % buffer->size; + + /* fade out (modifies buffer directly) */ + fade = 0; + length = out_len; + while(length > 0) { + gint16 *p = (gint16 *)((gchar *)(buffer->data) + index); //yaz + gint blen = buffer->size - index; + if(blen > length) blen = length; + + for(n = blen/4; n > 0; n--) { + gfloat factor = 1.0f - (((gfloat)fade / out_len) * out_scale); + *p = (gfloat)*p * factor; p++; + *p = (gfloat)*p * factor; p++; + fade += 4; + } + + index = (index + blen) % buffer->size; + length -= blen; + } + + /* Initialize fadein. Note that the actual fading / mixing will be done + * on-the-fly when audio data is received by xfade_write_audio() */ + + /* start fading in */ + if(in_len > 0) { + buffer->fade = in_len; + buffer->fade_len = in_len; + buffer->fade_scale = in_scale; + } + else + buffer->fade = 0; + + /* start mixing */ + if(offset < 0) { + length = -offset; + buffer->mix = length; + buffer->used -= length; + } + else + buffer->mix = 0; + + /* start silence if applicable (will be applied in buffer_thread_f) */ + if(offset > 0) { + if((buffer->silence > 0) || (buffer->silence_len > 0)) + DEBUG(("[crossfade] apply_config: WARNING: silence in progress (%d/%d ms)\n", + B2MS(buffer->silence), B2MS(buffer->silence_len))); + + buffer->silence = buffer->used; + buffer->silence_len = offset; + } + + /* done */ + DEBUG(("[crossfade] apply_fade_config: avail=%d out=%d in=%d offset=%d skip=%d\n", + B2MS(avail), B2MS(out_len), B2MS(in_len), B2MS(offset), B2MS(skip))); +} + +static gint +extract_track(gchar *name) +{ + /* Remove all but numbers. + * Will not work if a filename has number in the title, like "track-03-U2.mp3" + * Ideally, should look into id3 track entry and fallback to filename + * */ + + gchar temp[8]; + int t = 0 ; + + memset(temp,0,sizeof(temp)); + while(*name != '\0' && t < sizeof(temp)) { + if(strcmp(name,"mp3") == 0) + break; + if(isdigit(*name)) + temp[t++] = *name; + name++; + } + return atoi(temp); +} + +static gint +album_match(gchar *old, gchar *new) +{ + gchar *old_dir, *new_dir; + gboolean same_dir; + gint old_track = 0, new_track = 0; + + if(!old || !new) return 0; + + old_dir = g_dirname(old); + new_dir = g_dirname(new); + same_dir = !strcmp(old_dir, new_dir); + g_free(old_dir); + g_free(new_dir); + + if(!same_dir) { + DEBUG(("[crossfade] album_match: " + "no match (different dirs)\n")); + return 0; + } + + old_track = extract_track(g_path_get_basename(old)); + new_track = extract_track(g_path_get_basename(new)); + + if(new_track <= 0) { + DEBUG(("[crossfade] album_match: can't parse track number:\n")); + DEBUG(("[crossfade] album_match: ... \"%s\"\n", g_basename(new))); + return 0; + } + + if((old_track < 0) || (old_track+1 != new_track)) { + DEBUG(("[crossfade] album_match: " + "no match (same dir, but non-successive (%d, %d))\n", + old_track, new_track)); + return 0; + } + + DEBUG(("[crossfade] album_match: " + "match detected (same dir, successive tracks (%d, %d))\n", + old_track, new_track)); + + return old_track; +} + +static gint +xfade_open_audio(AFormat fmt, int rate, int nch) +{ + gint pos; + gchar *file; + + struct timeval tv; + glong dt; + + DEBUG(("[crossfade]\n")); + DEBUG(("[crossfade] open_audio: XMMS-crossfade "VERSION"\n")); + +#if 0 + DEBUG(("[crossfade] open_audio: pid=%d\n", getpid())); +#endif + + /* sanity... don't do anything about it */ + if(opened) + DEBUG(("[crossfade] open_audio: WARNING: already opened!\n")); + + /* get filename */ +#ifdef HAVE_PLAYLIST_GET_FILENAME + pos = get_playlist_position(); /* XMMS */ + file = playlist_get_filename(pos); /* XMMS */ +#else + pos = xmms_remote_get_playlist_pos (session_id); + file = xmms_remote_get_playlist_file(session_id, pos); +#endif + DEBUG(("[crossfade] open_audio: bname=\"%s\"\n", g_basename(file))); + + /* is this an automatic crossfade? */ + if(last_filename && (fade_config == &config->fc[FADE_CONFIG_XFADE])) { + /* check if next song is the same as the current one */ + if(config->no_xfade_if_same_file && !strcmp(last_filename, file)) { + DEBUG(("[crossfade] open_audio: same file, disabling crossfade\n")); + fade_config = &config->fc[FADE_CONFIG_ALBUM]; + } + + /* check if next song is the next song from the same album */ + else if(config->album_detection && album_match(last_filename, file)) { + gboolean use_fc_album = FALSE; + + if(xfade_cfg_gap_trail_enable(config)) { + DEBUG(("[crossfade] album_match: " + "trailing gap: length=%d/%d ms\n", + B2MS(buffer->gap_killed), B2MS(buffer->gap_len))); + + if(buffer->gap_killed < buffer->gap_len) { + DEBUG(("[crossfade] album_match: " + "trailing gap: -> no silence, probably pre-faded\n")); + use_fc_album = TRUE; + } + else { + DEBUG(("[crossfade] album_match: " + "trailing gap: -> silence, sticking to XFADE\n")); + } + } + else { + DEBUG(("[crossfade] album_match: " + "trailing gap killer disabled\n")); + use_fc_album = TRUE; + } + + if(use_fc_album) { + DEBUG(("[crossfade] album_match: " + "-> using FADE_CONFIG_ALBUM\n")); + fade_config = &config->fc[FADE_CONFIG_ALBUM]; + } + } + g_free(last_filename); + } + last_filename = g_strdup(file); + +#if 0 + /* FIXME: finish this */ + /* Check if this is a short song. */ + if(fade_config == &config->fc[FADE_CONFIG_XFADE]) { + DEBUG(("*** XFADE:\n")); + int current_length = playlist_get_current_length(); + DEBUG(("*** length=%d\n", current_length)); + if (current_length < 30*1000) + fade_config = &config->fc[FADE_CONFIG_ALBUM]; + } +#endif + +#ifdef VOLUME_NORMALIZER + /* turn off volume normalizing per default, until we get a target RMS for this song */ + volume_set_active(&volume_context, FALSE); + + /* thomas: add support for quantaudio per-song mixing info */ + if(config->volnorm_use_qa) { + /* remember timing info from last song */ + last_qa = qa; + have_last_qa = have_qa; + + /* try to read quantaudio comment from ID3v1 tag */ + have_qa = get_timing_comment(file, &qa); + if(have_qa) { + DEBUG(("[crossfade] open_audio: quantaudio comment found:\n")); + DEBUG(("[crossfade] open_audio: ... RMS=%d, mix_in=%.2f, mix_out=%.2f, length=%.2f\n", + qa.RMS, qa.mix_in, qa.mix_out, qa.length)); + volume_set_song_rms(&volume_context, qa.RMS); + volume_set_active (&volume_context, TRUE); + } + else + DEBUG(("[crossfade] open_audio: no quantaudio comment found\n")); + +#if 0 + /* configure fade_config with fadeout from last song and fadein from new song */ + if(fade_config) { + config->fc[FADE_CONFIG_TIMING].out_enable = fade_config->out_enable; + config->fc[FADE_CONFIG_TIMING].out_volume = fade_config->out_volume; + config->fc[FADE_CONFIG_TIMING].out_len_ms = fade_config->out_len_ms; + config->fc[FADE_CONFIG_TIMING].ofs_custom_ms = 0; + config->fc[FADE_CONFIG_TIMING].in_enable = fade_config->in_enable; + config->fc[FADE_CONFIG_TIMING].in_volume = fade_config->in_volume; + config->fc[FADE_CONFIG_TIMING].in_len_ms = fade_config->in_len_ms; + + if(have_last_qa && ((fade_config->config == FADE_CONFIG_XFADE) || + (fade_config->config == FADE_CONFIG_ALBUM))) { + fade_config = &config->fc[FADE_CONFIG_TIMING]; + fade_config->out_enable = TRUE; + fade_config->out_volume = 0; + fade_config->out_len_ms = (gint)((last_qa.length - last_qa.mix_out) * 1000); + fade_config->ofs_custom_ms = - fade_config->out_len_ms; /* FIXME: */ + DEBUG(("[crossfade] open_audio: CONFIG_TIMING: using last song's fadeout parameter\n")); + } + + if(have_qa && ((fade_config->config == FADE_CONFIG_XFADE) || + (fade_config->config == FADE_CONFIG_ALBUM) || + (fade_config->config == FADE_CONFIG_MANUAL))) { + fade_config = &config->fc[FADE_CONFIG_TIMING]; + fade_config->in_enable = TRUE; + fade_config->in_volume = 0; + fade_config->in_len_ms = (gint)(qa.mix_in * 1000); + DEBUG(("[crossfade] open_audio: CONFIG_TIMING: using new song's fadein parameter\n")); + } + } +#endif + } + else have_qa = FALSE; + /* thomas: end */ + + if(volume_context.active && config->volnorm_enable) + DEBUG(("[crossfade] open_audio: volume normalizer: factor=%.3f\n", + volume_context.factor)); +#endif + + /* check for HTTP streaming */ + if(config->enable_http_workaround && (0 == strncasecmp(file, "http://", 7))) { + DEBUG(("[crossfade] open_audio: HTTP underrun workaround enabled.\n")); + is_http = TRUE; + } + else + is_http = FALSE; + + /* lock buffer */ + g_static_mutex_lock(&buffer_mutex); + + /* reset writer timeout */ + gettimeofday(&last_write, NULL); + + /* calculate time since last close() (don't care about overflows at 24h) */ + if(output_opened) { + gettimeofday(&tv, NULL); + dt = (tv.tv_sec - last_close.tv_sec) * 1000 + + (tv.tv_usec - last_close.tv_usec) / 1000; + } + else + dt = 0; + + DEBUG(("[crossfade] open_audio: fmt=%s rate=%d nch=%d dt=%ld ms\n", + format_name(fmt), rate, nch, dt)); + + /* check format */ + if(setup_format(fmt, rate, nch, &in_format) < 0) { + DEBUG(("[crossfade] open_audio: format not supported!\n")); + return 0; + } + + /* (re)open the device if necessary */ + if(!output_opened) { + if(open_output()) { + DEBUG(("[crossfade] open_audio: error opening/configuring output!\n")); + g_static_mutex_unlock(&buffer_mutex); + return 0; + } + fade_config = &config->fc[FADE_CONFIG_START]; + } + + /* reset */ + streampos = 0; + playing = TRUE; + opened = TRUE; + paused = FALSE; + + /* reset mix/fade/gap */ + buffer_mfg_reset(buffer, config); + + /* enable gap killer / zero crossing only for automatic/album songchange */ + switch(fade_config->config) { + case FADE_CONFIG_XFADE: + case FADE_CONFIG_ALBUM: + break; + default:; + buffer->gap = GAP_SKIPPING_DONE; + } + + /* restart realtime throttling */ + output_written = 0; + + /* start mixing */ + switch(fade_config ? fade_config->type : -1) { + case FADE_TYPE_FLUSH: + DEBUG(("[crossfade] open_audio: FLUSH:\n")); + + /* flush output plugin */ + the_op->flush(0); + output_streampos = 0; + + /* flush buffer */ + buffer_reset(buffer, config); + + /* apply fade config (pause/fadein after flush) */ + xfade_apply_fade_config(fade_config); + + /* also repopen device (if configured so in the plugin compat. options) */ + if(the_op_config.force_reopen) { + buffer->reopen = 0; + buffer->reopen_sync = FALSE; + } + break; + + case FADE_TYPE_REOPEN: + DEBUG(("[crossfade] open_audio: REOPEN:\n")); + + /* flush buffer if applicable */ + if(fade_config->flush) + buffer_reset(buffer, config); + + if(buffer->reopen >= 0) + DEBUG(("[crossfade] open_audio: REOPEN: WARNING: reopen in progress (%d ms)\n", + B2MS(buffer->reopen))); + + /* start reopen countdown (will be executed in buffer_thread_f) */ + buffer->reopen = buffer->used; /* may be 0 */ + buffer->reopen_sync = FALSE; + break; + + case FADE_TYPE_NONE: + case FADE_TYPE_PAUSE: + case FADE_TYPE_SIMPLE_XF: + case FADE_TYPE_ADVANCED_XF: + case FADE_TYPE_FADEIN: + case FADE_TYPE_FADEOUT: + DEBUG(("[crossfade] open_audio: XFADE:\n")); + + /* apply fade config (do fadeout, init mix/fade/gap, add silence) */ + xfade_apply_fade_config(fade_config); + + /* set reopen countdown. after buffer_thread_f has written + * buffer->reopen bytes, it will close/reopen the output plugin. */ + if(the_op_config.force_reopen && !(fade_config->config == FADE_CONFIG_START)) { + if(buffer->reopen >= 0) + DEBUG(("[crossfade] open_audio: XFADE: WARNING: reopen in progress (%d ms)\n", + B2MS(buffer->reopen))); + buffer->reopen = buffer->used; + buffer->reopen_sync = TRUE; + } + break; + } + + /* calculate offset of the output plugin */ + output_offset = the_op->written_time() + B2MS(buffer->used) + B2MS(buffer->silence_len); + + /* unlock buffer */ + g_static_mutex_unlock(&buffer_mutex); + + /* done */ + return 1; +} + +void +xfade_write_audio(void *ptr, int length) +{ + gint free; + gint ofs = 0; + format_t format; + +#ifdef DEBUG_HARDCORE + // yaz: XXX 64bit environment + DEBUG(("[crossfade] write_audio: ptr=0x%08lx, length=%d\n", (long)ptr, length)); +#endif + + /* sanity */ + if(length <= 0) return; + if(length & 3) { + DEBUG(("[crossfade] write_audio: truncating %d bytes!\n", length & 3)); + length &= -4; + } + + /* update input accumulator (using input format size) */ + streampos += length; + + /* apply pre-mixing effect (NOTE: format might change!) */ + format_copy(&format, &in_format); + length = effect_flow(&effect_context, (gpointer*)&ptr, length, &format, TRUE); + + /* convert sample format (signed-16bit-ne 44100hz stereo) */ + length = convert_flow(&convert_context, (gpointer*)&ptr, length, &format); + + /* convert rate */ + if(!rate_context.valid || (rate_context.in_rate != format.rate)) + rate_config(&rate_context, format.rate, out_format.rate, config->output_quality); + + length = rate_flow(&rate_context, (gpointer*)&ptr, length); + +#ifdef VOLUME_NORMALIZER + /* normalize volume */ + if(config->volnorm_enable) + volume_flow(&volume_context, (gpointer*)&ptr, length); +#endif + + /* lock buffer */ + g_static_mutex_lock(&buffer_mutex); + + /* check if device has been closed, reopen if necessary */ + if(!output_opened) { + if(open_output()) { + DEBUG(("[crossfade] write_audio: reopening failed!\n")); + g_static_mutex_unlock(&buffer_mutex); + return; + } + } + + /* reset timeout */ + gettimeofday(&last_write, NULL); + + /* calculate free buffer space, check for overflow (should never happen :) */ + free = buffer->size - buffer->used; + if(length > free) { + DEBUG(("[crossfade] write_audio: %d bytes truncated!\n", length-free)); + length = free; + } + + /* kill leading gap */ + if((length > 0) && (buffer->gap > 0)) { + gint blen = MIN(length, buffer->gap); + gint16 *p = ptr; + gint index = 0; + + gint16 left, right; + while(index < blen) { + left = *p++, right = *p++; + if(ABS(left) >= buffer->gap_level) break; + if(ABS(right) >= buffer->gap_level) break; + index += 4; + } + + buffer->gap -= index; + length -= index; + ptr = (gchar *)ptr + index; //yaz + + if((index < blen) || (buffer->gap <= 0)) { + buffer->gap_killed = buffer->gap_len - buffer->gap; + buffer->gap = 0; + + DEBUG(("[crossfade] write_audio: leading gap size: %d/%d ms\n", + B2MS(buffer->gap_killed), B2MS(buffer->gap_len))); + + /* fix streampos */ + streampos -= (gint64)buffer->gap_killed * in_format.bps / out_format.bps; + } + } + + /* start skipping to next crossing (if enabled) */ + if(buffer->gap == 0) { + if(config->gap_crossing) { + buffer->gap = GAP_SKIPPING_POSITIVE; + buffer->gap_skipped = 0; + } + else + buffer->gap = GAP_SKIPPING_DONE; + } + + /* skip until next zero crossing (pos -> neg) */ + if((length > 0) && (buffer->gap == GAP_SKIPPING_POSITIVE)) { + gint16 *p = ptr; + gint index = 0; + + gint16 left; + while(index < length) { + left = *p++; p++; + if(left < 0) break; + index += 4; + } + + buffer->gap_skipped += index; + length -= index; + ptr = (gchar *)ptr + index; //yaz + + if(index < length) + buffer->gap = GAP_SKIPPING_NEGATIVE; + } + + /* skip until next zero crossing (neg -> pos) */ + if((length > 0) && (buffer->gap == GAP_SKIPPING_NEGATIVE)) { + gint16 *p = ptr; + gint index = 0; + + gint16 left; + while(index < length) { + left = *p++; p++; + if(left >= 0) break; + index += 4; + } + + buffer->gap_skipped += index; + length -= index; + ptr = (gchar *)ptr + index; //yaz + + if(index < length) { + DEBUG(("[crossfade] write_audio: %d samples to next crossing\n", + buffer->gap_skipped)); + buffer->gap = GAP_SKIPPING_DONE; + } + } + + /* update preload. the buffer thread will not write any + * data to the device before preload is decreased below 1. */ + if((length > 0) && (buffer->preload > 0)) + buffer->preload -= length; + + /* fadein -- FIXME: is modifying the input/effect buffer safe? */ + if((length > 0) && (buffer->fade > 0)) { + gint16 *p = ptr; + gint blen = MIN(length, buffer->fade); + gint n; + + for(n = blen/4; n > 0; n--) { + gfloat factor = 1.0f - (((gfloat)buffer->fade / buffer->fade_len) * buffer->fade_scale); + *p = (gfloat)*p * factor; p++; + *p = (gfloat)*p * factor; p++; + buffer->fade -= 4; + } + } + + /* mix */ + while((length > 0) && (buffer->mix > 0)) { + gint wr_index = (buffer->rd_index + buffer->used) % buffer->size; + gint blen = buffer->size - wr_index; + gint16 *p1 = (gint16 *)((gchar *)(buffer->data) + wr_index); //yaz + gint16 *p2 = (gint16 *)((gchar *)ptr + ofs); //yaz + gint n; + + if(blen > length) blen = length; + if(blen > buffer->mix) blen = buffer->mix; + + for(n = blen/2; n > 0; n--) { + gint out = (gint)*p1 + *p2++; /* add */ + if(out > 32767) /* clamp */ + *p1++ = 32767; + else if(out < -32768) + *p1++ = -32768; + else + *p1++ = out; + } + + buffer->used += blen; + buffer->mix -= blen; + length -= blen; + ofs += blen; + } + + /* normal write */ + while(length > 0) { + gint wr_index = (buffer->rd_index + buffer->used) % buffer->size; + gint blen = buffer->size - wr_index; + + if(blen > length) blen = length; + + memcpy((gchar *)(buffer->data) + wr_index, (gchar *)ptr + ofs, blen); //yaz + + buffer->used += blen; + length -= blen; + ofs += blen; + } + + /* unlock buffer */ + g_static_mutex_unlock(&buffer_mutex); +#ifdef DEBUG_HARDCORE + DEBUG(("[crossfade] write_audio: done.\n")); +#endif +} + +/* sync_output: wait for output plugin to finish playback */ +/* is only called from within buffer_thread_f */ +static void sync_output() +{ + glong dt, total; + gint opt, opt_last; + struct timeval tv, tv_start, tv_last_change; + gboolean was_closed = !opened; + + if(!the_op->buffer_playing || !the_op->buffer_playing()) { + DEBUG(("[crossfade] sync_output: nothing to do\n")); + return; + } + + DEBUG(("[crossfade] sync_output: waiting for plugin...\n")); + + dt = 0; + opt_last = 0; + gettimeofday(&tv_start, NULL); + gettimeofday(&tv_last_change, NULL); + + while((dt < SYNC_OUTPUT_TIMEOUT) + && !stopped + && output_opened + && !(was_closed && opened) + && the_op && the_op->buffer_playing()) { + + /* use output_time() to check if the output plugin is still active */ + if(the_op->output_time) { + opt = the_op->output_time(); + if(opt != opt_last) { + /* output_time has changed */ + opt_last = opt; + gettimeofday(&tv_last_change, NULL); + } + else { + /* calculate time since last change of the_op->output_time() */ + gettimeofday(&tv, NULL); + dt = (tv.tv_sec - tv_last_change.tv_sec) * 1000 + + (tv.tv_usec - tv_last_change.tv_usec) / 1000; + } + } + + /* yield */ + g_static_mutex_unlock(&buffer_mutex); + xmms_usleep(10000); + g_static_mutex_lock(&buffer_mutex); + } + + /* calculate total time we spent in here */ + gettimeofday(&tv, NULL); + total = (tv.tv_sec - tv_start.tv_sec) * 1000 + + (tv.tv_usec - tv_start.tv_usec) / 1000; + + /* print some debug info */ + if(stopped) + DEBUG(("[crossfade] sync_output: ... stopped\n")) + else if(was_closed && opened) + DEBUG(("[crossfade] sync_output: ... reopened\n")) + else if(dt >= SYNC_OUTPUT_TIMEOUT) + DEBUG(("[crossfade] sync_output: ... TIMEOUT! (%ld ms)\n", total)) + else + DEBUG(("[crossfade] sync_output: ... done (%ld ms)\n", total)); +} + +void * +buffer_thread_f(void *arg) +{ + gpointer data; + gint sync; + gint op_free; + gint length_bak, length, blen; + glong timeout, dt; + gboolean stopping; + + struct timeval tv; + struct timeval mark; + + DEBUG(("[crossfade] buffer_thread_f: thread started (pid=%d)\n", + (int)getpid())); + + /* lock buffer */ + g_static_mutex_lock(&buffer_mutex); + + while(!stopped) { + /* yield */ +#ifdef DEBUG_HARDCORE + DEBUG(("[crossfade] buffer_thread_f: yielding...\n")); +#endif + g_static_mutex_unlock(&buffer_mutex); + xmms_usleep(10000); + g_static_mutex_lock(&buffer_mutex); + + /* --------------------------------------------------------------------- */ + + stopping = FALSE; + + /* V0.3.0: New timeout detection */ + if(!opened) { + gboolean current = bmp_playback_get_playing(); /* BEEP */ + + /* also see fini() */ + if(last_close.tv_sec || last_close.tv_usec) { + gettimeofday(&tv, NULL); + timeout = (tv.tv_sec - last_close.tv_sec) * 1000 + + (tv.tv_usec - last_close.tv_usec) / 1000; + } + else + timeout = -1; + + if(current != input_playing) { + input_playing = current; + if(current) + DEBUG(("[crossfade] buffer_thread_f:" + " input restarted after %ld ms\n", timeout)) + else + DEBUG(("[crossfade] buffer_thread_f:" + " input stopped after + %ld ms\n", timeout)); + } + + /* 0.3.0: HACK: output_keep_opened: play silence during prebuffering */ + if(input_playing && config->output_keep_opened && (buffer->used == 0)) { + buffer->silence = 0; + buffer->silence_len = MS2B(100); + } + + /* FIXME: make timeout configureable */ + if(((timeout < 0) || (timeout >= 100)) && !input_playing) { + if(playing) + DEBUG(("[crossfade] buffer_thread_f: timeout:" + " input did not restart after %ld ms (playing=%d)\n", timeout, playing)); + stopping = TRUE; + } + } + + /* V0.2.4: Moved the timeout checks in front of the buffer_free() check + * below. Before, buffer_thread_f could (theoretically) loop + * endlessly if buffer_free() returned 0 all the time. */ + + /* calculate time since last write to the buffer (ignore overflows) */ + gettimeofday(&tv, NULL); + timeout = (tv.tv_sec - last_write.tv_sec) * 1000 + + (tv.tv_usec - last_write.tv_usec) / 1000; + + /* check for timeout/eop (note this is the only way out of this loop) */ + if(stopping) { + if(playing) { + DEBUG(("[crossfade] buffer_thread_f: timeout: manual stop\n")); + + /* if CONFIG_STOP is of TYPE_NONE, immediatelly close the device... */ + if((config->fc[FADE_CONFIG_STOP].type == FADE_TYPE_NONE) + && !config->output_keep_opened) break; + + /* special handling for pause */ + if(paused) { + DEBUG(("[crossfade] buffer_thread_f: timeout: paused, closing now...\n")); + paused = FALSE; + if(config->output_keep_opened) + the_op->pause(0); + else + break; + } + else if(buffer->pause >= 0) { + DEBUG(("[crossfade] buffer_thread_f: timeout: cancelling pause countdown\n")); + buffer->pause = -1; + } + + /* ...otherwise, do the fadeout first */ + xfade_apply_fade_config(&config->fc[FADE_CONFIG_STOP]); + + /* force CONFIG_START in case the user restarts playback during fadeout */ + fade_config = &config->fc[FADE_CONFIG_START]; + playing = FALSE; + eop = TRUE; + } + else { + if(!eop) { + DEBUG(("[crossfade] buffer_thread_f: timeout: end of playback\n")); + + /* 0.3.3: undo trailing gap killer at end of playlist */ + if(buffer->gap_killed) { + buffer->used += buffer->gap_killed; + DEBUG(("[crossfade] buffer_thread_f: timeout:" + " undoing trailing gap (%d ms)\n", B2MS(buffer->gap_killed))); + } + + /* do the fadeout if applicable */ + if(config->fc[FADE_CONFIG_EOP].type != FADE_TYPE_NONE) + xfade_apply_fade_config(&config->fc[FADE_CONFIG_EOP]); + + fade_config = &config->fc[FADE_CONFIG_START]; /* see above */ + eop = TRUE; + } + + if(buffer->used == 0) { + if(config->output_keep_opened) { + /* 0.3.0: play silence while keeping the output opened */ + buffer->silence = 0; + buffer->silence_len = MS2B(100); + } + else if(buffer->silence_len <= 0) { + sync_output(); + if(opened) { + DEBUG(("[crossfade] buffer_thread_f: timeout, eop: device has been reopened\n")); + DEBUG(("[crossfade] buffer_thread_f: timeout, eop: -> continuing playback\n")); + eop = FALSE; + } + else { + DEBUG(("[crossfade] buffer_thread_f: timeout, eop: closing output...\n")); + break; + } + } + } + } + } + else + eop = FALSE; + + /* -----------------------------