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