1 /*  MikMod module player
2 	(c) 1998 - 2000 Miodrag Vallat and others - see file AUTHORS for
3 	complete list.
4 
5 	This program is free software; you can redistribute it and/or modify
6 	it under the terms of the GNU General Public License as published by
7 	the Free Software Foundation; either version 2 of the License, or
8 	(at your option) any later version.
9 
10 	This program is distributed in the hope that it will be useful,
11 	but WITHOUT ANY WARRANTY; without even the implied warranty of
12 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 	GNU General Public License for more details.
14 
15 	You should have received a copy of the GNU General Public License
16 	along with this program; if not, write to the Free Software
17 	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
18 	02111-1307, USA.
19 */
20 
21 /*==============================================================================
22 
23   $Id: mlist.c,v 1.1.1.1 2004/01/16 02:07:37 raph Exp $
24 
25   Playlist management functions
26 
27 ==============================================================================*/
28 
29 #ifdef HAVE_CONFIG_H
30 #include "config.h"
31 #endif
32 
33 #ifdef HAVE_UNISTD_H
34 #include <unistd.h>
35 #endif
36 #ifndef HAVE_FNMATCH_H
37 #include "mfnmatch.h"
38 #else
39 #include <fnmatch.h>
40 #endif
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <time.h>
45 #ifdef SRANDOM_IN_MATH_H
46 #include <math.h>
47 #endif
48 
49 #include "mlist.h"
50 #include "marchive.h"
51 #include "mutilities.h"
52 
mikmod_random(int limit)53 static int mikmod_random(int limit)
54 {
55 #if defined(__OS2__)||defined(__EMX__)||defined(__DJGPP__)||defined(_WIN32)||defined(_mikmod_amiga)
56 	return rand() % limit;
57 #else
58 	return random() % limit;
59 #endif
60 }
61 
62 /* Mark all the modules in the playlist as not played */
PL_ClearPlayed(PLAYLIST * pl)63 static void PL_ClearPlayed(PLAYLIST * pl)
64 {
65 	int i;
66 
67 	for (i = 0; i < pl->length; i++)
68 		pl->entry[i].played = 0;
69 }
70 
PL_isPlaylistFilename(const CHAR * filename)71 BOOL PL_isPlaylistFilename(const CHAR *filename)
72 {
73 	char *cfg_name = NULL;
74 	if (!fnmatch("*.mpl", filename, 0))
75 		return 1;
76 	if ((cfg_name = PL_GetFilename())) {
77 		const char *p1 = FIND_LAST_DIRSEP(cfg_name);
78 		const char *p2 = FIND_LAST_DIRSEP(filename);
79 		if (!p1) p1 = cfg_name;
80 		if (!p2) p2 = filename;
81 		if (!filecmp(p1, p2)) {
82 			free(cfg_name);
83 			return 1;
84 		}
85 		free(cfg_name);
86 	}
87 	return 0;
88 }
89 
PL_InitList(PLAYLIST * pl)90 void PL_InitList(PLAYLIST * pl)
91 {
92 	pl->entry = NULL;
93 	pl->length = 0;
94 	pl->current = -1;
95 	pl->curr_deleted = 0;
96 	pl->add_pos = -1;
97 #if defined(__OS2__)||defined(__EMX__)||defined(__DJGPP__)||defined(_WIN32)||defined(_mikmod_amiga)
98 	srand(time(NULL));
99 #else
100 	{
101 		const char * s = getenv("MIKMOD_SRAND_CONSTANT");
102 		if (s)
103 		{
104 			srandom((unsigned int)atoi(s));
105 		}
106 		else
107 		{
108 			srandom(time(NULL));
109 		}
110 	}
111 #endif
112 }
113 
114 /* Choose the first non-played module */
PL_InitCurrent(PLAYLIST * pl)115 void PL_InitCurrent(PLAYLIST * pl)
116 {
117 	pl->current = 0;
118 	while ((pl->current < pl->length) && (pl->entry[pl->current].played))
119 		pl->current++;
120 	if (pl->current >= pl->length) {
121 		PL_ClearPlayed(pl);
122 		pl->current = 0;
123 	}
124 	pl->current--;
125 }
126 
PL_ClearList(PLAYLIST * pl)127 void PL_ClearList(PLAYLIST * pl)
128 {
129 	int i;
130 
131 	for (i = 0; i < pl->length; i++) {
132 		if (pl->entry[i].file)
133 			free(pl->entry[i].file);
134 		if (pl->entry[i].archive)
135 			free(pl->entry[i].archive);
136 	}
137 	if (pl->entry) {
138 		free(pl->entry);
139 		pl->entry = NULL;
140 	}
141 	pl->current = -1;
142 	pl->curr_deleted = 0;
143 	pl->length = 0;
144 }
145 
PL_CurrentDeleted(PLAYLIST * pl)146 BOOL PL_CurrentDeleted(PLAYLIST * pl)
147 {
148 	return pl->curr_deleted;
149 }
150 
PL_GetCurrent(PLAYLIST * pl)151 PLAYENTRY *PL_GetCurrent(PLAYLIST * pl)
152 {
153 	if (pl->current < 0 || !pl->length)
154 		return NULL;
155 	return &pl->entry[pl->current];
156 }
157 
PL_GetCurrentPos(PLAYLIST * pl)158 int PL_GetCurrentPos(PLAYLIST * pl)
159 {
160 	if (pl->current < 0 || !pl->length)
161 		return -1;
162 	return pl->current;
163 }
164 
PL_GetEntry(PLAYLIST * pl,int number)165 PLAYENTRY *PL_GetEntry(PLAYLIST * pl, int number)
166 {
167 	if ((number < 0) || (number >= pl->length))
168 		return NULL;
169 	return &pl->entry[number];
170 }
171 
PL_GetLength(PLAYLIST * pl)172 int PL_GetLength(PLAYLIST * pl)
173 {
174 	return pl->length;
175 }
176 
PL_SetTimeCurrent(PLAYLIST * pl,long sngtime)177 void PL_SetTimeCurrent(PLAYLIST * pl, long sngtime)
178 {
179 	if (!pl->curr_deleted && pl->current >= 0 && pl->current < pl->length)
180 		pl->entry[pl->current].time = sngtime >> 10;
181 }
182 
PL_SetPlayedCurrent(PLAYLIST * pl)183 void PL_SetPlayedCurrent(PLAYLIST * pl)
184 {
185 	if (!pl->curr_deleted && pl->current >= 0 && pl->current < pl->length)
186 		pl->entry[pl->current].played = 1;
187 }
188 
PL_DelEntry(PLAYLIST * pl,int number)189 BOOL PL_DelEntry(PLAYLIST * pl, int number)
190 {
191 	int i;
192 
193 	if (!pl->length)
194 		return 0;
195 
196 	if (pl->entry[number].file)
197 		free(pl->entry[number].file);
198 	if (pl->entry[number].archive)
199 		free(pl->entry[number].archive);
200 
201 	pl->length--;
202 	if (number <= pl->current) {
203 		if (number == pl->current)
204 			pl->curr_deleted = 1;
205 		pl->current--;
206 	}
207 	for (i = number; i < pl->length; i++)
208 		pl->entry[i] = pl->entry[i + 1];
209 
210 	pl->entry = (PLAYENTRY *) realloc(pl->entry, pl->length * sizeof(PLAYENTRY));
211 
212 	return 1;
213 }
214 
PL_DelDouble(PLAYLIST * pl)215 BOOL PL_DelDouble(PLAYLIST * pl)
216 {
217 	int i, j;
218 
219 	if (!pl->length)
220 		return 0;
221 
222 	for (i = pl->length - 2; i >= 0; i--)
223 		for (j = i + 1; j < pl->length; j++)
224 			if (!filecmp(pl->entry[i].file, pl->entry[j].file) &&
225 				(!(pl->entry[i].archive || pl->entry[j].archive) ||
226 				 (pl->entry[i].archive && pl->entry[j].archive &&
227 				  !filecmp(pl->entry[i].archive, pl->entry[j].archive)))) {
228 
229 				/* keep the time and played information whenever possible */
230 				if (!pl->entry[i].time)
231 					pl->entry[i].time = pl->entry[j].time;
232 				if (!pl->entry[i].played)
233 					pl->entry[i].played = pl->entry[j].played;
234 				PL_DelEntry(pl, j);
235 			}
236 	return 1;
237 }
238 
239 /* Following PL_Add will insert at pos */
PL_StartInsert(PLAYLIST * pl,int pos)240 void PL_StartInsert(PLAYLIST * pl, int pos)
241 {
242 	pl->add_pos = pos;
243 }
244 
245 /* Following PL_Add will append at end of playlist */
PL_StopInsert(PLAYLIST * pl)246 void PL_StopInsert(PLAYLIST * pl)
247 {
248 	pl->add_pos = -1;
249 }
250 
PL_Insert(PLAYLIST * pl,int pos,const CHAR * file,const CHAR * arc,int time,BOOL played)251 static void PL_Insert(PLAYLIST * pl, int pos, const CHAR *file, const CHAR *arc,
252 					 int time, BOOL played)
253 {
254 	int i;
255 
256 	pl->length++;
257 	pl->entry = (PLAYENTRY *) realloc(pl->entry, pl->length * sizeof(PLAYENTRY));
258 
259 	for (i = pl->length - 1; i > pos; i--)
260 		pl->entry[i] = pl->entry[i - 1];
261 	if (pos <= pl->current)
262 		pl->current++;
263 
264 	pl->entry[pos].file = strdup(file);
265 
266 	if (arc) {
267 		pl->entry[pos].archive = strdup(arc);
268 	} else
269 		pl->entry[pos].archive = NULL;
270 
271 	pl->entry[pos].time = time;
272 	pl->entry[pos].played = played;
273 }
274 
275 /* pl->add_pos < 0 => Append entry at end of playlist
276    pl->add_pos >= 0 => Insert entry at pl->add_pos and increment pl->add_pos */
PL_Add(PLAYLIST * pl,const CHAR * file,const CHAR * arc,int time,BOOL played)277 void PL_Add(PLAYLIST * pl, const CHAR *file, const CHAR *arc, int time, BOOL played)
278 {
279 	if (pl->add_pos >= 0) {
280 		PL_Insert(pl, pl->add_pos, file, arc, time, played);
281 		pl->add_pos++;
282 	} else
283 		PL_Insert(pl, pl->length, file, arc, time, played);
284 }
285 
286 #define LINE_LEN (PATH_MAX*2+20)	/* "file" "arc" time played */
287 /* Loads a playlist */
PL_Load(PLAYLIST * pl,const CHAR * filename)288 BOOL PL_Load(PLAYLIST * pl, const CHAR *filename)
289 {
290 	FILE *file;
291 	CHAR line[LINE_LEN];
292 	CHAR *mod, *arc, *pos, *slash;
293 	int time, played;
294 	CHAR *ok = NULL;
295 
296 	if (!(file = fopen(path_conv_sys(filename), "r")))
297 		return 0;
298 
299 	while ((ok = fgets(line, LINE_LEN, file)) &&
300 		   (strcasecmp(line, PL_IDENT)));
301 	if (!ok) {
302 		fclose(file);
303 		return 0;				/* file is not a playlist */
304 	}
305 
306 	slash = FIND_LAST_DIRSEP(filename);
307 	while (fgets(line, LINE_LEN, file)) {
308 		if (*line != '"') continue;			/* line == '"file" "arc" time played' */
309 
310 		mod = line + 1;						/* file */
311 		pos = mod;
312 		while (*pos != '"' && *pos)
313 			pos++;
314 		if (*pos != '"' || pos == mod) continue;
315 		*pos = '\0';
316 
317 		pos++;								/* archive */
318 		while (*pos != '"' && *pos)
319 			pos++;
320 		if (*pos == '"') pos++;
321 		arc = pos;
322 		while (*pos != '"' && *pos)
323 			pos++;
324 
325 		time = played = 0;
326 		if (*pos) {
327 			*pos = '\0';
328 			if (arc == pos)
329 				arc = NULL;
330 
331 			pos += 2;							/* time played */
332 			sscanf(pos, "%d %d", &time, &played);
333 		} else
334 			arc = NULL;
335 		path_conv (arc);
336 		path_conv (mod);
337 		if (!arc && !time && !played)
338 			MA_FindFiles(pl, mod);
339 		else {
340 			/* we're loading a playlist, so it might be necessary to convert
341 			   playlist paths to relative paths from cwd */
342 			if (slash && path_relative(arc ? arc : mod)) {
343 				CHAR *dummy;
344 
345 				dummy = (CHAR *)
346 				  malloc(slash + 1 - filename + strlen(arc ? arc : mod) + 1);
347 				strncpy(dummy, filename, slash + 1 - filename);
348 				dummy[slash + 1 - filename] = '\0';
349 				strcat(dummy, arc ? arc : mod);
350 
351 				PL_Add(pl, arc ? mod : dummy, arc ? dummy : NULL, time,
352 					   (BOOL)played);
353 
354 				free (dummy);
355 			} else
356 				PL_Add(pl, mod, arc, time, (BOOL)played);
357 		}
358 	}
359 
360 	fclose(file);
361 	return 1;
362 }
363 
PL_Save(PLAYLIST * pl,const CHAR * filename)364 BOOL PL_Save(PLAYLIST * pl, const CHAR *filename)
365 {
366 	FILE *file;
367 	int i;
368 	PLAYENTRY *entry;
369 
370 	if (!(file = fopen(path_conv_sys(filename), "w")))
371 		return 0;
372 
373 	if (fputs(PL_IDENT, file) != EOF) {
374 		for (i = 0; i < pl->length; i++) {
375 			entry = &pl->entry[i];
376 			if (entry->archive)
377 				fprintf(file, "\"%s\" \"%s\" %d %d\n",
378 						entry->file, entry->archive, entry->time,
379 						(int)entry->played);
380 			else
381 				fprintf(file, "\"%s\" \"\" %d %d\n",
382 						entry->file, entry->time, (int)entry->played);
383 		}
384 		fclose(file);
385 		return 1;
386 	}
387 	fclose(file);
388 	return 0;
389 }
390 
PL_GetFilename(void)391 char *PL_GetFilename(void)
392 {
393 #if defined(__OS2__)||defined(__EMX__)||defined(__DJGPP__)||defined(_mikmod_amiga)
394 	return get_cfg_name("mikmodpl.cfg");
395 #elif defined(_WIN32)
396 	return get_cfg_name("mikmod_playlist.mpl");
397 #else
398 	return get_cfg_name(".mikmod_playlist");
399 #endif
400 }
401 
PL_LoadDefault(PLAYLIST * pl)402 BOOL PL_LoadDefault(PLAYLIST * pl)
403 {
404 	char *name = PL_GetFilename();
405 	BOOL ret = 0;
406 
407 	if (name) {
408 		ret = PL_Load(pl, name);
409 		free(name);
410 	}
411 	return ret;
412 }
413 
PL_SaveDefault(PLAYLIST * pl)414 BOOL PL_SaveDefault(PLAYLIST * pl)
415 {
416 	char *name = PL_GetFilename();
417 	BOOL ret = 0;
418 
419 	if (name) {
420 		ret = PL_Save(pl, name);
421 		free(name);
422 	}
423 	return ret;
424 }
425 
426 /* check if selected file is a playlist and exchange it with the
427    playlist */
PL_CheckPlaylist(PLAYLIST * pl,BOOL * ok,int old_current,int cont,CHAR ** retfile,CHAR ** retarc,int arg)428 static BOOL PL_CheckPlaylist(PLAYLIST * pl, BOOL *ok, int old_current,
429 							 int cont, CHAR **retfile, CHAR **retarc, int arg)
430 {
431 	/* check if selected file is a playlist */
432 	if ((pl->entry[pl->current].file) && (!pl->entry[pl->current].archive)) {
433 		pl->add_pos = pl->current + 1;
434 		if (PL_Load(pl, pl->entry[pl->current].file)) {
435 			/* Yes -> del playlist-entry and get next entry in now modified
436 			   list */
437 			pl->add_pos = -1;
438 			PL_DelEntry(pl, pl->current);
439 			pl->current = old_current;
440 			switch (cont) {
441 			  case PL_CONT_NEXT:
442 				*ok = PL_ContNext(pl, retfile, retarc, arg);
443 				return 1;
444 			  case PL_CONT_PREV:
445 				*ok = PL_ContPrev(pl, retfile, retarc);
446 				return 1;
447 			  case PL_CONT_POS:
448 				*ok = PL_ContPos(pl, retfile, retarc, arg);
449 				return 1;
450 			}
451 		}
452 		pl->add_pos = -1;
453 	}
454 	return 0;
455 }
456 
457 /* get next module to play
458    mode: PM_MODULE, PM_MULTI, PM_SHUFFLE, or PM_RANDOM
459    return: was there a module? */
PL_ContNext(PLAYLIST * pl,CHAR ** retfile,CHAR ** retarc,int mode)460 BOOL PL_ContNext(PLAYLIST * pl, CHAR **retfile, CHAR **retarc, int mode)
461 {
462 	int num, i, not_played, old_current = pl->current;
463 	BOOL ok = 1;
464 
465 	pl->curr_deleted = 0;
466 	if (!pl->length)
467 		return 0;
468 
469 	if (BTST(mode, PM_RANDOM)) {
470 		not_played = 0;
471 		for (i = 0; i < pl->length; i++)
472 			if (!pl->entry[i].played)
473 				not_played++;
474 
475 		if (!not_played) {
476 			PL_ClearPlayed(pl);
477 			not_played = pl->length;
478 			if (BTST(mode, PM_SHUFFLE))
479 				PL_Randomize(pl);
480 			if (!BTST(mode, PM_MULTI))
481 				return 0;
482 		}
483 
484 		num = mikmod_random(not_played) + 1;
485 		while (num > 0) {
486 			pl->current++;
487 			if (pl->current == pl->length)
488 				pl->current = 0;
489 			if (!pl->entry[pl->current].played)
490 				num--;
491 		}
492 	} else {
493 		pl->current++;
494 		if (pl->current >= pl->length) {
495 			not_played = 0;
496 			for (i = 0; i < pl->length; i++)
497 				if (!pl->entry[i].played)
498 					not_played++;
499 			if (!not_played) {
500 				PL_ClearPlayed(pl);
501 				if (BTST(mode, PM_SHUFFLE))
502 					PL_Randomize(pl);
503 			}
504 			pl->current = 0;
505 			if (!BTST(mode, PM_MULTI))
506 				return 0;
507 		}
508 	}
509 
510 	/* check if selected file is a playlist and load it */
511 	if (PL_CheckPlaylist(pl, &ok, old_current, PL_CONT_NEXT,
512 						 retfile, retarc, mode))
513 		return ok;
514 
515 	if (retfile)
516 		*retfile = pl->entry[pl->current].file;
517 	if (retarc)
518 		*retarc = pl->entry[pl->current].archive;
519 
520 	return 1;
521 }
522 
PL_ContPrev(PLAYLIST * pl,CHAR ** retfile,CHAR ** retarc)523 BOOL PL_ContPrev(PLAYLIST * pl, CHAR **retfile, CHAR **retarc)
524 {
525 	int old_current = pl->current;
526 	BOOL ok = 1;
527 
528 	pl->curr_deleted = 0;
529 	if (!pl->length)
530 		return 0;
531 
532 	pl->current--;
533 	if (pl->current < 0)
534 		pl->current = pl->length - 1;
535 
536 	/* check if selected file is a playlist and load it */
537 	if (PL_CheckPlaylist(pl, &ok, old_current, PL_CONT_PREV,
538 						 retfile, retarc, 0))
539 		return ok;
540 
541 	if (retfile)
542 		*retfile = pl->entry[pl->current].file;
543 	if (retarc)
544 		*retarc = pl->entry[pl->current].archive;
545 
546 	return 1;
547 }
548 
PL_ContPos(PLAYLIST * pl,CHAR ** retfile,CHAR ** retarc,int number)549 BOOL PL_ContPos(PLAYLIST * pl, CHAR **retfile, CHAR **retarc, int number)
550 {
551 	int old_current = pl->current;
552 	BOOL ok = 1;
553 
554 	pl->curr_deleted = 0;
555 	if ((number < 0) || (number >= pl->length))
556 		return 0;
557 
558 	pl->current = number;
559 
560 	/* check if selected file is a playlist and load it */
561 	if (PL_CheckPlaylist(pl, &ok, old_current, PL_CONT_POS,
562 						 retfile, retarc, number))
563 		return ok;
564 
565 	if (retfile)
566 		*retfile = pl->entry[pl->current].file;
567 	if (retarc)
568 		*retarc = pl->entry[pl->current].archive;
569 
570 	return 1;
571 }
572 
PL_Sort(PLAYLIST * pl,int (* compar)(PLAYENTRY * small,PLAYENTRY * big))573 void PL_Sort(PLAYLIST * pl,
574 			 int (*compar) (PLAYENTRY * small, PLAYENTRY * big))
575 {
576 	int i, j;
577 	BOOL end = 0;
578 	PLAYENTRY tmp;
579 
580 	for (i = 0; i < pl->length && !end; i++) {
581 		end = 1;
582 		for (j = pl->length - 1; j > i; j--)
583 			if (compar(&pl->entry[j - 1], &pl->entry[j]) > 0) {
584 				tmp = pl->entry[j];
585 				pl->entry[j] = pl->entry[j - 1];
586 				pl->entry[j - 1] = tmp;
587 
588 				if (pl->current == j)
589 					pl->current = j - 1;
590 				else if (pl->current == j - 1)
591 					pl->current = j;
592 				end = 0;
593 			}
594 	}
595 }
596 
PL_Randomize(PLAYLIST * pl)597 void PL_Randomize(PLAYLIST * pl)
598 {
599 	if (pl->length > 1) {
600 		int i, target;
601 
602 		for (i = 0; i < pl->length - 1; i++) {
603 			target = mikmod_random(pl->length - i) + i;
604 			if (target != i) {
605 				PLAYENTRY temp;
606 
607 				temp = pl->entry[i];
608 				pl->entry[i] = pl->entry[target];
609 				pl->entry[target] = temp;
610 
611 				/* track selection */
612 				if (pl->current == i)
613 					pl->current = target;
614 				else if (pl->current == target)
615 					pl->current = i;
616 			}
617 		}
618 	}
619 }
620 
621 /* ex:set ts=4: */
622