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