cdplay.c revision 1.23 1 /* $NetBSD: cdplay.c,v 1.23 2003/02/19 12:55:58 simonb Exp $ */
2
3 /*
4 * Copyright (c) 1999, 2000, 2001 Andrew Doran.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 *
28 */
29
30 /*
31 * Compact Disc Control Utility, originally by Serge V. Vakulenko
32 * <vak (at) cronyx.ru>. First appeared in FreeBSD under the guise of
33 * cdcontrol(1). Based on the non-X based CD player by Jean-Marc
34 * Zucconi and Andrey A. Chernov. Fixed and further modified on
35 * by Jukka Ukkonen <jau (at) funet.fi>. Lots of fixes and improvements
36 * made subsequently by The NetBSD Project.
37 *
38 * from FreeBSD: cdcontrol.c,v 1.17.2.1 1999/01/31 15:36:01 billf Exp
39 */
40
41 #include <sys/cdefs.h>
42 #ifndef lint
43 __RCSID("$NetBSD: cdplay.c,v 1.23 2003/02/19 12:55:58 simonb Exp $");
44 #endif /* not lint */
45
46 #include <sys/types.h>
47
48 #include <sys/endian.h>
49 #include <sys/ioctl.h>
50 #include <sys/file.h>
51 #include <sys/cdio.h>
52
53 #include <assert.h>
54
55 #include <ctype.h>
56 #include <err.h>
57 #include <errno.h>
58 #include <histedit.h>
59 #include <limits.h>
60 #include <signal.h>
61 #include <stdio.h>
62 #include <stdlib.h>
63 #include <string.h>
64 #include <unistd.h>
65 #include <util.h>
66
67 enum cmd {
68 CMD_CLOSE,
69 CMD_EJECT,
70 CMD_HELP,
71 CMD_INFO,
72 CMD_NEXT,
73 CMD_PAUSE,
74 CMD_PLAY,
75 CMD_PREV,
76 CMD_QUIT,
77 CMD_RESET,
78 CMD_RESUME,
79 CMD_SET,
80 CMD_SHUFFLE,
81 CMD_SKIP,
82 CMD_STATUS,
83 CMD_STOP,
84 CMD_VOLUME,
85 };
86
87 struct cmdtab {
88 enum cmd command;
89 const char *name;
90 unsigned int min;
91 const char *args;
92 } const cmdtab[] = {
93 { CMD_HELP, "?", 1, 0 },
94 { CMD_CLOSE, "close", 1, NULL },
95 { CMD_EJECT, "eject", 1, NULL },
96 { CMD_HELP, "help", 1, NULL },
97 { CMD_INFO, "info", 1, NULL },
98 { CMD_NEXT, "next", 1, NULL },
99 { CMD_PAUSE, "pause", 2, NULL },
100 { CMD_PLAY, "play", 1, "min1:sec1[.fram1] [min2:sec2[.fram2]]" },
101 { CMD_PLAY, "play", 1, "track1[.index1] [track2[.index2]]" },
102 { CMD_PLAY, "play", 1, "tr1 m1:s1[.f1] [[tr2] [m2:s2[.f2]]]" },
103 { CMD_PLAY, "play", 1, "[#block [len]]" },
104 { CMD_PREV, "prev", 2, NULL },
105 { CMD_QUIT, "quit", 1, NULL },
106 { CMD_RESET, "reset", 4, NULL },
107 { CMD_RESUME, "resume", 4, NULL },
108 { CMD_SET, "set", 2, "msf | lba" },
109 { CMD_SHUFFLE, "shuffle", 2, NULL },
110 { CMD_SKIP, "skip", 2, NULL },
111 { CMD_STATUS, "status", 3, NULL },
112 { CMD_STOP, "stop", 3, NULL },
113 { CMD_VOLUME, "volume", 1, "<l> <r>|left|right|mute|mono|stereo" },
114 };
115
116 #define CD_MAX_TRACK 99 /* largest 2 digit BCD number */
117
118 struct cd_toc_entry toc_buffer[CD_MAX_TRACK + 1];
119
120 const char *cdname;
121 int fd = -1;
122 int msf = 1;
123 int shuffle;
124 int interactive = 1;
125 struct itimerval itv_timer;
126
127 History *hist;
128 HistEvent he;
129 EditLine *elptr;
130
131 int get_vol(int *, int *);
132 int get_status(int *, int *, int *, int *, int *);
133 void help(void);
134 int info(const char *);
135 void lba2msf(u_long, u_int *, u_int *, u_int *);
136 int main(int, char **);
137 u_int msf2lba(u_int, u_int, u_int);
138 int opencd(void);
139 const char *parse(char *, int *);
140 int play(const char *, int);
141 int play_blocks(int, int);
142 int play_msf(int, int, int, int, int, int);
143 int play_track(int, int, int, int);
144 int print_status(const char *);
145 void print_track(struct cd_toc_entry *);
146 const char *prompt(void);
147 int read_toc_entrys(int);
148 int run(int, const char *);
149 int setvol(int, int);
150 void sig_timer(int, int, struct sigcontext *);
151 int skip(int, int);
152 const char *strstatus(int);
153 void usage(void);
154
155 void toc2msf(u_int, u_int *, u_int *, u_int *);
156 int toc2lba(u_int);
157 void addmsf(u_int *, u_int *, u_int *, u_int, u_int, u_int);
158
159 int
160 main(int argc, char **argv)
161 {
162 const char *arg;
163 char buf[80], *p;
164 static char defdev[16];
165 int cmd, len, c;
166 char *line;
167 const char *elline;
168 int scratch, rv;
169 struct sigaction sa_timer;
170
171 cdname = getenv("MUSIC_CD");
172 if (cdname == NULL)
173 cdname = getenv("CD_DRIVE");
174 if (cdname == NULL)
175 cdname = getenv("DISC");
176 if (cdname == NULL)
177 cdname = getenv("CDPLAY");
178
179 while ((c = getopt(argc, argv, "f:h")) != -1)
180 switch (c) {
181 case 'f':
182 cdname = optarg;
183 continue;
184 case 'h':
185 default:
186 usage();
187 /* NOTREACHED */
188 }
189 argc -= optind;
190 argv += optind;
191
192 if (argc > 0 && strcasecmp(*argv, "help") == 0)
193 usage();
194
195 if (cdname == NULL) {
196 sprintf(defdev, "cd0%c", 'a' + getrawpartition());
197 cdname = defdev;
198 }
199
200 opencd();
201 srandom(time(NULL));
202
203 if (argc > 0) {
204 interactive = 0;
205 for (p = buf; argc-- > 0; argv++) {
206 len = strlen(*argv);
207
208 if (p + len >= buf + sizeof(buf) - 1)
209 usage();
210 if (p > buf)
211 *p++ = ' ';
212
213 strcpy(p, *argv);
214 p += len;
215 }
216 *p = '\0';
217 arg = parse(buf, &cmd);
218 return (run(cmd, arg));
219 }
220
221 printf("Type `?' for command list\n\n");
222
223 hist = history_init();
224 history(hist, &he, H_SETSIZE, 100); /* 100 elt history buffer */
225 elptr = el_init(getprogname(), stdin, stdout, stderr);
226 el_set(elptr, EL_EDITOR, "emacs");
227 el_set(elptr, EL_PROMPT, prompt);
228 el_set(elptr, EL_HIST, history, hist);
229 el_source(elptr, NULL);
230
231 sigemptyset(&sa_timer.sa_mask);
232 sa_timer.sa_handler = (void (*)(int))sig_timer;
233 sa_timer.sa_flags = SA_RESTART;
234 if ((rv = sigaction(SIGALRM, &sa_timer, NULL)) < 0)
235 err(EXIT_FAILURE, "sigaction()");
236
237 for (;;) {
238 line = NULL;
239 do {
240 if (((elline = el_gets(elptr, &scratch)) != NULL)
241 && (scratch != 0)){
242 history(hist, &he, H_ENTER, elline);
243 line = strdup(elline);
244 arg = parse(line, &cmd);
245 } else {
246 cmd = CMD_QUIT;
247 fprintf(stderr, "\r\n");
248 arg = 0;
249 break;
250 }
251 } while (arg == NULL);
252
253 if (run(cmd, arg) < 0) {
254 if (fd != -1)
255 close(fd);
256 fd = -1;
257 }
258 fflush(stdout);
259 if (line != NULL)
260 free(line);
261 }
262
263 el_end(elptr);
264 history_end(hist);
265 exit(EXIT_SUCCESS);
266 /* NOTREACHED */
267 }
268
269 void
270 usage(void)
271 {
272
273 fprintf(stderr, "usage: cdplay [-f device] [command ...]\n");
274 exit(EXIT_FAILURE);
275 /* NOTREACHED */
276 }
277
278 void
279 help(void)
280 {
281 const struct cmdtab *c, *mc;
282 const char *s;
283 int i, n;
284
285 mc = cmdtab + sizeof(cmdtab) / sizeof(cmdtab[0]);
286 for (c = cmdtab; c < mc; c++) {
287 for (i = c->min, s = c->name; *s != '\0'; s++, i--) {
288 n = (i > 0 ? toupper(*s) : *s);
289 putchar(n);
290 }
291 if (c->args != NULL)
292 printf(" %s", c->args);
293 putchar('\n');
294 }
295 printf(
296 "\nThe word \"play\" is not required for the play commands.\n"
297 "The plain target address is taken as a synonym for play.\n");
298 }
299
300 int
301 run(int cmd, const char *arg)
302 {
303 int l, r, rv;
304
305 if (cmd == CMD_QUIT) {
306 close(fd);
307 exit(EXIT_SUCCESS);
308 /* NOTREACHED */
309 }
310
311 if (fd < 0 && !opencd())
312 return (0);
313
314 switch (cmd) {
315 case CMD_INFO:
316 rv = info(arg);
317 break;
318
319 case CMD_STATUS:
320 rv = print_status(arg);
321 break;
322
323 case CMD_PAUSE:
324 if ((rv = ioctl(fd, CDIOCPAUSE)) < 0)
325 warn("ioctl(CDIOCPAUSE)");
326 break;
327
328 case CMD_RESUME:
329 if ((rv = ioctl(fd, CDIOCRESUME)) < 0)
330 warn("ioctl(CDIOCRESUME)");
331 break;
332
333 case CMD_STOP:
334 if ((rv = ioctl(fd, CDIOCSTOP)) < 0)
335 warn("ioctl(CDIOCSTOP)");
336 if (ioctl(fd, CDIOCALLOW) < 0)
337 warn("ioctl(CDIOCALLOW)");
338 break;
339
340 case CMD_RESET:
341 if ((rv = ioctl(fd, CDIOCRESET)) >= 0) {
342 close(fd);
343 fd = -1;
344 } else
345 warn("ioctl(CDIOCRESET)");
346 return (0);
347
348 case CMD_EJECT:
349 if (shuffle)
350 run(CMD_SHUFFLE, NULL);
351 if (ioctl(fd, CDIOCALLOW) < 0)
352 warn("ioctl(CDIOCALLOW)");
353 if ((rv = ioctl(fd, CDIOCEJECT)) < 0)
354 warn("ioctl(CDIOCEJECT)");
355 break;
356
357 case CMD_CLOSE:
358 ioctl(fd, CDIOCALLOW);
359 if ((rv = ioctl(fd, CDIOCCLOSE)) >= 0) {
360 close(fd);
361 fd = -1;
362 } else
363 warn("ioctl(CDIOCCLOSE)");
364 break;
365
366 case CMD_PLAY:
367 while (isspace(*arg))
368 arg++;
369 rv = play(arg, 1);
370 break;
371
372 case CMD_PREV:
373 rv = skip(-1, 1);
374 break;
375
376 case CMD_NEXT:
377 rv = skip(1, 1);
378 break;
379
380 case CMD_SHUFFLE:
381 if (interactive == 0)
382 errx(EXIT_FAILURE,
383 "`shuffle' valid only in interactive mode");
384 if (shuffle == 0) {
385 itv_timer.it_interval.tv_sec = 1;
386 itv_timer.it_interval.tv_usec = 0;
387 itv_timer.it_value.tv_sec = 1;
388 itv_timer.it_value.tv_usec = 0;
389 if (setitimer(ITIMER_REAL, &itv_timer, NULL) == 0) {
390 shuffle = 1;
391 skip(0, 1);
392 }
393 } else {
394 itv_timer.it_interval.tv_sec = 0;
395 itv_timer.it_interval.tv_usec = 0;
396 itv_timer.it_value.tv_sec = 0;
397 itv_timer.it_value.tv_usec = 0;
398 if (setitimer(ITIMER_REAL, &itv_timer, NULL) == 0)
399 shuffle = 0;
400 }
401 printf("shuffle play:\t%s\n", shuffle ? "on" : "off");
402 rv = 0;
403 break;
404
405 case CMD_SKIP:
406 if (!interactive)
407 errx(EXIT_FAILURE,
408 "`skip' valid only in interactive mode");
409 if (!shuffle)
410 warnx("`skip' valid only in shuffle mode");
411 else
412 skip(0, 1);
413 break;
414
415 case CMD_SET:
416 if (strcasecmp(arg, "msf") == 0)
417 msf = 1;
418 else if (strcasecmp(arg, "lba") == 0)
419 msf = 0;
420 else
421 warnx("invalid command arguments");
422 break;
423
424 case CMD_VOLUME:
425 if (strncasecmp(arg, "left", strlen(arg)) == 0)
426 rv = ioctl(fd, CDIOCSETLEFT);
427 else if (strncasecmp(arg, "right", strlen(arg)) == 0)
428 rv = ioctl(fd, CDIOCSETRIGHT);
429 else if (strncasecmp(arg, "mono", strlen(arg)) == 0)
430 rv = ioctl(fd, CDIOCSETMONO);
431 else if (strncasecmp(arg, "stereo", strlen(arg)) == 0)
432 rv = ioctl(fd, CDIOCSETSTEREO);
433 else if (strncasecmp(arg, "mute", strlen(arg)) == 0)
434 rv = ioctl(fd, CDIOCSETMUTE);
435 else {
436 rv = 0;
437 if (sscanf(arg, "%d %d", &l, &r) != 2) {
438 if (sscanf(arg, "%d", &l) == 1)
439 r = l;
440 else {
441 warnx("invalid command arguments");
442 break;
443 }
444 }
445 rv = setvol(l, r);
446 }
447 break;
448
449 case CMD_HELP:
450 default:
451 help();
452 rv = 0;
453 break;
454 }
455
456 return (rv);
457 }
458
459 int
460 play(const char *arg, int fromuser)
461 {
462 int rv, n, start, end, istart, iend, blk, len, relend;
463 u_int tr1, tr2, m1, m2, s1, s2, f1, f2, tm, ts, tf;
464 struct ioc_toc_header h;
465
466 if (shuffle && fromuser) {
467 warnx("`play' not valid in shuffle mode");
468 return (0);
469 }
470
471 if ((rv = ioctl(fd, CDIOREADTOCHEADER, &h)) < 0) {
472 warn("ioctl(CDIOREADTOCHEADER)");
473 return (rv);
474 }
475
476 end = 0;
477 istart = iend = 1;
478 n = h.ending_track - h.starting_track + 1;
479 rv = read_toc_entrys((n + 1) * sizeof(struct cd_toc_entry));
480 if (rv < 0)
481 return (rv);
482
483 if (arg == NULL || *arg == '\0') {
484 /* Play the whole disc */
485 return (play_track(h.starting_track, 1, h.ending_track, 99));
486 }
487
488 if (strchr(arg, '#') != NULL) {
489 /* Play block #blk [ len ] */
490 len = 0;
491
492 if (2 != sscanf(arg, "#%d%d", &blk, &len) &&
493 1 != sscanf(arg, "#%d", &blk))
494 goto Clean_up;
495
496 if (len == 0) {
497 len = toc2lba(n);
498 }
499 return (play_blocks(blk, len));
500 }
501
502 if (strchr(arg, ':') != NULL) {
503 /*
504 * Play MSF m1:s1 [ .f1 ] [ m2:s2 [ .f2 ] ]
505 *
506 * Will now also undestand timed addresses relative
507 * to the beginning of a track in the form...
508 *
509 * tr1 m1:s1[.f1] [[tr2] [m2:s2[.f2]]]
510 */
511 relend = 1;
512 tr2 = m2 = s2 = f2 = f1 = 0;
513 if (8 == sscanf(arg, "%d %d:%d.%d %d %d:%d.%d", &tr1, &m1,
514 &s1, &f1, &tr2, &m2, &s2, &f2))
515 goto Play_Relative_Addresses;
516
517 tr2 = m2 = s2 = f2 = f1 = 0;
518 if (7 == sscanf(arg, "%d %d:%d %d %d:%d.%d", &tr1, &m1, &s1,
519 &tr2, &m2, &s2, &f2))
520 goto Play_Relative_Addresses;
521
522 tr2 = m2 = s2 = f2 = f1 = 0;
523 if (7 == sscanf(arg, "%d %d:%d.%d %d %d:%d", &tr1, &m1, &s1,
524 &f1, &tr2, &m2, &s2))
525 goto Play_Relative_Addresses;
526
527 tr2 = m2 = s2 = f2 = f1 = 0;
528 if (7 == sscanf(arg, "%d %d:%d.%d %d:%d.%d", &tr1, &m1, &s1,
529 &f1, &m2, &s2, &f2))
530 goto Play_Relative_Addresses;
531
532 tr2 = m2 = s2 = f2 = f1 = 0;
533 if (6 == sscanf(arg, "%d %d:%d.%d %d:%d", &tr1, &m1, &s1, &f1,
534 &m2, &s2))
535 goto Play_Relative_Addresses;
536
537 tr2 = m2 = s2 = f2 = f1 = 0;
538 if (6 == sscanf(arg, "%d %d:%d %d:%d.%d", &tr1, &m1, &s1, &m2,
539 &s2, &f2))
540 goto Play_Relative_Addresses;
541
542 tr2 = m2 = s2 = f2 = f1 = 0;
543 if (6 == sscanf(arg, "%d %d:%d.%d %d %d", &tr1, &m1, &s1, &f1,
544 &tr2, &m2))
545 goto Play_Relative_Addresses;
546
547 tr2 = m2 = s2 = f2 = f1 = 0;
548 if (6 == sscanf(arg, "%d %d:%d %d %d:%d", &tr1, &m1, &s1, &tr2,
549 &m2, &s2))
550 goto Play_Relative_Addresses;
551
552 tr2 = m2 = s2 = f2 = f1 = 0;
553 if (5 == sscanf(arg, "%d %d:%d %d:%d", &tr1, &m1, &s1, &m2,
554 &s2))
555 goto Play_Relative_Addresses;
556
557 tr2 = m2 = s2 = f2 = f1 = 0;
558 if (5 == sscanf(arg, "%d %d:%d %d %d", &tr1, &m1, &s1, &tr2,
559 &m2))
560 goto Play_Relative_Addresses;
561
562 relend=0;
563 tr2 = m2 = s2 = f2 = f1 = 0;
564 if (5 == sscanf(arg, "%d %d:%d.%d %d", &tr1, &m1, &s1, &f1,
565 &tr2))
566 goto Play_Relative_Addresses;
567
568 tr2 = m2 = s2 = f2 = f1 = 0;
569 if (4 == sscanf(arg, "%d %d:%d %d", &tr1, &m1, &s1, &tr2))
570 goto Play_Relative_Addresses;
571
572 tr2 = m2 = s2 = f2 = f1 = 0;
573 if (4 == sscanf(arg, "%d %d:%d.%d", &tr1, &m1, &s1, &f1))
574 goto Play_Relative_Addresses;
575
576 tr2 = m2 = s2 = f2 = f1 = 0;
577 if (3 == sscanf(arg, "%d %d:%d", &tr1, &m1, &s1))
578 goto Play_Relative_Addresses;
579
580 tr2 = m2 = s2 = f2 = f1 = 0;
581 goto Try_Absolute_Timed_Addresses;
582
583 Play_Relative_Addresses:
584 if (!tr1)
585 tr1 = 1;
586 else if (tr1 > n)
587 tr1 = n;
588
589 toc2msf(tr1-1, &tm, &ts, &tf);
590 addmsf(&m1, &s1, &f1, tm, ts, tf);
591
592 toc2msf(tr1, &tm, &ts, &tf);
593
594 if ((m1 > tm) || ((m1 == tm) && ((s1 > ts) || ((s1 == ts) &&
595 (f1 > tf))))) {
596 warnx("Track %d is not that long.", tr1);
597 return (0);
598 }
599 tr1--; /* XXXXX ???? */
600
601
602 if (!tr2) {
603 if (relend) {
604 tr2 = tr1;
605
606 addmsf(&m2, &s2, &f2, m1, s1, f1);
607 } else {
608 tr2 = n;
609
610 toc2msf(n, &m2, &s2, &f2);
611 }
612 } else {
613 if (tr2 > n) {
614 tr2 = n;
615 m2 = s2 = f2 = 0;
616 } else {
617 if (relend)
618 tr2--;
619
620 toc2msf(tr2, &tm, &ts, &tf);
621 addmsf(&m2, &s2, &f2, tm, ts, tf);
622 }
623 }
624
625 toc2msf(n, &tm, &ts, &tf);
626
627 if ((tr2 < n) && ((m2 > tm) || ((m2 == tm) && ((s2 > ts) ||
628 ((s2 == ts) && (f2 > tf)))))) {
629 warnx("The playing time of the disc is not that long.");
630 return (0);
631 }
632
633 return (play_msf(m1, s1, f1, m2, s2, f2));
634
635 Try_Absolute_Timed_Addresses:
636 m2 = UINT_MAX;
637
638 if (6 != sscanf(arg, "%d:%d.%d%d:%d.%d",
639 &m1, &s1, &f1, &m2, &s2, &f2) &&
640 5 != sscanf(arg, "%d:%d.%d%d:%d", &m1, &s1, &f1, &m2, &s2) &&
641 5 != sscanf(arg, "%d:%d%d:%d.%d", &m1, &s1, &m2, &s2, &f2) &&
642 3 != sscanf(arg, "%d:%d.%d", &m1, &s1, &f1) &&
643 4 != sscanf(arg, "%d:%d%d:%d", &m1, &s1, &m2, &s2) &&
644 2 != sscanf(arg, "%d:%d", &m1, &s1))
645 goto Clean_up;
646
647 if (m2 == UINT_MAX) {
648 if (msf) {
649 m2 = toc_buffer[n].addr.msf.minute;
650 s2 = toc_buffer[n].addr.msf.second;
651 f2 = toc_buffer[n].addr.msf.frame;
652 } else {
653 lba2msf(be32toh(toc_buffer[n].addr.lba),
654 &tm, &ts, &tf);
655 m2 = tm;
656 s2 = ts;
657 f2 = tf;
658 }
659 }
660 return (play_msf(m1, s1, f1, m2, s2, f2));
661 }
662
663 /*
664 * Play track trk1 [ .idx1 ] [ trk2 [ .idx2 ] ]
665 */
666 if (4 != sscanf(arg, "%d.%d%d.%d", &start, &istart, &end, &iend) &&
667 3 != sscanf(arg, "%d.%d%d", &start, &istart, &end) &&
668 3 != sscanf(arg, "%d%d.%d", &start, &end, &iend) &&
669 2 != sscanf(arg, "%d.%d", &start, &istart) &&
670 2 != sscanf(arg, "%d%d", &start, &end) &&
671 1 != sscanf(arg, "%d", &start))
672 goto Clean_up;
673
674 if (end == 0)
675 end = n;
676 return (play_track(start, istart, end, iend));
677
678 Clean_up:
679 warnx("invalid command arguments");
680 return (0);
681 }
682
683 void
684 sig_timer(int sig, int code, struct sigcontext *scp)
685 {
686 sigset_t anymore;
687
688 sigpending(&anymore);
689 if (sigismember(&anymore, SIGALRM))
690 return;
691 setitimer(ITIMER_REAL, &itv_timer, NULL);
692 if (fd != -1)
693 skip(0, 0);
694 }
695
696 int
697 skip(int dir, int fromuser)
698 {
699 char str[16];
700 int rv, trk, idx, m, s, f;
701 struct ioc_toc_header h;
702
703 if ((rv = ioctl(fd, CDIOREADTOCHEADER, &h)) < 0) {
704 warn("ioctl(CDIOREADTOCHEADER)");
705 return (rv);
706 }
707 if ((rv = get_status(&trk, &idx, &m, &s, &f)) < 0)
708 return (rv);
709
710 if (dir == 0) {
711 if (fromuser || (rv != CD_AS_PLAY_IN_PROGRESS &&
712 rv != CD_AS_PLAY_PAUSED))
713 trk = h.starting_track +
714 random() % (h.ending_track - h.starting_track + 1);
715 else
716 return (0);
717 } else {
718 trk += dir;
719 if (trk > h.ending_track)
720 trk = h.starting_track;
721 else if(trk < h.starting_track)
722 trk = h.ending_track;
723 }
724
725 if (shuffle)
726 sprintf(str, "%d %d", trk, trk);
727 else
728 sprintf(str, "%d", trk);
729
730 return (play(str, 0));
731 }
732
733 const char *
734 strstatus(int sts)
735 {
736 const char *str;
737
738 switch (sts) {
739 case CD_AS_AUDIO_INVALID:
740 str = "invalid";
741 break;
742 case CD_AS_PLAY_IN_PROGRESS:
743 str = "playing";
744 break;
745 case CD_AS_PLAY_PAUSED:
746 str = "paused";
747 break;
748 case CD_AS_PLAY_COMPLETED:
749 str = "completed";
750 break;
751 case CD_AS_PLAY_ERROR:
752 str = "error";
753 break;
754 case CD_AS_NO_STATUS:
755 str = "not playing";
756 break;
757 default:
758 str = "<unknown>";
759 break;
760 }
761
762 return (str);
763 }
764
765 int
766 print_status(const char *arg)
767 {
768 struct cd_sub_channel_info data;
769 struct ioc_read_subchannel ss;
770 int rv, trk, idx, m, s, f;
771 struct ioc_vol v;
772
773 if ((rv = get_status(&trk, &idx, &m, &s, &f)) >= 0) {
774 printf("audio status:\t%s\n", strstatus(rv));
775 printf("current track:\t%d\n", trk);
776 printf("current index:\t%d\n", idx);
777 printf("position:\t%d:%02d.%02d\n", m, s, f);
778 } else
779 printf("audio status:\tno info available\n");
780
781 printf("shuffle play:\t%s\n", shuffle ? "on" : "off");
782
783 bzero(&ss, sizeof(ss));
784 ss.data = &data;
785 ss.data_len = sizeof(data);
786 ss.address_format = msf ? CD_MSF_FORMAT : CD_LBA_FORMAT;
787 ss.data_format = CD_MEDIA_CATALOG;
788
789 if (ioctl(fd, CDIOCREADSUBCHANNEL, (char *)&ss) >= 0) {
790 printf("media catalog:\t%sactive",
791 ss.data->what.media_catalog.mc_valid ? "" : "in");
792 if (ss.data->what.media_catalog.mc_valid &&
793 ss.data->what.media_catalog.mc_number[0])
794 printf(" (%.15s)",
795 ss.data->what.media_catalog.mc_number);
796 putchar('\n');
797 } else
798 printf("media catalog:\tnone\n");
799
800 if (ioctl(fd, CDIOCGETVOL, &v) >= 0) {
801 printf("left volume:\t%d\n", v.vol[0]);
802 printf("right volume:\t%d\n", v.vol[1]);
803 } else {
804 printf("left volume:\tnot available\n");
805 printf("right volume:\tnot available\n");
806 }
807
808 ; return (0);
809 }
810
811 int
812 info(const char *arg)
813 {
814 struct ioc_toc_header h;
815 int rc, i, n;
816
817 if ((rc = ioctl(fd, CDIOREADTOCHEADER, &h)) < 0) {
818 warn("ioctl(CDIOREADTOCHEADER)");
819 return (rc);
820 }
821
822 n = h.ending_track - h.starting_track + 1;
823 rc = read_toc_entrys((n + 1) * sizeof(struct cd_toc_entry));
824 if (rc < 0)
825 return (rc);
826
827 printf("track start duration block length type\n");
828 printf("--------------------------------------------------\n");
829
830 for (i = 0; i < n; i++) {
831 printf("%5d ", toc_buffer[i].track);
832 print_track(toc_buffer + i);
833 }
834 printf(" - ", toc_buffer[n].track); /* Lead-out area */
835 print_track(toc_buffer + n);
836 return (0);
837 }
838
839 void
840 lba2msf(u_long lba, u_int *m, u_int *s, u_int *f)
841 {
842
843 lba += 150; /* block start offset */
844 lba &= 0xffffff; /* negative lbas use only 24 bits */
845 *m = lba / (60 * 75);
846 lba %= (60 * 75);
847 *s = lba / 75;
848 *f = lba % 75;
849 }
850
851 u_int
852 msf2lba(u_int m, u_int s, u_int f)
853 {
854
855 return (((m * 60) + s) * 75 + f) - 150;
856 }
857
858 void
859 print_track(struct cd_toc_entry *e)
860 {
861 int block, next, len;
862 u_int m, s, f;
863
864 if (msf) {
865 /* Print track start */
866 printf("%2d:%02d.%02d ", e->addr.msf.minute,
867 e->addr.msf.second, e->addr.msf.frame);
868
869 block = msf2lba(e->addr.msf.minute, e->addr.msf.second,
870 e->addr.msf.frame);
871 } else {
872 block = e->addr.lba;
873 lba2msf(block, &m, &s, &f);
874 /* Print track start */
875 printf("%2d:%02d.%02d ", m, s, f);
876 }
877 if (e->track > CD_MAX_TRACK) {
878 /* lead-out area -- print block */
879 printf(" - %6d - lead-out\n", block);
880 return;
881 }
882 if (msf)
883 next = msf2lba(e[1].addr.msf.minute, e[1].addr.msf.second,
884 e[1].addr.msf.frame);
885 else
886 next = e[1].addr.lba;
887 len = next - block;
888 lba2msf(len, &m, &s, &f);
889
890 /* Print duration, block, length, type */
891 printf("%2d:%02d.%02d %6d %6d %8s\n", m, s, f, block, len,
892 (e->control & 4) ? "data" : "audio");
893 }
894
895 int
896 play_track(int tstart, int istart, int tend, int iend)
897 {
898 struct ioc_play_track t;
899 int rv;
900
901 t.start_track = tstart;
902 t.start_index = istart;
903 t.end_track = tend;
904 t.end_index = iend;
905
906 if ((rv = ioctl(fd, CDIOCPLAYTRACKS, &t)) < 0)
907 warn("ioctl(CDIOCPLAYTRACKS)");
908 return (rv);
909 }
910
911 int
912 play_blocks(int blk, int len)
913 {
914 struct ioc_play_blocks t;
915 int rv;
916
917 t.blk = blk;
918 t.len = len;
919
920 if ((rv = ioctl(fd, CDIOCPLAYBLOCKS, &t)) < 0)
921 warn("ioctl(CDIOCPLAYBLOCKS");
922 return (rv);
923 }
924
925 int
926 setvol(int left, int right)
927 {
928 struct ioc_vol v;
929 int rv;
930
931 v.vol[0] = left;
932 v.vol[1] = right;
933 v.vol[2] = 0;
934 v.vol[3] = 0;
935
936 if ((rv = ioctl(fd, CDIOCSETVOL, &v)) < 0)
937 warn("ioctl(CDIOCSETVOL)");
938 return (rv);
939 }
940
941 int
942 read_toc_entrys(int len)
943 {
944 struct ioc_read_toc_entry t;
945 int rv;
946
947 t.address_format = msf ? CD_MSF_FORMAT : CD_LBA_FORMAT;
948 t.starting_track = 0;
949 t.data_len = len;
950 t.data = toc_buffer;
951
952 if ((rv = ioctl(fd, CDIOREADTOCENTRYS, &t)) < 0)
953 warn("ioctl(CDIOREADTOCENTRYS)");
954 return (rv);
955 }
956
957 int
958 play_msf(int start_m, int start_s, int start_f, int end_m, int end_s,
959 int end_f)
960 {
961 struct ioc_play_msf a;
962 int rv;
963
964 a.start_m = start_m;
965 a.start_s = start_s;
966 a.start_f = start_f;
967 a.end_m = end_m;
968 a.end_s = end_s;
969 a.end_f = end_f;
970
971 if ((rv = ioctl(fd, CDIOCPLAYMSF, &a)) < 0)
972 warn("ioctl(CDIOREADTOCENTRYS)");
973 return (rv);
974 }
975
976 int
977 get_status(int *trk, int *idx, int *min, int *sec, int *frame)
978 {
979 struct ioc_read_subchannel s;
980 struct cd_sub_channel_info data;
981 u_int mm, ss, ff;
982 int rv;
983
984 bzero(&s, sizeof(s));
985 s.data = &data;
986 s.data_len = sizeof(data);
987 s.address_format = msf ? CD_MSF_FORMAT : CD_LBA_FORMAT;
988 s.data_format = CD_CURRENT_POSITION;
989
990 if ((rv = ioctl(fd, CDIOCREADSUBCHANNEL, &s)) < 0) {
991 warn("ioctl(CDIOCREADSUBCHANNEL)");
992 return (rv);
993 }
994
995 *trk = s.data->what.position.track_number;
996 *idx = s.data->what.position.index_number;
997 if (msf) {
998 *min = s.data->what.position.reladdr.msf.minute;
999 *sec = s.data->what.position.reladdr.msf.second;
1000 *frame = s.data->what.position.reladdr.msf.frame;
1001 } else {
1002 lba2msf(be32toh(s.data->what.position.reladdr.lba), &mm,
1003 &ss, &ff);
1004 *min = mm;
1005 *sec = ss;
1006 *frame = ff;
1007 }
1008
1009 return (s.data->header.audio_status);
1010 }
1011
1012 const char *
1013 prompt(void)
1014 {
1015
1016 return ("cdplay> ");
1017 }
1018
1019 const char *
1020 parse(char *buf, int *cmd)
1021 {
1022 const struct cmdtab *c, *mc;
1023 char *p, *q;
1024 int len;
1025
1026 for (p = buf; isspace(*p); p++)
1027 continue;
1028
1029 if (isdigit(*p) || (p[0] == '#' && isdigit(p[1]))) {
1030 *cmd = CMD_PLAY;
1031 return (p);
1032 }
1033
1034 for (buf = p; *p != '\0' && !isspace(*p); p++)
1035 continue;
1036
1037 if ((len = p - buf) == 0)
1038 return (0);
1039
1040 if (*p != '\0') { /* It must be a spacing character! */
1041 *p++ = 0;
1042 for (q = p; *q != '\0' && *q != '\n' && *q != '\r'; q++)
1043 continue;
1044 *q = 0;
1045 }
1046
1047 *cmd = -1;
1048
1049 mc = cmdtab + sizeof(cmdtab) / sizeof(cmdtab[0]);
1050 for (c = cmdtab; c < mc; c++) {
1051 /* Is it an exact match? */
1052 if (strcasecmp(buf, c->name) == 0) {
1053 *cmd = c->command;
1054 break;
1055 }
1056 /* Try short hand forms then... */
1057 if (len >= c->min && strncasecmp(buf, c->name, len) == 0) {
1058 if (*cmd != -1 && *cmd != c->command) {
1059 warnx("ambiguous command");
1060 return (0);
1061 }
1062 *cmd = c->command;
1063 }
1064 }
1065
1066 if (*cmd == -1) {
1067 warnx("invalid command, enter ``help'' for commands");
1068 return (0);
1069 }
1070
1071 while (isspace(*p))
1072 p++;
1073 return (p);
1074 }
1075
1076 int
1077 opencd(void)
1078 {
1079 char devbuf[80];
1080
1081 if (fd > -1)
1082 return (1);
1083
1084 fd = opendisk(cdname, O_RDONLY, devbuf, sizeof(devbuf), 0);
1085 if (fd < 0) {
1086 if (errno == ENXIO) {
1087 /*
1088 * ENXIO has an overloaded meaning here. The
1089 * original "Device not configured" should be
1090 * interpreted as "No disc in drive %s".
1091 */
1092 warnx("no disc in drive %s", devbuf);
1093 return (0);
1094 }
1095 err(EXIT_FAILURE, "%s", devbuf);
1096 }
1097
1098 return (1);
1099 }
1100
1101 void
1102 toc2msf(u_int i, u_int *m, u_int *s, u_int *f)
1103 {
1104 struct cd_toc_entry *ctep;
1105
1106 assert(i >= 0);
1107 assert(i <= CD_MAX_TRACK);
1108
1109 ctep = &toc_buffer[i];
1110
1111 if (msf) {
1112 *m = ctep->addr.msf.minute;
1113 *s = ctep->addr.msf.second;
1114 *f = ctep->addr.msf.frame;
1115 } else {
1116 lba2msf(be32toh(ctep->addr.lba), m, s, f);
1117 }
1118 }
1119
1120 int
1121 toc2lba(u_int i)
1122 {
1123 struct cd_toc_entry *ctep;
1124
1125 assert(i > 0);
1126 assert(i <= CD_MAX_TRACK);
1127
1128 ctep = &toc_buffer[i-1];
1129
1130 if (msf) {
1131 return msf2lba(
1132 ctep->addr.msf.minute,
1133 ctep->addr.msf.second,
1134 ctep->addr.msf.frame);
1135 } else {
1136 return be32toh(ctep->addr.lba);
1137 }
1138 }
1139
1140 void
1141 addmsf(u_int *m, u_int *s, u_int *f, u_int m2, u_int s2, u_int f2)
1142 {
1143 *f += f2;
1144 if (*f > 75) {
1145 *s += *f / 75;
1146 *f %= 75;
1147 }
1148
1149 *s += s2;
1150 if (*s > 60) {
1151 *m += *s / 60;
1152 *s %= 60;
1153 }
1154
1155 *m += m2;
1156 }
1157