1 /*
2  * $Id: cdrom.c,v 1.10 1999/05/05 16:34:19 dirk Exp $
3  *
4  * This file is part of WorkMan, the civilized CD player library
5  * (c) 1991-1997 by Steven Grimm (original author)
6  * (c) by Dirk F�rsterling (current 'author' = maintainer)
7  * The maintainer can be contacted by his e-mail address:
8  * milliByte@DeathsDoor.com
9  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Library General Public
12  * License as published by the Free Software Foundation; either
13  * version 2 of the License, or (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Library General Public License for more details.
19  *
20  * You should have received a copy of the GNU Library General Public
21  * License along with this library; if not, write to the Free
22  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
23  *
24  *
25  * Interface between most of WorkMan and the low-level CD-ROM library
26  * routines defined in plat_*.c and drv_*.c.  The goal is to have no
27  * platform- or drive-dependent code here.
28  */
29 
30 static char cdrom_id[] = "$Id: cdrom.c,v 1.10 1999/05/05 16:34:19 dirk Exp $";
31 
32 #include <errno.h>
33 #include <stdio.h>
34 #include <unistd.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <sys/types.h>
38 /* #include <sys/time.h> */
39 
40 #include "include/wm_config.h"
41 #include "include/wm_struct.h"
42 #include "include/wm_cddb.h"
43 #include "include/wm_cdrom.h"
44 #include "include/wm_database.h"
45 #include "include/wm_platform.h"
46 #include "include/wm_helpers.h"
47 #include "include/wm_cdinfo.h"
48 
49 #ifdef CAN_CLOSE
50 #include <fcntl.h>
51 #endif
52 
53 #define WM_MSG_CLASS WM_MSG_CLASS_CDROM
54 
55 /* extern struct wm_drive generic_proto, toshiba_proto, sony_proto; */
56 /*	toshiba33_proto; <=== Somehow, this got lost */
57 
58 /*
59  * The supported drive types are listed here.  NULL means match anything.
60  * The first match in the list is used, and substring matches are done (so
61  * put long names before their shorter prefixes.)
62  */
63 struct drivelist {
64 	char		*ven;
65 	char		*mod;
66 	char		*rev;
67 	struct wm_drive	*proto;
68 } drives[] = {
69 {	"TOSHIBA",		"XM-3501",		NULL,		&toshiba_proto	},
70 {	"TOSHIBA",		"XM-3401",		NULL,		&toshiba_proto	},
71 {	"TOSHIBA",		"XM-3301",		NULL,		&toshiba_proto	},
72 {	"SONY",			"CDU-8012",		NULL,		&sony_proto	},
73 {	"SONY",			"CDU 561",		NULL,		&sony_proto	},
74 {	WM_STR_GENVENDOR,	WM_STR_GENMODEL,	WM_STR_GENREV,	&generic_proto  },
75 {	NULL,			NULL,			NULL,		&generic_proto	}
76 };
77 
78 /*
79  * Solaris 2.2 will remove the device out from under us.  Getting an ENOENT
80  * is therefore sometimes not a problem.
81  */
82 int	intermittent_dev = 0;
83 
84 /*
85  * Do we want to keep the CD device open after quitting by default?
86  *
87    int	keep_open = 0;
88  */
89 
90 #if defined DEFAULT_CD_DEVICE
91 char	*cd_device = DEFAULT_CD_DEVICE;
92 #else
93 char	*cd_device = NULL;
94 #endif
95 
96 
97 int wm_cd_cur_balance = 10;
98 
99 struct wm_drive	drive = { -1, "", "", "", NULL, NULL };
100 
101 char _wm_drive_vendor[32] = 	"Generic";
102 char _wm_drive_model[32] = 	"drive type";
103 char _wm_drive_revision[32] =	"";
104 
105 /*
106  * Give information about the drive we found during wmcd_open()
107  */
108 
wm_drive_vendor(void)109 char *wm_drive_vendor( void )
110 {
111 	char *s = NULL;
112 
113 	wm_strmcpy( &s, _wm_drive_vendor );
114 	return s;
115 }
116 
wm_drive_model(void)117 char *wm_drive_model( void )
118 {
119 	char *s = NULL;
120 
121 	wm_strmcpy( &s, _wm_drive_model );
122 	return s;
123 }
124 
wm_drive_revision(void)125 char *wm_drive_revision( void )
126 {
127 	char *s = NULL;
128 
129 	wm_strmcpy( &s, _wm_drive_revision );
130 	return s;
131 }
132 
wm_drive_settype(char * vendor,char * model,char * revision)133 void wm_drive_settype( char *vendor, char *model, char *revision )
134 {
135 	sprintf( _wm_drive_vendor, "%s", vendor );
136 	sprintf( _wm_drive_model, "%s", model );
137 	sprintf( _wm_drive_revision, "%s", revision );
138 }
139 
140 /*
141  * Figure out which prototype drive structure we should be using based
142  * on the vendor, model, and revision of the current drive.
143  */
144 struct wm_drive *
find_drive_struct(char * vendor,char * model,char * rev)145 find_drive_struct(char *vendor, char *model, char *rev)
146 {
147 	struct drivelist	*d;
148 
149 	for (d = drives; d; d++)
150 	{
151 		if( ( (d->ven != NULL) && strncmp(d->ven, vendor, strlen(d->ven)) ) ||
152 		    ( (d->mod != NULL) && strncmp(d->mod, model, strlen(d->mod)) ) ||
153 		    ( (d->rev != NULL) && strncmp(d->rev, rev, strlen(d->rev)) ) )
154 			continue;
155 
156 		if (d->proto->vendor[0] == '\0')
157 			strcpy(d->proto->vendor, vendor);
158 		if (d->proto->model[0] == '\0')
159 			strcpy(d->proto->model, model);
160 
161 		return (d->proto);
162 	}
163 
164 	return (NULL);	/* this means the list is badly terminated. */
165 } /* find_drive_struct() */
166 
167 /*
168  * read_toc()
169  *
170  * Read the table of contents from the CD.  Return a pointer to a wm_cdinfo
171  * struct containing the relevant information (minus artist/cdname/etc.)
172  * This is a static struct.  Returns NULL if there was an error.
173  *
174  * XXX allocates one trackinfo too many.
175  */
176 struct wm_cdinfo *
read_toc()177 read_toc()
178 {
179 	struct wm_playlist		*l;
180 	int			i, pos;
181 
182 	if ((drive.get_trackcount)(&drive, &thiscd.ntracks) < 0)
183 	{
184 		perror("trackcount");
185 		return (NULL);
186 	}
187 
188 	thiscd.artist[0] = thiscd.cdname[0] = '\0';
189 	thiscd.whichdb = thiscd.otherrc = thiscd.otherdb = thiscd.user = NULL;
190 	thiscd.length = 0;
191 	thiscd.autoplay = thiscd.playmode = thiscd.volume = 0;
192 
193 	/* Free up any left-over playlists. */
194 	if (thiscd.lists != NULL)
195 	{
196 		for (l = thiscd.lists; l->name != NULL; l++)
197 		{
198 			free(l->name);
199 			free(l->list);
200 		}
201 		free(thiscd.lists);
202 		thiscd.lists = NULL;
203 	}
204 
205 	if (thiscd.trk != NULL)
206 		free(thiscd.trk);
207 
208 	thiscd.trk = malloc((thiscd.ntracks + 1) * sizeof(struct wm_trackinfo));
209 	if (thiscd.trk == NULL)
210 	{
211 		perror("malloc");
212 		return (NULL);
213 	}
214 
215 	for (i = 0; i < thiscd.ntracks; i++)
216 	{
217 		if ((drive.get_trackinfo)(&drive, i + 1, &thiscd.trk[i].data,
218 					&thiscd.trk[i].start) < 0)
219 		{
220 			perror("CD track info read");
221 			return (NULL);
222 		}
223 
224 		thiscd.trk[i].avoid = thiscd.trk[i].data;
225 		thiscd.trk[i].length = thiscd.trk[i].start / 75;
226 
227 		thiscd.trk[i].songname = thiscd.trk[i].otherrc =
228 		thiscd.trk[i].otherdb = NULL;
229 		thiscd.trk[i].contd = 0;
230 		thiscd.trk[i].volume = 0;
231 		thiscd.trk[i].track = i + 1;
232 		thiscd.trk[i].section = 0;
233 	}
234 
235 	if ((drive.get_cdlen)(&drive, &thiscd.trk[i].start) < 0)
236 	{
237 		perror("CD length read");
238 		return (NULL);
239 	}
240 	thiscd.trk[i].length = thiscd.trk[i].start / 75;
241 
242 /* Now compute actual track lengths. */
243 	pos = thiscd.trk[0].length;
244 
245 	for (i = 0; i < thiscd.ntracks; i++)
246 	{
247 		thiscd.trk[i].length = thiscd.trk[i+1].length - pos;
248 		pos = thiscd.trk[i+1].length;
249 		if (thiscd.trk[i].data)
250 			thiscd.trk[i].length = (thiscd.trk[i + 1].start -
251 				thiscd.trk[i].start) * 2;
252 		if (thiscd.trk[i].avoid)
253 			wm_strmcpy(&thiscd.trk[i].songname, "DATA TRACK");
254 	}
255 
256 	thiscd.length = thiscd.trk[thiscd.ntracks].length;
257         thiscd.cddbid = cddb_discid(drive);
258 
259 	return (&thiscd);
260 }
261 
262 /*
263  * wm_cd_status()
264  *
265  * Return values:
266  *
267  *	0	No CD in drive.
268  *	1	CD in drive.
269  *	2	CD has just been inserted (TOC has been read)
270  *
271  * Updates cur_track, cur_pos_rel, cur_pos_abs and other variables.
272  */
273 int
wm_cd_status(void)274 wm_cd_status( void )
275 {
276 	static enum wm_cd_modes	oldmode = WM_CDM_UNKNOWN;
277 	enum wm_cd_modes		mode;
278 	int			status, trackno = cur_track;
279 	int			ret = WM_CDS_DISC_READY;
280 
281 	/* Open the drive.  This returns 1 if the device isn't ready. */
282 
283 	status = wmcd_open(&drive);
284 
285 	if (status < 0)
286 		return (status);
287 	if (status > 0)
288 		return (WM_CDS_NO_DISC);
289 
290 	/* If the user hit the stop button, don't pass PLAYING as oldmode.
291          * Likewise, if we've just started playing, don't remember that
292          * we were stopped before (or the state machine in get_drive_status
293          * can get confused.)
294          */
295 	if( (cur_cdmode == WM_CDM_STOPPED) || (cur_cdmode == WM_CDM_PLAYING) )
296 		oldmode = cur_cdmode;
297 
298 	if( (drive.get_drive_status)(&drive, oldmode, &mode, &cur_frame,
299 					&trackno, &cur_index) < 0)
300 	{
301 		perror("CD get drive status");
302 		return (-1);
303 	}
304 	oldmode = mode;
305 
306 	if (mode == WM_CDM_EJECTED || mode == WM_CDM_UNKNOWN)
307 	{
308 		cur_cdmode = WM_CDM_EJECTED;
309 		cur_track = -1;
310 		cur_cdlen = cur_tracklen = 1;
311 		cur_pos_abs = cur_pos_rel = cur_frame = 0;
312 
313 		if (exit_on_eject)
314 			exit(0);
315 
316 		return (WM_CDS_NO_DISC);
317 	}
318 
319 	/* If there wasn't a CD before and there is now, learn about it. */
320 	if (cur_cdmode == WM_CDM_EJECTED)
321 	{
322 		cur_pos_rel = cur_pos_abs = 0;
323 
324 
325 		status = wmcd_reopen( &drive );
326 
327 		if ((cd = read_toc()) == NULL)
328                 {
329 			if (exit_on_eject)
330 			{
331 				exit(-1);
332 			} else {
333 				return (-1);
334 			}
335 		}
336 
337 		cur_nsections = 0;
338 		cur_ntracks = cd->ntracks;
339 		cur_cdlen = cd->length;
340 		load();
341 		cur_artist = cd->artist;
342 		cur_cdname = cd->cdname;
343 		cur_cdmode = WM_CDM_STOPPED;
344 		ret = WM_CDS_JUST_INSERTED;
345 	}
346 
347 	switch (mode) {
348 	case WM_CDM_PLAYING:
349 	case WM_CDM_PAUSED:
350 		cur_pos_abs = cur_frame / 75;
351 
352 		/* Only look up the current track number if necessary. */
353 		if (cur_track < 1 || cur_frame < cd->trk[cur_track-1].start ||
354 				cur_frame >= (cur_track >= cur_ntracks ?
355 				(cur_cdlen + 1) * 75 :
356 				cd->trk[cur_track].start))
357 		{
358 			cur_track = 0;
359 			while (cur_track < cur_ntracks && cur_frame >=
360 					cd->trk[cur_track].start)
361 				cur_track++;
362 		}
363 		if (cur_track >= 1 && trackno > cd->trk[cur_track-1].track)
364 			cur_track++;
365 		/* Fall through */
366 
367 	case WM_CDM_UNKNOWN:
368 		if (mode == WM_CDM_UNKNOWN)
369 		{
370 			mode = WM_CDM_STOPPED;
371 			cur_lasttrack = cur_firsttrack = -1;
372 		}
373 		/* Fall through */
374 
375 	case WM_CDM_STOPPED:
376 		if (cur_track >= 1 && cur_track <= cur_ntracks)
377 		{
378 			cur_trackname = cd->trk[cur_track-1].songname;
379 			cur_avoid = cd->trk[cur_track-1].avoid;
380 			cur_contd = cd->trk[cur_track-1].contd;
381 			cur_pos_rel = (cur_frame -
382 				cd->trk[cur_track-1].start) / 75;
383 			if (cur_pos_rel < 0)
384 				cur_pos_rel = -cur_pos_rel;
385 		}
386 
387 		if( (playlist != NULL) && playlist[0].start & (cur_listno > 0))
388 		{
389 			cur_pos_abs -= cd->trk[playlist[cur_listno-1].
390 				start - 1].start / 75;
391 			cur_pos_abs += playlist[cur_listno-1].starttime;
392 		}
393 		if (cur_pos_abs < 0)
394 			cur_pos_abs = cur_frame = 0;
395 
396 		if (cur_track < 1)
397 			cur_tracklen = cd->length;
398 		else
399 			cur_tracklen = cd->trk[cur_track-1].length;
400 		/* Fall through */
401 
402 	case WM_CDM_TRACK_DONE:
403 		cur_cdmode = mode;
404 		break;
405 	case WM_CDM_FORWARD:
406 	case WM_CDM_EJECTED:
407 		break;
408 	}
409 
410 	return (ret);
411 }
412 
413 #undef CLIF_VOL
414 #ifdef CLIF_VOL
415 /*
416  * cd_volume(vol, bal, max)
417  *
418  * Set the volume levels.  "vol" and "bal" are the volume and balance knob
419  * settings, respectively.  "max" is the maximum value of the volume knob
420  * (the balance knob is assumed to always go from 0 to 20.)
421  */
422 void
cd_volume(vol,bal,max)423 cd_volume(vol, bal, max)
424 	int	vol, bal, max;
425 {
426 	int	left, right, scale;
427 
428 /*
429  * Set "left" and "right" to volume-slider values accounting for the
430  * balance setting.
431  */
432 /*	printf("Vol = %d, Bal = %d, Max = %d\n", vol, bal, max);
433 */
434 
435         vol  = (vol * 100 + max - 16) / max;
436 	scale = (vol + 5) / 10;
437 
438         if (bal < 9)
439 	{
440                 right = vol - scale * (10 - bal);
441 #ifdef SYMETRIC_BALANCE
442 		left  = vol + scale * (10 - bal);
443 #else
444 		left = vol;
445 #endif
446         }
447 	else if (bal > 11)
448 	{
449 #ifdef SYMETRIC_BALANCE
450                 right = vol + scale * (bal - 10);
451 #else
452 		right = vol;
453 #endif
454 		left  = vol - scale * (bal - 10);
455         }
456 	else
457                 left = right = vol;
458 
459 /*
460  * some plat_*.c is missing the limitation
461  */
462 	left = left < 0 ? 0 : left > 100 ? 100 : left;
463 	right = right < 0 ? 0 : right > 100 ? 100 : right;
464 /*	printf("Left = %d, Right = %d\n", left, right);
465 */
466 	(void) (drive.set_volume)(&drive, left, right);
467 } /* cd_volume() */
468 
469 #else
470 
471 /*
472  * cd_volume(vol, bal, max)
473  *
474  * Set the volume levels.  "vol" and "bal" are the volume and balance knob
475  * settings, respectively.  "max" is the maximum value of the volume knob
476  * (the balance knob is assumed to always go from 0 to 20.)
477  */
478 void
cd_volume(int vol,int bal,int max)479 cd_volume( int vol, int bal, int max )
480 {
481 	int	left, right;
482 
483 /*
484  * Set "left" and "right" to volume-slider values accounting for the
485  * balance setting.
486  *
487  * XXX - the maximum volume setting is assumed to be in the 20-30 range.
488  */
489 	if (bal < 9)
490 		right = vol - (9 - bal) * 2;
491 	else
492 		right = vol;
493 	if (bal > 11)
494 		left = vol - (bal - 11) * 2;
495 	else
496 		left = vol;
497 
498 	left = (left * 100 + max - 1) / max;
499 	right = (right * 100 + max - 1) / max;
500 	if (left > 100)
501 		left = 100;
502 	if (right > 100)
503 		right = 100;
504 
505 	(void) (drive.set_volume)(&drive, left, right);
506 } /* cd_volume() */
507 
508 #endif /* CLIF_VOL */
509 
510 
511 /*
512  * wm_cd_pause()
513  *
514  * Pause the CD, if it's in play mode.  If it's already paused, go back to
515  * play mode.
516  */
517 void
wm_cd_pause(void)518 wm_cd_pause( void )
519 {
520 	static int paused_pos;
521 
522 	if (cur_cdmode == WM_CDM_EJECTED)	/* do nothing if there's no CD! */
523 		return;
524 
525 	switch (cur_cdmode) {
526 	case WM_CDM_PLAYING:		/* playing */
527 		cur_cdmode = WM_CDM_PAUSED;
528 		(drive.pause)(&drive);
529                 paused_pos = cur_pos_rel;
530 		break;
531 
532 	case WM_CDM_PAUSED:		/* paused */
533 		cur_cdmode = WM_CDM_PLAYING;
534 /*		(drive.resume)(&drive); */
535 		if ((drive.resume)(&drive))
536 			wm_cd_play(cur_track, paused_pos,
537 				playlist[cur_listno-1].end);
538 	default: /* */
539 		break;
540 	}
541 } /* wm_cd_pause() */
542 
543 /*
544  * wm_cd_stop()
545  *
546  * Stop the CD if it's not already stopped.
547  */
548 void
wm_cd_stop(void)549 wm_cd_stop( void )
550 {
551 	if (cur_cdmode == WM_CDM_EJECTED)
552 		return;
553 
554 	if (cur_cdmode != WM_CDM_STOPPED)
555 	{
556 		cur_lasttrack = cur_firsttrack = -1;
557 		cur_cdmode = WM_CDM_STOPPED;
558 		(drive.stop)(&drive);
559 		cur_track = 1;
560 	}
561 } /* wm_cd_stop() */
562 
563 /*
564  * wm_cd_play_chunk(start, end)
565  *
566  * Play the CD from one position to another (both in frames.)
567  */
568 void
wm_cd_play_chunk(int start,int end,int realstart)569 wm_cd_play_chunk( int start, int end, int realstart )
570 {
571 	if (cur_cdmode == WM_CDM_EJECTED || cd == NULL)
572 		return;
573 
574 	end--;
575 	if (start >= end)
576 		start = end-1;
577 
578 	(drive.play)(&drive, start, end, realstart);
579 }
580 
581 /*
582  * wm_cd_play(starttrack, pos, endtrack)
583  *
584  * Start playing the CD or jump to a new position.  "pos" is in seconds,
585  * relative to start of track.
586  */
587 void
wm_cd_play(int start,int pos,int end)588 wm_cd_play( int start, int pos, int end )
589 {
590 	if (cur_cdmode == WM_CDM_EJECTED || cd == NULL)
591 		return;
592 
593 	cur_firsttrack = start;
594 	start--;
595 	end--;
596 	cur_lasttrack = end;
597 
598 	wm_cd_play_chunk(cd->trk[start].start + pos * 75, end >= cur_ntracks ?
599 		cur_cdlen * 75 : cd->trk[end].start - 1,
600 		cd->trk[start].start);
601 
602 	/* So we don't update the display with the old frame number */
603 	wm_cd_status();
604 	cur_frame = cd->trk[start].start + pos * 75;
605 	cur_track = cur_firsttrack;
606 	cur_cdmode = WM_CDM_PLAYING;
607 }
608 
609 /*
610  * Set the offset into the current track and play.  -1 means end of track
611  * (i.e., go to next track.)
612  */
613 void
wm_cd_play_from_pos(int pos)614 wm_cd_play_from_pos( int pos )
615 {
616 	if (pos == -1)
617 		if (cd)
618 			pos = cd->trk[cur_track - 1].length - 1;
619 	if (cur_cdmode == WM_CDM_PLAYING)
620 		wm_cd_play(cur_track, pos, playlist[cur_listno-1].end);
621 } /* wm_cd_play_from_pos() */
622 
623 /*
624  * Eject the current CD, if there is one, and set the mode to 5.
625  *
626  * Returns 0 on success, 1 if the CD couldn't be ejected, or 2 if the
627  * CD contains a mounted filesystem.
628  */
629 int
wm_cd_eject(void)630 wm_cd_eject( void )
631 {
632 	int	status;
633 
634 	status = (drive.eject)(&drive);
635 	if (status < 0)
636 	{
637 		if (status == -3)
638 		{
639 			return (2);
640 		} else {
641 			return (1);
642 		}
643 	}
644 
645 	if (exit_on_eject)
646 		exit(0);
647 
648 	cur_track = -1;
649 	cur_cdlen = cur_tracklen = 1;
650 	cur_pos_abs = cur_pos_rel = cur_frame = 0;
651 	cur_cdmode = WM_CDM_EJECTED;
652 
653 	return (0);
654 }
655 
wm_cd_closetray(void)656 int wm_cd_closetray(void)
657 {
658 	return((drive.closetray)(&drive) ? 0 : wm_cd_status()==2 ? 1 : 0);
659 } /* wm_cd_closetray() */
660 
661 /*
662  * find_trkind(track, index, start)
663  *
664  * Start playing at a particular track and index, optionally using a particular
665  * frame as a starting position.  Returns a frame number near the start of the
666  * index mark if successful, 0 if the track/index didn't exist.
667  *
668  * This is made significantly more tedious (though probably easier to port)
669  * by the fact that CDROMPLAYTRKIND doesn't work as advertised.  The routine
670  * does a binary search of the track, terminating when the interval gets to
671  * around 10 frames or when the next track is encountered, at which point
672  * it's a fair bet the index in question doesn't exist.
673  */
674 int
find_trkind(int track,int index,int start)675 find_trkind( int track, int index, int start )
676 {
677 	int	top = 0, bottom, current, interval, ret = 0, i;
678 
679 	if( cur_cdmode == WM_CDM_EJECTED || cd == NULL )
680 		return ( 0 ); /* WARNING: was nothing */
681 
682 	for (i = 0; i < cur_ntracks; i++)
683 		if (cd->trk[i].track == track)
684 			break;
685 	bottom = cd->trk[i].start;
686 
687 	for (; i < cur_ntracks; i++)
688 		if (cd->trk[i].track > track)
689 			break;
690 
691 	top = i == cur_ntracks ? (cd->length - 1) * 75 : cd->trk[i].start;
692 
693 	if (start > bottom && start < top)
694 		bottom = start;
695 
696 	current = (top + bottom) / 2;
697 	interval = (top - bottom) / 4;
698 
699 	do {
700 		wm_cd_play_chunk(current, current + 75, current);
701 
702 		if (wm_cd_status() != 1)
703 			return (0);
704 		while (cur_frame < current)
705 			if (wm_cd_status() != 1 || cur_cdmode != WM_CDM_PLAYING)
706 				return (0);
707 			else
708 				wm_susleep(1);
709 
710 		if (cd->trk[cur_track - 1].track > track)
711 			break;
712 
713 		if (cur_index >= index)
714 		{
715 			ret = current;
716 			current -= interval;
717 		}
718 		else
719 			current += interval;
720 		interval /= 2;
721 	} while (interval > 2);
722 
723 	return (ret);
724 } /* find_trkind() */
725 
726 /*
727  * Read the initial volume from the drive, if available.  Set cur_balance to
728  * the balance level (0-20, 10=centered) and return the proper setting for
729  * the volume knob.
730  *
731  * "max" is the maximum value of the volume knob.
732  */
733 int
wm_cd_read_initial_volume(int max)734 wm_cd_read_initial_volume( int max )
735 {
736 	int	left, right;
737 
738 	if ((drive.get_volume)(&drive, &left, &right) < 0 || left == -1)
739 		return (max);
740 
741 	left = (left * max + 99) / 100;
742 	right = (right * max + 99) / 100;
743 
744 	if (left < right)
745 	{
746 		wm_cd_cur_balance = (right - left) / 2 + 11;
747 		if (wm_cd_cur_balance > 20)
748 			wm_cd_cur_balance = 20;
749 
750 		return (right);
751 	}
752 	else if (left == right)
753 	{
754 		wm_cd_cur_balance = 10;
755 		return (left);
756 	}
757 	else
758 	{
759 		wm_cd_cur_balance = (right - left) / 2 + 9;
760 		if (wm_cd_cur_balance < 0)
761 			wm_cd_cur_balance = 0;
762 
763 		return (left);
764 	}
765 } /* wm_cd_read_initial_volume() */
766 
767 /*
768  * Prototype wm_drive structure, with generic functions.  The generic functions
769  * will be replaced with drive-specific functions as appropriate once the drive
770  * type has been sensed.
771  */
772 struct wm_drive generic_proto = {
773 	-1,			/* fd */
774 	"Generic\0",		/* vendor */
775 	"drive type\0     ",	/* model */
776 	"\0",			/* revision */
777 	NULL,			/* aux */
778 	NULL,			/* daux */
779 
780 	gen_init,		/* functions... */
781 	gen_get_trackcount,
782 	gen_get_cdlen,
783 	gen_get_trackinfo,
784 	gen_get_drive_status,
785 	gen_get_volume,
786 	gen_set_volume,
787 	gen_pause,
788 	gen_resume,
789 	gen_stop,
790 	gen_play,
791 	gen_eject,
792 	gen_closetray
793 };
794