view audacious/input.c @ 1938:1d9c1026d9f8 trunk

[svn] - DoubleSize support. This has bugs, the most notable one being that DoubleSize only works right if you restart the player. The second bug is rather obvious too. No osmosis skinengine. No TinyPlayer. Classic-esque skinengine only. This is because the doublesize algorithm hates you and wants you to go die in a fire.
author nenolod
date Sun, 05 Nov 2006 04:43:16 -0800
parents c186ee9524ed
children c2a63f41d8c6
line wrap: on
line source

/*  BMP - Cross-platform multimedia player
 *  Copyright (C) 2003-2004  BMP development team.
 *
 *  Based on XMMS:
 *  Copyright (C) 1998-2003  XMMS development team.
 *
 *  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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include <glib.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include <string.h>

#include "fft.h"
#include "input.h"
#include "main.h"
#include "mainwin.h"
#include "output.h"
#include "util.h"
#include "visualization.h"
#include "playback.h"
#include "widgets/widgetcore.h"
#include "pluginenum.h"
#include "titlestring.h"

#include "libaudacious/util.h"
#include "libaudacious/xentry.h"

G_LOCK_DEFINE_STATIC(vis_mutex);

struct _VisNode {
    gint time;
    gint nch;
    gint length;                /* number of samples per channel */
    gint16 data[2][512];
};

typedef struct _VisNode VisNode;


InputPluginData ip_data = {
    NULL,
    NULL,
    FALSE,
    FALSE,
    FALSE,
    FALSE,
    NULL
};

static GList *vis_list = NULL;

gchar *input_info_text = NULL;

InputPlugin *
get_current_input_plugin(void)
{
    return ip_data.current_input_plugin;
}

void
set_current_input_plugin(InputPlugin * ip)
{
    ip_data.current_input_plugin = ip;
}

GList *
get_input_list(void)
{
    return ip_data.input_list;
}


gboolean
input_is_enabled(const gchar * filename)
{
    gchar *basename = g_path_get_basename(filename);
    gint enabled;

    enabled = GPOINTER_TO_INT(g_hash_table_lookup(plugin_matrix, basename));
    g_free(basename);

    return enabled;
}

static void
disabled_iplugins_foreach_func(const gchar * name,
                               gboolean enabled,
                               GString * list)
{
    g_return_if_fail(list != NULL);

    if (enabled)
        return;

    if (list->len > 0)
        g_string_append(list, ":");

    g_string_append(list, name);
}

gchar *
input_stringify_disabled_list(void)
{
    GString *disabled_list;

    disabled_list = g_string_new("");
    g_hash_table_foreach(plugin_matrix,
                         (GHFunc) disabled_iplugins_foreach_func,
                         disabled_list);

    return g_string_free(disabled_list, FALSE);
}

void
free_vis_data(void)
{
    G_LOCK(vis_mutex);
    g_list_foreach(vis_list, (GFunc) g_free, NULL);
    g_list_free(vis_list);
    vis_list = NULL;
    G_UNLOCK(vis_mutex);
}

static void
convert_to_s16_ne(AFormat fmt, gpointer ptr, gint16 * left,
                  gint16 * right, gint nch, gint max)
{
    gint16 *ptr16;
    guint16 *ptru16;
    guint8 *ptru8;
    gint i;

    switch (fmt) {
    case FMT_U8:
        ptru8 = ptr;
        if (nch == 1)
            for (i = 0; i < max; i++)
                left[i] = ((*ptru8++) ^ 128) << 8;
        else
            for (i = 0; i < max; i++) {
                left[i] = ((*ptru8++) ^ 128) << 8;
                right[i] = ((*ptru8++) ^ 128) << 8;
            }
        break;
    case FMT_S8:
        ptru8 = ptr;
        if (nch == 1)
            for (i = 0; i < max; i++)
                left[i] = (*ptru8++) << 8;
        else
            for (i = 0; i < max; i++) {
                left[i] = (*ptru8++) << 8;
                right[i] = (*ptru8++) << 8;
            }
        break;
    case FMT_U16_LE:
        ptru16 = ptr;
        if (nch == 1)
            for (i = 0; i < max; i++, ptru16++)
                left[i] = GUINT16_FROM_LE(*ptru16) ^ 32768;
        else
            for (i = 0; i < max; i++) {
                left[i] = GUINT16_FROM_LE(*ptru16) ^ 32768;
                ptru16++;
                right[i] = GUINT16_FROM_LE(*ptru16) ^ 32768;
                ptru16++;
            }
        break;
    case FMT_U16_BE:
        ptru16 = ptr;
        if (nch == 1)
            for (i = 0; i < max; i++, ptru16++)
                left[i] = GUINT16_FROM_BE(*ptru16) ^ 32768;
        else
            for (i = 0; i < max; i++) {
                left[i] = GUINT16_FROM_BE(*ptru16) ^ 32768;
                ptru16++;
                right[i] = GUINT16_FROM_BE(*ptru16) ^ 32768;
                ptru16++;
            }
        break;
    case FMT_U16_NE:
        ptru16 = ptr;
        if (nch == 1)
            for (i = 0; i < max; i++)
                left[i] = (*ptru16++) ^ 32768;
        else
            for (i = 0; i < max; i++) {
                left[i] = (*ptru16++) ^ 32768;
                right[i] = (*ptru16++) ^ 32768;
            }
        break;
    case FMT_S16_LE:
        ptr16 = ptr;
        if (nch == 1)
            for (i = 0; i < max; i++, ptr16++)
                left[i] = GINT16_FROM_LE(*ptr16);
        else
            for (i = 0; i < max; i++) {
                left[i] = GINT16_FROM_LE(*ptr16);
                ptr16++;
                right[i] = GINT16_FROM_LE(*ptr16);
                ptr16++;
            }
        break;
    case FMT_S16_BE:
        ptr16 = ptr;
        if (nch == 1)
            for (i = 0; i < max; i++, ptr16++)
                left[i] = GINT16_FROM_BE(*ptr16);
        else
            for (i = 0; i < max; i++) {
                left[i] = GINT16_FROM_BE(*ptr16);
                ptr16++;
                right[i] = GINT16_FROM_BE(*ptr16);
                ptr16++;
            }
        break;
    case FMT_S16_NE:
        ptr16 = ptr;
        if (nch == 1)
            for (i = 0; i < max; i++)
                left[i] = (*ptr16++);
        else
            for (i = 0; i < max; i++) {
                left[i] = (*ptr16++);
                right[i] = (*ptr16++);
            }
        break;
    }
}

InputVisType
input_get_vis_type()
{
    return INPUT_VIS_OFF;
}

void
input_add_vis(gint time, guchar * s, InputVisType type)
{
    g_warning("plugin uses obsoleted input_add_vis()");
}

void
input_add_vis_pcm(gint time, AFormat fmt, gint nch, gint length, gpointer ptr)
{
    VisNode *vis_node;
    gint max;

    max = length / nch;
    if (fmt == FMT_U16_LE || fmt == FMT_U16_BE || fmt == FMT_U16_NE ||
        fmt == FMT_S16_LE || fmt == FMT_S16_BE || fmt == FMT_S16_NE)
        max /= 2;
    max = CLAMP(max, 0, 512);

    vis_node = g_new0(VisNode, 1);
    vis_node->time = time;
    vis_node->nch = nch;
    vis_node->length = max;
    convert_to_s16_ne(fmt, ptr, vis_node->data[0], vis_node->data[1], nch,
                      max);

    G_LOCK(vis_mutex);
    vis_list = g_list_append(vis_list, vis_node);
    G_UNLOCK(vis_mutex);
}

void
input_dont_show_warning(GtkObject * object, gpointer user_data)
{
    *((gboolean *) user_data) =
        !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(object));
}


void
input_show_unplayable_files(const gchar * filename)
{
    static GtkWidget *dialog = NULL;
    static GtkListStore *store = NULL;

    const gchar *markup = 
        N_("<b><big>Unable to play files.</big></b>\n\n"
           "The following files could not be played. Please check that:\n"
           "1. they are accessible.\n"
           "2. you have enabled the media plugins required.");

    GtkTreeIter iter;

    gchar *filename_utf8;

    if (!dialog) {
        GtkWidget *vbox, *check;
        GtkWidget *expander;
        GtkWidget *scrolled, *treeview;
        GtkCellRenderer *renderer;

        dialog =
            gtk_message_dialog_new_with_markup(GTK_WINDOW(mainwin),
                                               GTK_DIALOG_DESTROY_WITH_PARENT,
                                               GTK_MESSAGE_ERROR,
                                               GTK_BUTTONS_OK,
                                               _(markup));

        vbox = gtk_vbox_new(FALSE, 6);

        check = gtk_check_button_new_with_label
                  (_("Don't show this warning anymore"));

        expander = gtk_expander_new_with_mnemonic(_("Show more _details"));

        scrolled = gtk_scrolled_window_new(NULL, NULL);
        gtk_container_add(GTK_CONTAINER(expander), scrolled);

        store = gtk_list_store_new(1, G_TYPE_STRING);

        treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
        gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview), FALSE);
        gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled),
                                              treeview);
        
        renderer = gtk_cell_renderer_text_new();
        gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(treeview), 
                                                    -1, _("Filename"),
                                                    renderer,
                                                    "text", 0, 
                                                    NULL);

        vbox = GTK_DIALOG(dialog)->vbox;
        gtk_box_pack_start(GTK_BOX(vbox), check, FALSE, FALSE, 0);
        gtk_box_pack_start(GTK_BOX(vbox), expander, TRUE, TRUE, 0);

        g_signal_connect(dialog, "response",
                         G_CALLBACK(gtk_widget_destroy),
                         dialog);
        g_signal_connect(dialog, "destroy",
                         G_CALLBACK(gtk_widget_destroyed),
                         &dialog);
        g_signal_connect(check, "clicked",
                         G_CALLBACK(input_dont_show_warning),
                         &cfg.warn_about_unplayables);

        gtk_widget_show_all(dialog);
    }

    gtk_window_present(GTK_WINDOW(dialog));

    filename_utf8 = filename_to_utf8(filename);
    gtk_list_store_append(store, &iter);
    gtk_list_store_set(store, &iter, 0, filename_utf8, -1);
    g_free(filename_utf8);
}


void
input_file_not_playable(const gchar * filename)
{
    if (cfg.warn_about_unplayables)
        input_show_unplayable_files(filename);
}

/*
 * input_check_file()
 *
 * Inputs:
 *       filename to check recursively against input plugins
 *       whether or not to show an error
 *
 * Outputs:
 *       pointer to input plugin which can handle this file
 *       otherwise, NULL
 *
 *       (the previous code returned a boolean of whether or not we can
 *        play the file... even WORSE for performance)
 *
 * Side Effects:
 *       various input plugins open the file and probe it
 *       -- this can have very ugly effects performance wise on streams
 *
 * --nenolod, Dec 31 2005
 */
InputPlugin *
input_check_file(const gchar * filename, gboolean show_warning)
{
    GList *node;
    InputPlugin *ip;
    gchar *filename_proxy;
    gint ret = 1;

    filename_proxy = g_strdup(filename);

    for (node = get_input_list(); node != NULL; node = g_list_next(node)) {
        ip = INPUT_PLUGIN(node->data);
        if (ip && input_is_enabled(ip->filename) &&
            (ret = ip->is_our_file(filename_proxy)) > 0) {
            g_free(filename_proxy);
            return ip;
        }
	else if (ret <= -1)
	    break;
    }

    g_free(filename_proxy);

    if (show_warning && !(ret <= -1)) {
        input_file_not_playable(filename);
    }

    return NULL;
}


void
input_set_eq(gint on, gfloat preamp, gfloat * bands)
{
    if (!ip_data.playing)
        return;

    if (!get_current_input_plugin())
        return;

    if (get_current_input_plugin()->set_eq)
        get_current_input_plugin()->set_eq(on, preamp, bands);
}

void
input_get_song_info(const gchar * filename, gchar ** title, gint * length)
{
    InputPlugin *ip = NULL;
    BmpTitleInput *input;
    GList *node;
    gchar *tmp = NULL, *ext;
    gchar *filename_proxy;

    g_return_if_fail(filename != NULL);
    g_return_if_fail(title != NULL);
    g_return_if_fail(length != NULL);

    filename_proxy = g_strdup(filename);

    for (node = get_input_list(); node != NULL; node = g_list_next(node)) {
        ip = INPUT_PLUGIN(node->data);
        if (input_is_enabled(ip->filename) && ip->is_our_file(filename_proxy))
            break;
    }

    if (ip && node && ip->get_song_info) {
        ip->get_song_info(filename_proxy, &tmp, length);
        *title = str_to_utf8(tmp);
        g_free(tmp);
    }
    else {
        input = bmp_title_input_new();

        tmp = g_strdup(filename);
        if ((ext = strrchr(tmp, '.')))
            *ext = '\0';

        input->file_name = g_path_get_basename(tmp);
        input->file_ext = ext ? ext + 1 : NULL;
        input->file_path = tmp;

        if ((tmp = xmms_get_titlestring(xmms_get_gentitle_format(), input))) {
            (*title) = str_to_utf8(tmp);
            g_free(tmp);
        }
        else {
            (*title) = filename_to_utf8(input->file_name);
        }

        (*length) = -1;

        bmp_title_input_free(input);
        input = NULL;
    }

    g_free(filename_proxy);
}

TitleInput *
input_get_song_tuple(const gchar * filename)
{
    InputPlugin *ip = NULL;
    TitleInput *input;
    GList *node;
    gchar *tmp = NULL, *ext;
    gchar *filename_proxy;

    if (filename == NULL)
	return NULL;

    filename_proxy = g_strdup(filename);

    for (node = get_input_list(); node != NULL; node = g_list_next(node)) {
        ip = INPUT_PLUGIN(node->data);
        if (input_is_enabled(ip->filename) && ip->is_our_file(filename_proxy))
            break;
    }

    if (ip && node && ip->get_song_tuple)
        input = ip->get_song_tuple(filename_proxy);
    else {
        input = bmp_title_input_new();

        tmp = g_strdup(filename);
        if ((ext = strrchr(tmp, '.')))
            *ext = '\0';

	input->track_name = NULL;
	input->length = -1;
	input_get_song_info(filename, &input->track_name, &input->length);
        input->file_name = g_path_get_basename(tmp);
        input->file_ext = ext ? ext + 1 : NULL;
        input->file_path = tmp;
    }

    return input;
}

static void
input_general_file_info_box(const gchar * filename, InputPlugin * ip)
{
    GtkWidget *window, *vbox;
    GtkWidget *label, *filename_hbox, *filename_entry;
    GtkWidget *bbox, *cancel;

    gchar *title, *fileinfo, *basename, *iplugin;
    gchar *filename_utf8;

    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_resizable(GTK_WINDOW(window), FALSE);
    gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);

    basename = g_path_get_basename(filename);
    fileinfo = filename_to_utf8(basename);
    title = g_strdup_printf(_("audacious: %s"), fileinfo);

    gtk_window_set_title(GTK_WINDOW(window), title);

    g_free(title);
    g_free(fileinfo);
    g_free(basename);

    gtk_container_set_border_width(GTK_CONTAINER(window), 10);

    vbox = gtk_vbox_new(FALSE, 10);
    gtk_container_add(GTK_CONTAINER(window), vbox);

    filename_hbox = gtk_hbox_new(FALSE, 5);
    gtk_box_pack_start(GTK_BOX(vbox), filename_hbox, FALSE, TRUE, 0);

    label = gtk_label_new(_("Filename:"));
    gtk_box_pack_start(GTK_BOX(filename_hbox), label, FALSE, TRUE, 0);

    filename_entry = xmms_entry_new();
    filename_utf8 = filename_to_utf8(filename);

    gtk_entry_set_text(GTK_ENTRY(filename_entry), filename_utf8);
    gtk_editable_set_editable(GTK_EDITABLE(filename_entry), FALSE);
    gtk_box_pack_start(GTK_BOX(filename_hbox), filename_entry, TRUE, TRUE, 0);

    g_free(filename_utf8);

    if (ip)
        if (ip->description)
            iplugin = ip->description;
        else
            iplugin = ip->filename;
    else
        iplugin = _("No input plugin recognized this file");

    title = g_strdup_printf(_("Input plugin: %s"), iplugin);

    label = gtk_label_new(title);
    gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
    gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
    g_free(title);
    gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, 0);

    bbox = gtk_hbutton_box_new();
    gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
    gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, FALSE, 0);

    cancel = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
    g_signal_connect_swapped(G_OBJECT(cancel), "clicked",
                             GTK_SIGNAL_FUNC(gtk_widget_destroy),
                             GTK_OBJECT(window));
    GTK_WIDGET_SET_FLAGS(cancel, GTK_CAN_DEFAULT);
    gtk_box_pack_start(GTK_BOX(bbox), cancel, TRUE, TRUE, 0);

    gtk_widget_show_all(window);
}

void
input_file_info_box(const gchar * filename)
{
    GList *node;
    InputPlugin *ip;
    gchar *filename_proxy;

    filename_proxy = g_strdup(filename);

    for (node = get_input_list(); node != NULL; node = g_list_next(node)) {
        ip = INPUT_PLUGIN(node->data);
        if (input_is_enabled(ip->filename)
            && ip->is_our_file(filename_proxy)) {
            if (ip->file_info_box)
                ip->file_info_box(filename_proxy);
            else
                input_general_file_info_box(filename, ip);

            g_free(filename_proxy);
            return;
        }
    }

    input_general_file_info_box(filename, NULL);
    g_free(filename_proxy);
}

GList *
input_scan_dir(const gchar * path)
{
    GList *node, *result = NULL;
    InputPlugin *ip;
    gchar *path_proxy;

    g_return_val_if_fail(path != NULL, NULL);

    if (*path == '/')
        while (path[1] == '/')
            path++;

    path_proxy = g_strdup(path);

    for (node = get_input_list(); node; node = g_list_next(node)) {
        ip = INPUT_PLUGIN(node->data);

        if (!ip)
            continue;

        if (!ip->scan_dir)
            continue;

        if (!input_is_enabled(ip->filename))
            continue;

        if ((result = ip->scan_dir(path_proxy)))
            break;
    }

    g_free(path_proxy);

    return result;
}

void
input_get_volume(gint * l, gint * r)
{
    *l = -1;
    *r = -1;
    if (bmp_playback_get_playing()) {
        if (get_current_input_plugin() &&
            get_current_input_plugin()->get_volume) {
            get_current_input_plugin()->get_volume(l, r);
            return;
        }
    }
    output_get_volume(l, r);
}

void
input_set_volume(gint l, gint r)
{
    if (bmp_playback_get_playing()) {
        if (get_current_input_plugin() &&
            get_current_input_plugin()->set_volume) {
            get_current_input_plugin()->set_volume(l, r);
            return;
        }
    }
    output_set_volume(l, r);
}

void
input_update_vis(gint time)
{
    GList *node;
    VisNode *vis = NULL, *visnext = NULL;
    gboolean found = FALSE;

    G_LOCK(vis_mutex);
    node = vis_list;
    while (g_list_next(node) && !found) {
        visnext = g_list_next(node)->data;
        vis = node->data;

        if (vis->time >= time)
            break;

        vis_list = g_list_delete_link(vis_list, node);

        if (visnext->time >= time) {
            found = TRUE;
            break;
        }
        g_free(vis);
        node = vis_list;
    }
    G_UNLOCK(vis_mutex);

    if (found) {
        vis_send_data(vis->data, vis->nch, vis->length);
        g_free(vis);
    }
    else
        vis_send_data(NULL, 0, 0);
}


gchar *
input_get_info_text(void)
{
    return g_strdup(input_info_text);
}

void
input_set_info_text(const gchar * text)
{
    g_free(input_info_text);
    input_info_text = g_strdup(text);
    mainwin_set_info_text();
}

void
input_set_status_buffering(gboolean status)
{
    if (!bmp_playback_get_playing())
        return;

    if (!get_current_input_plugin())
        return;

    ip_data.buffering = status;

    g_return_if_fail(mainwin_playstatus != NULL);

    if (ip_data.buffering == TRUE && mainwin_playstatus != NULL && mainwin_playstatus->ps_status == STATUS_STOP)
        mainwin_playstatus->ps_status = STATUS_PLAY;

    playstatus_set_status_buffering(mainwin_playstatus, ip_data.buffering);
}

void
input_about(gint index)
{
    InputPlugin *ip;

    ip = g_list_nth(ip_data.input_list, index)->data;
    if (ip && ip->about)
        ip->about();
}

void
input_configure(gint index)
{
    InputPlugin *ip;

    ip = g_list_nth(ip_data.input_list, index)->data;
    if (ip && ip->configure)
        ip->configure();
}