/* * $Id$ * * This file is part of WorkMan, the civilized CD player library * (c) 1991-1997 by Steven Grimm (original author) * (c) by Dirk Försterling (current 'author' = maintainer) * The maintainer can be contacted by his e-mail address: * milliByte@DeathsDoor.com * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the Free * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * * Get information about a CD. */ #include #include #include #include #include #include "include/wm_config.h" #include "include/wm_struct.h" #include "include/wm_cdrom.h" #include "include/wm_cdinfo.h" #include "include/wm_database.h" #include "include/wm_helpers.h" struct wm_play *playlist = NULL; struct wm_cdinfo thiscd, *cd = &thiscd; int cur_track = -1; /* Current track number, starting at 1 */ int cur_index = 0; /* Current index mark */ int cur_lasttrack = 999; /* Last track to play in current chunk */ int cur_firsttrack = 0; /* First track of current chunk */ int cur_pos_abs; /* Current absolute position in seconds */ int cur_frame; /* Current frame number */ int cur_pos_rel; /* Current track-relative position in seconds */ int cur_tracklen; /* Length in seconds of current track */ int cur_cdlen; /* Length in seconds of entire CD */ int cur_ntracks; /* Number of tracks on CD (= tracks + sections) */ int cur_nsections; /* Number of sections currently defined */ int cur_listno; /* Current index into the play list, if playing */ char * cur_artist; /* Name of current CD's artist */ char * cur_cdname; /* Album name */ char * cur_trackname; /* Take a guess */ char cur_contd; /* Continued flag */ char cur_avoid; /* Avoid flag */ int exit_on_eject = 0; int cur_stopmode = -1; int info_modified; /* * insert_trackinfo() * * Add a new track to the CD info structure. Pass the position of the new * entry in the track list -- 0 will make this the first track, 1 the second, * etc. The new entry will be zeroed out. */ static void insert_trackinfo(int num) { struct wm_trackinfo *newtrk; /* Easy case: the list is empty */ if (cd->trk == NULL) { if ((cd->trk = (struct wm_trackinfo *) calloc(1, sizeof(*newtrk))) == NULL) { perror("insert_trackinfo"); exit(1); } else { return; } /* if() else */ } /* if() */ /* Stick the new entry in cd->trk[]. */ if ((newtrk = (struct wm_trackinfo *) malloc(sizeof(*newtrk) * (cur_ntracks + 1))) == NULL) { perror("insert_trackinfo"); exit(1); } if (num) memcpy(newtrk, cd->trk, sizeof(*newtrk) * num); memset(&newtrk[num], 0, sizeof(*newtrk)); if (num < cur_ntracks) memcpy(&newtrk[num + 1], &cd->trk[num], sizeof(*newtrk) * (cur_ntracks - num)); free(cd->trk); cd->trk = newtrk; } /* * split_trackinfo() * * Split a track in two at a particular position (absolute, in frames). All * internal data structures and variables will be adjusted to the new * numbering scheme. Pass in the track number (>=1) to split, which is also * the index into cd->trk[] of the new entry. * * If pos is within 1 second of the start of another track, the split fails. * * Returns 1 on success, 0 if the track couldn't be inserted. * * Note: updating user interface elements is up to the caller. */ int split_trackinfo( int pos ) { int i, l, num; if (pos < cd->trk[0].start) return (0); /* First find the appropriate track. */ for (num = 0; num < cur_ntracks; num++) if (cd->trk[num].start - 75 < pos && cd->trk[num].start + 75 > pos) return (0); else if (cd->trk[num].start > pos) break; if (num == 0) return (0); /* Insert the new entry into the track array. */ insert_trackinfo(num); /* Update the easy variables. */ if (cur_track > num) cur_track++; if (cur_firsttrack > num) cur_firsttrack++; if (cur_lasttrack > num) cur_lasttrack++; /* Update the user-defined playlists. */ if (cd->lists != NULL) for (l = 0; cd->lists[l].name != NULL; l++) if (cd->lists[l].list != NULL) for (i = 0; cd->lists[l].list[i]; i++) if (cd->lists[l].list[i] > num) cd->lists[l].list[i]++; /* Update the internal playlist. */ if (playlist != NULL) for (i = 0; playlist[i].start; i++) { if (playlist[i].start > num) playlist[i].start++; if (playlist[i].end > num) playlist[i].end++; } /* Now adjust the information in cd->trk[]. */ cd->trk[num].start = pos; if (num == cur_ntracks) cd->trk[num].length = cur_cdlen - pos / 75; else cd->trk[num].length = (cd->trk[num + 1].start - pos) / 75; cd->trk[num - 1].length -= cd->trk[num].length; if (cur_track == num) cur_tracklen -= cd->trk[num].length; cd->trk[num].track = cd->trk[num - 1].track; cd->trk[num].data = cd->trk[num - 1].data; cd->trk[num].contd = 1; cd->trk[num].volume = cd->trk[num - 1].volume; if (cd->trk[num - 1].section == 0) cd->trk[num - 1].section = 1; cd->trk[num].section = cd->trk[num - 1].section + 1; cur_ntracks++; cur_nsections++; for (i = num + 1; i < cur_ntracks; i++) if (cd->trk[i].track == cd->trk[num].track) cd->trk[i].section++; return (1); } /* * remove_trackinfo() * * Remove a track's internal data. This is similar to split_trackinfo() * above, but simpler. A track's initial section can't be removed. Track * numbers start at 0. * * Returns 1 on success, 0 on failure. */ int remove_trackinfo( int num ) { int i, l; if (num < 1 || num >= cur_ntracks || cd->trk[num].section < 2) return (0); cd->trk[num - 1].length += cd->trk[num].length; for (i = num; i < cur_ntracks - 1; i++) memcpy(&cd->trk[i], &cd->trk[i + 1], sizeof(cd->trk[0])); if (cur_track > num) cur_track--; if (cur_firsttrack > num) cur_firsttrack--; if (cur_lasttrack > num) cur_lasttrack--; /* Update the user-defined playlists. */ if (cd->lists != NULL) for (l = 0; cd->lists[l].name != NULL; l++) if (cd->lists[l].list != NULL) for (i = 0; cd->lists[l].list[i]; i++) if (cd->lists[l].list[i] > num) cd->lists[l].list[i]--; /* Update the internal playlist. */ if (playlist != NULL) for (i = 0; playlist[i].start; i++) { if (playlist[i].start > num) playlist[i].start--; if (playlist[i].end > num) playlist[i].end--; } cur_ntracks--; cur_nsections--; /* * Update the section numbers for this track. If this is the only * user-created section in a track, get rid of the section number * in the track's entry. */ if (num == cur_ntracks || cd->trk[num - 1].track != cd->trk[num].track) { if (cd->trk[num - 1].section == 1) cd->trk[num - 1].section = 0; } else for (i = num; i < cur_ntracks; i++) if (cd->trk[i].track == cd->trk[num - 1].track) cd->trk[i].section--; return (1); } /* * listentry() * * Return a scrolling list entry. */ char * listentry( int num ) { static char buf[600]; const char *name; char tracknum[20]; int digits; int sdigits; if (num >= 0 && num < cur_ntracks) { /* if (big_spaces) { digits = 2; sdigits = cur_nsections < 9 ? -1 : -2; } else { digits = cd->trk[num].track < 10 ? 3 : 2; sdigits = cur_nsections < 9 ? -1 : -3; } */ digits = 2; sdigits = cur_nsections < 9 ? -1 : -2; name = cd->trk[num].songname ? cd->trk[num].songname : ""; if (cur_nsections) { if (cd->trk[num].section > 9) { sprintf(tracknum, "%*d.%d", digits, cd->trk[num].track, cd->trk[num].section); } else { if (cd->trk[num].section) { sprintf(tracknum, "%*d.%*d", digits, cd->trk[num].track, sdigits, cd->trk[num].section); } else { sprintf(tracknum, "%*d%*s", digits, cd->trk[num].track, 2 - sdigits, " "); /* 2 - sdigits - big_spaces, " ");*/ } } } else { sprintf(tracknum, "%*d", digits, cd->trk[num].track); } if (cd->trk[num].data) { sprintf(buf, "%s) %3dMB %s", tracknum, cd->trk[num].length / 1024, name); } else { sprintf(buf, "%s) %02d:%02d %s", tracknum, cd->trk[num].length / 60, cd->trk[num].length % 60, name); } return (buf); } else { return (NULL); } } /* listentry() */ /* * trackname() * * Return a track's name. */ const char * trackname( int num ) { if (num >= 0 && num < cur_ntracks) { if (cd->trk[num].songname) { return (cd->trk[num].songname); } else { return (""); } } else { return (NULL); } } /* trackname() */ /* * tracklen() * * Return a track's length in seconds. */ int tracklen( int num ) { if (cd != NULL && num >= 0 && num < cur_ntracks) return (cd->trk[num].length); else return (0); } /* * get_default_volume() * * Return the default volume (0-32, 0=none) for the CD or a track. */ int get_default_volume( int track ) { if (! track) return (cd->volume); else if (track <= cur_ntracks) return (cd->trk[track - 1].volume); else return (0); } /* * get_contd() * * Return the contd value for a track. */ int get_contd( int num ) { if (num >= 0 && num < cur_ntracks) return (cd->trk[num].contd); else return (0); } /* * get_avoid() * * Return the avoid value for a track. */ int get_avoid( int num ) { if (num >= 0 && num < cur_ntracks) return (cd->trk[num].avoid); else return (0); } /* * get_autoplay() * * Is autoplay set on this disc? */ int get_autoplay( void ) { return ( cd->autoplay ); } /* * get_playmode() * * Return the default playmode for the CD. */ int get_playmode( void ) { return ( cd->playmode ); } /* * get_runtime() * * Return the total running time for the current playlist in seconds. */ int get_runtime( void ) { int i; if (playlist == NULL || playlist[0].start == 0 || cur_firsttrack == -1) return (cd == NULL ? 0 : cd->length); for (i = 0; playlist[i].start; i++) ; return (playlist[i].starttime); } /* * default_volume() * * Set the default volume for the CD or a track. */ void default_volume( int track, int vol ) { if (track == 0) cd->volume = vol; else if (track <= cur_ntracks) cd->trk[track - 1].volume = vol; } /* * Play the next thing on the playlist, if any. */ void play_next_entry( int forward ) { if (cd == NULL) return; if (playlist != NULL && playlist[cur_listno].start) { wm_cd_play(playlist[cur_listno].start, 0, playlist[cur_listno].end); cur_listno++; } else wm_cd_stop(); } /* * Play the next track, following playlists as necessary. */ void play_next_track( int forward ) { if (cd == NULL) return; /* Is the current playlist entry done? Move on, if so. */ if (playlist == NULL || cur_track + 1 == playlist[cur_listno - 1].end) play_next_entry( forward ); else wm_cd_play(cur_track + 1, 0, playlist[cur_listno - 1].end); } /* * Play the previous track, hopping around the playlist as necessary. */ void play_prev_track( int forward ) { if (cd == NULL) return; if (playlist == NULL) return; /* If we're in the middle of this playlist entry, go back one track */ if (cur_track > playlist[cur_listno - 1].start) wm_cd_play(cur_track - 1, 0, playlist[cur_listno - 1].end); else if (cur_listno > 1) { cur_listno--; wm_cd_play(playlist[cur_listno - 1].end - 1, 0, playlist[cur_listno - 1].end); } else wm_cd_play(playlist[0].start, 0, playlist[0].end); } /* * stash_cdinfo(artist, cdname) */ void stash_cdinfo(char *artist, char *cdname, int autoplay, int playmode ) { if (cd != NULL) { if (strcmp(cd->artist, artist)) info_modified = 1; strncpy(cd->artist, artist,sizeof(cd->artist)-1); cd->artist[sizeof(cd->artist)-1]='\0'; if (strcmp(cd->cdname, cdname)) info_modified = 1; strncpy(cd->cdname, cdname,sizeof(cd->cdname)-1); cd->cdname[sizeof(cd->cdname)-1]='\0'; if (!!cd->autoplay != !!autoplay) info_modified = 1; cd->autoplay = autoplay; if (!!cd->playmode != !!playmode) info_modified = 1; cd->playmode = playmode; } } /* stash_cdinfo() */ /* * wipe_cdinfo() * * Clear out all a CD's soft information (presumably in preparation for * reloading from the database.) */ void wipe_cdinfo( void ) { struct wm_playlist *l; int i; if (cd != NULL) { cd->artist[0] = cd->cdname[0] = '\0'; cd->autoplay = cd->playmode = cd->volume = 0; cd->whichdb = NULL; freeup(&cd->otherrc); freeup(&cd->otherdb); if (thiscd.lists != NULL) { for (l = thiscd.lists; l->name != NULL; l++) { free(l->name); free(l->list); } free(thiscd.lists); thiscd.lists = NULL; } for (i = 0; i < cur_ntracks; i++) { freeup(&cd->trk[i].songname); freeup(&cd->trk[i].otherrc); freeup(&cd->trk[i].otherdb); cd->trk[i].avoid = cd->trk[i].contd = 0; cd->trk[i].volume = 0; if (cd->trk[i].section > 1) remove_trackinfo(i--); } } } /* * stash_trkinfo(track, songname, contd, avoid) * * Update information about a track on the current CD. */ void stash_trkinfo( int track, char *songname, int contd, int avoid ) { if (cd != NULL) { track--; if (!!cd->trk[track].contd != !!contd) info_modified = 1; cd->trk[track].contd = track ? contd : 0; if (!!cd->trk[track].avoid != !!avoid) info_modified = 1; cd->trk[track].avoid = avoid; if ((cd->trk[track].songname == NULL && songname[0]) || (cd->trk[track].songname != NULL && strcmp(cd->trk[track].songname, songname))) { info_modified = 1; wm_strmcpy(&cd->trk[track].songname, songname); } } } /* * new_playlist() * * Add a playlist to a CD. */ struct wm_playlist * new_playlist(struct wm_cdinfo* cdinfo, char* listname) { int nlists = 0; struct wm_playlist *l; if (cdinfo->lists != NULL) { for (nlists = 0; cdinfo->lists[nlists].name != NULL; nlists++) ; l = (struct wm_playlist *)realloc(cdinfo->lists, (nlists + 2) * sizeof (struct wm_playlist)); } else l = (struct wm_playlist *)malloc(2 * sizeof (struct wm_playlist)); if (l == NULL) return (NULL); l[nlists + 1].name = NULL; l[nlists].name = NULL; /* so wm_strmcpy doesn't free() it */ wm_strmcpy(&l[nlists].name, listname); l[nlists].list = NULL; cdinfo->lists = l; return (&l[nlists]); } /* * make_playlist() * * Construct a playlist for the current CD. If we're in shuffle mode, play * each non-avoided track once, keeping continued tracks in the right order. * * If playmode is 2, use playlist number (playmode-2). XXX should do * bounds checking on this, probably. * * If consecutive tracks are being played, only make one playlist entry for * them, so the CD player won't pause between tracks while we wake up. */ void make_playlist( int playmode, int starttrack ) { int i, avoiding = 1, entry = 0, count, track, *thislist; cur_listno = 0; if (playlist != NULL) free(playlist); playlist = malloc(sizeof (*playlist) * (cur_ntracks + 1)); if (playlist == NULL) { perror("playlist"); exit(1); } /* If this is a data-only CD, we can't play it. */ if ((starttrack && cd->trk[starttrack - 1].data) || (cur_ntracks == 1 && cd->trk[0].data)) { playlist[0].start = 0; playlist[0].end = 0; playlist[1].start = 0; return; } if (playmode == 1) { char *done = malloc(cur_ntracks); if (done == NULL) { perror("randomizer"); exit(1); } count = cur_ntracks; if (starttrack && cd->trk[starttrack - 1].avoid) count++; for (i = 0; i < cur_ntracks; i++) if (cd->trk[i].contd || cd->trk[i].avoid || cd->trk[i].data) { done[i] = 1; count--; } else done[i] = 0; for (i = 0; i < count; i++) { int end; /* for readability */ if (starttrack) { track = starttrack - 1; starttrack = 0; } else while (done[track = rand() % cur_ntracks]) ; playlist[i].start = track + 1; /* play all subsequent continuation tracks too */ for (end = track + 1; end < cur_ntracks + 1; end++) if (! cd->trk[end].contd || cd->trk[end].avoid || cd->trk[end].data) break; playlist[i].end = end + 1; done[track]++; } playlist[i].start = 0; free(done); } else if (playmode >= 2 && cd->lists && cd->lists[playmode - 2].name) { count = 2; /* one terminating entry, and one for start */ thislist = cd->lists[playmode - 2].list; for (i = 0; thislist[i]; i++) if (thislist[i + 1] != thislist[i] + 1) count++; if (playlist != NULL) free(playlist); playlist = malloc(sizeof (*playlist) * count); if (playlist == NULL) { perror("playlist"); exit(1); } count = 0; if (starttrack) { playlist[0].start = starttrack; for (track = 0; thislist[track]; track++) if (starttrack == thislist[track]) break; if (! thislist[track]) { playlist[0].end = starttrack + 1; playlist[1].start = thislist[0]; count = 1; track = 0; } } else { playlist[0].start = thislist[0]; track = 0; } for (i = track; thislist[i]; i++) if (thislist[i + 1] != thislist[i] + 1) { playlist[count].end = thislist[i] + 1; count++; playlist[count].start = thislist[i + 1]; } } else { for (i = starttrack ? starttrack - 1 : 0; i < cur_ntracks; i++) if (avoiding && ! (cd->trk[i].avoid || cd->trk[i].data)) { playlist[entry].start = i + 1; avoiding = 0; } else if (! avoiding && (cd->trk[i].avoid || cd->trk[i].data)) { playlist[entry].end = i + 1; avoiding = 1; entry++; } if (! avoiding) playlist[entry].end = i + 1; playlist[entry + !avoiding].start = 0; } /* * Now go through the list, whatever its source, and figure out * cumulative starting times for each entry. */ entry = count = 0; do { playlist[entry].starttime = count; if (playlist[entry].start) for (i = playlist[entry].start; i < playlist[entry].end; i++) count += cd->trk[i - 1].length; } while (playlist[entry++].start); } /* * Find a particular track's location in the current playlist. Sets the * appropriate variables (cur_listno, cur_firsttrack, cur_lasttrack). */ void pl_find_track( int track ) { int i; if (playlist == NULL) { #ifndef NDEBUG fprintf(stderr, "Null playlist! Huh?\n"); #endif return; } for (i = 0; playlist[i].start; i++) if (track >= playlist[i].start && track < playlist[i].end) { cur_listno = i + 1; cur_firsttrack = playlist[i].start; cur_lasttrack = playlist[i].end - 1; return; } /* * Couldn't find the track in question. Make a special entry with * just that track. */ if (! playlist[i].start) { playlist = realloc(playlist, (i + 2) * sizeof(*playlist)); if (playlist == NULL) { perror("playlist realloc"); exit(1); } playlist[i + 1].start = playlist[i + 1].end = 0; playlist[i + 1].starttime = playlist[i].starttime + cd->trk[track - 1].length; playlist[i].start = track; playlist[i].end = track + 1; cur_listno = i + 1; cur_firsttrack = track; cur_lasttrack = track; } }