1 /*
2     TiMidity++ -- MIDI to WAVE converter and player
3     Copyright (C) 1999-2002 Masanao Izumo <mo@goice.co.jp>
4     Copyright (C) 1995 Tuukka Toivonen <tt@cgs.fi>
5 
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10 
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15 
16     You should have received a copy of the GNU General Public License
17     along with this program; if not, write to the Free Software
18     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19 
20 
21     sun_a.c - Functions to play sound on a Sun and NetBSD /dev/audio.
22 				Written by Masanao Izumo <mo@goice.co.jp>
23 */
24 
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif /* HAVE_CONFIG_H */
28 #include <stdio.h>
29 #include <stdlib.h>
30 #ifndef NO_STRING_H
31 #include <string.h>
32 #endif
33 #include <fcntl.h>
34 #include <sys/ioctl.h>
35 #include <sys/types.h>
36 #include <sys/filio.h>
37 #include <sys/stat.h>
38 #include <signal.h>
39 #include <unistd.h>
40 
41 #ifdef HAVE_STROPTS_H
42 #include <stropts.h>
43 #endif /* HAVE_STROPTS_H */
44 
45 #if defined(HAVE_SYS_AUDIOIO_H)
46 #include <sys/audioio.h>
47 #elif defined(HAVE_SUN_AUDIOIO_H)
48 #include <sun/audioio.h>
49 #endif
50 
51 #include "timidity.h"
52 #include "common.h"
53 #include "aenc.h"
54 #include "output.h"
55 #include "controls.h"
56 
57 #if defined(__NetBSD__) || defined(__OpenBSD__)
58 #ifdef LITTLE_ENDIAN
59 #define AUDIO_LINEAR_TAG	AUDIO_ENCODING_SLINEAR_LE
60 #else
61 #define AUDIO_LINEAR_TAG	AUDIO_ENCODING_SLINEAR_BE
62 #endif
63 #else /* Sun */
64 #define AUDIO_LINEAR_TAG	AUDIO_ENCODING_LINEAR
65 #endif
66 
67 #ifdef LITTLE_ENDIAN
68 #define SUNAUDIO_AENC_SIGWORD	AENC_SIGWORDL
69 #else
70 #define SUNAUDIO_AENC_SIGWORD	AENC_SIGWORDB
71 #endif
72 
73 #ifndef __NetBSD__
74 #define AUDIO_DEV    "/dev/audio"
75 #else
76 #define AUDIO_DEV    "/dev/sound"
77 #endif
78 #define AUDIO_CTLDEV "/dev/audioctl"
79 
80 
81 static int open_output(void); /* 0=success, 1=warning, -1=fatal error */
82 static void close_output(void);
83 static int output_data(char *buf, int32 bytes);
84 static int acntl(int request, void *arg);
85 static int output_counter, play_samples_offset;
86 
87 /* export the playback mode */
88 #define dpm sun_play_mode
89 PlayMode dpm = {
90     DEFAULT_RATE, PE_16BIT|PE_SIGNED,
91     PF_PCM_STREAM|PF_CAN_TRACE,
92     -1,
93     {0,0,0,0,0}, /* no extra parameters so far */
94 
95 #if defined(sun)
96     "Sun audio device",
97 #elif defined(__NetBSD__)
98     "NetBSD audio device",
99 #elif defined(__OpenBSD__)
100     "OpenBSD audio device",
101 #else
102     AUDIO_DEV,
103 #endif
104 
105     'd',
106     AUDIO_DEV,
107     open_output,
108     close_output,
109     output_data,
110     acntl
111 };
112 static int audioctl_fd = -1;
113 
sun_audio_encoding(int enc)114 static int sun_audio_encoding(int enc)
115 {
116     if(enc & PE_ULAW)
117 	return AUDIO_ENCODING_ULAW;
118     if(enc & PE_ALAW)
119 	return AUDIO_ENCODING_ALAW;
120 #ifdef AUDIO_ENCODING_LINEAR8
121     if(!(enc & PE_16BIT))
122 	return AUDIO_ENCODING_LINEAR8;
123 #else
124     if(!(enc & PE_16BIT))
125 	return 105;
126 #endif /* AUDIO_ENCODING_LINEAR8 */
127     return AUDIO_LINEAR_TAG;
128 }
129 
sun_audio_getinfo(audio_info_t * auinfo)130 static int sun_audio_getinfo(audio_info_t *auinfo)
131 {
132     AUDIO_INITINFO(auinfo);
133     return ioctl(audioctl_fd, AUDIO_GETINFO, auinfo);
134 }
135 
sun_audio_setinfo(audio_info_t * auinfo)136 static int sun_audio_setinfo(audio_info_t *auinfo)
137 {
138     return ioctl(dpm.fd, AUDIO_SETINFO, auinfo);
139 }
140 
open_output(void)141 static int open_output(void)
142 {
143     int include_enc = 0, exclude_enc = PE_BYTESWAP;
144     struct stat sb;
145     audio_info_t auinfo;
146     char *audio_dev, *audio_ctl_dev, *tmp_audio;
147 
148 
149     /* See if the AUDIODEV environment variable is defined, and set the
150        audio device accordingly  - Lalit Chhabra 23/Oct/2001 */
151     if((audio_dev  = getenv("AUDIODEV")) != NULL)
152     {
153       dpm.id_name = safe_malloc(strlen(audio_dev)+1);
154       dpm.name = safe_malloc(strlen(audio_dev)+1);
155       strcpy(dpm.name, audio_dev);
156       strcpy(dpm.id_name, audio_dev);
157 
158       tmp_audio = safe_malloc(strlen(audio_dev) + 3);
159       audio_ctl_dev = safe_malloc(strlen(audio_dev) + 3);
160 
161       strcpy(tmp_audio, audio_dev);
162       strcpy(audio_ctl_dev, strcat(tmp_audio, "ctl"));
163     }
164     else
165     {
166       audio_ctl_dev = safe_malloc(strlen(AUDIO_CTLDEV) + 3);
167       strcpy(audio_ctl_dev, AUDIO_CTLDEV);
168     }
169 
170     output_counter = play_samples_offset = 0;
171 
172     /* Open the audio device */
173     if((audioctl_fd = open(audio_ctl_dev, O_RDWR)) < 0)
174     {
175 	ctl->cmsg(CMSG_ERROR, VERB_NORMAL,
176 		  "%s: %s", audio_ctl_dev, strerror(errno));
177 	return -1;
178     }
179 
180 /* ############## */
181 #if 0
182     if((dpm.fd = open(dpm.name, O_WRONLY | O_NDELAY)) == -1)
183     {
184 	ctl->cmsg(CMSG_WARNING, VERB_NORMAL,
185 		  "%s: %s", dpm.name, strerror(errno));
186 	if(errno == EBUSY)
187 	{
188 	    if((dpm.fd = open(dpm.name, O_WRONLY)) == -1)
189 	    {
190 		ctl->cmsg(CMSG_ERROR, VERB_NORMAL,
191 			  "%s: %s", dpm.name, strerror(errno));
192 		close_output();
193 		return -1;
194 	    }
195 	}
196     }
197 #endif
198 
199     if((dpm.fd = open(dpm.name, O_WRONLY)) == -1)
200     {
201 	ctl->cmsg(CMSG_ERROR, VERB_NORMAL,
202 		  "%s: %s", dpm.name, strerror(errno));
203 	close_output();
204 	return -1;
205     }
206 
207     if(stat(dpm.name, &sb) < 0)
208     {
209 	ctl->cmsg(CMSG_ERROR, VERB_NORMAL,
210 		  "%s: %s", dpm.name, strerror(errno));
211 	close_output();
212 	return -1;
213     }
214 
215     if(!S_ISCHR(sb.st_mode))
216     {
217 	ctl->cmsg(CMSG_ERROR, VERB_NORMAL,
218 		  "%s: Not a audio device", dpm.name);
219 	close_output();
220 	return -1;
221     }
222 
223     if(sun_audio_getinfo(&auinfo) < 0)
224     {
225 	/* from Francesco Zanichelli's */
226 	/* If it doesn't give info, it probably won't take requests
227 	    either. Assume it's an old device that does 8kHz uLaw only.
228 
229 	      Disclaimer: I don't know squat about the various Sun audio
230 		  devices, so if this is not what we should do, I'll gladly
231 		      accept modifications. */
232 	ctl->cmsg(CMSG_WARNING, VERB_NORMAL, "Cannot inquire %s", dpm.name);
233 	include_enc = PE_ULAW|PE_ALAW|PE_MONO;
234 	exclude_enc = PE_SIGNED|PE_16BIT|PE_BYTESWAP;
235 	dpm.encoding = validate_encoding(dpm.encoding,
236 					 include_enc, exclude_enc);
237 	if(dpm.rate != 8000)
238 	    ctl->cmsg(CMSG_WARNING, VERB_VERBOSE,
239 		      "Sample rate is changed %d to 8000",
240 		      dpm.rate);
241 	dpm.rate = 8000;
242 	return 1;
243     }
244 
245     if(!(dpm.encoding & PE_16BIT))
246 	exclude_enc |= PE_SIGNED; /* Always unsigned */
247     dpm.encoding = validate_encoding(dpm.encoding, include_enc, exclude_enc);
248 
249     AUDIO_INITINFO(&auinfo);
250     auinfo.play.sample_rate = dpm.rate;
251     auinfo.play.channels = (dpm.encoding & PE_MONO) ? 1 : 2;
252     auinfo.play.encoding = sun_audio_encoding(dpm.encoding);
253     auinfo.play.precision = (dpm.encoding & PE_16BIT) ? 16 : 8;
254 
255     if(sun_audio_setinfo(&auinfo) == -1)
256     {
257 	ctl->cmsg(CMSG_ERROR, VERB_NORMAL,
258 		  "rate=%d, channels=%d, precision=%d, encoding=%s",
259 		  auinfo.play.sample_rate, auinfo.play.channels,
260 		  auinfo.play.precision, output_encoding_string(dpm.encoding));
261 	close_output();
262 	return -1;
263     }
264 
265     return 0;
266 }
267 
close_output(void)268 static void close_output(void)
269 {
270     if(dpm.fd != -1)
271     {
272 	close(dpm.fd);
273 	dpm.fd = -1;
274     }
275     if(audioctl_fd != -1)
276     {
277 	close(audioctl_fd);
278 	audioctl_fd = -1;
279     }
280 }
281 
output_data(char * buff,int32 nbytes)282 int output_data(char *buff, int32 nbytes)
283 {
284     int n;
285 
286   retry_write:
287     n = write(dpm.fd, buff, nbytes);
288 
289     if(n < 0)
290     {
291 	ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "%s: %s",
292 		  dpm.name, strerror(errno));
293 	if(errno == EINTR || errno == EAGAIN)
294 	    goto retry_write;
295 	return -1;
296     }
297 
298     output_counter += n;
299 
300     return n;
301 }
302 
303 
304 #if !defined(I_FLUSH) || !defined(FLUSHW)
305 #if defined(AUDIO_FLUSH)  /* BSD extension */
sun_discard_playing(void)306 static int sun_discard_playing(void)
307 {
308     if(ioctl(dpm.fd, AUDIO_FLUSH, NULL) < 0)
309     {
310        ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "%s: (ioctl) %s",
311                  dpm.name, strerror(errno));
312        return -1;
313     }
314     return 0;
315 }
316 #else
null_proc()317 static void null_proc(){}
sun_discard_playing(void)318 static int sun_discard_playing(void)
319 {
320     void (* orig_alarm_handler)();
321 
322     orig_alarm_handler = signal(SIGALRM, null_proc);
323     ualarm(10000, 10000);
324     close_output();
325     ualarm(0, 0);
326     signal(SIGALRM, orig_alarm_handler);
327     return open_output();
328 }
329 #endif /* AUDIO_FLASH */
330 #else
sun_discard_playing(void)331 static int sun_discard_playing(void)
332 {
333     if(ioctl(dpm.fd, I_FLUSH, FLUSHW) < 0)
334     {
335 	ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "%s: (ioctl) %s",
336 		  dpm.name, strerror(errno));
337 	return -1;
338     }
339     return 0;
340 }
341 #endif
342 
acntl(int request,void * arg)343 static int acntl(int request, void *arg)
344 {
345     audio_info_t auinfo;
346     int i;
347 
348     switch(request)
349     {
350        case PM_REQ_GETQSIZ:
351 	if(ioctl(audioctl_fd, AUDIO_GETINFO, &auinfo) < 0)
352 	    return -1;
353 	*((int *)arg) = auinfo.play.buffer_size;
354 	return 0;
355 
356 #if defined(__NetBSD__) || defined(__OpenBSD__)
357       case PM_REQ_GETFRAGSIZ:
358 	if(ioctl(audioctl_fd, AUDIO_GETINFO, &auinfo) < 0)
359 	    return -1;
360 	*((int *)arg) = auinfo.blocksize;
361 	return 0;
362 #endif
363 
364       case PM_REQ_OUTPUT_FINISH:
365 	return ioctl(audioctl_fd, AUDIO_DRAIN, NULL);
366 
367       case PM_REQ_GETFILLED:
368 	if(ioctl(audioctl_fd, AUDIO_GETINFO, &auinfo) < 0)
369 	    return -1;
370 #if defined(__NetBSD__) || defined(__OpenBSD__)
371 	*((int *)arg) = auinfo.play.seek;
372 #else
373 	if(auinfo.play.samples == play_samples_offset)
374 	    return -1; /* auinfo.play.samples is not active */
375 	i = output_counter;
376 	if(!(dpm.encoding & PE_MONO)) i >>= 1;
377 	if(dpm.encoding & PE_16BIT) i >>= 1;
378 	*((int *)arg) = i - (auinfo.play.samples - play_samples_offset);
379 #endif
380 	return 0;
381 
382       case PM_REQ_GETSAMPLES:
383 	if(ioctl(audioctl_fd, AUDIO_GETINFO, &auinfo) < 0)
384 	    return -1;
385 	if(auinfo.play.samples == play_samples_offset)
386 	    return -1; /* auinfo.play.samples is not active */
387 	*((int *)arg) = auinfo.play.samples - play_samples_offset;
388 	return 0;
389 
390       case PM_REQ_DISCARD:
391 	if(ioctl(audioctl_fd, AUDIO_GETINFO, &auinfo) != -1)
392 	    play_samples_offset = auinfo.play.samples;
393 	output_counter = 0;
394 	return sun_discard_playing();
395 
396       case PM_REQ_FLUSH:
397 	if(ioctl(audioctl_fd, AUDIO_GETINFO, &auinfo) != -1)
398 	    play_samples_offset = auinfo.play.samples;
399 	output_counter = 0;
400 	return 0;
401 
402       case PM_REQ_RATE: {
403 	  audio_info_t auinfo;
404 	  int rate;
405 
406 	  rate = *(int *)arg;
407 	  AUDIO_INITINFO(&auinfo);
408 	  auinfo.play.sample_rate = rate;
409 	  if(sun_audio_setinfo(&auinfo) == -1)
410 	      return -1;
411 	  play_mode->rate = rate;
412 	  return 0;
413         }
414 
415       case PM_REQ_PLAY_START: /* Called just before playing */
416       case PM_REQ_PLAY_END: /* Called just after playing */
417         return 0;
418     }
419     return -1;
420 }
421