/*************************************************************** * Linux MPEGPlus support for XMMS. * E-Mail: thomas.juerges@astro.ruhr-uni-bochum.de * ***************************************************************/ #define NCH 2 #define SAMPLERATE 44100 #define BPS 16 #define PI 3.141592653589793238462643383276 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "requant.h" #include "huff_old.h" #include "huff_new.h" #include "bitstream.h" #include "mpp_dec.h" #include "mpplus_blue.xpm" typedef struct { unsigned int StreamVersion; unsigned int Bitrate; unsigned int Frames; unsigned int MS; unsigned int ByteLength; unsigned int Profile; } StreamInfo; typedef struct { char tag[3]; /* always "TAG": defines ID3v1 tag 128 bytes before EOF */ char title[30]; char artist[30]; char album[30]; char year[4]; char comment[30]; unsigned char genre; } ID3V1_1; static ID3V1_1 id3info; char displayed_info[128]; static GtkWidget *mp_info=NULL; static GtkWidget *mp_conf=NULL; static GtkWidget *bitrate, *clip, *ID3, *titleformat_entry; static GtkWidget *filename_entry, *title_entry, *artist_entry, *album_entry, *year_entry, *comment_entry; static GtkWidget *genre_combo; static GList *genre_list; static gchar *current_filename; static gboolean id3_found=FALSE; char lastfn[PATH_MAX]; // currently playing file (used for getting info on the current file) int decode_pos_ms; // current decoding position, in milliseconds int paused; // are we paused? int seek_needed; // if != -1, it is the point that the decode thread should seek to, in ms. static char sample_buffer[1152*NCH*(BPS/8)]; // sample buffer char INFOFN[PATH_MAX]; char INFO1[40],INFO2[32],INFO3[32],INFO4[32],INFO5[32],INFO6[32],INFO7[32]; char TitleFormat[32] = "%1 - %2\0"; int EQdB =12; int ClipPrevUsed = 0; int DisplayID3Names = 1; int UpdateBitrate = 0; int MaxBrokenFrames = 0; FILE *inputFile=NULL; int killDecodeThread=0; // the kill switch for the decode thread static pthread_t thread_handle; int NoGenres = 148; gchar *GenreList[]={"Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge", "Hip-Hop", "Jazz", "Metal", "New Age", "Oldies", "Other", "Pop", "R&B", "Rap", "Reggae", "Rock", "Techno", "Industrial", "Alternative", "Ska", "Death Metal", "Pranks", "Soundtrack", "Euro-Techno", "Ambient", "Trip-Hop", "Vocal", "Jazz+Funk", "Fusion", "Trance", "Classical", "Instrumental", "Acid", "House", "Game", "Sound Clip", "Gospel", "Noise", "AlternRock", "Bass", "Soul", "Punk", "Space", "Meditative", "Instrumental Pop", "Instrumental Rock", "Ethnic", "Gothic", "Darkwave", "Techno-Industrial", "Electronic", "Pop-Folk", "Eurodance", "Dream", "Southern Rock", "Comedy", "Cult", "Gangsta", "Top 40", "Christian Rap", "Pop/Funk", "Jungle", "Native American", "Cabaret", "New Wave", "Psychadelic", "Rave", "Showtunes", "Trailer", "Lo-Fi", "Tribal", "Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical", "Rock & Roll", "Hard Rock", "Folk", "Folk/Rock", "National Folk", "Swing", "Fast-Fusion", "Bebob", "Latin", "Revival", "Celtic", "Bluegrass", "Avantgarde", "Gothic Rock", "Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock", "Big Band", "Chorus", "Easy Listening", "Acoustic", "Humour", "Speech", "Chanson", "Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass", "Primus", "Porn Groove", "Satire", "Slow Jam", "Club", "Tango", "Samba", "Folklore", "Ballad", "Power Ballad", "Rhythmic Soul", "Freestyle", "Duet", "Punk Rock", "Drum Solo", "A capella", "Euro-House", "Dance Hall", "Goa", "Drum & Bass", "Club House", "Hardcore", "Terror", "Indie", "BritPop", "NegerPunk", "Polsk Punk", "Beat", "Christian Gangsta", "Heavy Metal", "Black Metal", "Crossover", "Contemporary C", "Christian Rock", "Merengue", "Salsa", "Thrash Metal", "Anime", "JPop", "SynthPop" }; enum id3_format_codes { ID3_ARTIST = '1', ID3_TITLE, ID3_ALBUM, ID3_YEAR, ID3_COMMENT, ID3_GENRE, FILE_NAME, FILE_PATH, FILE_EXT }; extern InputPlugin mod; InputPlugin *get_iplugin_info(void); static void get_entry_tag(GtkEntry *,gchar *,gint); void FileInfo(const char *); void Config_dialog(void); static void config_ok(GtkWidget *,gpointer); static int find_genre_id(gchar *); static void remove_id3_cb(GtkWidget *,gpointer); static void set_entry_tag(GtkEntry *,gchar *, gint); guint entry_strip_spaces(char *,size_t); void get_id3_tags(const char *); int ReadFileHeader(const char *, StreamInfo*); int getlength(void); int getoutputtime(void); int isourfile(char *); int checkEQ(void); void init(void); void quit(void); void stop(void); void setoutputtime(const int); int perform_jump(int *, int *); void play(char *); void setvolume(const int, const int); void infoDlg(char *); void getfileinfo(char *, char **, int *); void config(void); void about(void); void EQSet(int, float, float *); void Pause(const short); void itoa(const int, char *, const int); void write_cfg_file(void); void read_cfg_file(void); static gint genre_comp_func(gconstpointer,gconstpointer); static void save_cb(GtkWidget *, gpointer); void *DecodeThread(void *b); char *extname(const char *); char *eval_id3_format(const char *, const char *, const char *, const char *, const char *, const char *, const unsigned char); InputPlugin mod = { NULL, NULL, NULL, /* TJ: Description, copied in later when registering the plugin. */ init, about, config, isourfile, NULL, play, stop, Pause, setoutputtime, EQSet, getoutputtime, NULL, setvolume, quit, NULL, NULL, NULL, NULL, getfileinfo, infoDlg, /* TJ: file_info_box with editable ID3 tags and other groovy things :) */ NULL }; void itoa(const int source, char *destination, const int format) { sprintf(destination,"%d",source); } /* TJ: O.k. tell the world about our great plugin. */ InputPlugin *get_iplugin_info(void) { mod.description=g_strdup_printf(("MP+ Audio Player %s"),VERSION); return &mod; } void write_cfg_file(void) { ConfigFile *config; gchar *conf_filename; conf_filename=g_strconcat(g_get_home_dir(),"/.xmms/config",NULL); config=xmms_cfg_open_file(conf_filename); if(!config) config=xmms_cfg_new(); xmms_cfg_write_int(config,"MPEGplus","EQdB",EQdB); xmms_cfg_write_int(config,"MPEGplus","ClipPrevEnabled",ClipPrevUsed); xmms_cfg_write_int(config,"MPEGplus","DisplayID3Names",DisplayID3Names); xmms_cfg_write_string(config,"MPEGplus","TitleFormat",(char *)&TitleFormat); xmms_cfg_write_int(config,"MPEGplus","UpdateBitrate",UpdateBitrate); xmms_cfg_write_int(config,"MPEGplus","MaxBrokenFrames",MaxBrokenFrames); xmms_cfg_write_file(config,conf_filename); xmms_cfg_free(config); g_free(conf_filename); } void read_cfg_file(void) { ConfigFile *config; gchar *conf_filename; conf_filename=g_strconcat(g_get_home_dir(),"/.xmms/config",NULL); config=xmms_cfg_open_file(conf_filename); if(config!=NULL) { gchar *dummy; dummy=g_strdup(TitleFormat); xmms_cfg_read_int(config,"MPEGplus","EQdB",(int *)&EQdB); xmms_cfg_read_int(config,"MPEGplus","ClipPrevEnabled",(int *)&ClipPrevUsed); xmms_cfg_read_int(config,"MPEGplus","DisplayID3Names",(int *)&DisplayID3Names); xmms_cfg_read_string(config,"MPEGplus","TitleFormat",&dummy); xmms_cfg_read_int(config,"MPEGplus","UpdateBitrate",(int *)&UpdateBitrate); xmms_cfg_read_int(config,"MPEGplus","MaxBrokenFrames",(int *)&MaxBrokenFrames); xmms_cfg_free(config); strncpy(TitleFormat,dummy,sizeof(TitleFormat)); } g_free(conf_filename); } /********************* FUNCTIONS *******************/ int ReadFileHeader(const char *fn, StreamInfo *Info) { unsigned int HeaderData[3]; FILE *tmpFile=NULL; char Tag[4]={0,0,0,0}; // Reset Info-Data Info->Bitrate = Info->MS = Info->StreamVersion = Info->Frames = Info->ByteLength = 0; // load file tmpFile=fopen(fn, "rb"); if(tmpFile!=NULL) { fread((void*)HeaderData, sizeof(int),3,tmpFile); fseek(tmpFile, 0, SEEK_END); Info->ByteLength = ftell(tmpFile); } else return 1; // file not found or read-protected fclose(tmpFile); // search for id3-tag memcpy(Tag, HeaderData, 3); if(!strcmp(Tag,"MP+")) Info->StreamVersion = HeaderData[0]>>24; if(Info->StreamVersion>=7) { // read the file-header (SV7 and above) Info->Bitrate = 0; Info->Frames = HeaderData[1]; Info->MS = (HeaderData[2]<<1)>>31; if( (HeaderData[2]<<8)>>31 ) Info->Profile = (HeaderData[2]<<9)>>29; else Info->Profile = 6; } else { // read the file-header (SV6 and below) Info->Bitrate = (HeaderData[0]>>23); Info->MS = ((HeaderData[0]<<10)>>31); Info->StreamVersion = ((HeaderData[0]<<11)>>22); Info->Profile = 6; if(Info->StreamVersion>=5) Info->Frames = HeaderData[1]; else Info->Frames = (HeaderData[1]>>16); } // Bugfix: last frame was invalid for up to SV5 if(Info->StreamVersion<6) Info->Frames -= 1; return 0; } void config(void) { // save old configuration char old_TitleFormat[32]; int old_ClipPrevUsed = ClipPrevUsed; int old_DisplayID3Names = DisplayID3Names; int old_UpdateBitrate = UpdateBitrate; int old_EQdB = EQdB; int old_MaxBrokenFrames = MaxBrokenFrames; int has_changed = 0; memcpy(old_TitleFormat, TitleFormat, sizeof(TitleFormat)); // open dialog-box Config_dialog(); // check for change if(ClipPrevUsed != old_ClipPrevUsed || DisplayID3Names != old_DisplayID3Names || strcmp(old_TitleFormat, TitleFormat) || UpdateBitrate != old_UpdateBitrate || EQdB != old_EQdB || MaxBrokenFrames != old_MaxBrokenFrames) has_changed=1; // rewrite ini-file if (has_changed) write_cfg_file(); } void about(void) { xmms_show_message("About MP+ plugin", "MPEGPlus plugin " VERSION "\n" "Linux version by Thomas Juerges\n" " Contact: thomas.juerges@astro.ruhr-uni-bochum.de\n" " Homepage: http://www.sourceforge.net/projects/mpegplus/\n\n" "- Plays MP+ (*.mp+, *.mpp, *.mpc) encoded files.\n" "- Supports ID3 V1.1 only\n" "- Supports fast EQ\n" "- Supports cross-fader plugins\n\n" "For more information about the superb music\n" "encoder MPEGPlus contact the inventor and author\n" "of the encoder, Andree Buschmann:\n" "Andree.Buschmann@web.de", "Ok",FALSE,NULL,NULL); } void display_id3_button(GtkWidget *widget, gpointer data) { if(GTK_TOGGLE_BUTTON(ID3)->active) { gchar *dummy; DisplayID3Names=1; dummy=gtk_entry_get_text(GTK_ENTRY(titleformat_entry)); sprintf(TitleFormat,"%s",dummy); if(strcmp(lastfn,"\0")!=0) get_id3_tags(lastfn); } else DisplayID3Names=0; gtk_widget_set_sensitive(titleformat_entry,DisplayID3Names); } void config_ok(GtkWidget *widget, gpointer data) { if(GTK_TOGGLE_BUTTON(clip)->active) ClipPrevUsed=1; else ClipPrevUsed=0; if(GTK_TOGGLE_BUTTON(bitrate)->active) UpdateBitrate=1; else UpdateBitrate=0; if(GTK_TOGGLE_BUTTON(ID3)->active) { gchar *dummy; DisplayID3Names=1; dummy=gtk_entry_get_text(GTK_ENTRY(titleformat_entry)); sprintf(TitleFormat,"%s",dummy); if(strcmp(lastfn,"\0")!=0) get_id3_tags(lastfn); } else DisplayID3Names=0; write_cfg_file(); gtk_widget_destroy(mp_conf); } void Config_dialog(void) { // Buttons: GtkWidget *ok, *cancel, *mainbox, *vbox, *hbox, *separator, *logo_id, *title_label, *title_box, *id3_description_label1, *id3_description_label2, *format_box; GdkPixmap *logo; GdkBitmap *mask; // Check if we've just opened the dialog window: if (!mp_conf) { // No window until now. Create a new one. mp_conf=gtk_window_new(GTK_WINDOW_DIALOG); gtk_object_set_data(GTK_OBJECT(mp_conf),"mp_conf",mp_conf); gtk_window_set_title(GTK_WINDOW(mp_conf),"MPEGPlus Configuration"); gtk_window_set_position(GTK_WINDOW(mp_conf),GTK_WIN_POS_MOUSE); // We receive the window_close event. O.k., let's close the dialog. // This is done for us by GTK. gtk_signal_connect(GTK_OBJECT(mp_conf),"destroy", GTK_SIGNAL_FUNC(gtk_widget_destroyed),&mp_conf); gtk_container_border_width(GTK_CONTAINER(mp_conf),10); // Create maincontainer for all the visual stuff. mainbox=gtk_vbox_new(FALSE,0); gtk_container_add(GTK_CONTAINER(mp_conf),mainbox); gtk_widget_show(mainbox); // Now we display the MP+-logo. logo=gdk_pixmap_colormap_create_from_xpm_d(NULL,gtk_widget_get_colormap(mainbox),&mask,NULL,(gchar **)mpplus_blue_xpm); logo_id=gtk_pixmap_new(logo,mask); gdk_pixmap_unref(logo); gdk_pixmap_unref(mask); gtk_widget_show(logo_id); gtk_container_add(GTK_CONTAINER(mainbox),logo_id); gtk_widget_show(logo_id); // Herein the checkboxes go: vbox=gtk_vbox_new(FALSE,10); gtk_box_pack_start (GTK_BOX(mainbox),vbox,TRUE,TRUE,0); gtk_widget_show(vbox); // Setup all the configurable stuff: bitrate=gtk_check_button_new_with_label("Bitrate"); gtk_box_pack_start(GTK_BOX(vbox),bitrate,TRUE,TRUE,0); gtk_widget_show(bitrate); if(UpdateBitrate==1) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bitrate),TRUE); clip=gtk_check_button_new_with_label("Clipping"); gtk_box_pack_start(GTK_BOX(vbox),clip,TRUE,TRUE,0); gtk_widget_show(clip); if(ClipPrevUsed==1) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(clip),TRUE); ID3=gtk_check_button_new_with_label("Display ID3 Information"); gtk_box_pack_start(GTK_BOX(vbox),ID3,TRUE,TRUE,0); gtk_widget_show(ID3); if(DisplayID3Names==1) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ID3),TRUE); /* the titleformat string gets a label, therefore create a new box to put the required stuff into */ title_box=gtk_hbox_new(FALSE,5); gtk_box_pack_start(GTK_BOX(vbox), title_box, FALSE, FALSE, 0); title_label=gtk_label_new("Titleformat:"); gtk_box_pack_start(GTK_BOX(title_box), title_label, FALSE, FALSE, 0); gtk_widget_show(title_label); titleformat_entry=gtk_entry_new_with_max_length(sizeof(TitleFormat)); gtk_entry_set_text(GTK_ENTRY(titleformat_entry),TitleFormat); gtk_widget_set_sensitive(titleformat_entry,DisplayID3Names); gtk_box_pack_start(GTK_BOX(title_box),titleformat_entry,TRUE,TRUE,0); gtk_widget_show(titleformat_entry); gtk_widget_show(title_box); format_box=gtk_hbox_new(FALSE,5); gtk_box_pack_start(GTK_BOX(vbox), format_box, FALSE, FALSE, 0); id3_description_label1=gtk_label_new("%1 = Artist\n%3 = Album\n%5 = Comment\n%7 = File name\n%9 = File extension"); gtk_misc_set_alignment(GTK_MISC(id3_description_label1), 0, 0); gtk_label_set_justify(GTK_LABEL(id3_description_label1), GTK_JUSTIFY_LEFT); gtk_box_pack_start(GTK_BOX(format_box),id3_description_label1,TRUE,TRUE,0); gtk_widget_show(id3_description_label1); id3_description_label2=gtk_label_new("%2 = Title\n%4 = Year\n%6 = Genre\n%8 = Path"); gtk_misc_set_alignment(GTK_MISC(id3_description_label2), 0, 0); gtk_label_set_justify(GTK_LABEL(id3_description_label2), GTK_JUSTIFY_LEFT); gtk_box_pack_start(GTK_BOX(format_box),id3_description_label2,TRUE,TRUE,0); gtk_widget_show(id3_description_label2); gtk_widget_show(format_box); // The OK & Cancel buttons are separated from the config stuff separator=gtk_hseparator_new(); gtk_box_pack_start(GTK_BOX(vbox),separator,FALSE,TRUE,0); gtk_widget_show(separator); hbox=gtk_hbox_new(FALSE,10); gtk_box_pack_start (GTK_BOX(vbox),hbox,TRUE,TRUE,5); gtk_widget_show(hbox); // connect button ID3 to func. display_id3_button gtk_signal_connect(GTK_OBJECT(ID3),"clicked",GTK_SIGNAL_FUNC(display_id3_button),NULL); // Now setup an ok button. ok=gtk_button_new_with_label("Ok"); // If we press ok, let's go to function config_ok. gtk_signal_connect(GTK_OBJECT(ok),"clicked",GTK_SIGNAL_FUNC(config_ok),NULL); // Add the ok button to our main dialog window. gtk_box_pack_start(GTK_BOX(hbox),ok,TRUE,TRUE,0); // Show the ok button: gtk_widget_show(ok); // Create the cancel button: cancel=gtk_button_new_with_label("Cancel"); // We press cancel -> close dialog. gtk_signal_connect_object(GTK_OBJECT(cancel),"clicked",GTK_SIGNAL_FUNC(gtk_widget_destroy),GTK_OBJECT(mp_conf)); GTK_WIDGET_SET_FLAGS(cancel,GTK_CAN_DEFAULT); // Add the cancel button to the dialog window. gtk_box_pack_start(GTK_BOX(hbox),cancel,TRUE,TRUE,0); // Display the cancel button. gtk_widget_show(cancel); gtk_widget_grab_default(cancel); // Setup of GTK dialog is done. Display it: gtk_widget_show(mp_conf); } else gdk_window_raise(mp_conf->window); // If the config dialog is already active, bring it to front } int genre_comp_func(gconstpointer a, gconstpointer b) { return strcasecmp(a, b); } int find_genre_id(gchar *text) { int i; for (i = 0; i < NoGenres; i++) { if (!strcmp(GenreList[i], text)) return i; } if (text[0] == '\0') return -1; return 0; } void set_entry_tag(GtkEntry *entry, gchar *dummy_tag, gint length) { gint stripped_len; gchar *text; stripped_len = entry_strip_spaces(dummy_tag, length); text = g_strdup_printf("%-*.*s", stripped_len, stripped_len, dummy_tag); gtk_entry_set_text(entry, text); g_free(text); } unsigned int entry_strip_spaces(char *src, size_t n) /* strips trailing spaces from string of length n returns length of adjusted string */ { gchar *space = NULL, /* last space in src */ *start = src; while (n--) switch (*src++) { case '\0': n = 0; /* breaks out of while loop */ src--; break; case ' ': if (space == NULL) space = src - 1; break; default: space = NULL; /* don't terminate intermediate spaces */ break; } if (space != NULL) { src = space; *src = '\0'; } return src - start; } char *extname(const char *filename) { gchar *ext = strrchr(filename, '.'); if (ext != NULL) ++ext; return ext; } char *eval_id3_format(const char *filename, const char *artist, const char *album, const char *title, const char *year, const char *comment, const unsigned char genre) /* returns ID3 and filename data as specified in format as in Nullsoft's Nitrane plugin v1.31b (their MPEG decoder) */ { gchar *id3_format=TitleFormat; gchar *ans, c, *base, *path, *ext; guint length = 0, allocated, baselen, pathlen, extlen, tmp; const size_t alloc_size = 256; /* size of memory block allocations */ gboolean got_field = FALSE; ans = g_malloc(allocated = alloc_size); pathlen = strlen(path = g_dirname(filename)); base = g_strdup(g_basename(filename)); if ((ext = extname(base)) == NULL) { ext = ""; extlen = 0; } else { *(ext - 1) = '\0'; extlen = strlen(ext); } baselen = strlen(base); while ((c = *id3_format++) != '\0') { tmp = 1; if (c == '%') { switch (*id3_format++) { case 0: id3_format--; /* otherwise we'll lose terminator */ case '%': ans[length] = '%'; break; case ID3_ARTIST: tmp = strlen(artist); if(tmp != 0) got_field = TRUE; strncpy(&ans[length], artist, tmp); break; case ID3_TITLE: tmp = strlen(title); if(tmp != 0) got_field = TRUE; strncpy(&ans[length], title, tmp); break; case ID3_ALBUM: tmp = strlen(album); if(tmp != 0) got_field = TRUE; strncpy(&ans[length], album, tmp); break; case ID3_YEAR: tmp = strlen(year); if(tmp != 0) got_field = TRUE; strncpy(&ans[length], year, tmp); break; case ID3_COMMENT: tmp = strlen(comment); if(tmp != 0) got_field = TRUE; strncpy(&ans[length], comment, tmp); break; case ID3_GENRE: tmp = strlen(GenreList[genre]); if(tmp != 0) got_field = TRUE; strncpy(&ans[length], GenreList[genre], tmp); break; case FILE_NAME: strncpy(&ans[length], base, tmp = baselen); got_field = TRUE; break; case FILE_PATH: strncpy(&ans[length], path, tmp = pathlen); got_field = TRUE; break; case FILE_EXT: strncpy(&ans[length], ext, tmp = extlen); got_field = TRUE; break; default: ans[length] = c; break; } } else ans[length] = c; ans[length += tmp] = '\0'; if (allocated - length <= 30) ans = g_realloc(ans, allocated += alloc_size); } ans = g_realloc(ans, length + 1); if(!got_field) { g_free(ans); ans = g_strdup(base); } g_free(base); g_free(path); return ans; } void get_id3_tags(const char *filename) { int fh; fh=open(filename, O_RDONLY); if(fh!=-1) { char artist[sizeof(id3info.artist)], album[sizeof(id3info.album)], title[sizeof(id3info.title)], comment[sizeof(id3info.comment)], year[sizeof(id3info.year)+1]; unsigned int len; lseek(fh, -sizeof(ID3V1_1), SEEK_END); if(read(fh, &id3info, sizeof(ID3V1_1))==sizeof(ID3V1_1)) { if(strncmp(id3info.tag,"TAG",3)==0) { id3_found=TRUE; strncpy(artist,id3info.artist,sizeof(id3info.artist)); len=entry_strip_spaces(artist,sizeof(id3info.artist)); strncpy(album,id3info.album,sizeof(id3info.album)); len=entry_strip_spaces(album,sizeof(id3info.album)); strncpy(title,id3info.title,sizeof(id3info.title)); len=entry_strip_spaces(title,sizeof(id3info.title)); strncpy(year,id3info.year,sizeof(id3info.year)); year[4]=0; //len=entry_strip_spaces(year,sizeof(id3info.year)); strncpy(comment,id3info.comment,sizeof(id3info.comment)); len=entry_strip_spaces(comment,sizeof(id3info.comment)); sprintf(displayed_info,"%s",eval_id3_format(filename,artist,album,title,year,comment,id3info.genre)); } else { id3_found=FALSE; close(fh); return; } } else { char text[256]; close(fh); sprintf(text, "File \"%s\" has broken ID3 information.\n",filename); xmms_show_message("ERROR: get_id3_tags()", text, "Ok",FALSE,NULL,NULL); return; } } else { char text[256]; sprintf(text, "Cannot open file \"%s\".\n",filename); xmms_show_message("ERROR: get_id3_tags()", text, "Ok",FALSE,NULL,NULL); return; } } static void get_entry_tag(GtkEntry *entry, gchar *tag, gint length) { gchar *text; text = gtk_entry_get_text(entry); memset(tag, ' ', length); memcpy(tag, text, strlen(text) > length ? length : strlen(text)); } static void save_cb(GtkWidget *w, gpointer data) { int fd; ID3V1_1 file_tag; fd=open(current_filename,O_RDWR); if (fd!=-1) { lseek(fd, -sizeof(ID3V1_1), SEEK_END); read(fd,&file_tag, sizeof(ID3V1_1)); if (strncmp(file_tag.tag, "TAG", 3)==0) lseek(fd, -sizeof(ID3V1_1), SEEK_END); else lseek(fd, 0, SEEK_END); file_tag.tag[0] = 'T'; file_tag.tag[1] = 'A'; file_tag.tag[2] = 'G'; get_entry_tag(GTK_ENTRY(title_entry), file_tag.title, sizeof(file_tag.title)); get_entry_tag(GTK_ENTRY(artist_entry), file_tag.artist, sizeof(file_tag.artist)); get_entry_tag(GTK_ENTRY(album_entry), file_tag.album, sizeof(file_tag.album)); get_entry_tag(GTK_ENTRY(year_entry), file_tag.year, sizeof(file_tag.year)); get_entry_tag(GTK_ENTRY(comment_entry), file_tag.comment, sizeof(file_tag.comment)); file_tag.genre = find_genre_id(gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(genre_combo)->entry))); if (write(fd, &file_tag, sizeof(ID3V1_1)) != sizeof(ID3V1_1)) xmms_show_message("File Info", "Couldn't write tag!", "Ok", FALSE, NULL, NULL); close(fd); } else xmms_show_message("File Info", "Couldn't write tag!", "Ok",FALSE, NULL, NULL); gtk_widget_destroy(mp_info); } static void remove_id3_cb(GtkWidget *w,gpointer data) { int fd, len; ID3V1_1 file_tag; fd=open(current_filename,O_RDWR); if(fd!=-1) { len=lseek(fd, -(sizeof(ID3V1_1)), SEEK_END); read(fd, &file_tag, sizeof(ID3V1_1)); if(!strncmp(file_tag.tag, "TAG", 3)) { if(ftruncate(fd, len)) xmms_show_message("File Info", "Couldn't remove tag!", "Ok",FALSE, NULL, NULL); } else xmms_show_message("File Info", "No tag to remove!", "Ok",FALSE, NULL, NULL); close(fd); } else xmms_show_message("File Info", "Couldn't remove tag!", "Ok", FALSE, NULL, NULL); gtk_widget_destroy(mp_info); } void FileInfo(const char *filename) { gint i; gchar *tmp, *title; GtkWidget *mp_level, *mp_bitrate, *mp_samplerate, *mp_flags, *mp_fileinfo; if (!mp_info) { GtkWidget *vbox, *hbox, *left_vbox, *id3_frame, *table; GtkWidget *mp_frame, *mp_box; GtkWidget *label, *filename_hbox; GtkWidget *bbox, *save, *remove_id3, *cancel; mp_info = gtk_window_new(GTK_WINDOW_DIALOG); gtk_window_set_policy(GTK_WINDOW(mp_info), FALSE, FALSE, FALSE); gtk_signal_connect(GTK_OBJECT(mp_info), "destroy", GTK_SIGNAL_FUNC(gtk_widget_destroyed), &mp_info); gtk_container_set_border_width(GTK_CONTAINER(mp_info), 10); vbox = gtk_vbox_new(FALSE, 10); gtk_container_add(GTK_CONTAINER(mp_info), 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 = gtk_entry_new(); gtk_editable_set_editable(GTK_EDITABLE(filename_entry), FALSE); gtk_box_pack_start(GTK_BOX(filename_hbox), filename_entry, TRUE, TRUE, 0); hbox = gtk_hbox_new(FALSE, 10); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0); left_vbox = gtk_vbox_new(FALSE, 10); gtk_box_pack_start(GTK_BOX(hbox), left_vbox, FALSE, FALSE, 0); id3_frame = gtk_frame_new("ID3 Tag:"); gtk_box_pack_start(GTK_BOX(left_vbox), id3_frame, FALSE, FALSE, 0); table = gtk_table_new(4, 5, FALSE); gtk_container_set_border_width(GTK_CONTAINER(table), 5); gtk_container_add(GTK_CONTAINER(id3_frame), table); label = gtk_label_new("Title:"); gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, GTK_FILL, 5, 5); title_entry = gtk_entry_new_with_max_length(sizeof(id3info.title)); gtk_table_attach(GTK_TABLE(table), title_entry, 1, 4, 0, 1, GTK_FILL | GTK_EXPAND | GTK_SHRINK, GTK_FILL | GTK_EXPAND | GTK_SHRINK, 0, 5); label = gtk_label_new("Artist:"); gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2, GTK_FILL, GTK_FILL, 5, 5); artist_entry = gtk_entry_new_with_max_length(sizeof(id3info.artist)); gtk_table_attach(GTK_TABLE(table), artist_entry, 1, 4, 1, 2, GTK_FILL | GTK_EXPAND | GTK_SHRINK, GTK_FILL | GTK_EXPAND | GTK_SHRINK, 0, 5); label = gtk_label_new("Album:"); gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); gtk_table_attach(GTK_TABLE(table), label, 0, 1, 2, 3, GTK_FILL, GTK_FILL, 5, 5); album_entry = gtk_entry_new_with_max_length(sizeof(id3info.album)); gtk_table_attach(GTK_TABLE(table), album_entry, 1, 4, 2, 3, GTK_FILL | GTK_EXPAND | GTK_SHRINK, GTK_FILL | GTK_EXPAND | GTK_SHRINK, 0, 5); label = gtk_label_new("Comment:"); gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); gtk_table_attach(GTK_TABLE(table), label, 0, 1, 3, 4, GTK_FILL, GTK_FILL, 5, 5); comment_entry = gtk_entry_new_with_max_length(sizeof(id3info.comment)); gtk_table_attach(GTK_TABLE(table), comment_entry, 1, 4, 3, 4, GTK_FILL | GTK_EXPAND | GTK_SHRINK, GTK_FILL | GTK_EXPAND | GTK_SHRINK, 0, 5); label = gtk_label_new("Year:"); gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); gtk_table_attach(GTK_TABLE(table), label, 0, 1, 4, 5, GTK_FILL, GTK_FILL, 5, 5); year_entry = gtk_entry_new_with_max_length(sizeof(id3info.year)); gtk_widget_set_usize(year_entry, 40, -1); gtk_table_attach(GTK_TABLE(table), year_entry, 1, 2, 4, 5, GTK_FILL | GTK_EXPAND | GTK_SHRINK, GTK_FILL | GTK_EXPAND | GTK_SHRINK, 0, 5); label = gtk_label_new("Genre:"); gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); gtk_table_attach(GTK_TABLE(table), label, 2, 3, 4, 5, GTK_FILL, GTK_FILL, 5, 5); genre_combo = gtk_combo_new(); gtk_entry_set_editable(GTK_ENTRY(GTK_COMBO(genre_combo)->entry), FALSE); if (!genre_list) { for (i = 0; i < NoGenres; i++) { genre_list = g_list_append(genre_list, (gchar *)GenreList[i]); genre_list = g_list_sort(genre_list, genre_comp_func); } } gtk_combo_set_popdown_strings(GTK_COMBO(genre_combo), genre_list); gtk_table_attach(GTK_TABLE(table), genre_combo, 3, 4, 4, 5, GTK_FILL | GTK_EXPAND | GTK_SHRINK, GTK_FILL | GTK_EXPAND | GTK_SHRINK, 0, 5); bbox = gtk_hbutton_box_new(); gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); gtk_button_box_set_spacing(GTK_BUTTON_BOX(bbox), 5); gtk_box_pack_start(GTK_BOX(left_vbox), bbox, FALSE, FALSE, 0); save = gtk_button_new_with_label("Save"); gtk_signal_connect(GTK_OBJECT(save), "clicked", GTK_SIGNAL_FUNC(save_cb), NULL); GTK_WIDGET_SET_FLAGS(save, GTK_CAN_DEFAULT); gtk_box_pack_start(GTK_BOX(bbox), save, TRUE, TRUE, 0); gtk_widget_grab_default(save); remove_id3 = gtk_button_new_with_label("Remove ID3"); gtk_signal_connect(GTK_OBJECT(remove_id3), "clicked", GTK_SIGNAL_FUNC(remove_id3_cb), NULL); GTK_WIDGET_SET_FLAGS(remove_id3, GTK_CAN_DEFAULT); gtk_box_pack_start(GTK_BOX(bbox), remove_id3, TRUE, TRUE, 0); cancel = gtk_button_new_with_label("Cancel"); gtk_signal_connect_object(GTK_OBJECT(cancel), "clicked", GTK_SIGNAL_FUNC(gtk_widget_destroy), GTK_OBJECT(mp_info)); GTK_WIDGET_SET_FLAGS(cancel, GTK_CAN_DEFAULT); gtk_box_pack_start(GTK_BOX(bbox), cancel, TRUE, TRUE, 0); mp_frame = gtk_frame_new("MPEGPlus Info:"); gtk_box_pack_start(GTK_BOX(hbox), mp_frame, FALSE, FALSE, 0); mp_box = gtk_vbox_new(FALSE, 5); gtk_container_add(GTK_CONTAINER(mp_frame), mp_box); gtk_container_set_border_width(GTK_CONTAINER(mp_box), 10); gtk_box_set_spacing(GTK_BOX(mp_box), 0); mp_level = gtk_label_new(""); gtk_widget_set_usize(mp_level, 200, -2); gtk_misc_set_alignment(GTK_MISC(mp_level), 0, 0); gtk_box_pack_start(GTK_BOX(mp_box), mp_level, FALSE, FALSE, 0); mp_bitrate = gtk_label_new(""); gtk_misc_set_alignment(GTK_MISC(mp_bitrate), 0, 0); gtk_label_set_justify(GTK_LABEL(mp_bitrate), GTK_JUSTIFY_LEFT); gtk_box_pack_start(GTK_BOX(mp_box), mp_bitrate, FALSE, FALSE, 0); mp_samplerate = gtk_label_new(""); gtk_misc_set_alignment(GTK_MISC(mp_samplerate), 0, 0); gtk_box_pack_start(GTK_BOX(mp_box), mp_samplerate, FALSE, FALSE, 0); mp_flags = gtk_label_new(""); gtk_misc_set_alignment(GTK_MISC(mp_flags), 0, 0); gtk_label_set_justify(GTK_LABEL(mp_flags), GTK_JUSTIFY_LEFT); gtk_box_pack_start(GTK_BOX(mp_box), mp_flags, FALSE, FALSE, 0); mp_fileinfo = gtk_label_new(""); gtk_misc_set_alignment(GTK_MISC(mp_fileinfo), 0, 0); gtk_label_set_justify(GTK_LABEL(mp_fileinfo), GTK_JUSTIFY_LEFT); gtk_box_pack_start(GTK_BOX(mp_box), mp_fileinfo, FALSE, FALSE, 0); gtk_widget_show_all(mp_info); } if(current_filename) g_free(current_filename); current_filename = g_strdup(filename); title = g_strdup_printf("File Info - %s", g_basename(filename)); gtk_window_set_title(GTK_WINDOW(mp_info), title); g_free(title); gtk_entry_set_text(GTK_ENTRY(filename_entry), current_filename); gtk_editable_set_position(GTK_EDITABLE(filename_entry), -1); title = g_strdup(g_basename(current_filename)); if ((tmp = strrchr(title, '.')) != NULL) *tmp = '\0'; gtk_entry_set_text(GTK_ENTRY(title_entry), title); g_free(title); gtk_entry_set_text(GTK_ENTRY(artist_entry), ""); gtk_entry_set_text(GTK_ENTRY(album_entry), ""); gtk_entry_set_text(GTK_ENTRY(year_entry), ""); gtk_entry_set_text(GTK_ENTRY(comment_entry), ""); gtk_list_select_item(GTK_LIST(GTK_COMBO(genre_combo)->list), g_list_index(genre_list, "")); gtk_label_set_text(GTK_LABEL(mp_level), INFO1); gtk_label_set_text(GTK_LABEL(mp_bitrate), INFO2); gtk_label_set_text(GTK_LABEL(mp_samplerate), INFO3); gtk_label_set_text(GTK_LABEL(mp_flags), INFO4); gtk_label_set_text(GTK_LABEL(mp_fileinfo), INFO5); if(id3_found==TRUE) { set_entry_tag(GTK_ENTRY(title_entry), id3info.title, sizeof(id3info.title)); set_entry_tag(GTK_ENTRY(artist_entry), id3info.artist, sizeof(id3info.artist)); set_entry_tag(GTK_ENTRY(album_entry), id3info.album, sizeof(id3info.album)); set_entry_tag(GTK_ENTRY(year_entry), id3info.year, sizeof(id3info.year)); set_entry_tag(GTK_ENTRY(comment_entry), id3info.comment, sizeof(id3info.comment)); gtk_list_select_item(GTK_LIST(GTK_COMBO(genre_combo)->list), g_list_index(genre_list, (gchar *)GenreList[id3info.genre])); } } void init() { read_cfg_file(); // any one-time initialization goes here (configuration reading, etc) initialisiere_Quantisierungstabellen(); Huffman_SV6_Decoder(); Huffman_SV7_Decoder(); } void quit() { /* one-time deinit, such as memory freeing */ // closing file if(inputFile!=NULL) { fclose(inputFile); inputFile=NULL; } if(mod.output) mod.output->close_audio(); } int isourfile(char *fn) { char *ext; ext=strrchr(fn,'.'); if(ext) { if(!strcasecmp(ext,".mp+") || !strcasecmp(ext,".mpp") || !strcasecmp(ext,".mpc")) return TRUE; } return FALSE; } /* used for detecting URL streams.. unused here. strncmp(fn,"http://",7) to detect HTTP streams, etc */ void play(char *fn) { float ClipPrevFactor = 1.0f; char _tag[4] = {0,0,0,0}; int maxlatency; /* AB: resetting global variables */ RESET_Globals(); RESET_Synthesis(); // AB: open bitstream inputFile=fopen(fn, "rb"); if (inputFile==NULL) { char text[256]; sprintf(text, "File \"%s\" not found or is read protected!\n",fn); xmms_show_message("ERROR: file-info()", text, "Ok",FALSE,NULL,NULL); return; } // AB: disable buffering setvbuf( inputFile, NULL, _IONBF, 0); // AB: fill buffer fread((void*)Speicher, 4, MEMSIZE, inputFile); // AB: search for "MP+"-tag (from SV7 on...) memcpy(_tag, Speicher, 3); if (!strcmp(_tag,"MP+")) StreamVersion = Speicher[0]>>24; // AB: read file-header if (StreamVersion>=7) { unsigned int dummy; unsigned int IS_Flag; // initialize bitstream-decoder (SV7 and up) dword = Speicher[Zaehler+=1]; // read the file-header OverallFrames = Bitstream_read(32); IS_Flag = Bitstream_read( 1); MS_used = Bitstream_read( 1); Max_Band = (int)Bitstream_read( 6); // future purpose data arrays dummy = Bitstream_read( 8); // contains "profile" ClipPrevFactor = (float)(32767/((Bitstream_read(16)+1e-10f)*1.18f)); // contains max sample dummy = Bitstream_read( 8); // dummy dummy = Bitstream_read(32); dummy = Bitstream_read(32); dummy = Bitstream_read(32); // modes not supported anymore if (IS_Flag!=0) { xmms_show_message("ERROR: function play()", "Files uses Intensity Stereo, not supported aynmore!\n" "Please decode with command-line tool.\n", "Ok",FALSE,NULL,NULL); fclose(inputFile); inputFile = NULL; return; } } else { unsigned int Blockgroesse, Bitrate, IS_Flag; // initialize bitstream-decoder (SV6 and below) dword = Speicher[Zaehler]; // read the file-header Bitrate = Bitstream_read( 9); IS_Flag = Bitstream_read( 1); MS_used = Bitstream_read( 1); StreamVersion = Bitstream_read(10); Max_Band = (int)Bitstream_read( 5); Blockgroesse = Bitstream_read( 6); if (StreamVersion>4) OverallFrames = Bitstream_read(32); else OverallFrames = Bitstream_read(16); // modes not supported anymore if (StreamVersion==7) { xmms_show_message("ERROR: function play()", "SV7-preview: not supported.", "Ok",FALSE,NULL,NULL); fclose(inputFile); inputFile = NULL; return; } if (Blockgroesse!=1) { xmms_show_message("ERROR: function play()", "Superframe-size!=1: not supported anymore.\n" "Please decode with command-line tool!\n", "Ok",FALSE,NULL,NULL); fclose(inputFile); inputFile = NULL; return; } if (Bitrate!=0) { xmms_show_message("ERROR: function play()", "CBR-file: not supported aynmore.\n" "Please decode with command-line tool!\n", "Ok",FALSE,NULL,NULL); fclose(inputFile); inputFile = NULL; return; } if (IS_Flag!=0) { xmms_show_message("ERROR: function play()", "Files uses Intensity Stereo, which not supported aynmore.\n" "Please decode with command-line tool!\n", "Ok",FALSE,NULL,NULL); fclose(inputFile); inputFile = NULL; return; } } // AB: clipping prevention needed for this track? // adapts scf for clipping prevention or restores original scf if not used ClipPrev((ClipPrevUsed && ClipPrevFactor<1), ClipPrevFactor); // Bugfix: last frame was possibly invalid for all StreamVersions < 6 if (StreamVersion<6) OverallFrames-=1; // AB: no valid file if(StreamVersion<4 || StreamVersion>7) { xmms_show_message("ERROR: function play()", "Invalid or unknown MPEGplus bitstream!", "Ok",FALSE,NULL,NULL); fclose(inputFile); inputFile = NULL; return; } // setting some globals strcpy(lastfn,fn); paused = 0; decode_pos_ms = 0; seek_needed =-1; get_id3_tags(lastfn); // opening sound-device maxlatency = mod.output->open_audio(FMT_S16_LE, SAMPLERATE, NCH); if(maxlatency<0) { fclose(inputFile); inputFile=NULL; return; } // cannot open sound device // AB: set info for overall-VBR if (!UpdateBitrate) { int VBR_Bitrate; int bytelength; int pos = ftell(inputFile); // determine file-length fseek(inputFile, 0, SEEK_END); bytelength = ftell(inputFile); // setting file-pointer to position before seek fseek(inputFile, pos, SEEK_SET); // calculate and display average bitrate VBR_Bitrate = (int)(bytelength*8/(OverallFrames*1152/44.1f)+0.5f)*1000; if(id3_found) { if(DisplayID3Names==1) mod.set_info(displayed_info,(int)(OverallFrames*1152/44.1f),VBR_Bitrate,SAMPLERATE/1000,NCH); else mod.set_info(NULL,(int)(OverallFrames*1152/44.1f),VBR_Bitrate,SAMPLERATE/1000,NCH); } else mod.set_info(NULL,(int)(OverallFrames*1152/44.1f),VBR_Bitrate,SAMPLERATE/1000,NCH); } else if(id3_found) { if(DisplayID3Names==1) mod.set_info(displayed_info,(int)(OverallFrames*1152/44.1f),0,SAMPLERATE/1000,NCH); else mod.set_info(NULL,(int)(OverallFrames*1152/44.1f),0,SAMPLERATE/1000,NCH); } else mod.set_info(NULL,(int)(OverallFrames*1152/44.1f),0,SAMPLERATE/1000,NCH); // create decoding thread killDecodeThread=0; pthread_create(&thread_handle,NULL,DecodeThread,NULL); return; } void Pause(short pause) { if(paused==1) { paused=0; mod.output->pause(pause); } else { paused=1; mod.output->pause(pause); } } void stop() { // closing file if (inputFile!=NULL) { fclose(inputFile); inputFile = NULL; } // killing decode-thread /* if (thread_handle != INVALID_HANDLE_VALUE) { killDecodeThread=1; MessageBox(mod.hMainWindow,"term thr","ERROR: file-info()",0); if (WaitForSingleObject(thread_handle,INFINITE) == WAIT_TIMEOUT) { MessageBox(mod.hMainWindow,"error asking decode-thread to die!\n","stop()",0); TerminateThread(thread_handle,0); } MessageBox(mod.hMainWindow,"terminated thr","ERROR: file-info()",0); CloseHandle(thread_handle); thread_handle = INVALID_HANDLE_VALUE; } */ // AB: terminate decode-thread killDecodeThread=1; pthread_join(thread_handle,NULL); // close output- and visualization-plugs mod.output->close_audio(); } int getlength() { return (int)(OverallFrames*1152/44.1f+0.5f); } int getoutputtime() { if( !inputFile ) return -1; if( !mod.output ) return -1; if( killDecodeThread && !mod.output->buffer_playing() ) return -1; return decode_pos_ms+(mod.output->output_time()-mod.output->written_time()); } void setoutputtime(int time_in_ms) { seek_needed = time_in_ms*1000; } void setvolume(int lvolume, int rvolume) { mod.output->set_volume(lvolume,rvolume); } void infoDlg(char *fn) { char y[20]; StreamInfo Info; unsigned int tmpStreamVersion,tmpBitrate,tmpMS,tmpFrames,tmpDuration; unsigned int tmpH,tmpM,tmpS,tmpLength,tmpSize,MB,KB,B,M,K,C; // AB: reading file-header if(ReadFileHeader(fn, &Info)) { char text[256]; sprintf(text,"File \"%s\" not found or is read protected!\n",fn); xmms_show_message("ERROR: file-info()", text, "Ok",FALSE,NULL,NULL); return; } // AB: setting local variables tmpBitrate = Info.Bitrate; tmpMS = Info.MS; tmpStreamVersion = Info.StreamVersion; tmpFrames = Info.Frames; tmpSize = Info.ByteLength; if(id3_found==TRUE) tmpLength=Info.ByteLength-sizeof(ID3V1_1); else tmpLength=Info.ByteLength; // calculate duration tmpDuration = (int)(tmpFrames*1152/44.1f); tmpH = tmpDuration/3600000; // hours tmpM = (tmpDuration-3600000*tmpH)/60000; // minutes tmpS = (tmpDuration-3600000*tmpH-60000*tmpM)/1000; // seconds // copy path and filename strcpy( INFOFN, fn); // StreamVersion and profile strcpy( INFO1, "Stream, Profile: SV"); itoa( tmpStreamVersion, y, 10); strcat( INFO1, y); if(Info.Profile==0) strcat( INFO1, ", \"thumb\""); else if(Info.Profile==1) strcat( INFO1, ", \"radio\""); else if(Info.Profile==2) strcat( INFO1, ", \"standard\""); else if(Info.Profile==3) strcat( INFO1, ", \"xtreme\""); else if(Info.Profile==4) strcat( INFO1, ", \"insane\""); else strcat( INFO1, ", n.a."); // calculating and formatting bitrate if(tmpBitrate>0) sprintf(INFO2,"Bitrate: %i kbit/s, CBR",tmpBitrate); else sprintf(INFO2,"Bitrate: %3.1f kbit/s, VBR",tmpLength*8/(tmpFrames*1152/44.1)); // display samplerate sprintf(INFO3,"Samplerate: 44.1 kHz"); // formatting no of frames M = tmpFrames/1000000; // million frames K = (tmpFrames-M*1000000)/1000; // thousand frames C = tmpFrames%1000; // single frames if(M>0) sprintf(INFO4, "Frames: %i.03%i.%03i",M,K,C); else if(M==0 && K>0) sprintf(INFO4, "Frames: %i.%03i",K,C); else sprintf(INFO4, "Frames: %i",C); // calculating and formatting duration sprintf(INFO5, "Duration: %02i:%02i:%02i hh:mm:ss",tmpH,tmpM,tmpS); // display m/s-coding if(tmpMS) sprintf(INFO6,"Mid/Side Stereo: enabled"); else sprintf(INFO6,"Mid/Side Stereo: disabled"); // formatting filesize MB = tmpSize/1000000; // million byte KB = (tmpSize-1000000*MB)/1000; // thousand byte B = tmpSize%1000; // single byte if(MB>0) sprintf(INFO7, "Size: (%1.1f MB) %i.%03i.%03i Bytes",tmpSize/1048576.0f,MB,KB,B); else if(MB==0 && KB>0) sprintf(INFO7, "Size: (%1.1f KB) %i.%03i Bytes" ,tmpSize/1024.0f ,KB,B); else sprintf(INFO7, "Size: (%1.1f KB) %03i Bytes" ,tmpSize/1024.0f ,B); // open file-info dialogbox FileInfo(fn); return; } void getfileinfo(char *filename, char **_title, int *length_in_ms) { *length_in_ms = getlength(); } void EQSet(int on, float preamp, float *data) { /* Der EQ verwendet die vorhandenen Eckpunkte zur Interpolation der Pegel. Alle Subbaender außer den untersten beiden werden um den mittleren berechneten Pegel im entsprechenden Subband angehoben. Die untersten Subbänder werden mittels eines FIR-Filters des Grads N=EQ_TAP gefiltert, da die Auflösung des EQs hier wesentlich höher ist als die Bandbreite der Subbänder es zulaesst. */ if (on) { static int sym = (EQ_TAP-1)/2; float set[512],x[512]; float mid[32],power[10]; float win; int i,n,k,idx; // calculate desired attenuation for (n=0; n<10; ++n) { power[n]=data[n]*EQdB/10.f; power[n]+=preamp*EQdB/10.f; } // calculate desired attenuation for each bin set[0] = power[0]; for (k= 1; k< 4; ++k) set[k]= (power[0]*(4 -k) + power[1]*(k- 1))/ 3.f; for (k= 4; k< 7; ++k) set[k]= (power[1]*(7 -k) + power[2]*(k- 4))/ 3.f; for (k= 7; k< 14; ++k) set[k]= (power[2]*(14 -k) + power[3]*(k- 7))/ 7.f; for (k= 14; k< 23; ++k) set[k]= (power[3]*(23 -k) + power[4]*(k- 14))/ 9.f; for (k= 23; k< 70; ++k) set[k]= (power[4]*(70 -k) + power[5]*(k- 23))/ 47.f; for (k= 70; k<139; ++k) set[k]= (power[5]*(139-k) + power[6]*(k- 70))/ 69.f; for (k=139; k<279; ++k) set[k]= (power[6]*(279-k) + power[7]*(k-139))/140.f; for (k=279; k<325; ++k) set[k]= (power[7]*(325-k) + power[8]*(k-279))/ 46.f; for (k=325; k<372; ++k) set[k]= (power[8]*(372-k) + power[9]*(k-325))/ 47.f; for (k=372; k<512; ++k) set[k]= power[9]; // transform from level to power for (k=0; k<512; ++k) set[k] = (float)(pow(10,set[k]/10.f)); /************************** gain for upper subbands ****************************/ // transform to attenuation (gain) per subband memset(mid,0,sizeof(mid)); for (k=FIR_BANDS*16; k<512; ++k) { mid[k/16] += set[k]; } for (n=0; n<32; ++n) EQ_gain[n-FIR_BANDS] = (float)(sqrt(mid[n]/16)); /***************************** FIR for lowest subbands ****************************/ for (i=0; i0) { // set ActDecodePos to actual position in bitstream ActDecodePos = ftell(inputFile)*8 + pos + 20; // read first data-element in bitstream fread((void*)&Speicher, sizeof(int), 2, inputFile); Zaehler = 0; dword = Speicher[0]; // read jump-info FwdJumpInfo = Bitstream_read(20); // calculate desired pos (in Bits) DesiredDecPos = ActDecodePos + FwdJumpInfo; // proceed until 32 frames before (!!) desired position (allows to scan the scalefactors) while(JumpedFrames < pre_fwd) { // set and read desired position in bitstream (break if feof) fseek(inputFile, (DesiredDecPos>>5)<<2, SEEK_SET); if(feof(inputFile)) return 0; fread((void*)Speicher, sizeof(int), 2, inputFile); // set bitstream decoder Zaehler=0; pos=DesiredDecPos&31; dword=Speicher[0]; // calculate desired pos (in Bits) DesiredDecPos+=(20+Bitstream_read(20)); // increase counter for number of jumped frames DecodedFrames=++JumpedFrames; } // set and read desired position in bitstream (break if feof) fseek(inputFile, (DesiredDecPos>>5)<<2, SEEK_SET); if(feof(inputFile)) return 0; fread((void*)Speicher, 4, MEMSIZE, inputFile); // set bitstream decoder Zaehler=0; pos=DesiredDecPos&31; dword=Speicher[0]; // increase counter for number of jumped frames DecodedFrames=++JumpedFrames; } // seeking position is in between the first 32 frames else { // fill buffer and set bitstream decoder fread((void*)Speicher, 4, MEMSIZE, inputFile); Zaehler=0; dword=Speicher[0]; } // read the last 32 frames before the desired position to // scan the scalefactors (artifactless jumping) while(JumpedFrames < fwd) { RING=Zaehler; // read jump-info FwdJumpInfo=Bitstream_read(20); ActDecodePos=(Zaehler<<5) + pos; // scanning the scalefactors and check for validity of frame FrameBitCnt=BitsRead(); if(StreamVersion>=7) Lese_Bitstrom_SV7(); else Lese_Bitstrom_SV6(); if(BitsRead()-FrameBitCnt!=FwdJumpInfo) return 0; // increase counter for number of jumped frames DecodedFrames=++JumpedFrames; // update buffer if((RING^Zaehler)&MEMSIZE2) fread((void*)&Speicher[RING & MEMSIZE2], 4, MEMSIZE2, inputFile); } decode_pos_ms=(int)(DecodedFrames*1152/44.1f+0.5f); *done=0; // file is not done RESET_Synthesis(); // resetting synthesis filter to avoid "clicks" mod.output->flush(decode_pos_ms);// flush sound buffer *Frames=-1; // AB: to restart calculation of avg bitrate return 1; } void *DecodeThread(void *b) { int done = 0; int avg_bitrate = 0; int Frames = -1; int NumberOfConsecutiveBrokenFrames = 0; int BitCounter = 0;// counter for displaying avg bitrate int valid_samples; // no of valid samples unsigned int RING; while(killDecodeThread==0) { /********************** SEEK ************************/ if (seek_needed != -1) { // only perform jump when bitstream is still allocated if (inputFile!=NULL && done!=1) { if (!perform_jump(&done, &Frames)) { char text[256]; sprintf(text, "File seek error in file \"%s\", Frame # %i / %i !", lastfn,DecodedFrames+1,OverallFrames); xmms_show_message("ERROR: File seek", text, "Ok",FALSE,NULL,NULL); done = 1; } } else done = 1; // seek is done or impossible seek_needed = -1; } /********************* QUIT *************************/ if (done) { mod.output->buffer_free(); while(mod.output->buffer_playing()) xmms_usleep( 20000 ); // does not jump back to file in playlist, sets "green" on idle or out of snyc if (UpdateBitrate) { if(id3_found) { if(DisplayID3Names==1) mod.set_info(displayed_info,(int)(OverallFrames*1152/44.1f),avg_bitrate,SAMPLERATE/1000,NCH); else mod.set_info(NULL,(int)(OverallFrames*1152/44.1f),avg_bitrate,SAMPLERATE/1000,NCH); } else mod.set_info(NULL,(int)(OverallFrames*1152/44.1f),avg_bitrate,SAMPLERATE/1000,NCH); } killDecodeThread = 1; pthread_exit(NULL); return 0; xmms_usleep(10000); } /******************* DECODE TO BUFFER ****************/ else if (mod.output->buffer_free() >= ((1152*NCH*(BPS/8)) << (mod.output->buffer_playing()?1:0))) { RING = Zaehler; // call decoder-kernel valid_samples = DECODE(sample_buffer); /**************** ERROR CONCEALMENT *****************/ // error in bitstream occured if (FrameWasValid==0) { // one more invalid frame ++NumberOfConsecutiveBrokenFrames; // too much broken frames -> cancel decoding if (NumberOfConsecutiveBrokenFrames>MaxBrokenFrames) { char text[256]; sprintf(text, "Lost sync in file \"%s\", Frame # %i / %i !", lastfn,DecodedFrames,OverallFrames); xmms_show_message("ERROR: Out of sync", text, "Ok",FALSE,NULL,NULL); valid_samples = 0; } // conceal error -> send zeroes, try to re-sync else { // calculate and set desired position int DesiredDecPos = ActDecodePos + FwdJumpInfo; Zaehler = DesiredDecPos>>5; //DesiredDecPos/32; pos = DesiredDecPos&31; //DesiredDecPos%32; // set current decoded word dword = Speicher[Zaehler]; // filling broken frames with zeroes valid_samples = 4608; memset(sample_buffer, 0, sizeof(sample_buffer)); } } else NumberOfConsecutiveBrokenFrames = 0; /***************************************************/ // update buffer if((RING^Zaehler)&MEMSIZE2) fread((void*)&Speicher[RING & MEMSIZE2], 4, MEMSIZE2, inputFile); // update bitrate-display ++Frames; if (UpdateBitrate && Frames%20==0) { avg_bitrate=(int)((BitsRead()-BitCounter)*44.1f/1152/20)*1000; if(id3_found) { if(DisplayID3Names==1) mod.set_info(displayed_info,(int)(OverallFrames*1152/44.1f),avg_bitrate,SAMPLERATE/1000,NCH); else mod.set_info(NULL,(int)(OverallFrames*1152/44.1f),avg_bitrate,SAMPLERATE/1000,NCH); } else mod.set_info(NULL,(int)(OverallFrames*1152/44.1f),avg_bitrate,SAMPLERATE/1000,NCH); BitCounter = BitsRead(); } // copy pcm-buffer to output-, visualization- and dsp-buffer if (valid_samples) { mod.add_vis_pcm(mod.output->written_time(),FMT_S16_LE,NCH,valid_samples,sample_buffer); mod.output->write_audio(sample_buffer,valid_samples); decode_pos_ms = (int)(DecodedFrames*1152/44.1f+0.5f); //if (mod.output->buffer_playing()) // valid_samples = (short*)sample_buffer,valid_samples/NCH/(BPS/8),BPS,NCH,SAMPLERATE)*(NCH*(BPS/8)); //mod.output->write_audio(sample_buffer,valid_samples); } else { done=1; if (inputFile!=NULL) { fclose(inputFile); inputFile = NULL; } } } else xmms_usleep(5000); } mod.output->close_audio(); pthread_exit(NULL); return 0; }