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