1 /*	MikMod sound library
2 	(c) 1998, 1999, 2000, 2001, 2002 Miodrag Vallat and others - see file
3 	AUTHORS for complete list.
4 
5 	This library is free software; you can redistribute it and/or modify
6 	it under the terms of the GNU Library General Public License as
7 	published by the Free Software Foundation; either version 2 of
8 	the License, or (at your option) any later version.
9 
10 	This program is distributed in the hope that it will be useful,
11 	but WITHOUT ANY WARRANTY; without even the implied warranty of
12 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 	GNU Library General Public License for more details.
14 
15 	You should have received a copy of the GNU Library General Public
16 	License along with this library; if not, write to the Free Software
17 	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
18 	02111-1307, USA.
19 */
20 
21 /*==============================================================================
22 
23   $Id$
24 
25   Driver for output on the Sun audio device (/dev/audio).
26   Also works under NetBSD and OpenBSD
27 
28 ==============================================================================*/
29 
30 /*
31 
32 	Written by Valtteri Vuorikoski <vuori@sci.fi>
33 	NetBSD/OpenBSD code from Miodrag Vallat <miod@mikmod.org>
34 
35 */
36 
37 #ifdef HAVE_CONFIG_H
38 #include "config.h"
39 #endif
40 
41 #include "mikmod_internals.h"
42 
43 #ifdef DRV_SUN
44 
45 #ifdef HAVE_UNISTD_H
46 #include <unistd.h>
47 #endif
48 #ifdef HAVE_FCNTL_H
49 #include <fcntl.h>
50 #endif
51 #include <stdio.h>
52 #include <stdlib.h>
53 #include <string.h>
54 #ifdef HAVE_SYS_IOCTL_H
55 #include <sys/ioctl.h>
56 #endif
57 #include <sys/types.h>
58 
59 #ifdef HAVE_SUN_AUDIOIO_H
60 #include <sun/audioio.h>
61 #endif
62 #ifdef HAVE_SYS_AUDIOIO_H
63 #include <sys/audioio.h>
64 #endif
65 
66 #ifdef SUNOS
67 extern int ioctl(int, unsigned long, ...);
68 extern int fputs(const char *, FILE *);
69 #endif
70 
71 #define DEFAULT_FRAGSIZE 12
72 
73 #if !defined __NetBSD__  && !defined __OpenBSD__
74 #ifdef HAVE_SUN_AUDIOIO_H
75 #define SUNOS4
76 #else
77 #define SOLARIS
78 #endif
79 #endif
80 
81 /* Sound device to open */
82 #ifdef SUNOS4
83 #define SOUNDDEVICE "/dev/sound"
84 #else /* Solaris, *BSD */
85 #define SOUNDDEVICE "/dev/audio"
86 #endif
87 
88 /* Solaris doesn't have these */
89 #ifdef SOLARIS
90 #define AUDIO_ENCODING_SLINEAR AUDIO_ENCODING_LINEAR
91 #define AUDIO_ENCODING_ULINEAR AUDIO_ENCODING_LINEAR8
92 #endif
93 
94 /* Compatibility defines, for old *BSD or SunOS systems */
95 #ifndef AUDIO_ENCODING_PCM16
96 #define AUDIO_ENCODING_PCM16 AUDIO_ENCODING_LINEAR
97 #endif
98 #ifndef AUDIO_ENCODING_PCM8
99 #define AUDIO_ENCODING_PCM8 AUDIO_ENCODING_LINEAR8
100 #endif
101 #ifndef AUDIO_ENCODING_SLINEAR_LE
102 #define AUDIO_ENCODING_SLINEAR_LE AUDIO_ENCODING_PCM16
103 #endif
104 #ifndef AUDIO_ENCODING_ULINEAR_LE
105 #define AUDIO_ENCODING_ULINEAR_LE AUDIO_ENCODING_PCM8
106 #endif
107 #ifndef AUDIO_ENCODING_SLINEAR
108 #if BYTE_ORDER == BIG_ENDIAN
109 #define AUDIO_ENCODING_SLINEAR AUDIO_ENCODING_SLINEAR_BE
110 #else
111 #define AUDIO_ENCODING_SLINEAR AUDIO_ENCODING_SLINEAR_LE
112 #endif
113 #endif
114 #ifndef AUDIO_ENCODING_ULINEAR
115 #if BYTE_ORDER == BIG_ENDIAN
116 #define AUDIO_ENCODING_ULINEAR AUDIO_ENCODING_ULINEAR_BE
117 #else
118 #define AUDIO_ENCODING_ULINEAR AUDIO_ENCODING_ULINEAR_LE
119 #endif
120 #endif
121 
122 /* Compatibility defines, for old *BSD systems */
123 #ifndef AUDIO_SPEAKER
124 #define AUDIO_SPEAKER 0x01
125 #endif
126 #ifndef AUDIO_HEADPHONE
127 #define AUDIO_HEADPHONE 0x02
128 #endif
129 
130 /* ``normalize'' AUDIO_ENCODING_xxx values for comparison */
normalize(int encoding)131 static int normalize(int encoding)
132 {
133 	switch (encoding) {
134 #ifdef AUDIO_ENCODING_LINEAR
135 	case AUDIO_ENCODING_LINEAR:
136 			return AUDIO_ENCODING_PCM16;
137 #endif
138 #ifdef AUDIO_ENCODING_LINEAR8
139 	case AUDIO_ENCODING_LINEAR8:
140 			return AUDIO_ENCODING_PCM8;
141 #endif
142 #if BYTE_ORDER == BIG_ENDIAN
143 #ifdef AUDIO_ENCODING_SLINEAR_BE
144 	case AUDIO_ENCODING_SLINEAR:
145 		return AUDIO_ENCODING_SLINEAR_BE;
146 #endif
147 #ifdef AUDIO_ENCODING_ULINEAR_BE
148 	case AUDIO_ENCODING_ULINEAR:
149 		return AUDIO_ENCODING_ULINEAR_BE;
150 #endif
151 #else
152 #ifdef AUDIO_ENCODING_SLINEAR_LE
153 	case AUDIO_ENCODING_SLINEAR:
154 		return AUDIO_ENCODING_SLINEAR_LE;
155 #endif
156 #ifdef AUDIO_ENCODING_ULINEAR_LE
157 	case AUDIO_ENCODING_ULINEAR:
158 		return AUDIO_ENCODING_ULINEAR_LE;
159 #endif
160 #endif
161 	default:
162 		return encoding;
163 	}
164 }
165 
166 static int sndfd = -1;
167 static unsigned int port = 0;
168 static int play_encoding;
169 static int play_precision;
170 static int fragsize = 1 << DEFAULT_FRAGSIZE;
171 static SBYTE *audiobuffer = NULL;
172 
Sun_CommandLine(const CHAR * cmdline)173 static void Sun_CommandLine(const CHAR *cmdline)
174 {
175 	CHAR *ptr;
176 
177 	if ((ptr = MD_GetAtom("buffer", cmdline, 0)) != NULL) {
178 		int buf = atoi(ptr);
179 
180 		if (buf >= 7 && buf <= 17)
181 			fragsize = 1 << buf;
182 
183 		MikMod_free(ptr);
184 	}
185 
186 	if ((ptr = MD_GetAtom("headphone", cmdline, 1)) != NULL) {
187 		port = AUDIO_HEADPHONE;
188 		MikMod_free(ptr);
189 	} else if ((ptr = MD_GetAtom("speaker", cmdline, 1)) != NULL) {
190 		port = AUDIO_SPEAKER;
191 		MikMod_free(ptr);
192 	}
193 }
194 
Sun_IsThere(void)195 static BOOL Sun_IsThere(void)
196 {
197 	if (getenv("AUDIODEV"))
198 		return (access(getenv("AUDIODEV"), W_OK) == 0);
199 	else {
200 		if (access(SOUNDDEVICE, W_OK) == 0)
201 			return 1;
202 #if defined __NetBSD__ || defined __OpenBSD__
203 		/* old OpenBSD/sparc installation program creates /dev/audio0 but no
204 		   /dev/audio. Didn't check NetBSD behaviour */
205 		if (access(SOUNDDEVICE "0", W_OK) == 0)
206 			return 1;
207 #endif
208 	}
209 	return 0;
210 }
211 
Sun_Init(void)212 static int Sun_Init(void)
213 {
214 	int play_stereo, play_rate;
215 #ifdef SUNOS4
216 	int audiotype;
217 #else
218 	audio_device_t audiotype;
219 #endif
220 	struct audio_info audioinfo;
221 
222 	if (getenv("AUDIODEV"))
223 		sndfd = open(getenv("AUDIODEV"), O_WRONLY);
224 	else {
225 		sndfd = open(SOUNDDEVICE, O_WRONLY);
226 #if defined __NetBSD__ || defined __OpenBSD__
227 		if (sndfd < 0)
228 			sndfd = open(SOUNDDEVICE "0", O_WRONLY);
229 #endif
230 	}
231 	if (sndfd < 0) {
232 		_mm_errno = MMERR_OPENING_AUDIO;
233 		return 1;
234 	}
235 
236 	if (!(audiobuffer = (SBYTE *)MikMod_malloc(fragsize)))
237 		return 1;
238 
239 	play_precision = (md_mode & DMODE_16BITS) ? 16 : 8;
240 	play_stereo = (md_mode & DMODE_STEREO) ? 2 : 1;
241 	play_rate = md_mixfreq;
242 	/* attempt to guess the encoding */
243 	play_encoding = -1;
244 
245 	if (ioctl(sndfd, AUDIO_GETDEV, &audiotype) < 0) {
246 #ifdef MIKMOD_DEBUG
247 		fputs("\rSun driver warning: could not determine audio device type\n",
248 			  stderr);
249 #endif
250 	} else {
251 #if defined SUNOS4				/* SunOS 4 */
252 		switch (audiotype) {
253 		  case AUDIO_DEV_AMD:
254 			/* AMD 79C30 */
255 			/* 8bit mono ulaw 8kHz */
256 		  	play_rate = md_mixfreq = 8000;
257 			md_mode &= ~(DMODE_STEREO | DMODE_16BITS);
258 			play_precision = 8;
259 			play_stereo = 1;
260 			play_encoding = AUDIO_ENCODING_ULAW;
261 			break;
262 		  case AUDIO_DEV_SPEAKERBOX:
263 		  case AUDIO_DEV_CODEC:
264 			/* CS 4231 or DBRI or speaker box */
265 			/* 16bit mono/stereo linear 8kHz - 48kHz */
266 			if (play_precision == 16)
267 				play_encoding = AUDIO_ENCODING_LINEAR;
268 			/* 8bit mono ulaw 8kHz - 48kHz */
269 			else if (play_precision == 8) {
270 				md_mode &= ~(DMODE_STEREO);
271 				play_stereo = 1;
272 				play_encoding = AUDIO_ENCODING_ULAW;
273 			} else {
274 				_mm_errno = MMERR_SUN_INIT;
275 				return 1;
276 			}
277 			break;
278 		}
279 #elif defined SOLARIS			/* Solaris */
280 		if (!strcmp(audiotype.name, "SUNW,am79c30")) {
281 			/* AMD 79C30 */
282 			/* 8bit mono ulaw 8kHz */
283 		  	play_rate = md_mixfreq = 8000;
284 			md_mode &= ~(DMODE_STEREO | DMODE_16BITS);
285 			play_precision = 8;
286 			play_stereo = 1;
287 			play_encoding = AUDIO_ENCODING_ULAW;
288 		} else
289 			if ((!strcmp(audiotype.name, "SUNW,CS4231")) ||
290 				(!strcmp(audiotype.name, "SUNW,dbri")) ||
291 				(!strcmp(audiotype.name, "speakerbox"))) {
292 			/* CS 4231 or DBRI or speaker box */
293 			/* 16bit mono/stereo linear 8kHz - 48kHz */
294 			if (play_precision == 16)
295 				play_encoding = AUDIO_ENCODING_LINEAR;
296 			/* 8bit mono ulaw 8kHz - 48kHz */
297 			else if (play_precision == 8) {
298 				md_mode &= ~(DMODE_STEREO);
299 				play_stereo = 1;
300 				play_encoding = AUDIO_ENCODING_ULAW;
301 			} else {
302 				_mm_errno = MMERR_SUN_INIT;
303 				return 1;
304 			}
305 		}
306 #else /* NetBSD, OpenBSD */
307 		if (!strcmp(audiotype.name, "amd7930")) {
308 			/* AMD 79C30 */
309 			/* 8bit mono ulaw 8kHz */
310 		  	play_rate = md_mixfreq = 8000;
311 			md_mode &= ~(DMODE_STEREO | DMODE_16BITS);
312 			play_precision = 8;
313 			play_stereo = 1;
314 			play_encoding = AUDIO_ENCODING_ULAW;
315 		}
316 		if ((!strcmp(audiotype.name, "Am78C201")) ||
317 			(!strcmp(audiotype.name, "UltraSound"))
318 		   ) {
319 			/* Gravis UltraSound, AMD Interwave and compatible cards */
320 			/* 16bit stereo linear 44kHz */
321 		  	play_rate = md_mixfreq = 44100;
322 			md_mode |= (DMODE_STEREO | DMODE_16BITS);
323 			play_precision = 16;
324 			play_stereo = 2;
325 			play_encoding = AUDIO_ENCODING_SLINEAR;
326 		}
327 #endif
328 	}
329 
330 	/* Sound devices which were not handled above don't have specific
331 	   limitations, so try and guess optimal settings */
332 	if (play_encoding == -1) {
333 		if ((play_precision == 8) && (play_stereo == 1) &&
334 			(play_rate <= 8000)) play_encoding = AUDIO_ENCODING_ULAW;
335 		else
336 #ifdef SUNOS4
337 			play_encoding = AUDIO_ENCODING_LINEAR;
338 #else
339 			play_encoding = (play_precision == 16) ?
340 				 AUDIO_ENCODING_SLINEAR : AUDIO_ENCODING_ULINEAR;
341 #endif
342 	}
343 
344 	/* get current audio settings if we want to keep the playback output
345 	   port */
346 	if (!port) {
347 		AUDIO_INITINFO(&audioinfo);
348 		if (ioctl(sndfd, AUDIO_GETINFO, &audioinfo) < 0) {
349 			_mm_errno = MMERR_SUN_INIT;
350 			return 1;
351 		}
352 		port = audioinfo.play.port;
353 	}
354 
355 	AUDIO_INITINFO(&audioinfo);
356 	audioinfo.play.precision = play_precision;
357 	audioinfo.play.channels = play_stereo;
358 	audioinfo.play.sample_rate = play_rate;
359 	audioinfo.play.encoding = play_encoding;
360 	audioinfo.play.port = port;
361 #if defined __NetBSD__ || defined __OpenBSD__
362 #if defined AUMODE_PLAY_ALL
363 	audioinfo.mode = AUMODE_PLAY | AUMODE_PLAY_ALL;
364 #else
365 	audioinfo.mode = AUMODE_PLAY;
366 #endif
367 #endif
368 
369 	if (ioctl(sndfd, AUDIO_SETINFO, &audioinfo) < 0) {
370 		_mm_errno = MMERR_SUN_INIT;
371 		return 1;
372 	}
373 
374 	/* check if our changes were accepted */
375 	if (ioctl(sndfd, AUDIO_GETINFO, &audioinfo) < 0) {
376 		_mm_errno = MMERR_SUN_INIT;
377 		return 1;
378 	}
379 	if ((audioinfo.play.precision != play_precision) ||
380 		(audioinfo.play.channels != play_stereo) ||
381 		(normalize(audioinfo.play.encoding) != normalize(play_encoding))) {
382 		_mm_errno = MMERR_SUN_INIT;
383 		return 1;
384 	}
385 
386 	if (audioinfo.play.sample_rate != play_rate) {
387 		/* Accept a shift inferior to 5% of the expected rate */
388 		int delta = audioinfo.play.sample_rate - play_rate;
389 
390 		if (delta < 0)
391 			delta = -delta;
392 
393 		if (delta * 20 > play_rate) {
394 			_mm_errno = MMERR_SUN_INIT;
395 			return 1;
396 		}
397 		/* Align to what the card gave us */
398 		md_mixfreq = audioinfo.play.sample_rate;
399 	}
400 
401 	return VC_Init();
402 }
403 
Sun_Exit(void)404 static void Sun_Exit(void)
405 {
406 	VC_Exit();
407 	MikMod_free(audiobuffer);
408 	audiobuffer = NULL;
409 	if (sndfd >= 0) {
410 		close(sndfd);
411 		sndfd = -1;
412 	}
413 }
414 
Sun_Update(void)415 static void Sun_Update(void)
416 {
417 	int done;
418 
419 	done = VC_WriteBytes((char *)audiobuffer, fragsize);
420 	if (play_encoding == AUDIO_ENCODING_ULAW)
421 		unsignedtoulaw(audiobuffer, done);
422 	write(sndfd, audiobuffer, done);
423 }
424 
Sun_Pause(void)425 static void Sun_Pause(void)
426 {
427 	int done;
428 
429 	done = VC_SilenceBytes((char *)audiobuffer, fragsize);
430 	write(sndfd, audiobuffer, done);
431 }
432 
Sun_PlayStart(void)433 static int Sun_PlayStart(void)
434 {
435 	struct audio_info audioinfo;
436 
437 	AUDIO_INITINFO(&audioinfo);
438 	audioinfo.play.pause = 0;
439 	if (ioctl(sndfd, AUDIO_SETINFO, &audioinfo) < 0)
440 		return 1;
441 
442 	return VC_PlayStart();
443 }
444 
Sun_PlayStop(void)445 static void Sun_PlayStop(void)
446 {
447 	struct audio_info audioinfo;
448 
449 	VC_PlayStop();
450 
451 	if (ioctl(sndfd, AUDIO_DRAIN) < 0)
452 		return;
453 	AUDIO_INITINFO(&audioinfo);
454 	audioinfo.play.pause = 1;
455 	ioctl(sndfd, AUDIO_SETINFO, &audioinfo);
456 }
457 
458 MIKMODAPI MDRIVER drv_sun = {
459 	NULL,
460 #if defined __OpenBSD__
461 	"OpenBSD Audio",
462 	"OpenBSD audio driver v1.0",
463 #elif defined __NetBSD__
464 	"NetBSD Audio",
465 	"NetBSD audio driver v1.0",
466 #elif defined SUNOS4
467 	"SunOS Audio",
468 	"SunOS audio driver v1.4",
469 #elif defined SOLARIS
470 	"Solaris Audio",
471 	"Solaris audio driver v1.4",
472 #endif
473 	0, 255,
474 	"audio",
475         "buffer:r:7,17,12:Audio buffer log2 size\n"
476 #if defined(SUNOS) || defined(SOLARIS)
477         "headphone:b:0:Use headphone\n"
478         "speaker:b:0:Use speaker\n"
479 #endif
480 	,
481 
482 	Sun_CommandLine,
483 	Sun_IsThere,
484 	VC_SampleLoad,
485 	VC_SampleUnload,
486 	VC_SampleSpace,
487 	VC_SampleLength,
488 	Sun_Init,
489 	Sun_Exit,
490 	NULL,
491 	VC_SetNumVoices,
492 	Sun_PlayStart,
493 	Sun_PlayStop,
494 	Sun_Update,
495 	Sun_Pause,
496 	VC_VoiceSetVolume,
497 	VC_VoiceGetVolume,
498 	VC_VoiceSetFrequency,
499 	VC_VoiceGetFrequency,
500 	VC_VoiceSetPanning,
501 	VC_VoiceGetPanning,
502 	VC_VoicePlay,
503 	VC_VoiceStop,
504 	VC_VoiceStopped,
505 	VC_VoiceGetPosition,
506 	VC_VoiceRealVolume
507 };
508 
509 #else
510 
511 MISSING(drv_sun);
512 
513 #endif
514 
515 /* ex:set ts=4: */
516