diff Plugins/Input/adplug/adplug-xmms.cc @ 359:8df427a314a8 trunk

[svn] Adlib synthesizer (AdPlug) support.
author chainsaw
date Fri, 30 Dec 2005 16:31:39 -0800
parents
children db298f2d3dd9
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Input/adplug/adplug-xmms.cc	Fri Dec 30 16:31:39 2005 -0800
@@ -0,0 +1,937 @@
+/*
+   AdPlug/XMMS - AdPlug XMMS Plugin
+   Copyright (C) 2002, 2003 Simon Peter <dn.tlp@gmx.net>
+
+   AdPlug/XMMS is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   This plugin 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this plugin; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#include <algorithm>
+#include <sstream>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <gtk/gtk.h>
+#include "adplug.h"
+#include "emuopl.h"
+#include "silentopl.h"
+#include "players.h"
+#include "audacious/plugin.h"
+#include "libaudacious/util.h"
+#include "libaudacious/configdb.h"
+
+/***** Defines *****/
+
+// Version string
+#define ADPLUG_NAME	"AdPlug"
+
+// Sound buffer size in samples
+#define SNDBUFSIZE	512
+
+// AdPlug's 8 and 16 bit audio formats
+#define FORMAT_8	FMT_U8
+#define FORMAT_16	FMT_S16_NE
+
+// Default file name of AdPlug's database file
+#define ADPLUGDB_FILE		"adplug.db"
+
+// Default AdPlug user's configuration subdirectory
+#define ADPLUG_CONFDIR		".adplug"
+
+/***** Global variables *****/
+
+extern "C" InputPlugin	adplug_ip;
+static gboolean		audio_error = FALSE;
+
+// Configuration (and defaults)
+static struct {
+  gint		freq;
+  gboolean	bit16, stereo, endless, quickdetect;
+  CPlayers	players;
+} cfg = { 44100l, true, false, false, true, CAdPlug::players };
+
+// Player variables
+static struct {
+  CPlayer		*p;
+  CAdPlugDatabase	*db;
+  unsigned int		subsong, songlength;
+  int			seek;
+  char			filename[PATH_MAX];
+  char			*songtitle;
+  float			time_ms;
+  bool			playing;
+  GThread		*play_thread;
+  GtkLabel		*infobox;
+  GtkDialog		*infodlg;
+} plr = { 0, 0, 0, 0, -1, "", NULL, 0.0f, false, 0, NULL, NULL };
+
+/***** Debugging *****/
+
+#ifdef DEBUG
+
+#include <stdarg.h>
+
+static void dbg_printf(const char *fmt, ...)
+{
+  va_list argptr;
+
+  va_start(argptr, fmt);
+  vfprintf(stderr, fmt, argptr);
+  va_end(argptr);
+}
+
+#else
+
+static void dbg_printf(const char *fmt, ...)
+{ }
+
+#endif
+
+/***** [Dialog]: Utility functions *****/
+
+static GtkWidget *make_framed(GtkWidget *what, const gchar *label)
+{
+  GtkWidget *framebox = gtk_frame_new(label);
+
+  gtk_container_add(GTK_CONTAINER(framebox), what);
+  return framebox;
+}
+
+static GtkWidget *print_left(const gchar *text)
+{
+  GtkLabel *label = GTK_LABEL(gtk_label_new(text));
+
+  gtk_label_set_justify(label, GTK_JUSTIFY_LEFT);
+  gtk_misc_set_padding(GTK_MISC(label), 2, 2);
+  return GTK_WIDGET(label);
+}
+
+static void MessageBox(const char *title, const char *text, const char *button)
+{
+  char *tmptitle = (char *)malloc(strlen(title) + 1),
+    *tmptxt = (char *)malloc(strlen(text) + 1),
+    *tmpbutton = (char *)malloc(strlen(button) + 1);
+
+  strcpy(tmptitle, title); strcpy(tmptxt, text); strcpy(tmpbutton, button);
+
+  GtkWidget *msgbox = xmms_show_message(tmptitle, tmptxt, tmpbutton, FALSE,
+					GTK_SIGNAL_FUNC(gtk_widget_destroyed), &msgbox);
+
+  free(tmptitle); free(tmptxt); free(tmpbutton);
+}
+
+/***** Dialog boxes *****/
+
+static void adplug_about(void)
+{
+  std::ostringstream text;
+
+  text << ADPLUG_NAME "\n"
+    "Copyright (C) 2002, 2003 Simon Peter <dn.tlp@gmx.net>\n\n"
+    "This plugin is released under the terms and conditions of the GNU LGPL.\n"
+    "See http://www.gnu.org/licenses/lgpl.html for details."
+    "\n\nThis plugin uses the AdPlug library, which is copyright (C) Simon Peter, et al.\n"
+    "Linked AdPlug library version: " << CAdPlug::get_version() << std::ends;
+
+  MessageBox("About " ADPLUG_NAME, text.str().c_str(), "Ugh!");
+}
+
+static void close_config_box_ok(GtkButton *button, GPtrArray *rblist)
+{
+  // Apply configuration settings
+  cfg.bit16 = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g_ptr_array_index(rblist, 0)));
+  cfg.stereo = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g_ptr_array_index(rblist, 1)));
+
+  if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g_ptr_array_index(rblist, 2)))) cfg.freq = 11025;
+  if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g_ptr_array_index(rblist, 3)))) cfg.freq = 22050;
+  if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g_ptr_array_index(rblist, 4)))) cfg.freq = 44100;
+  if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g_ptr_array_index(rblist, 5)))) cfg.freq = 48000;
+
+  cfg.endless = !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g_ptr_array_index(rblist, 6)));
+  cfg.quickdetect = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g_ptr_array_index(rblist, 7)));
+
+  cfg.players = *(CPlayers *)g_ptr_array_index(rblist, 8);
+  delete (CPlayers *)g_ptr_array_index(rblist, 8);
+
+  g_ptr_array_free(rblist, FALSE);
+}
+
+static void close_config_box_cancel(GtkButton *button, GPtrArray *rblist)
+{
+  delete (CPlayers *)g_ptr_array_index(rblist, 8);
+  g_ptr_array_free(rblist, FALSE);
+}
+
+static void config_fl_row_select(GtkCList *fl, gint row, gint col,
+				 GdkEventButton *event, CPlayers *pl)
+{
+  pl->push_back((CPlayerDesc *)gtk_clist_get_row_data(fl, row));
+  pl->unique();
+}
+
+static void config_fl_row_unselect(GtkCList *fl, gint row, gint col,
+				   GdkEventButton *event, CPlayers *pl)
+{
+  pl->remove((CPlayerDesc *)gtk_clist_get_row_data(fl, row));
+}
+
+static void adplug_config(void)
+{
+  GtkDialog *config_dlg = GTK_DIALOG(gtk_dialog_new());
+  GtkNotebook *notebook = GTK_NOTEBOOK(gtk_notebook_new());
+  GtkTable *table;
+  GtkTooltips *tooltips = gtk_tooltips_new();
+  GPtrArray *rblist = g_ptr_array_new();
+
+  gtk_window_set_title(GTK_WINDOW(config_dlg), "AdPlug :: Configuration");
+  gtk_window_set_policy(GTK_WINDOW(config_dlg), FALSE, FALSE, TRUE); // Window is auto sized
+  gtk_window_set_modal(GTK_WINDOW(config_dlg), TRUE);
+  gtk_container_add(GTK_CONTAINER(config_dlg->vbox), GTK_WIDGET(notebook));
+
+  // Add Ok & Cancel buttons
+  {
+    GtkWidget *button;
+
+    button = gtk_button_new_with_label("Ok");
+    gtk_signal_connect(GTK_OBJECT(button), "clicked",
+		       GTK_SIGNAL_FUNC(close_config_box_ok),
+		       (gpointer)rblist);
+    gtk_signal_connect_object_after(GTK_OBJECT(button), "clicked",
+				    GTK_SIGNAL_FUNC(gtk_widget_destroy),
+				    GTK_OBJECT(config_dlg));
+    gtk_container_add(GTK_CONTAINER(config_dlg->action_area), button);
+
+    button = gtk_button_new_with_label("Cancel");
+    gtk_signal_connect(GTK_OBJECT(button), "clicked",
+		       GTK_SIGNAL_FUNC(close_config_box_cancel),
+		       (gpointer)rblist);
+    gtk_signal_connect_object(GTK_OBJECT(button), "clicked",
+			      GTK_SIGNAL_FUNC(gtk_widget_destroy),
+			      GTK_OBJECT(config_dlg));
+    gtk_container_add(GTK_CONTAINER(config_dlg->action_area), button);
+  }
+
+  /***** Page 1: General *****/
+
+  table = GTK_TABLE(gtk_table_new(1, 2, TRUE));
+  gtk_table_set_row_spacings(table, 5); gtk_table_set_col_spacings(table, 5);
+  gtk_notebook_append_page(notebook, GTK_WIDGET(table), print_left("General"));
+
+  // Add "Sound quality" section
+  {
+    GtkTable *sqt = GTK_TABLE(gtk_table_new(2, 2, FALSE));
+    GtkVBox *fvb;
+    GtkRadioButton *rb;
+
+    gtk_table_set_row_spacings(sqt, 5);
+    gtk_table_set_col_spacings(sqt, 5);
+    gtk_table_attach_defaults(table, make_framed(GTK_WIDGET(sqt), "Sound quality"),
+			      0, 1, 0, 1);
+
+    // Add "Resolution" section
+    fvb = GTK_VBOX(gtk_vbox_new(TRUE, 0));
+    gtk_table_attach_defaults(sqt, make_framed(GTK_WIDGET(fvb), "Resolution"),
+			      0, 1, 0, 1);
+    rb = GTK_RADIO_BUTTON(gtk_radio_button_new_with_label(NULL, "8bit"));
+    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rb), !cfg.bit16);
+    gtk_container_add(GTK_CONTAINER(fvb), GTK_WIDGET(rb));
+    rb = GTK_RADIO_BUTTON(gtk_radio_button_new_with_label_from_widget(rb, "16bit"));
+    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rb), cfg.bit16);
+    gtk_container_add(GTK_CONTAINER(fvb), GTK_WIDGET(rb));
+    g_ptr_array_add(rblist, (gpointer)rb);
+
+    // Add "Channels" section
+    fvb = GTK_VBOX(gtk_vbox_new(TRUE, 0));
+    gtk_table_attach_defaults(sqt, make_framed(GTK_WIDGET(fvb), "Channels"),
+			      0, 1, 1, 2);
+    rb = GTK_RADIO_BUTTON(gtk_radio_button_new_with_label(NULL, "Mono"));
+    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rb), !cfg.stereo);
+    gtk_container_add(GTK_CONTAINER(fvb), GTK_WIDGET(rb));
+    rb = GTK_RADIO_BUTTON(gtk_radio_button_new_with_label_from_widget(rb, "Stereo"));
+    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rb), cfg.stereo);
+    gtk_container_add(GTK_CONTAINER(fvb), GTK_WIDGET(rb));
+    gtk_tooltips_set_tip(tooltips, GTK_WIDGET(rb),
+			 "Setting stereo is not recommended, unless you need to. "
+			 "This won't add any stereo effects to the sound - OPL2 "
+			 "is just mono - but eats up more CPU power!", NULL);
+    g_ptr_array_add(rblist, (gpointer)rb);
+
+    // Add "Frequency" section
+    fvb = GTK_VBOX(gtk_vbox_new(TRUE, 0));
+    gtk_table_attach_defaults(sqt, make_framed(GTK_WIDGET(fvb), "Frequency"),
+			      1, 2, 0, 2);
+    rb = GTK_RADIO_BUTTON(gtk_radio_button_new_with_label(NULL, "11025"));
+    if(cfg.freq == 11025) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rb), TRUE);
+    gtk_container_add(GTK_CONTAINER(fvb), GTK_WIDGET(rb));
+    g_ptr_array_add(rblist, (gpointer)rb);
+    rb = GTK_RADIO_BUTTON(gtk_radio_button_new_with_label_from_widget(rb, "22050"));
+    if(cfg.freq == 22050) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rb), TRUE);
+    gtk_container_add(GTK_CONTAINER(fvb), GTK_WIDGET(rb));
+    g_ptr_array_add(rblist, (gpointer)rb);
+    rb = GTK_RADIO_BUTTON(gtk_radio_button_new_with_label_from_widget(rb, "44100"));
+    if(cfg.freq == 44100) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rb), TRUE);
+    gtk_container_add(GTK_CONTAINER(fvb), GTK_WIDGET(rb));
+    g_ptr_array_add(rblist, (gpointer)rb);
+    rb = GTK_RADIO_BUTTON(gtk_radio_button_new_with_label_from_widget(rb, "48000"));
+    if(cfg.freq == 48000) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rb), TRUE);
+    gtk_container_add(GTK_CONTAINER(fvb), GTK_WIDGET(rb));
+    g_ptr_array_add(rblist, (gpointer)rb);
+  }
+
+  // Add "Playback" section
+  {
+    GtkVBox *vb = GTK_VBOX(gtk_vbox_new(FALSE, 0));
+    GtkCheckButton *cb;
+
+    gtk_table_attach_defaults(table, make_framed(GTK_WIDGET(vb), "Playback"),
+			      1, 2, 0, 1);
+
+    cb = GTK_CHECK_BUTTON(gtk_check_button_new_with_label("Detect songend"));
+    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb), !cfg.endless);
+    gtk_container_add(GTK_CONTAINER(vb), GTK_WIDGET(cb));
+    gtk_tooltips_set_tip(tooltips, GTK_WIDGET(cb),
+			 "If enabled, XMMS will detect a song's ending, stop "
+			 "it and advance in the playlist. If disabled, XMMS "
+			 "won't take notice of a song's ending and loop it all "
+			 "over again and again.", NULL);
+    g_ptr_array_add(rblist, (gpointer)cb);
+
+    cb = GTK_CHECK_BUTTON(gtk_check_button_new_with_label("Quick file detection"));
+    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb), cfg.quickdetect);
+    gtk_container_add(GTK_CONTAINER(vb), GTK_WIDGET(cb));
+    gtk_tooltips_set_tip(tooltips, GTK_WIDGET(cb),
+			 "If enabled, we'll try to determine a file's type by "
+			 "only looking at the file extension, instead of "
+			 "processing the file's contents. This speeds up the "
+			 "load time of files a lot, but can be inaccurate if "
+			 "the file has the wrong extension.", NULL);
+    g_ptr_array_add(rblist, (gpointer)cb);
+  }
+
+  /***** Page 2: Formats *****/
+
+  table = GTK_TABLE(gtk_table_new(1, 1, TRUE));
+  gtk_notebook_append_page(notebook, GTK_WIDGET(table), print_left("Formats"));
+
+  // Add "Format selection" section
+  {
+    GtkHBox *vb = GTK_HBOX(gtk_hbox_new(FALSE, 0));
+    gtk_table_attach_defaults(table, make_framed(GTK_WIDGET(vb), "Format selection"),
+			      0, 1, 0, 1);
+    // Add scrollable list
+    {
+      gchar *rowstr[] = {"Format", "Extension"};
+      GtkEventBox *eventbox = GTK_EVENT_BOX(gtk_event_box_new());
+      GtkScrolledWindow *formatswnd = GTK_SCROLLED_WINDOW(gtk_scrolled_window_new(NULL, NULL));
+      GtkCList *fl = GTK_CLIST(gtk_clist_new_with_titles(2, rowstr));
+      CPlayers::const_iterator i;
+      unsigned int j;
+      gtk_clist_set_selection_mode(fl, GTK_SELECTION_MULTIPLE);
+
+      // Build list
+      for(i = CAdPlug::players.begin(); i != CAdPlug::players.end(); i++) {
+	gint rownum;
+
+	gchar *rws[2];
+	rws[0] = g_strdup((*i)->filetype.c_str());
+	rws[1] = g_strdup((*i)->get_extension(0));
+	for(j = 1; (*i)->get_extension(j); j++)
+	  rws[1] = g_strjoin(", ", rws[1], (*i)->get_extension(j), NULL);
+	rownum = gtk_clist_append(fl, rws);
+	g_free(rws[0]); g_free(rws[1]);
+	gtk_clist_set_row_data(fl, rownum, (gpointer)(*i));
+	if(find(cfg.players.begin(), cfg.players.end(), *i) != cfg.players.end())
+	  gtk_clist_select_row(fl, rownum, 0);
+      }
+
+      gtk_clist_columns_autosize(fl);
+      gtk_scrolled_window_set_policy(formatswnd, GTK_POLICY_AUTOMATIC,
+				     GTK_POLICY_AUTOMATIC);
+      gpointer pl = (gpointer)new CPlayers(cfg.players);
+      gtk_signal_connect(GTK_OBJECT(fl), "select-row",
+			 GTK_SIGNAL_FUNC(config_fl_row_select), pl);
+      gtk_signal_connect(GTK_OBJECT(fl), "unselect-row",
+			 GTK_SIGNAL_FUNC(config_fl_row_unselect), pl);
+      gtk_container_add(GTK_CONTAINER(formatswnd), GTK_WIDGET(fl));
+      gtk_container_add(GTK_CONTAINER(eventbox), GTK_WIDGET(formatswnd));
+      gtk_container_add(GTK_CONTAINER(vb), GTK_WIDGET(eventbox));
+      gtk_tooltips_set_tip(tooltips, GTK_WIDGET(eventbox),
+			   "Selected file types will be recognized and played "
+			   "back by this plugin. Deselected types will be "
+			   "ignored to make room for other plugins to play "
+			   "these files.", NULL);
+      g_ptr_array_add(rblist, pl);
+    }
+  }
+
+  // Show window
+  gtk_widget_show_all(GTK_WIDGET(config_dlg));
+}
+
+static void add_instlist(GtkCList *instlist, const char *t1, const char *t2)
+{
+  gchar *rowstr[2];
+
+  rowstr[0] = g_strdup(t1); rowstr[1] = g_strdup(t2);
+  gtk_clist_append(instlist, rowstr);
+  g_free(rowstr[0]); g_free(rowstr[1]);
+}
+
+static CPlayer *factory(const std::string &filename, Copl *newopl)
+/* Wrapper factory method that implements the "quick detect" feature. */
+{
+  CPlayer			*p;
+  CPlayers::const_iterator	i;
+  unsigned int			j;
+
+  dbg_printf("factory(\"%s\",opl): ", filename.c_str());
+  if(cfg.quickdetect) {
+    // Quick detect! Just check according to file extensions.
+    dbg_printf("quick detect: ");
+    for(i = cfg.players.begin(); i != cfg.players.end(); i++)
+      for(j = 0; (*i)->get_extension(j); j++)
+	if(CFileProvider::extension(filename, (*i)->get_extension(j)))
+	  if((p = (*i)->factory(newopl)))
+	    if(p->load(filename)) {
+	      dbg_printf("%s\n", (*i)->filetype.c_str());
+	      return p;
+	    } else
+	      delete p;
+
+    dbg_printf("failed!\n");
+    return 0;	// quick detect failed.
+  } else {	// just call AdPlug's original factory()
+    dbg_printf("asking AdPlug...\n");
+    return CAdPlug::factory(filename, newopl, cfg.players);
+  }
+}
+
+static void adplug_stop(void);
+static void adplug_play(char *filename);
+
+static void subsong_slider(GtkAdjustment *adj)
+{
+  adplug_stop();
+  plr.subsong = (unsigned int)adj->value - 1;
+  adplug_play(plr.filename);
+}
+
+#if 0
+static void close_infobox(GtkDialog *infodlg)
+{
+  // Forget our references to the instance of the "currently playing song" info
+  // box. But only if we're really destroying that one... ;)
+  if(infodlg == plr.infodlg) {
+    plr.infobox = NULL;
+    plr.infodlg = NULL;
+  }
+}
+
+static void adplug_info_box(char *filename)
+{
+  CSilentopl tmpopl;
+  CPlayer *p = (strcmp(filename, plr.filename) || !plr.p) ?
+    factory(filename, &tmpopl) : plr.p;
+
+  if(!p) return; // bail out if no player could be created
+  if(p == plr.p && plr.infodlg) return; // only one info box for active song
+
+  std::ostringstream infotext;
+  unsigned int i;
+  GtkDialog *infobox = GTK_DIALOG(gtk_dialog_new());
+  GtkButton *okay_button = GTK_BUTTON(gtk_button_new_with_label("Ok"));
+  GtkPacker *packer = GTK_PACKER(gtk_packer_new());
+  GtkHBox *hbox = GTK_HBOX(gtk_hbox_new(TRUE, 2));
+
+  // Build file info box
+  gtk_window_set_title(GTK_WINDOW(infobox), "AdPlug :: File Info");
+  gtk_window_set_policy(GTK_WINDOW(infobox), FALSE, FALSE, TRUE); // Window is auto sized
+  gtk_container_add(GTK_CONTAINER(infobox->vbox), GTK_WIDGET(packer));
+  gtk_packer_set_default_border_width(packer, 2);
+  gtk_box_set_homogeneous(GTK_BOX(hbox), FALSE);
+  gtk_signal_connect_object(GTK_OBJECT(okay_button), "clicked",
+			    GTK_SIGNAL_FUNC(gtk_widget_destroy),
+			    GTK_OBJECT(infobox));
+  gtk_signal_connect(GTK_OBJECT(infobox), "destroy",
+		     GTK_SIGNAL_FUNC(close_infobox), 0);
+  gtk_container_add(GTK_CONTAINER(infobox->action_area), GTK_WIDGET(okay_button));
+
+  // Add filename section
+  gtk_packer_add_defaults(packer, make_framed(print_left(filename), "Filename"),
+			  GTK_SIDE_TOP, GTK_ANCHOR_CENTER, GTK_FILL_X);
+
+  // Add "Song info" section
+  infotext << "Title: " << p->gettitle() << std::endl <<
+    "Author: " << p->getauthor() << std::endl <<
+    "File Type: " << p->gettype() << std::endl <<
+    "Subsongs: " << p->getsubsongs() << std::endl <<
+    "Instruments: " << p->getinstruments();
+  if(plr.p == p)
+    infotext << std::ends;
+  else {
+    infotext << std::endl << "Orders: " << p->getorders() << std::endl <<
+      "Patterns: " << p->getpatterns() << std::ends;
+  }
+  gtk_container_add(GTK_CONTAINER(hbox),
+		    make_framed(print_left(infotext.str().c_str()), "Song"));
+
+  // Add "Playback info" section if currently playing
+  if(plr.p == p) {
+    plr.infobox = GTK_LABEL(gtk_label_new(""));
+    gtk_label_set_justify(plr.infobox, GTK_JUSTIFY_LEFT);
+    gtk_misc_set_padding(GTK_MISC(plr.infobox), 2, 2);
+    gtk_container_add(GTK_CONTAINER(hbox),
+		      make_framed(GTK_WIDGET(plr.infobox), "Playback"));
+  }
+  gtk_packer_add_defaults(packer, GTK_WIDGET(hbox), GTK_SIDE_TOP,
+			  GTK_ANCHOR_CENTER, GTK_FILL_X);
+
+  // Add instrument names section
+  if(p->getinstruments()) {
+    GtkScrolledWindow *instwnd = GTK_SCROLLED_WINDOW(gtk_scrolled_window_new(NULL, NULL));
+    GtkCList *instnames;
+    gchar tmpstr[10];
+
+    {
+      gchar *rowstr[] = {"#","Instrument name"};
+      instnames = GTK_CLIST(gtk_clist_new_with_titles(2, rowstr));
+    }
+    gtk_clist_set_column_justification(instnames, 0, GTK_JUSTIFY_RIGHT);
+
+    for(i=0;i<p->getinstruments();i++) {
+      sprintf(tmpstr, "%d", i + 1);
+      add_instlist(instnames, tmpstr, p->getinstrument(i).c_str());
+    }
+
+    gtk_clist_columns_autosize(instnames);
+    gtk_scrolled_window_set_policy(instwnd, GTK_POLICY_AUTOMATIC,
+				   GTK_POLICY_AUTOMATIC);
+    gtk_container_add(GTK_CONTAINER(instwnd), GTK_WIDGET(instnames));
+    gtk_packer_add(packer, GTK_WIDGET(instwnd), GTK_SIDE_TOP,
+		   GTK_ANCHOR_CENTER, GTK_FILL_X, 0, 0, 0, 0, 50);
+  }
+
+  // Add "Song message" section
+  if(!p->getdesc().empty()) {
+    GtkScrolledWindow *msgwnd = GTK_SCROLLED_WINDOW(gtk_scrolled_window_new(NULL, NULL));
+    GtkText *msg = GTK_TEXT(gtk_text_new(NULL, NULL));
+    gint pos = 0;
+
+    gtk_scrolled_window_set_policy(msgwnd, GTK_POLICY_AUTOMATIC,
+				   GTK_POLICY_AUTOMATIC);
+    gtk_text_set_editable(msg, FALSE);
+    gtk_text_set_word_wrap(msg, TRUE);
+    //    gtk_text_set_line_wrap(msg, TRUE);
+    gtk_editable_insert_text(GTK_EDITABLE(msg), p->getdesc().c_str(),
+			     p->getdesc().length(), &pos);
+
+    gtk_container_add(GTK_CONTAINER(msgwnd), GTK_WIDGET(msg));
+    gtk_packer_add(packer, make_framed(GTK_WIDGET(msgwnd), "Song message"),
+		   GTK_SIDE_TOP, GTK_ANCHOR_CENTER, GTK_FILL_X, 2, 0, 0, 200, 50);
+  }
+
+  // Add subsong slider section
+  if(p == plr.p && p->getsubsongs() > 1) {
+    GtkAdjustment *adj = GTK_ADJUSTMENT(gtk_adjustment_new(plr.subsong + 1, 1,
+							   p->getsubsongs() + 1,
+							   1, 5, 1));
+    GtkHScale *slider = GTK_HSCALE(gtk_hscale_new(adj));
+
+    gtk_signal_connect(GTK_OBJECT(adj), "value_changed",
+		       GTK_SIGNAL_FUNC(subsong_slider), NULL);
+    gtk_range_set_update_policy(GTK_RANGE(slider), GTK_UPDATE_DISCONTINUOUS);
+    gtk_scale_set_digits(GTK_SCALE(slider), 0);
+    gtk_packer_add_defaults(packer, make_framed(GTK_WIDGET(slider), "Subsong selection"),
+			    GTK_SIDE_TOP, GTK_ANCHOR_CENTER, GTK_FILL_X);
+  }
+
+  // Show dialog box
+  gtk_widget_show_all(GTK_WIDGET(infobox));
+  if(p == plr.p) { // Remember widget, so we could destroy it later
+    plr.infodlg = infobox;
+  } else // Delete temporary player
+    delete p;
+}
+
+/***** Main player (!! threaded !!) *****/
+
+static void update_infobox(void)
+{
+  std::ostringstream infotext;
+
+  // Recreate info string
+  infotext << "Order: " << plr.p->getorder() << " / " << plr.p->getorders() <<
+    std::endl << "Pattern: " << plr.p->getpattern() << " / " <<
+    plr.p->getpatterns() << std::endl << "Row: " << plr.p->getrow() <<
+    std::endl << "Speed: " << plr.p->getspeed() << std::endl << "Timer: " <<
+    plr.p->getrefresh() << "Hz" << std::ends;
+
+  GDK_THREADS_ENTER();
+  gtk_label_set_text(plr.infobox, infotext.str().c_str());
+  GDK_THREADS_LEAVE();
+}
+#endif
+
+// Define sampsize macro (only usable inside play_loop()!)
+#define sampsize ((bit16 ? 2 : 1) * (stereo ? 2 : 1))
+
+static void *play_loop(void *filename)
+/* Main playback thread. Takes the filename to play as argument. */
+{
+  dbg_printf("play_loop(\"%s\"): ", (char *)filename);
+  CEmuopl opl(cfg.freq, cfg.bit16, cfg.stereo);
+  long toadd = 0, i, towrite;
+  char *sndbuf, *sndbufpos;
+  bool playing = true,		// Song self-end indicator.
+    bit16 = cfg.bit16,		// Duplicate config, so it doesn't affect us if
+    stereo = cfg.stereo;	// the user changes it while we're playing.
+  unsigned long freq = cfg.freq;
+
+  // Try to load module
+  dbg_printf("factory, ");
+  if(!(plr.p = factory((char *)filename, &opl))) {
+    dbg_printf("error!\n");
+    MessageBox("AdPlug :: Error", "File could not be opened!", "Ok");
+    plr.playing = false;
+    g_thread_exit(NULL);
+  }
+
+  // Cache song length
+  dbg_printf("length, ");
+  plr.songlength = plr.p->songlength(plr.subsong);
+
+  // cache song title
+  dbg_printf("title, ");
+  if(!plr.p->gettitle().empty()) {
+    plr.songtitle = (char *)malloc(plr.p->gettitle().length() + 1);
+    strcpy(plr.songtitle, plr.p->gettitle().c_str());
+  }
+
+  // reset to first subsong on new file
+  dbg_printf("subsong, ");
+  if(strcmp((char *)filename, plr.filename)) {
+    strcpy(plr.filename, (char *)filename);
+    plr.subsong = 0;
+  }
+
+  // Allocate audio buffer
+  dbg_printf("buffer, ");
+  sndbuf = (char *)malloc(SNDBUFSIZE * sampsize);
+
+  // Set XMMS main window information
+  dbg_printf("xmms, ");
+  adplug_ip.set_info(plr.songtitle, plr.songlength, freq * sampsize * 8,
+		     freq, stereo ? 2 : 1);
+
+  // Rewind player to right subsong
+  dbg_printf("rewind, ");
+  plr.p->rewind(plr.subsong);
+
+  // main playback loop
+  dbg_printf("loop.\n");
+  while((playing || cfg.endless) && plr.playing) {
+    // seek requested ?
+    if(plr.seek != -1) {
+      // backward seek ?
+      if(plr.seek < plr.time_ms) {
+        plr.p->rewind(plr.subsong);
+        plr.time_ms = 0.0f;
+      }
+
+      // seek to requested position
+      while((plr.time_ms < plr.seek) && plr.p->update())
+        plr.time_ms += 1000 / plr.p->getrefresh();
+
+      // Reset output plugin and some values
+      adplug_ip.output->flush((int)plr.time_ms);
+      plr.seek = -1;
+    }
+
+    // fill sound buffer
+    towrite = SNDBUFSIZE; sndbufpos = sndbuf;
+    while (towrite > 0) {
+      while (toadd < 0) {
+        toadd += freq;
+	playing = plr.p->update();
+        plr.time_ms += 1000 / plr.p->getrefresh();
+      }
+      i = MIN(towrite, (long)(toadd / plr.p->getrefresh() + 4) & ~3);
+      opl.update((short *)sndbufpos, i);
+      sndbufpos += i * sampsize; towrite -= i;
+      toadd -= (long)(plr.p->getrefresh() * i);
+    }
+
+    // write sound buffer and update vis
+    adplug_ip.add_vis_pcm(adplug_ip.output->written_time(),
+			  bit16 ? FORMAT_16 : FORMAT_8,
+			  stereo ? 2 : 1, SNDBUFSIZE * sampsize, sndbuf);
+    while(adplug_ip.output->buffer_free() < SNDBUFSIZE * sampsize) xmms_usleep(10000);
+    adplug_ip.output->write_audio(sndbuf, SNDBUFSIZE * sampsize);
+
+#if 0
+    // update infobox, if necessary
+    if(plr.infobox && plr.playing) update_infobox();
+#endif
+  }
+
+  // playback finished - deinit
+  dbg_printf("play_loop(\"%s\"): ", (char *)filename);
+  if(!playing) { // wait for output plugin to finish if song has self-ended
+    dbg_printf("wait, ");
+    while(adplug_ip.output->buffer_playing()) xmms_usleep(10000);
+  } else { // or else, flush its output buffers
+    dbg_printf("flush, ");
+    adplug_ip.output->buffer_free(); adplug_ip.output->buffer_free();
+  }
+
+  // free everything and exit
+  dbg_printf("free");
+  delete plr.p; plr.p = 0;
+  if(plr.songtitle) { free(plr.songtitle); plr.songtitle = NULL; }
+  free(sndbuf);
+  plr.playing = false; // important! XMMS won't get a self-ended song without it.
+  dbg_printf(".\n");
+  g_thread_exit(NULL);
+}
+
+// sampsize macro not useful anymore.
+#undef sampsize
+
+/***** Informational *****/
+
+static int adplug_is_our_file(char *filename)
+{
+  CSilentopl tmpopl;
+  CPlayer *p = factory(filename,&tmpopl);
+
+  dbg_printf("adplug_is_our_file(\"%s\"): returned ",filename);
+
+  if(p) {
+    delete p;
+    dbg_printf("TRUE\n");
+    return TRUE;
+  }
+
+  dbg_printf("FALSE\n");
+  return FALSE;
+}
+
+static int adplug_get_time(void)
+{
+  if(audio_error) { dbg_printf("adplug_get_time(): returned -2\n"); return -2; }
+  if(!plr.playing) { dbg_printf("adplug_get_time(): returned -1\n"); return -1; }
+  return adplug_ip.output->output_time();
+}
+
+static void adplug_song_info(char *filename, char **title, int *length)
+{
+  CSilentopl tmpopl;
+  CPlayer *p = factory(filename, &tmpopl);
+
+  dbg_printf("adplug_song_info(\"%s\", \"%s\", %d): ", filename, *title, *length);
+
+  if(p) {
+    // allocate and set title string
+    if(p->gettitle().empty())
+      *title = 0;
+    else {
+      *title = (char *)malloc(p->gettitle().length() + 1);
+      strcpy(*title, p->gettitle().c_str());
+    }
+
+    // get song length
+    *length = p->songlength(plr.subsong);
+
+    // delete temporary player object
+    delete p;
+  }
+
+  dbg_printf("title = \"%s\", length = %d\n", *title, *length);
+}
+
+/***** Player control *****/
+
+static void adplug_play(char *filename)
+{
+  dbg_printf("adplug_play(\"%s\"): ", filename);
+  audio_error = FALSE;
+
+#if 0
+  // On new song, re-open "Song info" dialog, if open
+  dbg_printf("dialog, ");
+  if(plr.infobox && strcmp(filename, plr.filename))
+    gtk_widget_destroy(GTK_WIDGET(plr.infodlg));
+#endif
+
+  // open output plugin
+  dbg_printf("open, ");
+  if (!adplug_ip.output->open_audio(cfg.bit16 ? FORMAT_16 : FORMAT_8, cfg.freq, cfg.stereo ? 2 : 1)) {
+    audio_error = TRUE;
+    return;
+  }
+
+  // Initialize global player data (this is here to prevent a race condition
+  // between adplug_get_time() returning the playback state and adplug_loop()
+  // initializing the playback state)
+  dbg_printf("init, ");
+  plr.playing = true; plr.time_ms = 0.0f; plr.seek = -1;
+
+  // start player thread
+  dbg_printf("create");
+  plr.play_thread = g_thread_create(play_loop, filename, TRUE, NULL);
+  dbg_printf(".\n");
+}
+
+static void adplug_stop(void)
+{
+  dbg_printf("adplug_stop(): join, ");
+  plr.playing = false; g_thread_join(plr.play_thread); // stop player thread
+  dbg_printf("close"); adplug_ip.output->close_audio();
+  dbg_printf(".\n");
+}
+
+static void adplug_pause(short paused)
+{
+  dbg_printf("adplug_pause(%d)\n", paused);
+  adplug_ip.output->pause(paused);
+}
+
+static void adplug_seek(int time)
+{
+  dbg_printf("adplug_seek(%d)\n", time);
+  plr.seek = time * 1000; // time is in seconds, but we count in ms
+}
+
+/***** Configuration file handling *****/
+
+#define CFG_VERSION "AdPlug"
+
+static void adplug_init(void)
+{
+  dbg_printf("adplug_init(): open, ");
+  ConfigDb *db = bmp_cfg_db_open();
+
+  // Read configuration
+  dbg_printf("read, ");
+  bmp_cfg_db_get_bool(db, CFG_VERSION, "16bit", (gboolean *)&cfg.bit16);
+  bmp_cfg_db_get_bool(db, CFG_VERSION, "Stereo", (gboolean *)&cfg.stereo);
+  bmp_cfg_db_get_int(db, CFG_VERSION, "Frequency", (gint *)&cfg.freq);
+  bmp_cfg_db_get_bool(db, CFG_VERSION, "Endless", (gboolean *)&cfg.endless);
+  bmp_cfg_db_get_bool(db, CFG_VERSION, "QuickDetect", (gboolean *)&cfg.quickdetect);
+
+  // Read file type exclusion list
+  dbg_printf("exclusion, ");
+  {
+    gchar *cfgstr = "", *exclude;
+    gboolean cfgread;
+
+    cfgread = bmp_cfg_db_get_string(db, CFG_VERSION, "Exclude", &cfgstr);
+    exclude = (char *)malloc(strlen(cfgstr) + 2); strcpy(exclude, cfgstr);
+    exclude[strlen(exclude) + 1] = '\0';
+    if(cfgread) free(cfgstr);
+    g_strdelimit(exclude, ":", '\0');
+    for(gchar *p = exclude; *p; p += strlen(p) + 1)
+      cfg.players.remove(cfg.players.lookup_filetype(p));
+    free(exclude);
+  }
+  bmp_cfg_db_close(db);
+
+  // Load database from disk and hand it to AdPlug
+  dbg_printf("database");
+  plr.db = new CAdPlugDatabase;
+
+  {
+    const char *homedir = getenv("HOME");
+
+    if(homedir) {
+      char *userdb = (char *)malloc(strlen(homedir) + strlen(ADPLUG_CONFDIR) +
+				    strlen(ADPLUGDB_FILE) + 3);
+      strcpy(userdb, homedir); strcat(userdb, "/" ADPLUG_CONFDIR "/");
+      strcat(userdb, ADPLUGDB_FILE);
+      plr.db->load(userdb);		// load user's database
+      dbg_printf(" (userdb=\"%s\")", userdb);
+    }
+  }
+  CAdPlug::set_database(plr.db);
+  dbg_printf(".\n");
+}
+
+static void adplug_quit(void)
+{
+  dbg_printf("adplug_quit(): open, ");
+  ConfigDb *db = bmp_cfg_db_open();
+
+  // Close database
+  dbg_printf("db, ");
+  if(plr.db) delete plr.db;
+
+  // Write configuration
+  dbg_printf("write, ");
+  bmp_cfg_db_set_bool(db, CFG_VERSION, "16bit", cfg.bit16);
+  bmp_cfg_db_set_bool(db, CFG_VERSION, "Stereo", cfg.stereo);
+  bmp_cfg_db_set_int(db, CFG_VERSION, "Frequency", cfg.freq);
+  bmp_cfg_db_set_bool(db, CFG_VERSION, "Endless", cfg.endless);
+  bmp_cfg_db_set_bool(db, CFG_VERSION, "QuickDetect", cfg.quickdetect);
+
+  dbg_printf("exclude, ");
+  std::string exclude;
+  for(CPlayers::const_iterator i = CAdPlug::players.begin();
+      i != CAdPlug::players.end(); i++)
+    if(find(cfg.players.begin(), cfg.players.end(), *i) == cfg.players.end()) {
+      if(!exclude.empty()) exclude += ":";
+      exclude += (*i)->filetype;
+    }
+  gchar *cfgval = g_strdup(exclude.c_str());
+  bmp_cfg_db_set_string(db, CFG_VERSION, "Exclude", cfgval);
+  free(cfgval);
+
+  dbg_printf("close");
+  bmp_cfg_db_close(db);
+  dbg_printf(".\n");
+}
+
+/***** Plugin (exported) *****/
+
+InputPlugin adplug_ip =
+  {
+    NULL,                       // handle (filled by XMMS)
+    NULL,                       // filename (filled by XMMS)
+    ADPLUG_NAME,        // plugin description
+    adplug_init,                // plugin functions...
+    adplug_about,
+    adplug_config,
+    adplug_is_our_file,
+    NULL, // scan_dir (look in Input/cdaudio/cdaudio.c)
+    adplug_play,
+    adplug_stop,
+    adplug_pause,
+    adplug_seek,
+    NULL, // set_eq
+    adplug_get_time,
+    NULL,                       // get_volume (handled by output plugin)
+    NULL,                       // set_volume (...)
+    adplug_quit,
+    NULL,                       // OBSOLETE - DO NOT USE!
+    NULL,                       // add_vis_pcm (filled by XMMS)
+    NULL,                       // set_info (filled by XMMS)
+    NULL,                       // set_info_text (filled by XMMS)
+    adplug_song_info,
+    NULL,                       // adplug_info_box was here (but it used deprecated GTK+ functions)
+    NULL                        // output plugin (filled by XMMS)
+  };
+
+extern "C" InputPlugin *get_iplugin_info(void)
+{
+  return &adplug_ip;
+}