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