Logo Search packages:      
Sourcecode: mas version File versions

player.c

/*
 * Copyright (c) 2001-2003 Shiman Associates Inc. All Rights Reserved.
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <regex.h>
#include <errno.h>
#include <dirent.h>
#include <gtk/gtk.h>
#include "mas/mas.h"
#include "mas/mas_core.h"
#include "visual_config.h"

#define DB_CUTOFF -40.0
#define DEFAULT_BUFFER_TIME_MS 300
#define QUERY_MIX_VOLUME 0
#define QUERY_MIX_EPSILON 5

/* we shouldn't enforce sizes by default;
 * on the other hand, this will look good in 90% of cases as opposed
 * to 0% if we don't */

#define WIN_WIDTH  500
#define WIN_HEIGHT 300
#define LEFT_POSITION 370

#define NORMAL_COLOR "white"
#define HILITE_COLOR "pale green"


#define M_IMAGE "/usr/local/mas/share/pixmaps/M_80px_icon.png"

/* oops someone already had that idea (mas_constants.h)  */
/* #define MAX_FNAME_LENGTH 256 */

/* title artist album length genre */
enum
{
   FILENAME_COLUMN,
   SONG_COLUMN,
   ARTIST_COLUMN,
   TRACK_NO_COLUMN,
   ALBUM_COLUMN,
   TRACK_LEN_COLUMN,
   ROW_COLOR_COLUMN,
   ROW_IS_HILIT_COLUMN,
   N_COLUMNS
};

enum
{
    FILE_GETDIR,
    FILE_APPLY,
    FILE_CLOSE
};


static GtkWidget *main_window;
static GtkWidget *timelabel;
static guint timeout_handle;

static GtkListStore *liststore;
static GtkWidget *file_selector;
static GtkTreeSelection *p_select;
static mas_device_t mp1a_source_device;
static mas_device_t visual;

static mas_device_t sbuf;
static mas_device_t codec;
static mas_device_t id3_device;

static mas_device_t mix_device;
static mas_port_t mix_sink;

static mas_device_t sink_mc;
static mas_device_t source_mc;
static int32 sink_clkid;
static int32 source_clkid;
static double measured_sample_freq;

static int playing;
static int pausing;
/* having MAS play without a playlist set is very bad */
static gboolean have_playlist;

/* hack. see below */
static gboolean inserting = 0;
static gboolean insert_hack = 0;

GtkObject *adj; /* the volume slider adj; global until we see how this
                 * flies */

struct
{
    char *mp3dir;
    char **playlist;
    int n;
} config;

typedef struct
{
    char* fname;
    char* artist;
    char* title;
    char* album;
    char* comment;
    uint8 track_no;    
} id3_contents;


/* o bother! how to do this right?? */
enum
{
    PREV_BUTTON,
    STOP_BUTTON,
    PAUSE_BUTTON,
    PLAY_BUTTON,
    NEXT_BUTTON,
    PREF_BUTTON,
    N_BUTTONS
};

static const char* buttons[] = 
{
    "/usr/local/mas/share/pixmaps/previous_button.png",
    "/usr/local/mas/share/pixmaps/stop_button.png",
    "/usr/local/mas/share/pixmaps/pause_button.png",
    "/usr/local/mas/share/pixmaps/play_button.png",
    "/usr/local/mas/share/pixmaps/next_button.png",
};


/* local yokels */

static void show_file_dialog( void );
static gboolean check_selected( void );
static void setup_mas_for_mp1a( void );
static void get_stupid_config( void );
static void set_stupid_config( void );
static void set_playlist( void );
static id3_contents** fetch_id3s( gchar **fnames );
void append_to_list( id3_contents *tag );
gint elapsed_timer( gpointer o );
gint check_pos_timer( gpointer o );
void track_changed( int16 new_track );
int in_list( char **list, char *s );
char **uniquify_list( char **list );
int tag_compare( void *one, void *two );    
int string_compare( void *one, void *two );    
void sort_id3s( id3_contents ***id3_tags );
void free_tag_list( id3_contents **tags );
int filter_mp3_ending( char *d );
void import_entire_dir( char ***names );
void get_track_lengths( void );
static int32 get_measured_sample_freq( );
static int32 set_source_freq( );


/* callbacks */
static void cb_row_activated( GtkTreeView *tree_view, GtkTreePath *path,
                              GtkTreeViewColumn *column );
static gint cb_delete_event( GtkWidget *w, GdkEvent *e, gpointer o );
static gint cb_add_to_playlist( GtkWidget *w, gpointer data );
static gint cb_remove_from_playlist( GtkWidget *w, gpointer data );
static gint cb_filedialog_item_doubleclick( GtkWidget *w, gpointer data );
static void cb_response( GtkDialog *dialog, gint response_id, gpointer unused);
static void cb_visual_response( GtkDialog *dialog, gint response_id,
                                gpointer unused);
static gint cb_button_clicked( GtkWidget *w, gpointer data );
static void cb_reorder( GtkTreeModel *tree_model, GtkTreePath *path,
                        GtkTreeIter *iter, gint *new_order);
static gint cb_volume_slider_moved( GtkAdjustment *adj, gpointer o );


/****************************************************************************/

gint elapsed_timer( gpointer o )
{
    int sec;
    char a[64];

    if( !pausing )
    {
        sec = GPOINTER_TO_INT( g_object_get_data(
                                   G_OBJECT(timelabel), "sec") );
        
        ++sec;
        
        sprintf( a, "<span size='xx-large'>%02d:%02d</span>", sec/60, sec%60 );
        
        gtk_label_set_markup( GTK_LABEL(timelabel), a );
        
        g_object_set_data( G_OBJECT(timelabel), "sec", 
                           GINT_TO_POINTER(sec) );
    }
    
    return 1;
}

void stop_playing_man( void )
{
    
        mas_source_stop( mp1a_source_device );
        mas_source_stop( sbuf );

        playing = pausing = 0;

        gtk_timeout_remove( timeout_handle );
}


/* the track has changed (a song was over and mas plays the next) */
void track_changed( int16 new_track )
{
    gboolean valid, didit;
    GtkTreeIter iter;
    int i;
    
    
    /* zero the elapsed timer */
    g_object_set_data( G_OBJECT(timelabel), "sec", 
                       GINT_TO_POINTER(0) );

    
    didit = FALSE;
    valid = gtk_tree_model_get_iter_first( GTK_TREE_MODEL(liststore), &iter );

    i=0;
    while( valid )
    {
        if( ++i == new_track )
        {
/*             gtk_tree_selection_select_iter( p_select, &iter ); */

            gtk_list_store_set( liststore, &iter,
                                ROW_COLOR_COLUMN, HILITE_COLOR,
                                -1);
            gtk_list_store_set( liststore, &iter,
                                ROW_IS_HILIT_COLUMN, TRUE,
                                -1);

            didit = TRUE;
            
/*             { */
/*                 char *fname; */
/*                 gtk_tree_model_get( GTK_TREE_MODEL(liststore), */
/*                                     &iter, FILENAME_COLUMN, &fname, -1); */
/*                 system( "date" ); */
/*                 printf( "now playing: %s\n", fname ); */
/*             } */
        }
        else
        {
            gtk_list_store_set( liststore, &iter,
                                ROW_COLOR_COLUMN, NORMAL_COLOR,
                                -1);
            gtk_list_store_set( liststore, &iter,
                                ROW_IS_HILIT_COLUMN, FALSE,
                                -1);
        }
        
        valid = gtk_tree_model_iter_next( GTK_TREE_MODEL(liststore),
                                          &iter );
    }

    if( !didit )
    {
        /* we must have run off the end of the list */
        /* for now, just stop */

        stop_playing_man();
    }
}

#if QUERY_MIX_VOLUME
void check_mix_volume(void)
{
    struct mas_package pkg, ret;
    char buffer[128];
    int16 gain_db;

    masc_setup_package( &pkg, buffer, sizeof buffer, MASC_PACKAGE_STATIC );
    masc_push_int32( &pkg, mix_sink->portnum );
    masc_finalize_package( &pkg );

    mas_get( mix_device, "gain_db", &pkg, &ret );

    masc_pull_int16( &ret, &gain_db );
    
    masc_strike_package( &ret );
    masc_strike_package( &pkg );

    if( fabs( DB_CUTOFF*(10.0 - GTK_ADJUSTMENT(adj)->value/10.0) - gain_db ) > QUERY_MIX_EPSILON )
    {
        gtk_adjustment_set_value( GTK_ADJUSTMENT(adj), (gdouble)(100.0-10*gain_db/(DB_CUTOFF)) );
    }
}
#endif


gint check_pos_timer( gpointer o )
{
    struct mas_package nugget;
    int16 pos;
    int intrnl_pos;
    gboolean valid;
    GtkTreeIter iter;
    gboolean is_hilit;


#if QUERY_MIX_VOLUME
    check_mix_volume();
#endif


    /* abuse this routine for reordering synchronization */
    if( insert_hack )
    {
        insert_hack = 0;
        set_playlist();
        return 1;
    }

    
    mas_get( mp1a_source_device, "ctrack", NULL, &nugget );
    masc_pullk_int16( &nugget, "pos", &pos );
    masc_strike_package( &nugget );
    
    /*     printf("current track %d\n", pos); */
    
    valid = gtk_tree_model_get_iter_first( GTK_TREE_MODEL(liststore), &iter );

    intrnl_pos = 0;
    while( valid )
    {
        ++intrnl_pos;
        gtk_tree_model_get( GTK_TREE_MODEL(liststore),
                            &iter, ROW_IS_HILIT_COLUMN, &is_hilit, -1);
        
        if( is_hilit )
        {
            if( intrnl_pos != pos )
            {
                /* gtk_tree_selection_unselect_iter( p_select, &iter); */
                track_changed( pos );
            }
            
            /*                 printf("playlist is out of sync with MAS\n"); */
            break;
        }
        valid = gtk_tree_model_iter_next( GTK_TREE_MODEL(liststore),
                                          &iter );
    }
    
    return 1;
}

gint
adjust_sample_freq( gpointer o )
{
    get_measured_sample_freq( );
    set_source_freq( );
    return 1;
}

static void
show_file_dialog( void )
{
    GtkWidget *button;
    
    
    if( !GTK_IS_FILE_SELECTION(file_selector) )
    {
        file_selector = gtk_file_selection_new( "MASPlay" );
        
        gtk_file_selection_set_select_multiple(
            GTK_FILE_SELECTION(file_selector), TRUE );

        gtk_file_selection_hide_fileop_buttons(
            GTK_FILE_SELECTION(file_selector) );
        
        gtk_widget_hide( GTK_FILE_SELECTION(file_selector)->ok_button );

        g_signal_connect( GTK_OBJECT(
                              GTK_FILE_SELECTION(file_selector)->ok_button),
                          "clicked",
                          G_CALLBACK (cb_filedialog_item_doubleclick),
                          NULL );
        
        gtk_widget_hide( GTK_FILE_SELECTION(file_selector)->cancel_button );
        
        
/*         gtk_dialog_add_button( GTK_DIALOG(file_selector), */
/*                                "whole directory", */
/*                                FILE_GETDIR ); */
        
        button = gtk_button_new_from_stock( GTK_STOCK_APPLY );
        gtk_dialog_add_action_widget( GTK_DIALOG(file_selector),
                                      button, FILE_APPLY );
        gtk_widget_show( button );
        
        button = gtk_button_new_from_stock( GTK_STOCK_CLOSE );
        g_signal_connect_swapped( GTK_OBJECT(button),
                                  "clicked",
                                  G_CALLBACK (gtk_widget_hide),
                                  (gpointer) file_selector);
        gtk_dialog_add_action_widget( GTK_DIALOG(file_selector),
                                      button, FILE_CLOSE  );
        gtk_widget_show( button );
        
        
        if( config.mp3dir )
            gtk_file_selection_complete( GTK_FILE_SELECTION(file_selector),
                                         config.mp3dir );
    
        g_signal_connect ( GTK_OBJECT(file_selector), "response",
                           G_CALLBACK (cb_response),
                           NULL);
        
        gtk_widget_show (file_selector);
    }
    
    else
    {
        /* do already have the file selector dialog */
        gtk_widget_show( file_selector );
    }
    return;
    
}


void set_selected_pos( void )
{
    gboolean valid;
    GtkTreeIter iter;
    int pos;
    struct mas_package pkg;
    char pbuf[1024];
    
    valid = gtk_tree_model_get_iter_first( GTK_TREE_MODEL(liststore), &iter );
    
    /* a selected row has precedence */
    pos = 0;
    while( valid )
    {
        ++pos;
        if( gtk_tree_selection_iter_is_selected(p_select, &iter) )
        {
            masc_setup_package( &pkg, pbuf, sizeof pbuf, MASC_PACKAGE_STATIC );
            masc_pushk_int16( &pkg, "pos", pos );
            masc_finalize_package( &pkg );
            mas_set( mp1a_source_device, "ctrack", &pkg );
            masc_strike_package( &pkg );
            break;
        }
        valid = gtk_tree_model_iter_next( GTK_TREE_MODEL(liststore), &iter );
    }
}

static gboolean
check_selected( void )
{
    gboolean valid;
    gboolean found_selected_row;
    GtkTreeIter iter;
    gboolean is_hilit;
    
    found_selected_row = FALSE;
    valid = gtk_tree_model_get_iter_first( GTK_TREE_MODEL(liststore), &iter );

    
    while( valid )
    {
        gtk_tree_model_get( GTK_TREE_MODEL(liststore),
                            &iter, ROW_IS_HILIT_COLUMN, &is_hilit, -1);
  
        if( is_hilit )
        {
            found_selected_row = TRUE;
            break;
        }
        valid = gtk_tree_model_iter_next( GTK_TREE_MODEL(liststore),
                                          &iter );
    }

    if ( !found_selected_row )
    {
        gtk_tree_model_get_iter_first( GTK_TREE_MODEL(liststore), &iter );
        gtk_list_store_set( liststore, &iter,
                            ROW_COLOR_COLUMN, HILITE_COLOR,
                            -1);
        gtk_list_store_set( liststore, &iter,
                            ROW_IS_HILIT_COLUMN, TRUE,
                            -1);
        return FALSE;
    }
    return TRUE;
}



static void
get_stupid_config( void )
{
    char *home, *c;
    char  fname[MAX_FNAME_LENGTH];
#define INBUF_LEN 1024
#define MAX_PLAYLIST_LEN 512
    char  inbuf[INBUF_LEN];
    char *pl[MAX_PLAYLIST_LEN];
    int   i;
    
    FILE *f;

    home = getenv("HOME");

    if( home==NULL )
    {
        config.mp3dir=0;
        config.playlist=0;
        return;
    }

    /*     fname = masc_rtalloc( strlen(home)+strlen("/.mas_player")+1 ); */
    masc_strlcpy( fname, home, sizeof fname );
    if ( masc_strlcat( fname, "/.mas_player", sizeof fname ) < sizeof fname )
    {
        f = fopen( fname, "r" );
    }
    else
    {
        /* string was too long! */
        f = NULL;
    }
    
    if( f!=NULL )
    {
        int t;
        c = fgets( inbuf, INBUF_LEN, f );
        t = strlen(inbuf)-1;
        if( inbuf[t] == '\n' ) inbuf[t] = '\0';
        config.mp3dir = g_strdup( inbuf );

        i=0;
        while( c==inbuf )
        {
            c = fgets( inbuf, INBUF_LEN, f );
            t = strlen(inbuf)-1;
            if( inbuf[t] == '\n' ) inbuf[t] = '\0';
            if( c==inbuf )
                pl[i] = g_strdup( inbuf );
            else
                pl[i] = 0;
            ++i;

            /* don't read more than we can handle */
            if( i==MAX_PLAYLIST_LEN )
            {
                pl[MAX_PLAYLIST_LEN-1] = 0;
                break;
            }
            /*             printf( "%s\n", pl[i-1] ); */
        }
        
        fclose( f );
            
        if( *pl != NULL )
        {
            id3_contents **id3_tags;
            id3_contents **tmp;
            
            id3_tags = fetch_id3s( pl );
            /* don't sort--we want to keep the order from the file */
            /* sort_id3s( &id3_tags ); */
            
            inserting = 1;
            tmp = id3_tags;
            while( *tmp )
            {
                append_to_list( *(tmp++) );
            }
            inserting = 0;
        
            set_playlist( );
            get_track_lengths( );
            
            free_tag_list( id3_tags );
        
/*             config.playlist = malloc( i*sizeof(char*) ); */
/*             config.n = i-1; */
/*             for( i=0; i<config.n; i++ ) */
/*             { */
/*                 config.playlist[i] = pl[i]; */
/*             } */
/*             config.playlist[config.n] = 0;     */
        }
    }    
    else
    {
        config.mp3dir   = 0;
        config.playlist = 0;
        config.n        = 0;
    }    
    
    return;   
}

static void
set_stupid_config( void )
{
    char *home;
    char  fname[MAX_FNAME_LENGTH];
    FILE *f;
    int   i;
    
    home = getenv("HOME");

    if( home==NULL )
        return;

    /* fname = masc_rtalloc( strlen(home)+strlen("/.mas_player")+1 ); */

    /* only write file if playlist is out of sync */
    masc_strlcpy( fname, home, sizeof fname );
    if ( masc_strlcat( fname, "/.mas_player", sizeof fname ) < sizeof fname )
    {
        f = fopen( fname, "w" );
        if( f==NULL )
            return;
        
        fprintf( f, "%s\n", config.mp3dir );
        
        for( i=0; i<config.n; i++ )
        {
            fprintf( f, "%s\n", config.playlist[i] );
        }
        
        fclose( f );
    }

    /* else, the filename was too long */
    
    return;
}




static void
cb_row_activated (GtkTreeView       *tree_view,
                  GtkTreePath       *path,
              GtkTreeViewColumn *column)
{
    int16 pos;
    struct mas_package pkg;
    char pbuf[128];
    gboolean valid;
    GtkTreeIter iter;
    
    /* I know, I should be using the given treepath here */
        
    /* it just keeps getting worse... */
    valid = gtk_tree_model_get_iter_first( GTK_TREE_MODEL(liststore), &iter );
    while( valid )
    {
        gtk_list_store_set( liststore, &iter,
                            ROW_COLOR_COLUMN, NORMAL_COLOR,
                            -1);
        gtk_list_store_set( liststore, &iter,
                            ROW_IS_HILIT_COLUMN, FALSE,
                            -1);

        valid = gtk_tree_model_iter_next( GTK_TREE_MODEL(liststore),
                                          &iter );
    }
    
    
    valid = gtk_tree_model_get_iter_first( GTK_TREE_MODEL(liststore), &iter );
    pos = 0;

    while( valid )
    {
        ++pos;
        
        if( gtk_tree_selection_iter_is_selected(p_select, &iter) )
        {
            gtk_list_store_set( liststore, &iter,
                                ROW_COLOR_COLUMN, HILITE_COLOR,
                                -1);
            gtk_list_store_set( liststore, &iter,
                                ROW_IS_HILIT_COLUMN, TRUE,
                                -1);
            gtk_tree_selection_unselect_iter( p_select, &iter);    
            break;
        }
        else
        {    
            valid = gtk_tree_model_iter_next( GTK_TREE_MODEL(liststore),
                                              &iter );
        }
    }

    masc_setup_package( &pkg, pbuf, sizeof pbuf, MASC_PACKAGE_STATIC );
    masc_pushk_int16( &pkg, "pos", pos );
    masc_finalize_package( &pkg );
    mas_set( mp1a_source_device, "ctrack", &pkg );
    masc_strike_package( &pkg );
    
    g_object_set_data( G_OBJECT(timelabel), "sec", GINT_TO_POINTER(0) );
    
#ifdef DEBUG
    printf( "performed:\nmasset -n source_mp1a ctrack s pos %d\n", pos );
#endif


    if( have_playlist )
    {
        if( playing )
        {    
            if( pausing )
            {
/*                 set_selected_pos(); */
/*                 check_selected(); */
                mas_source_play( mp1a_source_device );
                mas_source_play( sbuf );
                
#ifdef DEBUG
                printf( "performed:\nmas_source_play()\n" );
#endif
                pausing = 0;
            }
        }
        else
        {
            mas_source_flush( codec );
            mas_source_play( mp1a_source_device );
            mas_source_play_on_mark( sbuf );
            
            timeout_handle = gtk_timeout_add( 1000, elapsed_timer, NULL );
#ifdef DEBUG
            printf( "mas_source_play()\n" );
#endif
            playing = 1;
            pausing = 0; /* just to be sure */
        }
    }
}


static gint
cb_delete_event(GtkWidget *w, GdkEvent *e, gpointer o)
{
    gtk_widget_hide_all( GTK_WIDGET(o) );
    gtk_main_quit();
    
    return TRUE;
}


void get_track_lengths( void )
{
    GtkTreeIter iter;
    gboolean valid;
    char pbuf[128];
    struct mas_package trklen_pkg;
    struct mas_package trklen_nugget;
    int i;
    float trklen;
    int tmp;
    char length[16];
    
    valid = gtk_tree_model_get_iter_first( GTK_TREE_MODEL(liststore), &iter );
    
    i=0;
    while( valid )
    {
        i++;

        masc_setup_package( &trklen_pkg, pbuf, sizeof pbuf, MASC_PACKAGE_STATIC );
        masc_pushk_int16( &trklen_pkg, "pos", i );
        masc_finalize_package( &trklen_pkg );
        
        mas_get( mp1a_source_device, "trklen", &trklen_pkg, &trklen_nugget );
        masc_strike_package( &trklen_pkg );

        masc_pullk_float( &trklen_nugget, "trklen", &trklen );
        masc_strike_package( &trklen_nugget );
        
        tmp = trklen;
        sprintf( length, "%02d:%02d", tmp/60, tmp%60 );
        
        gtk_list_store_set( liststore, &iter,
                            TRACK_LEN_COLUMN, length,
                            -1);

        valid = gtk_tree_model_iter_next( GTK_TREE_MODEL(liststore), &iter );
    }
    
}


static void
set_playlist( void )
{
    gboolean valid;
    GtkTreeIter iter;
    char *fname;
    char *hlp;
    char pbuf[10240];
    struct mas_package pkg;
    int p, pos;
    gboolean is_hilit;
    int n;
    
    
#ifdef DEBUG
    char pl[4096];
    char pl2[16];
#endif

    p = pos = 0;
    
    masc_setup_package( &pkg, pbuf, sizeof pbuf, MASC_PACKAGE_STATIC );

#ifdef DEBUG
    masc_strlcpy( pl, "performed:\nmasset -n source_mp1a playlist", sizeof pl );
#endif

    if( config.n )
        free( config.playlist );
    
    config.n = n = gtk_tree_model_iter_n_children ( GTK_TREE_MODEL(liststore), NULL );
    if( n )
        config.playlist = malloc( (n+1)*sizeof(char*) );
    
    
    /* stupid pos has to be the first key!! */
    valid = gtk_tree_model_get_iter_first( GTK_TREE_MODEL(liststore), &iter );
    while( valid )
    {
        ++p;
        gtk_tree_model_get( GTK_TREE_MODEL(liststore),
                            &iter, ROW_IS_HILIT_COLUMN, &is_hilit, -1);
        if( is_hilit )
        {
            pos = p;
            break;
        }
        valid = gtk_tree_model_iter_next( GTK_TREE_MODEL(liststore), &iter );
    }
    

#ifdef DEBUG
    sprintf( pl2, " s pos %d", pos );
    masc_strlcat( pl, pl2, sizeof pl );
#endif
    
    masc_pushk_int16( &pkg, "pos", pos );
    

    valid = gtk_tree_model_get_iter_first( GTK_TREE_MODEL(liststore), &iter );

    have_playlist = FALSE;

    n=0;
    while( valid )
    {
        gtk_tree_model_get( GTK_TREE_MODEL(liststore),
                            &iter, FILENAME_COLUMN, &fname, -1);

        masc_push_string( &pkg, fname );
        
#ifdef DEBUG
        masc_strlcat( pl, " a \"", sizeof pl );
        masc_strlcat( pl, fname, sizeof pl );
        masc_strlcat( pl, "\"", sizeof pl );
#endif

        
        /*     g_free( fname ); */
        config.playlist[n++] = fname;
        
        valid = gtk_tree_model_iter_next( GTK_TREE_MODEL(liststore), &iter );

        have_playlist = TRUE;
    }
    config.playlist[n] = 0;

    masc_finalize_package( &pkg );
    
    mas_set( mp1a_source_device, "playlist", &pkg );
    masc_strike_package( &pkg );
    

#ifdef DEBUG
    printf( "%s\n", pl);
#endif


    /* 1st file determines mp3 dir */
    valid = gtk_tree_model_get_iter_first( GTK_TREE_MODEL(liststore), &iter );

    if( valid )
    {
        gtk_tree_model_get( GTK_TREE_MODEL(liststore),
                            &iter, FILENAME_COLUMN, &fname, -1);
        
        hlp = fname + strlen( fname );
        while( *hlp != '/' )
        {
            --hlp;
        }
        *hlp = 0;
        
        
        if( (config.mp3dir==0) || (strcmp(config.mp3dir, fname)) )
        {
            g_free( config.mp3dir );
            config.mp3dir = fname;
        }
    }
    set_stupid_config();
}




static gint
cb_add_to_playlist( GtkWidget *w, gpointer data )
{
    show_file_dialog();
    return TRUE;
}


static gint
cb_remove_from_playlist( GtkWidget *w, gpointer data )
{
    gboolean valid, hlp;
    GtkTreeIter iter;
    gboolean is_hilit;
    
    hlp = FALSE;
    valid = gtk_tree_model_get_iter_first( GTK_TREE_MODEL(liststore), &iter );

    while( valid )
    {
        hlp = TRUE;
        
        if( gtk_tree_selection_iter_is_selected(p_select, &iter) )
        {
            /* stop playing if current song is removed */
            gtk_tree_model_get( GTK_TREE_MODEL(liststore),
                                &iter, ROW_IS_HILIT_COLUMN, &is_hilit, -1);
            
            if( is_hilit )
                stop_playing_man();
            
            gtk_list_store_remove( liststore, &iter );
            /* have to do this to avoid crashing on removal of last
               item */
            valid = gtk_tree_model_get_iter_first( GTK_TREE_MODEL(liststore), &iter );
        }
        else
        {    
            valid = gtk_tree_model_iter_next( GTK_TREE_MODEL(liststore),
                                              &iter );
        }
    }

    if( hlp )
    {
        set_playlist( );
        get_track_lengths( );
    }
    
    return TRUE;
}


static id3_contents**
fetch_id3s( gchar **fnames )
{
    int32 err;
    char *fname;
    char *artist;
    char *title;
    char *album;
    char *comment;
    char *p;
    int   i, n;
    
    id3_contents *id3;
    id3_contents **id3s;
    char pbuf[1024];
    struct mas_package pkg;
    struct mas_package nugget;


    /* get number of files */
    n=0;
    while( fnames[n] != NULL ) { ++n; }
    
    id3s = masc_rtalloc( (n+1) * sizeof( id3_contents* ) );
    id3s[n] = 0; /* terminator */
    
    
    for( i=0; i<n; i++ )
    {
        fname = fnames[i];
        
        masc_setup_package( &pkg, pbuf, sizeof pbuf, MASC_PACKAGE_STATIC );
        masc_push_string( &pkg, fname );
        masc_finalize_package( &pkg );
        
        mas_get( id3_device, "tag", &pkg, &nugget );
        masc_strike_package(&pkg);

        id3 = masc_rtalloc( sizeof(id3_contents) );
        mas_assert( id3 != NULL, "Out of memory" );
        
        err = masc_test_key( &nugget, "error" );
        if( err < 0 )
        {
            masc_pullk_string( &nugget, "artist", &artist, TRUE );
            masc_pullk_string( &nugget, "title", &title, TRUE );
            masc_pullk_string( &nugget, "album", &album, TRUE );
            masc_pullk_string( &nugget, "comment", &comment, TRUE );
            masc_pullk_uint8( &nugget, "track", &id3->track_no );
            
            id3->fname = g_strdup( fname );
            id3->artist = artist;
            id3->title = title;
            id3->comment = comment;
            if( *album == 0 )
            {
                id3->album = g_strdup( " " );
            }
            else
            {
                id3->album = album;
            }
            
            id3s[i] = id3;
        }
        else
        {
            
            id3->artist = 0;
            id3->comment = 0;
            id3->track_no = 0;
            
            /* CONVENTION:
             * if mp3's without ID3 tags will get album name " ",
             * so they'll all get grouped under this 'album name'
             * (and we avoid the "", which happens to be my list
             * terminator for char** lists)
             */ 
            id3->album = g_strdup( " " );
            
            
            id3->fname = g_strdup( fname );
            
            /* use the file name as title, but without any path */
            p = fname + strlen(fname);
            while( (*p != '/') && (p!=fname) )
                --p;
            ++p;
            
            id3->title = g_strdup( p );
            
            id3s[i] = id3;
        }

        masc_strike_package( &nugget );
    }
    
    return id3s;
}



void append_to_list( id3_contents *tag )
{
    GtkTreeIter iter;
    
    gtk_list_store_append( liststore, &iter );
    
    gtk_list_store_set( liststore, &iter,
                        FILENAME_COLUMN, tag->fname,
                        -1);

    gtk_list_store_set( liststore, &iter,
                        ROW_COLOR_COLUMN, NORMAL_COLOR,
                        -1);
    gtk_list_store_set( liststore, &iter,
                        ROW_IS_HILIT_COLUMN, FALSE,
                        -1);
    
    if( tag->artist )
    {
        gtk_list_store_set( liststore, &iter,
                            ARTIST_COLUMN, tag->artist,
                            -1);
    }
    
    if( tag->title )
    {
        gtk_list_store_set( liststore, &iter,
                            SONG_COLUMN, tag->title,
                            -1);
    }
    
    if( tag->album )
    {
        gtk_list_store_set( liststore, &iter,
                            ALBUM_COLUMN, tag->album,
                            -1);
    }

    if( tag->track_no )
    {
        char trk[16];

        sprintf( trk, "%d", tag->track_no );
        
        gtk_list_store_set( liststore, &iter,
                            TRACK_NO_COLUMN, trk,
                            -1);
    }
}


int match_regex( regex_t *regex, char *string )
{
    regmatch_t *result;
    size_t no_sub;
    int found;
    char string2[8];
    
    
    no_sub = regex->re_nsub+1;
    result = (regmatch_t *) malloc( sizeof(regmatch_t) * no_sub );
    
    found = regexec(regex, string,
                    no_sub, result, 0);
    
    if( found==0 )
    {
        if( (string[result->rm_eo-2] >= '0') &&
            (string[result->rm_eo-2] <= '9') )
        {
            string2[0] = string[result->rm_eo-2];
            string2[1] = string[result->rm_eo-1];
            string2[2] = 0;
        }
        else
        {
            string2[0] = string[result->rm_eo-1];
            string2[1] = 0;
        }
        return atoi( string2 );
    }
    
    return 0;
}


void fix_broken_track_numbers( id3_contents **tags, int n, regex_t *regex )
{
    int i, j, track;

    int weirdness;
    
    
    /* this is all based on some weird mp3 files I've seen; so we may
       not catch all cases */

    if( n==1 )
    {
        if( tags[0]->track_no == 0 )
        {
            /* 0 seems to be what the tag device returns if there
               isn't a track no */
            if( tags[0]->comment )
            {
                track = match_regex( regex, tags[0]->comment );
                if( track )
                {
                    tags[0]->track_no = track;
#ifdef DEBUG
                    printf( "\nobtained track number from comment field:\nfile name: %s\ncomment field: %s\ninferred track no:%d\n",
                            tags[0]->fname, tags[0]->comment,
                            tags[0]->track_no );
#endif
                }
            }
        }
        return;
    }


    
    /* n is larger than 1 */

    weirdness = 0;
    /* this tests all unique combinations of track numbers */
    for( i=0; i<n-1; i++ )
    {
        for( j=i+1; j<n; j++ )
        {
            if( tags[i]->track_no == tags[j]->track_no )
            {
                weirdness = 1;
                break;
            }
        }
        if( weirdness ) break;
    }

    if( weirdness )
    {
        /* ok we have several tracks from the same album with same
         * track numbers... try the comment field */
        
        for( i=0; i<n; i++ )
        {
            if( tags[i]->comment )
            {
                track = match_regex( regex, tags[i]->comment );
                if( track )
                {
                    tags[i]->track_no = track;
#ifdef DEBUG
                    printf( "\nobtained track number from comment field:\nfile name: %s\ncomment field: %s\ninferred track no:%d\n",
                            tags[i]->fname, tags[i]->comment,
                            tags[i]->track_no );
#endif
                }
            }
        }
    }
}


/* Ok, I've had it. We're going to stat every file to check user input. */
void remove_uncool_files( gchar **selected_filenames )
{
    gchar **fnms = selected_filenames;
    while( *fnms )
    {
        struct stat st;
        gchar **tmp1, **tmp2;
        
        stat( *fnms, &st );
        
        if( !S_ISREG(st.st_mode) )
        {
            tmp1 = tmp2 = fnms;
            while( *(++tmp1) )
            {
                g_free( *tmp2 );
                *(tmp2++) = *tmp1;
            }
                *tmp2 = NULL;
        }
        fnms++;
    }
}


static void
cb_response( GtkDialog *dialog, gint response_id, gpointer unused)
{
    gchar **selected_filenames;
    id3_contents **id3_tags;
    id3_contents **tmp;
    
    
    switch( response_id )
    {
    case FILE_APPLY:
    {
        /* get the filename(s) */
        selected_filenames = gtk_file_selection_get_selections(
            GTK_FILE_SELECTION (file_selector) );


        /* See if the user selected a directory.
           (last character of file name will be a '/') */
        if( *( *selected_filenames + strlen(*selected_filenames)-1 ) == '/' )
        {
            /* this func will free and re-create selected_filenames */ 
            import_entire_dir( &selected_filenames );
            if ( selected_filenames == NULL )
                return;
        }

        
        remove_uncool_files( selected_filenames );
        
        
        if( *selected_filenames == NULL )
        {
            return;
        }
        
        id3_tags = fetch_id3s( selected_filenames );

        g_strfreev( selected_filenames ); /* cause we just don't
                                           * need it anymore */

        sort_id3s( &id3_tags );
        

      
        /* hack. The rows-reordered signal doesn't work, so I'm using
           the 'inserted' signal, which is also fired during
           reordering. But we have to exclude the cases where we actually
           are inserting... */
        inserting = 1;
        
        tmp = id3_tags;
        while( *tmp )
        {
            append_to_list( *(tmp++) );
        }

        inserting = 0;
        
        
        set_playlist( );
        get_track_lengths( );
        
        free_tag_list( id3_tags );
        
        break;
    }
    
    case FILE_GETDIR:
        /* fname = gtk_file_selection_get_filename( file_selector );         */
        printf("implement me!\n");
        
    
        /* the 'close' button which hides the widget has its own
           callback! */
        
    default:
        break;
    }
}

static void
cb_visual_response( GtkDialog *dialog, gint response_id, gpointer unused)
{
    switch( response_id )
    {
    case GTK_RESPONSE_APPLY:

        do_mas_set( visual );
        break;

    case GTK_RESPONSE_OK:

        do_mas_set( visual );
        gtk_widget_hide( GTK_WIDGET(dialog) );
        break;
        
    default:
        gtk_widget_hide( GTK_WIDGET(dialog) );
        break;
    }
}


static gint
cb_filedialog_item_doubleclick( GtkWidget *w, gpointer data )
{
    gchar **selected_filenames;
    id3_contents **id3_tags;
    
    
    /* get the filename(s) */
    selected_filenames = gtk_file_selection_get_selections(
        GTK_FILE_SELECTION (file_selector) );

    
    id3_tags = fetch_id3s( selected_filenames );
    
    g_strfreev( selected_filenames );

    /* double-clicking can yield only one selection... */

    sort_id3s( &id3_tags );

    inserting = 1;
    append_to_list( id3_tags[0] );
    inserting = 0;
    
    set_playlist( );
    get_track_lengths( );
    
    free_tag_list( id3_tags );
        
    return TRUE;
}


static gint
cb_button_clicked( GtkWidget *w, gpointer data )
{
    int pos;
    int button_number;
    GtkTreeIter iter;
    GtkTreeIter prev_iter;
    gboolean valid;
    char pbuf[128];
    struct mas_package pkg;
    gboolean is_hilit;
    static GtkWidget *pref_dialog = 0;
    
    button_number = (int)data;

    switch( button_number )
    {
    case STOP_BUTTON:
        mas_source_stop( mp1a_source_device );
        mas_source_stop( sbuf );
#ifdef DEBUG
    printf( "performed:\nmas_source_stop()\n" );
#endif
        playing = 0;
        gtk_timeout_remove( timeout_handle );
        g_object_set_data( G_OBJECT(timelabel), "sec", GINT_TO_POINTER(0) );
        break;

    case PAUSE_BUTTON:
        if( pausing )
        {
            /* we don't want "pause" to toggle playing anymore */
/*             if( have_playlist ) */
/*             { */
/*                 mas_source_play( mp1a_source_device ); */
/*                 mas_source_play( sbuf ); */
/* #ifdef DEBUG */
/*                 printf( "performed:\nmas_source_play()\n" ); */
/* #endif */
/*             } */
/*             pausing = 0; */
        }
        else
        {
            mas_source_pause( mp1a_source_device );
            mas_source_pause( sbuf );
#ifdef DEBUG
    printf( "performed:\nmas_source_pause()\n" );
#endif
            pausing = 1;
        }
        break;
        
    case PLAY_BUTTON:
        if( playing )
        {
            if( have_playlist )
            {
                if( !pausing )
                {
                    mas_source_stop( mp1a_source_device );
                    mas_source_stop( sbuf );
#ifdef DEBUG
                    printf( "performed:\nmas_source_stop()\n" );
#endif                    
                }
                
                set_selected_pos();
                check_selected();
                mas_source_play( mp1a_source_device );
                mas_source_play( sbuf );
            }
#ifdef DEBUG
            if( have_playlist )
                printf( "performed:\nmas_source_play()\n" );
#endif
            pausing = 0;
/*             } */
        }
        else
        {
            if( have_playlist )
            {
                set_selected_pos();
                check_selected();
                timeout_handle = gtk_timeout_add( 1000, elapsed_timer, NULL );
                mas_source_flush( codec ); 
                mas_source_play( mp1a_source_device );
                mas_source_play_on_mark( sbuf );
            }
            
#ifdef DEBUG
            if( have_playlist )
                printf( "performed:\nmas_source_play()\n" );
#endif
            playing = 1;
        }
        
        break;

    case NEXT_BUTTON:

        pos = 0;
        
        g_object_set_data( G_OBJECT(timelabel), "sec",
                           GINT_TO_POINTER(0) );

        if ( !check_selected() )
        {
            pos = 1;
            masc_setup_package( &pkg, pbuf, sizeof pbuf, MASC_PACKAGE_STATIC );
            masc_pushk_int16( &pkg, "pos", pos );
            masc_finalize_package( &pkg );
            
            mas_set( mp1a_source_device, "ctrack", &pkg );
            masc_strike_package( &pkg );
            
#ifdef DEBUG
            printf( "performed:\nmasset -n source_mp1a ctrack s pos %d\n", pos );
#endif
            break;
        }
        
        valid = gtk_tree_model_get_iter_first( GTK_TREE_MODEL(liststore),
                                               &iter );
        while( valid )
        {
            ++pos;
            gtk_tree_model_get( GTK_TREE_MODEL(liststore),
                                &iter, ROW_IS_HILIT_COLUMN, &is_hilit, -1);
  
            if( is_hilit )
            {
                gtk_list_store_set( liststore, &iter,
                                    ROW_COLOR_COLUMN, NORMAL_COLOR,
                                    -1);
                gtk_list_store_set( liststore, &iter,
                                    ROW_IS_HILIT_COLUMN, FALSE,
                                    -1);
                valid = gtk_tree_model_iter_next(
                    GTK_TREE_MODEL(liststore), &iter );

                if( !valid ) /* went off the end of the list */
                {
                    valid = gtk_tree_model_get_iter_first(
                        GTK_TREE_MODEL(liststore), &iter );
                    pos = 0;
                }
                
                gtk_list_store_set( liststore, &iter,
                                    ROW_COLOR_COLUMN, HILITE_COLOR,
                                    -1);
                gtk_list_store_set( liststore, &iter,
                                    ROW_IS_HILIT_COLUMN, TRUE,
                                    -1);
                pos++;
                break;
            }
            valid = gtk_tree_model_iter_next( GTK_TREE_MODEL(liststore),
                                              &iter );
        }
        
/*         gtk_tree_model_get (liststore, &iter, FILENAME_COLUMN, &fname, -1); */
/*         err = mas_source_set_file( mp1a_source_device, fname); */
/*         if ( err < 0 ) masc_logerror( err, "set file to %s", fname
 *         ); */
        
        /* clear the packets in sbuf's queue, stop playback */
        /* mas_source_stop( sbuf );*/

        /* change the track */
        masc_setup_package( &pkg, pbuf, sizeof pbuf, MASC_PACKAGE_STATIC );
        masc_pushk_int16( &pkg, "pos", pos );
        masc_finalize_package( &pkg );

        mas_set( mp1a_source_device, "ctrack", &pkg );
        masc_strike_package( &pkg );

        /* start playing again */
        /* mas_source_play_on_mark( sbuf ); */
#ifdef DEBUG
    printf( "performed:\nmasset -n source_mp1a ctrack s pos %d\n", pos );
#endif
        break;
        
    case PREV_BUTTON:

        pos = 0;

        check_selected();
        
        valid = gtk_tree_model_get_iter_first( GTK_TREE_MODEL(liststore),
                                               &iter );

        g_object_set_data( G_OBJECT(timelabel), "sec",
                           GINT_TO_POINTER(0) );

        while( valid )
        {
            ++pos;

            gtk_tree_model_get( GTK_TREE_MODEL(liststore),
                                &iter, ROW_IS_HILIT_COLUMN, &is_hilit, -1);
            
            if( is_hilit )
            {
                gtk_list_store_set( liststore, &iter,
                                    ROW_COLOR_COLUMN, NORMAL_COLOR,
                                    -1);
                gtk_list_store_set( liststore, &iter,
                                    ROW_IS_HILIT_COLUMN, FALSE,
                                    -1);
                if( pos==1 ) /* went off beginning of list */
                {
                    /* get last node... this is stoopid */
                    while( valid )
                    {
                        ++pos;
                        prev_iter = iter;
                        valid = gtk_tree_model_iter_next(
                            GTK_TREE_MODEL(liststore), &iter );
                    }
                    --pos;
                    gtk_list_store_set( liststore, &prev_iter,
                                        ROW_COLOR_COLUMN, HILITE_COLOR,
                                        -1);
                    gtk_list_store_set( liststore, &prev_iter,
                                        ROW_IS_HILIT_COLUMN, TRUE,
                                        -1);
                    break;
                }
                gtk_list_store_set( liststore, &prev_iter,
                                    ROW_COLOR_COLUMN, HILITE_COLOR,
                                    -1);
                gtk_list_store_set( liststore, &prev_iter,
                                    ROW_IS_HILIT_COLUMN, TRUE,
                                    -1);
                --pos;
                break;
                
            }
            prev_iter = iter;
            valid = gtk_tree_model_iter_next( GTK_TREE_MODEL(liststore),
                                              &iter );
        }
        
        /* clear the packets in sbuf's queue, stop playback */
        /* mas_source_stop( sbuf ); */

        /* change the track */
        masc_setup_package( &pkg, pbuf, sizeof pbuf, MASC_PACKAGE_STATIC );
        masc_pushk_int16( &pkg, "pos", pos );
        masc_finalize_package( &pkg );

        mas_set( mp1a_source_device, "ctrack", &pkg );
        masc_strike_package( &pkg );

        /* start playing again */
        /* mas_source_play_on_mark( sbuf ); */
#ifdef DEBUG
        printf( "performed:\nmasset -n source_mp1a ctrack s pos %d\n", pos );
#endif
        break;

    case PREF_BUTTON:
    {
        GtkWidget *w;
        if( !( GTK_IS_DIALOG(pref_dialog) ) )
        {
            pref_dialog = gtk_dialog_new_with_buttons (
                "MAS", GTK_WINDOW(main_window),
                GTK_DIALOG_DESTROY_WITH_PARENT,
                GTK_STOCK_APPLY, GTK_RESPONSE_APPLY,
                GTK_STOCK_OK, GTK_RESPONSE_OK,
                GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                NULL );

            w = make_visual_widget( );
           gtk_container_add( GTK_CONTAINER(GTK_DIALOG(pref_dialog)->vbox), w);
            
            g_signal_connect ( GTK_OBJECT(pref_dialog), "response",
                               G_CALLBACK (cb_visual_response),
                               NULL);
            
/*             g_signal_connect_swapped (GTK_OBJECT (pref_dialog),  */
/*                                       "response",  */
/*                                       G_CALLBACK (gtk_widget_hide), */
/*                                       GTK_OBJECT (pref_dialog)); */

           gtk_widget_show_all( pref_dialog );
        }
        
        gtk_widget_show( pref_dialog );

        break;
    }
    
        
    default:
        printf("implement me!\n");
        
        break;
    }

    return TRUE;
}


static void
cb_reorder( GtkTreeModel *tree_model,
            GtkTreePath  *path,
            GtkTreeIter  *iter,
            gint         *new_order)
{
    if( !inserting )
    {
        insert_hack = 1;
    }
}


static gint
cb_volume_slider_moved( GtkAdjustment *adj, gpointer o )
{
    
    mas_mix_attenuate_db( mix_device, mix_sink, DB_CUTOFF*(10.0 - adj->value/10.0) );
    return TRUE;
}


/* taken from cranky.c */
static void
setup_mas_for_mp1a( void )
{
    int32 err;
    struct mas_data_characteristic* dc;
    struct mas_package nugget;
    mas_device_t anx;
    mas_port_t   tmp_source;
    mas_channel_t local;

    err = mas_get_local_control_channel( &local );
    if ( err < 0 ) masc_logerror( err, "getting local control channel" );

    /* Get the id of the sample clock provided by the anx device -- if
       we can! */
    err = mas_asm_get_device_by_name( "anx", &anx );
    mas_assert( err >= 0, "Couldn't get anx device" );

    sink_clkid = 0;
    err = mas_get( anx, "mc_clkid", NULL, &nugget );
    if ( err >= 0 )
    {
        masc_pull_int32( &nugget, &sink_clkid );
        masc_strike_package( &nugget );
    }
    
    /* CODEC */
    err = mas_asm_instantiate_device( "codec_mp1a_mad", 0, 0, &codec );
    if (err < 0 )
    {
        masc_log_message( MAS_VERBLVL_INFO, "Couldn't instantiate mp1a_mad codec, trying mp1a codec.");
        err = mas_asm_instantiate_device( "codec_mp1a", 0, 0, &codec );
        mas_assert( err >= 0, "Couldn't instantiate MPEG codec");
    }

    /* source - instantiated on the local MAS server */
    err = mas_asm_instantiate_device_on_channel( "source_mp1a", 0, 0, &mp1a_source_device, local );
    mas_assert( err >= 0, "Couldn't instantiate source_mp1a" );

    /* buffer */
    err = mas_asm_instantiate_device( "sbuf", 0, 0, &sbuf );
    mas_assert( err >= 0, "Couldn't instantiate sbuf device" );

    visual = NULL;
    
#ifdef USE_VISUAL
    err = mas_asm_instantiate_device( "visual", 0, 0, &visual );
    if ( err < 0 )
    {
        masc_log_message( 0, "Couldn't instantiate visual device.  It's okay, I just won't use it." );
        visual = NULL;
    }
#endif

    /* id3 tag parser */
    err = mas_asm_instantiate_device_on_channel( "tag", 0, 0, &id3_device, local );
    mas_assert( err >= 0, "Couldn't instantiate tag device" );

    /* get a handle to the mixer */
    err = mas_asm_get_device_by_name( "mix", &mix_device );
    mas_assert( err >= 0, "Couldn't get mixer device" );

    /* start making connections:
     *  
     *    source->codec->visual->mix
     */

    err = mas_asm_connect_devices( mp1a_source_device, codec, "source", "sink" );
    mas_assert( err >= 0, "Couldn't connect MPEG source to MPEG codec" );
    
    dc = masc_make_audio_basic_dc( MAS_LINEAR_FMT, 44100, 20, 2, MAS_HOST_ENDIAN_FMT );
    mas_assert( dc, "Couldn't create audio data characteristic." );
    
    err = mas_asm_get_port_by_name( mix_device, "default_mix_sink", &mix_sink );
    mas_assert( err >= 0, "Couldn't get default mixer sink" );

    err = mas_asm_connect_devices_dc( codec, sbuf, "source", "sink", dc );
    mas_assert( err >= 0, "Couldn't connect MPEG codec to sbuf" );

    if (visual != NULL)
    {
        err = mas_asm_connect_devices_dc( sbuf, visual, "source", "sink", dc );
        mas_assert( err >= 0, "Couldn't connect sbuf to visual device." );
        
        err = mas_asm_get_port_by_name( visual, "source", &tmp_source );
        mas_assert( err >= 0, "Couldn't get visual source port" );

        err = mas_asm_connect_source_sink( tmp_source, mix_sink, dc );
        mas_assert( err >= 0, "Couldn't connect visual device to mixer sink." );
    }
    else
    {
        err = mas_asm_get_port_by_name( sbuf, "source", &tmp_source );
        mas_assert( err >= 0, "Couldn't get sbuf source port" );

        err = mas_asm_connect_source_sink( tmp_source, mix_sink, dc );
        mas_assert( err >= 0, "Couldn't connect sbuf to mix device." );
    }
    
    /* set the buffer time */
    masc_setup_package( &nugget, NULL, 0, 0 );
    masc_pushk_uint32( &nugget, "buftime_ms", DEFAULT_BUFFER_TIME_MS );
    masc_finalize_package( &nugget );
    mas_set( sbuf, "buftime_ms", &nugget );
    masc_strike_package( &nugget );

    /* If we can use a sample clock, let's do it... */
    if ( sink_clkid > 0 )
    {
        mas_get_mc_device( &sink_mc );
        mas_get_mc_device_on_channel( &source_mc, local );

        get_measured_sample_freq();
        
        source_clkid = mas_mc_add_fixed_clock( "player", measured_sample_freq, local );
        masc_log_message( 0, "got clock: %d", source_clkid );

        /* the file source device uses the file clock */
        masc_setup_package( &nugget, NULL, 0, 0 );
        masc_pushk_int32( &nugget, "mc_clkid", source_clkid );
        masc_finalize_package( &nugget );
        mas_set( mp1a_source_device, "mc_clkid", &nugget );
        masc_strike_package( &nugget );

        /* and the sbuf, which could be on a different machine, uses
           the sample clock from that machine's anx device. */
        masc_setup_package( &nugget, NULL, 0, 0 );
        masc_pushk_int32( &nugget, "mc_clkid", sink_clkid );
        masc_finalize_package( &nugget );
        mas_set( sbuf, "mc_clkid", &nugget );
        masc_strike_package( &nugget );
    }

    return;
}

int32
get_measured_sample_freq( )
{
    struct mas_package arg;
    struct mas_package nugget;
    char pbuf[512];
    int32 err;
    
    masc_setup_package( &arg, pbuf, sizeof pbuf, MASC_PACKAGE_STATIC );
    masc_pushk_int32( &arg, "clkid", sink_clkid );
    masc_finalize_package( &arg );
    
    err = mas_get( sink_mc, "mc_rate", &arg, &nugget );
    masc_pull_double( &nugget, &measured_sample_freq );
    masc_strike_package( &arg );
    masc_strike_package( &nugget );
        
    return err;
}

int32
set_source_freq( )
{
    struct mas_package arg;
    char pbuf[512];
    int32 err;
    
    masc_setup_package( &arg, pbuf, sizeof pbuf, MASC_PACKAGE_STATIC );
    masc_pushk_int32( &arg, "clkid", source_clkid );
    masc_pushk_double( &arg, "freq", measured_sample_freq );
    masc_finalize_package( &arg );
    
    err = mas_set( source_mc, "mc_rate", &arg );
    masc_strike_package( &arg );
        
    return err;
}

int
main(int argc, char* argv[])
{
    int32 err;

    GtkWidget *button;
    GtkWidget *vbox;
    GtkWidget *hbox;
    GtkWidget *paned;
    GtkWidget *left_vbox;
    GtkWidget *image;
    GtkWidget *treeview;
    GtkWidget *sw;
    GtkCellRenderer *renderer;
    GtkTreeViewColumn *column;
    GdkColor color;
    GtkTreeModel *model;

    GtkWidget *volume_slider;
    char *title;
    

    have_playlist = FALSE;
    config.playlist = 0;
    config.n = 0;
    
    masc_log_verbosity( MAS_VERBLVL_DEBUG );

    /* initiate contact with MAS */
    err = mas_init();
    if (err < 0)
    {
      printf("\nconnection with MAS server failed.\n");
      exit(1);
    }
    
    setup_mas_for_mp1a();
    
    gtk_init( &argc, &argv );

    main_window = gtk_window_new( GTK_WINDOW_TOPLEVEL );

    title = masc_construct_title( "MAS Player");
    if( title )
    {
        gtk_window_set_title ( GTK_WINDOW (main_window), title );
        masc_rtfree( title );
    }
    else
    {
        gtk_window_set_title ( GTK_WINDOW (main_window), "MAS Player" );
    }
    
    
    gtk_window_set_policy( GTK_WINDOW(main_window), TRUE, TRUE, TRUE );  

    gtk_window_set_default_size( GTK_WINDOW(main_window),
                                 WIN_WIDTH, WIN_HEIGHT );
    
    gtk_signal_connect( GTK_OBJECT(main_window), "delete_event", 
                        GTK_SIGNAL_FUNC(cb_delete_event),
                        (gpointer)main_window );
    




    
    liststore = gtk_list_store_new( N_COLUMNS, G_TYPE_STRING,
                                    G_TYPE_STRING, G_TYPE_STRING,
                                    G_TYPE_STRING, G_TYPE_STRING,
                                    G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN );

    
    treeview = gtk_tree_view_new_with_model( GTK_TREE_MODEL(liststore) );

    gtk_tree_view_set_reorderable( GTK_TREE_VIEW(treeview), TRUE );
    
    model = gtk_tree_view_get_model( GTK_TREE_VIEW(treeview) );

    g_signal_connect ( model,"row-inserted",
                       G_CALLBACK (cb_reorder), NULL );
    

    
    renderer = gtk_cell_renderer_text_new( );

/*     g_object_set( G_OBJECT(renderer), "background", "gray", NULL ); */

    column = gtk_tree_view_column_new_with_attributes( "Song Title",
                                                       renderer,
                                                       "text", SONG_COLUMN,
                                                       "background",
                                                       ROW_COLOR_COLUMN,
                                                       NULL );
    gtk_tree_view_append_column( GTK_TREE_VIEW(treeview), column );

    
    renderer = gtk_cell_renderer_text_new( );
    column = gtk_tree_view_column_new_with_attributes( "Artist",
                                                       renderer,
                                                       "text", ARTIST_COLUMN,
                                                       "background",
                                                       ROW_COLOR_COLUMN,
                                                       NULL );
    gtk_tree_view_append_column( GTK_TREE_VIEW(treeview), column );

    renderer = gtk_cell_renderer_text_new( );
    column = gtk_tree_view_column_new_with_attributes( "Track",
                                                       renderer,
                                                       "text", TRACK_NO_COLUMN,
                                                       "background",
                                                       ROW_COLOR_COLUMN,
                                                       NULL );
    gtk_tree_view_append_column( GTK_TREE_VIEW(treeview), column );

    
    renderer = gtk_cell_renderer_text_new( );
    column = gtk_tree_view_column_new_with_attributes( "Album",
                                                       renderer,
                                                       "text", ALBUM_COLUMN,
                                                       "background",
                                                       ROW_COLOR_COLUMN,
                                                       NULL );
    gtk_tree_view_append_column( GTK_TREE_VIEW(treeview), column );

    renderer = gtk_cell_renderer_text_new( );
    column = gtk_tree_view_column_new_with_attributes( "Length",
                                                       renderer,
                                                       "text",
                                                       TRACK_LEN_COLUMN,
                                                       "background",
                                                       ROW_COLOR_COLUMN,
                                                       NULL );
    gtk_tree_view_append_column( GTK_TREE_VIEW(treeview), column );



    


    /* selection handling */
    
    p_select = gtk_tree_view_get_selection( GTK_TREE_VIEW(treeview) );
    gtk_tree_selection_set_mode( p_select, GTK_SELECTION_MULTIPLE );

    g_signal_connect ( GTK_TREE_VIEW(treeview),
                       "row_activated", G_CALLBACK (cb_row_activated), NULL );



    /* let's do some layout */

    paned = gtk_hpaned_new();
    
/*     outer_hbox = gtk_hbox_new( FALSE, 0 ); */
    
    vbox = gtk_vbox_new( FALSE, 0 );


    sw = gtk_scrolled_window_new(NULL, NULL);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
                                   GTK_POLICY_AUTOMATIC,
                                   GTK_POLICY_AUTOMATIC);

    gtk_container_add( GTK_CONTAINER(sw), treeview );

    
    gtk_box_pack_start( GTK_BOX(vbox), sw, TRUE, TRUE, 0);




    left_vbox = gtk_vbox_new( FALSE, 0 );
    hbox = gtk_hbox_new( TRUE, 0 );
    

    /* playlist related buttons */
    button = gtk_button_new_from_stock( GTK_STOCK_ADD );
    gtk_box_pack_start( GTK_BOX(hbox), button, TRUE, TRUE, 0);
    g_signal_connect( GTK_OBJECT(button), "clicked",
                      G_CALLBACK (cb_add_to_playlist), (gpointer)0 );

    button = gtk_button_new_from_stock( GTK_STOCK_REMOVE );
    gtk_box_pack_start( GTK_BOX(hbox), button, TRUE, TRUE, 0);
    g_signal_connect( GTK_OBJECT(button), "clicked",
                      G_CALLBACK (cb_remove_from_playlist), (gpointer)0 );


    

    image = gtk_image_new_from_file( M_IMAGE );
    gtk_box_pack_start( GTK_BOX(left_vbox), image, TRUE, TRUE, 0);

    
    button = gtk_event_box_new();

    color.red = 0;
    color.green = 0;
    color.blue = 0;

/*     gtk_widget_modify_bg( button, GTK_STATE_NORMAL, &color ); */
/*     gtk_container_set_border_width( GTK_CONTAINER(button), 20 ); */
    
    
    timelabel = gtk_label_new( "" );
    gtk_label_set_markup( GTK_LABEL(timelabel), "<span size='xx-large'>00:00</span>" );

    g_object_set_data( G_OBJECT(timelabel), "sec", 
                       GINT_TO_POINTER(0) );

    color.red = ~0;
    color.green = ~0;
    color.blue = ~0;

/*     gtk_widget_modify_fg( timelabel, GTK_STATE_NORMAL, &color ); */

    gtk_container_add( GTK_CONTAINER(button), timelabel );

    
    gtk_box_pack_start( GTK_BOX(left_vbox), button, TRUE, TRUE, 0);

    gtk_box_pack_start( GTK_BOX(vbox), hbox, FALSE, FALSE, 0);

/*     gtk_box_pack_start( GTK_BOX(outer_hbox), vbox, TRUE, TRUE, 0 ); */

    gtk_paned_pack1( GTK_PANED(paned), vbox, TRUE, TRUE );
    

    vbox = gtk_vbox_new( TRUE, 0 );
    
    /* /\* playing related buttons *\/ */
    
/*     for( i=0; i<N_BUTTONS; i++ ) */
/*     { */
/*         button = gtk_button_new( ); */
        
/*         gtk_signal_connect( GTK_OBJECT(button), "clicked", */
/*                             GTK_SIGNAL_FUNC(cb_button_clicked), */
/*                             (gpointer) i ); */

/*         image = gtk_image_new_from_file( buttons[i] ); */

/*         gtk_container_add( GTK_CONTAINER(button), image ); */

/*         gtk_box_pack_start( GTK_BOX(hbox), button, TRUE, TRUE, 0 ); */
/*     } */


    hbox = gtk_hbox_new( FALSE, 0 );
    
    
    button = gtk_button_new( );
    gtk_button_set_relief( GTK_BUTTON(button), GTK_RELIEF_NONE );
    gtk_signal_connect( GTK_OBJECT(button), "clicked",
                        GTK_SIGNAL_FUNC(cb_button_clicked),
                        (gpointer) PREV_BUTTON );
    image = gtk_image_new_from_file( buttons[PREV_BUTTON] );
    gtk_container_add( GTK_CONTAINER(button), image );
    gtk_box_pack_start( GTK_BOX(hbox), button, FALSE, FALSE, 0 );

    button = gtk_button_new( );      
    gtk_button_set_relief( GTK_BUTTON(button), GTK_RELIEF_NONE );
    gtk_signal_connect( GTK_OBJECT(button), "clicked",
                        GTK_SIGNAL_FUNC(cb_button_clicked),
                        (gpointer) PLAY_BUTTON );
    image = gtk_image_new_from_file( buttons[PLAY_BUTTON] );
    gtk_container_add( GTK_CONTAINER(button), image );
    gtk_box_pack_start( GTK_BOX(hbox), button, TRUE, TRUE, 0 );

    button = gtk_button_new( );      
    gtk_button_set_relief( GTK_BUTTON(button), GTK_RELIEF_NONE );
    gtk_signal_connect( GTK_OBJECT(button), "clicked",
                        GTK_SIGNAL_FUNC(cb_button_clicked),
                        (gpointer) NEXT_BUTTON );
    image = gtk_image_new_from_file( buttons[NEXT_BUTTON] );
    gtk_container_add( GTK_CONTAINER(button), image );
    gtk_box_pack_start( GTK_BOX(hbox), button, FALSE, FALSE, 0 );

    gtk_box_pack_start( GTK_BOX(vbox), hbox, TRUE, TRUE, 0 );
    

    hbox = gtk_hbox_new( FALSE, 0 );
    
    button = gtk_button_new( );      
    gtk_button_set_relief( GTK_BUTTON(button), GTK_RELIEF_NONE );
    gtk_signal_connect( GTK_OBJECT(button), "clicked",
                        GTK_SIGNAL_FUNC(cb_button_clicked),
                        (gpointer) STOP_BUTTON );
    image = gtk_image_new_from_file( buttons[STOP_BUTTON] );
    gtk_container_add( GTK_CONTAINER(button), image );
    gtk_box_pack_start( GTK_BOX(hbox), button, FALSE, FALSE, 0 );

    button = gtk_button_new( );      
    gtk_button_set_relief( GTK_BUTTON(button), GTK_RELIEF_NONE );
    gtk_signal_connect( GTK_OBJECT(button), "clicked",
                        GTK_SIGNAL_FUNC(cb_button_clicked),
                        (gpointer) PAUSE_BUTTON );
    image = gtk_image_new_from_file( buttons[PAUSE_BUTTON] );
    gtk_container_add( GTK_CONTAINER(button), image );
    gtk_box_pack_start( GTK_BOX(hbox), button, FALSE, FALSE, 0 );

    
    /* some folks may not have FFTW */
#ifdef USE_VISUAL
    button = gtk_button_new_with_label( "spectrum" );
    gtk_button_set_relief( GTK_BUTTON(button), GTK_RELIEF_NONE );
    gtk_signal_connect( GTK_OBJECT(button), "clicked",
                        GTK_SIGNAL_FUNC(cb_button_clicked),
                        (gpointer) PREF_BUTTON );
    gtk_box_pack_end( GTK_BOX(hbox), button, FALSE, FALSE, 0 );
#endif
    
    gtk_box_pack_start( GTK_BOX(vbox), hbox, TRUE, TRUE, 0 );
    

    
    
    gtk_box_pack_start( GTK_BOX(left_vbox), vbox, FALSE, FALSE, 0 );


    adj = gtk_adjustment_new( 100.0, 0.0, 100.0, 1.0, 10.0, 0.0 );

    volume_slider = gtk_hscale_new( GTK_ADJUSTMENT(adj) );
    gtk_scale_set_draw_value( GTK_SCALE(volume_slider), FALSE );

    gtk_signal_connect( GTK_OBJECT(adj), "value-changed",
                        GTK_SIGNAL_FUNC(cb_volume_slider_moved), (gpointer)0 );

    gtk_box_pack_start( GTK_BOX(left_vbox), volume_slider, FALSE, FALSE, 0 );


    button = gtk_button_new_from_stock( GTK_STOCK_QUIT );
    g_signal_connect( G_OBJECT(button), "clicked",
                      G_CALLBACK(gtk_main_quit), (gpointer)0 );

    gtk_box_pack_start( GTK_BOX(left_vbox), button, FALSE, FALSE, 0 );

    
/*     gtk_box_pack_start( GTK_BOX(outer_hbox), left_vbox, TRUE, TRUE, 0 ); */

    gtk_paned_pack2( GTK_PANED(paned), left_vbox, TRUE, TRUE );

    gtk_paned_set_position( GTK_PANED(paned), LEFT_POSITION );
    
/*     gtk_container_add( GTK_CONTAINER(main_window), outer_hbox ); */
    gtk_container_add( GTK_CONTAINER(main_window), paned );


    get_stupid_config();    
    

    gtk_widget_show_all( main_window );
    
    gtk_timeout_add( 2000, check_pos_timer, NULL );
    
    gtk_timeout_add( 5000, adjust_sample_freq, NULL );
    
    gtk_main();
    
    exit(0);
}






/* functions for manipulation of lists of strings... */


/* I think we can use g_strfreev() for this */
/* void free_list( char** list ) */
/* { */
/* } */



/* list is 0-terminated list of chars. returns 1 if s was found in the
   list, 0 if not. */
int in_list( char **list, char *s )
{
    int found = 0;

    while( *list )
    {
        if( !strcmp( *list, s ) )
        {
            found = 1;
            break;
        }
        ++list;
    }
    return found;
}


/* you give it a 0-terminated list of strings, it gives you a new
   list of all the unique strings in your list */
char **uniquify_list( char **list )
{
    int i,n;
    char **unique;
    char **start;
    
    n=0;
    while( list[n] ) { n++; }
    
    unique = start = malloc( (n+1) * sizeof( char* ) );

    *start = 0;
    for( i=0; i<n; i++ )
    {
        if( !in_list(unique, list[i]) )
        {
            *(start++) = strdup( list[i] );
            *start = 0;
        }
    }
    return unique;
}


/* this does pseudo "in place" sorting */
void sort_id3s( id3_contents ***id3_tags )
{
    id3_contents **tags;
    id3_contents **new_tags;
    id3_contents **head;
    id3_contents **i_head;
    int i_count;
    char **albums;
    char **unique_albums;

    int i, j, n, n_unique;

    regex_t *regex;
    int err_no = 0;
    char pattern[] = "[Tt]rack:* *([0123456789]+)";

    
    tags = *id3_tags;
    
    n=0;
    while( tags[n] ) { n++; }


    /* prepare for regex search (do it here so we only need to compile
     * the regex once) */
    
    regex = (regex_t *) malloc(sizeof(regex_t));
    memset(regex, 0, sizeof(regex_t));

    if( (err_no=regcomp(regex, pattern, REG_EXTENDED)) != 0 )
    {
        return;
    }


    /* get a unique list of albums into unique_albums */

    albums = masc_rtalloc( (n+1) * sizeof(char*) );
    albums[n] = 0;
    for( i=0; i<n; i++ )
    {
        albums[i] = g_strdup( tags[i]->album );
    }
    unique_albums = uniquify_list( albums );
    g_strfreev( albums ); /* will this work? */

    
    /* sort albums abphabetically */

    n_unique = 0;
    while( unique_albums[n_unique] ) { n_unique++; }

    qsort( unique_albums, n_unique, sizeof(char*), string_compare );

#ifdef DEBUG
    printf( "list of unique albums:\n" );
    for( i=0; i<n_unique; i++ )
    {
        printf( "%s\n", unique_albums[i] );
    }
#endif

    
    /* construct a new list of tags, going album by album, and
     * by track or alphabetically within an album */

    new_tags = head = masc_rtalloc( (n+1) * sizeof(id3_contents*) );

    for( i=0; i<n_unique; i++ )
    {
        i_head = head;
        i_count = 0;
        
        for( j=0; j<n; j++ )
        {
            if( !strcmp(tags[j]->album, unique_albums[i]) )
           {
                *(head++) = tags[j];
                *head = 0;
                ++i_count;
            }
        }

        /* see if track numbers are in comments maybe */
        fix_broken_track_numbers( i_head, i_count, regex );
        
        qsort( i_head, i_count, sizeof(id3_contents*), tag_compare );
    }
    
    
    tags = *id3_tags;
    *id3_tags = new_tags;
    masc_rtfree( tags );    

    regfree( regex );
    free( regex );
}


int string_compare( void *one, void *two )
{
    return strcmp( *(char**)one, *(char**)two );
}


int tag_compare( void *one, void *two )
{
    id3_contents *a = *( (id3_contents **)one );
    id3_contents *b = *( (id3_contents **)two );

    /* see if we can use track numbers */
    if( a->track_no != b->track_no )
    {
        if( a->track_no > b->track_no )
            return 1;
        else
            return -1;
    }

    /* no?! ok, sort alphabetically by title then */
    return strcmp( a->title, b->title );
}



void free_tag_list( id3_contents **tags )
{
    id3_contents **tmp;
    id3_contents *tag;
    
    tmp = tags;

    while( *tmp )
    {
        tag = *tmp;
        
        g_free( tag->fname );
        g_free( tag->artist );
        g_free( tag->title ); 
        g_free( tag->album );

        masc_rtfree( tag );

        tmp++;
    }

    masc_rtfree( tags );
}


int filter_mp3_ending( char *d )
{
    char *c;

    c = d + strlen( d ) - 1;

    if( *c != '3' )
        return 0;

    --c;
    
    if( (*c != 'p')&&(*c !='P') )
        return 0;
    
    --c;
    
    if( (*c != 'm')&&(*c !='M') )
        return 0;

    --c;
    
    if( *c != '.' )
        return 0;
    
    return 1;
}

int scan_directory( char *dir, char ***names_p )
{
#define MAX_FILES 512
    int n=0;
    int i, j;
    char **names;
    DIR *dirp;
    struct dirent *dp;

    names = *names_p = masc_rtalloc( MAX_FILES*sizeof(char*) );
    if( !names )
        return 0;
    
    dirp = opendir( dir );

    if( !dirp )
        return 0;

    /* get all files */
    while( dirp && (n<MAX_FILES) )
    {
        errno = 0;
        if( (dp = readdir(dirp)) != NULL )
        {
            names[n++] = strdup( dp->d_name );
        }
        else
        {
            if (errno == 0)
            {
                closedir(dirp);
                break;
            }
            closedir(dirp);
            return 0;
        }
    }

        
    /* filter out mp3's */
    i=0;
    while( i<n )
    {
        if( filter_mp3_ending(names[i])==0 )
        {
            free( names[i] );
            
            /* close the gap */
            for( j=i; j<n-1; j++ )
            {
                names[j] = names[j+1];
            }
            
            --n;
            continue; /* to avoid increasing i */
        }
        ++i;
    }

    return n;
}


void import_entire_dir( char ***names )
{
    char          **nl;
    char          **namelist;
    char           *dirname;
    char            filename[2048];
    
    int n;
    int i=0;
    
    nl      = *names;
    dirname = g_strdup( *nl );

    n = scan_directory( dirname, &namelist );

    g_strfreev( nl );
    *names = NULL;
    
    if (n <= 0)
    {
        return;
    }
    
    nl = malloc( n * sizeof(char*) + 1 );
    
    while( n-- )
    {
        masc_strlcpy( filename, dirname, sizeof filename );
        mas_assert( masc_strlcat( filename, namelist[n], sizeof filename ) < sizeof filename, "masplayer: not enough space to hold long filename");
        nl[i++] = g_strdup( filename );
        free(namelist[n]);
    }
    free(namelist);

    nl[i] = NULL;    
    
    *names = nl;
    g_free( dirname );
}

Generated by  Doxygen 1.6.0   Back to index