/* MikMod sound library (c) 1998, 1999, 2000, 2001, 2002 Miodrag Vallat and others - see file AUTHORS for complete list. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /*============================================================================== $Id: drv_sun.c,v 1.3 2004/02/10 14:02:24 raph Exp $ Driver for output on the Sun audio device (/dev/audio). Also works under NetBSD and OpenBSD ==============================================================================*/ /* Written by Valtteri Vuorikoski NetBSD/OpenBSD code from Miodrag Vallat */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "mikmod_internals.h" #ifdef DRV_SUN #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_FCNTL_H #include #endif #include #include #include #ifdef HAVE_SYS_IOCTL_H #include #endif #include #ifdef HAVE_SUN_AUDIOIO_H #include #endif #ifdef HAVE_SYS_AUDIOIO_H #include #endif #ifdef SUNOS extern int ioctl(int, unsigned long, ...); extern int fputs(const char *, FILE *); #endif #define DEFAULT_FRAGSIZE 12 #if !defined __NetBSD__ && !defined __OpenBSD__ #ifdef HAVE_SUN_AUDIOIO_H #define SUNOS4 #else #define SOLARIS #endif #endif /* Sound device to open */ #ifdef SUNOS4 #define SOUNDDEVICE "/dev/sound" #else /* Solaris, *BSD */ #define SOUNDDEVICE "/dev/audio" #endif /* Solaris doesn't have these */ #ifdef SOLARIS #define AUDIO_ENCODING_SLINEAR AUDIO_ENCODING_LINEAR #define AUDIO_ENCODING_ULINEAR AUDIO_ENCODING_LINEAR8 #endif /* Compatibility defines, for old *BSD or SunOS systems */ #ifndef AUDIO_ENCODING_PCM16 #define AUDIO_ENCODING_PCM16 AUDIO_ENCODING_LINEAR #endif #ifndef AUDIO_ENCODING_PCM8 #define AUDIO_ENCODING_PCM8 AUDIO_ENCODING_LINEAR8 #endif #ifndef AUDIO_ENCODING_SLINEAR_LE #define AUDIO_ENCODING_SLINEAR_LE AUDIO_ENCODING_PCM16 #endif #ifndef AUDIO_ENCODING_ULINEAR_LE #define AUDIO_ENCODING_ULINEAR_LE AUDIO_ENCODING_PCM8 #endif #ifndef AUDIO_ENCODING_SLINEAR #if BYTE_ORDER == BIG_ENDIAN #define AUDIO_ENCODING_SLINEAR AUDIO_ENCODING_SLINEAR_BE #else #define AUDIO_ENCODING_SLINEAR AUDIO_ENCODING_SLINEAR_LE #endif #endif #ifndef AUDIO_ENCODING_ULINEAR #if BYTE_ORDER == BIG_ENDIAN #define AUDIO_ENCODING_ULINEAR AUDIO_ENCODING_ULINEAR_BE #else #define AUDIO_ENCODING_ULINEAR AUDIO_ENCODING_ULINEAR_LE #endif #endif /* Compatibility defines, for old *BSD systems */ #ifndef AUDIO_SPEAKER #define AUDIO_SPEAKER 0x01 #endif #ifndef AUDIO_HEADPHONE #define AUDIO_HEADPHONE 0x02 #endif /* ``normalize'' AUDIO_ENCODING_xxx values for comparison */ static int normalize(int encoding) { switch (encoding) { #ifdef AUDIO_ENCODING_LINEAR case AUDIO_ENCODING_LINEAR: return AUDIO_ENCODING_PCM16; #endif #ifdef AUDIO_ENCODING_LINEAR8 case AUDIO_ENCODING_LINEAR8: return AUDIO_ENCODING_PCM8; #endif #if BYTE_ORDER == BIG_ENDIAN #ifdef AUDIO_ENCODING_SLINEAR_BE case AUDIO_ENCODING_SLINEAR: return AUDIO_ENCODING_SLINEAR_BE; #endif #ifdef AUDIO_ENCODING_ULINEAR_BE case AUDIO_ENCODING_ULINEAR: return AUDIO_ENCODING_ULINEAR_BE; #endif #else #ifdef AUDIO_ENCODING_SLINEAR_LE case AUDIO_ENCODING_SLINEAR: return AUDIO_ENCODING_SLINEAR_LE; #endif #ifdef AUDIO_ENCODING_ULINEAR_LE case AUDIO_ENCODING_ULINEAR: return AUDIO_ENCODING_ULINEAR_LE; #endif #endif default: return encoding; } } static int sndfd = -1; static unsigned int port = 0; static int play_encoding; static int play_precision; static int fragsize = 1 << DEFAULT_FRAGSIZE; static SBYTE *audiobuffer = NULL; static void Sun_CommandLine(CHAR *cmdline) { CHAR *ptr; if ((ptr = MD_GetAtom("buffer", cmdline, 0))) { int buf = atoi(ptr); if (buf >= 7 && buf <= 17) fragsize = 1 << buf; free(ptr); } if ((ptr = MD_GetAtom("headphone", cmdline, 1))) { port = AUDIO_HEADPHONE; free(ptr); } else if ((ptr = MD_GetAtom("speaker", cmdline, 1))) { port = AUDIO_SPEAKER; free(ptr); } } static BOOL Sun_IsThere(void) { if (getenv("AUDIODEV")) return (access(getenv("AUDIODEV"), W_OK) == 0); else { if (access(SOUNDDEVICE, W_OK) == 0) return 1; #if defined __NetBSD__ || defined __OpenBSD__ /* old OpenBSD/sparc installation program creates /dev/audio0 but no /dev/audio. Didn't check NetBSD behaviour */ if (access(SOUNDDEVICE "0", W_OK) == 0) return 1; #endif } return 0; } static BOOL Sun_Init(void) { int play_stereo, play_rate; #ifdef SUNOS4 int audiotype; #else audio_device_t audiotype; #endif struct audio_info audioinfo; if (getenv("AUDIODEV")) sndfd = open(getenv("AUDIODEV"), O_WRONLY); else { sndfd = open(SOUNDDEVICE, O_WRONLY); #if defined __NetBSD__ || defined __OpenBSD__ if (sndfd < 0) sndfd = open(SOUNDDEVICE "0", O_WRONLY); #endif } if (sndfd < 0) { _mm_errno = MMERR_OPENING_AUDIO; return 1; } if (!(audiobuffer = (SBYTE *)_mm_malloc(fragsize))) return 1; play_precision = (md_mode & DMODE_16BITS) ? 16 : 8; play_stereo = (md_mode & DMODE_STEREO) ? 2 : 1; play_rate = md_mixfreq; /* attempt to guess the encoding */ play_encoding = -1; if (ioctl(sndfd, AUDIO_GETDEV, &audiotype) < 0) { #ifdef MIKMOD_DEBUG fputs("\rSun driver warning: could not determine audio device type\n", stderr); #endif } else { #if defined SUNOS4 /* SunOS 4 */ switch (audiotype) { case AUDIO_DEV_AMD: /* AMD 79C30 */ /* 8bit mono ulaw 8kHz */ play_rate = md_mixfreq = 8000; md_mode &= ~(DMODE_STEREO | DMODE_16BITS); play_precision = 8; play_stereo = 1; play_encoding = AUDIO_ENCODING_ULAW; break; case AUDIO_DEV_SPEAKERBOX: case AUDIO_DEV_CODEC: /* CS 4231 or DBRI or speaker box */ /* 16bit mono/stereo linear 8kHz - 48kHz */ if (play_precision == 16) play_encoding = AUDIO_ENCODING_LINEAR; /* 8bit mono ulaw 8kHz - 48kHz */ else if (play_precision == 8) { md_mode &= ~(DMODE_STEREO); play_stereo = 1; play_encoding = AUDIO_ENCODING_ULAW; } else { _mm_errno = MMERR_SUN_INIT; return 1; } break; } #elif defined SOLARIS /* Solaris */ if (!strcmp(audiotype.name, "SUNW,am79c30")) { /* AMD 79C30 */ /* 8bit mono ulaw 8kHz */ play_rate = md_mixfreq = 8000; md_mode &= ~(DMODE_STEREO | DMODE_16BITS); play_precision = 8; play_stereo = 1; play_encoding = AUDIO_ENCODING_ULAW; } else if ((!strcmp(audiotype.name, "SUNW,CS4231")) || (!strcmp(audiotype.name, "SUNW,dbri")) || (!strcmp(audiotype.name, "speakerbox"))) { /* CS 4231 or DBRI or speaker box */ /* 16bit mono/stereo linear 8kHz - 48kHz */ if (play_precision == 16) play_encoding = AUDIO_ENCODING_LINEAR; /* 8bit mono ulaw 8kHz - 48kHz */ else if (play_precision == 8) { md_mode &= ~(DMODE_STEREO); play_stereo = 1; play_encoding = AUDIO_ENCODING_ULAW; } else { _mm_errno = MMERR_SUN_INIT; return 1; } } #else /* NetBSD, OpenBSD */ if (!strcmp(audiotype.name, "amd7930")) { /* AMD 79C30 */ /* 8bit mono ulaw 8kHz */ play_rate = md_mixfreq = 8000; md_mode &= ~(DMODE_STEREO | DMODE_16BITS); play_precision = 8; play_stereo = 1; play_encoding = AUDIO_ENCODING_ULAW; } if ((!strcmp(audiotype.name, "Am78C201")) || (!strcmp(audiotype.name, "UltraSound")) ) { /* Gravis UltraSound, AMD Interwave and compatible cards */ /* 16bit stereo linear 44kHz */ play_rate = md_mixfreq = 44100; md_mode |= (DMODE_STEREO | DMODE_16BITS); play_precision = 16; play_stereo = 2; play_encoding = AUDIO_ENCODING_SLINEAR; } #endif } /* Sound devices which were not handled above don't have specific limitations, so try and guess optimal settings */ if (play_encoding == -1) { if ((play_precision == 8) && (play_stereo == 1) && (play_rate <= 8000)) play_encoding = AUDIO_ENCODING_ULAW; else #ifdef SUNOS4 play_encoding = AUDIO_ENCODING_LINEAR; #else play_encoding = (play_precision == 16) ? AUDIO_ENCODING_SLINEAR : AUDIO_ENCODING_ULINEAR; #endif } /* get current audio settings if we want to keep the playback output port */ if (!port) { AUDIO_INITINFO(&audioinfo); if (ioctl(sndfd, AUDIO_GETINFO, &audioinfo) < 0) { _mm_errno = MMERR_SUN_INIT; return 1; } port = audioinfo.play.port; } AUDIO_INITINFO(&audioinfo); audioinfo.play.precision = play_precision; audioinfo.play.channels = play_stereo; audioinfo.play.sample_rate = play_rate; audioinfo.play.encoding = play_encoding; audioinfo.play.port = port; #if defined __NetBSD__ || defined __OpenBSD__ #if defined AUMODE_PLAY_ALL audioinfo.mode = AUMODE_PLAY | AUMODE_PLAY_ALL; #else audioinfo.mode = AUMODE_PLAY; #endif #endif if (ioctl(sndfd, AUDIO_SETINFO, &audioinfo) < 0) { _mm_errno = MMERR_SUN_INIT; return 1; } /* check if our changes were accepted */ if (ioctl(sndfd, AUDIO_GETINFO, &audioinfo) < 0) { _mm_errno = MMERR_SUN_INIT; return 1; } if ((audioinfo.play.precision != play_precision) || (audioinfo.play.channels != play_stereo) || (normalize(audioinfo.play.encoding) != normalize(play_encoding))) { _mm_errno = MMERR_SUN_INIT; return 1; } if (audioinfo.play.sample_rate != play_rate) { /* Accept a shift inferior to 5% of the expected rate */ int delta = audioinfo.play.sample_rate - play_rate; if (delta < 0) delta = -delta; if (delta * 20 > play_rate) { _mm_errno = MMERR_SUN_INIT; return 1; } /* Align to what the card gave us */ md_mixfreq = audioinfo.play.sample_rate; } return VC_Init(); } static void Sun_Exit(void) { VC_Exit(); _mm_free(audiobuffer); if (sndfd >= 0) { close(sndfd); sndfd = -1; } } static void Sun_Update(void) { int done; done = VC_WriteBytes((char *)audiobuffer, fragsize); if (play_encoding == AUDIO_ENCODING_ULAW) unsignedtoulaw(audiobuffer, done); write(sndfd, audiobuffer, done); } static void Sun_Pause(void) { int done; done = VC_SilenceBytes((char *)audiobuffer, fragsize); write(sndfd, audiobuffer, done); } static BOOL Sun_PlayStart(void) { struct audio_info audioinfo; AUDIO_INITINFO(&audioinfo); audioinfo.play.pause = 0; if (ioctl(sndfd, AUDIO_SETINFO, &audioinfo) < 0) return 1; return VC_PlayStart(); } static void Sun_PlayStop(void) { struct audio_info audioinfo; VC_PlayStop(); if (ioctl(sndfd, AUDIO_DRAIN) < 0) return; AUDIO_INITINFO(&audioinfo); audioinfo.play.pause = 1; ioctl(sndfd, AUDIO_SETINFO, &audioinfo); } MIKMODAPI MDRIVER drv_sun = { NULL, #if defined __OpenBSD__ "OpenBSD Audio", "OpenBSD audio driver v1.0", #elif defined __NetBSD__ "NetBSD Audio", "NetBSD audio driver v1.0", #elif defined SUNOS4 "SunOS Audio", "SunOS audio driver v1.4", #elif defined SOLARIS "Solaris Audio", "Solaris audio driver v1.4", #endif 0, 255, "audio", "buffer:r:7,17,12:Audio buffer log2 size\n" #if defined(SUNOS) || defined(SOLARIS) "headphone:b:0:Use headphone\n" "speaker:b:0:Use speaker\n" #endif , Sun_CommandLine, Sun_IsThere, VC_SampleLoad, VC_SampleUnload, VC_SampleSpace, VC_SampleLength, Sun_Init, Sun_Exit, NULL, VC_SetNumVoices, Sun_PlayStart, Sun_PlayStop, Sun_Update, Sun_Pause, VC_VoiceSetVolume, VC_VoiceGetVolume, VC_VoiceSetFrequency, VC_VoiceGetFrequency, VC_VoiceSetPanning, VC_VoiceGetPanning, VC_VoicePlay, VC_VoiceStop, VC_VoiceStopped, VC_VoiceGetPosition, VC_VoiceRealVolume }; #else MISSING(drv_sun); #endif /* ex:set ts=4: */