comparison src/sgiplay.c @ 428:3ecd8885ac67 r21-2-22

Import from CVS: tag r21-2-22
author cvs
date Mon, 13 Aug 2007 11:28:15 +0200
parents
children abe6d1db359e
comparison
equal deleted inserted replaced
427:0a0253eac470 428:3ecd8885ac67
1 /* Play sound using the SGI audio library
2 written by Simon Leinen <simon@lia.di.epfl.ch>
3 Copyright (C) 1992 Free Software Foundation, Inc.
4
5 This file is part of XEmacs.
6
7 XEmacs is free software; you can redistribute it and/or modify it
8 under the terms of the GNU General Public License as published by the
9 Free Software Foundation; either version 2, or (at your option) any
10 later version.
11
12 XEmacs is distributed in the hope that it will be useful, but WITHOUT
13 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with XEmacs; see the file COPYING. If not, write to
19 the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 Boston, MA 02111-1307, USA. */
21
22 /* Synched up with: Not in FSF. */
23
24 #include <config.h>
25 #include "lisp.h"
26
27 #include <string.h>
28 #include <sys/file.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <fcntl.h>
32 #include <unistd.h>
33 #include <audio.h>
34 #include <netinet/in.h> /* for ntohl() etc. */
35
36 /* Configuration options */
37
38 /* ability to parse Sun/NeXT (.au or .snd) audio file headers. The
39 .snd format supports all sampling rates and sample widths that are
40 commonly used, as well as stereo. It is also easy to parse. */
41 #ifndef HAVE_SND_FILES
42 #define HAVE_SND_FILES 1
43 #endif
44
45 /* support for eight-but mu-law encoding. This is a useful compaction
46 technique, and most sounds from the Sun universe are in this
47 format. */
48 #ifndef HAVE_MULAW_8
49 #define HAVE_MULAW_8 1
50 #endif
51
52 /* if your machine is very slow, you have to use a table lookup to
53 convert mulaw samples to linear. This makes Emacs bigger so try to
54 avoid it. */
55 #ifndef USE_MULAW_DECODE_TABLE
56 #define USE_MULAW_DECODE_TABLE 0
57 #endif
58
59 /* support for linear encoding -- useful if you want better quality.
60 This enables 8, 16 and 24 bit wide samples. */
61 #ifndef HAVE_LINEAR
62 #define HAVE_LINEAR 1
63 #endif
64
65 /* support for 32 bit wide samples. If you notice the difference
66 between 32 and 24 bit samples, you must have very good ears. Since
67 the SGI audio library only supports 24 bit samples, each sample has
68 to be shifted right by 8 bits anyway. So you should probably just
69 convert all your 32 bit audio files to 24 bit. */
70 #ifndef HAVE_LINEAR_32
71 #define HAVE_LINEAR_32 0
72 #endif
73
74 /* support for stereo sound. Imagine the cool applications of this:
75 finally you don't just hear a beep -- you also know immediately
76 *where* something went wrong! Unfortunately the programming
77 interface only takes a single volume argument so far. */
78 #ifndef HAVE_STEREO
79 #define HAVE_STEREO 1
80 #endif
81
82 /* the play routine can be interrupted between chunks, so we choose a
83 small chunksize to keep the system responsive (2000 samples
84 correspond to a quarter of a second for .au files. If you
85 HAVE_STEREO, the chunksize should probably be even. */
86 #define CHUNKSIZE 8000
87
88 /* the format assumed for header-less audio data. The following
89 assumes ".au" format (8000 samples/sec mono 8-bit mulaw). */
90 #define DEFAULT_SAMPLING_RATE 8000
91 #define DEFAULT_CHANNEL_COUNT 1
92 #define DEFAULT_FORMAT AFmulaw8
93
94 /* Exports */
95
96 /* all compilers on machines that have the SGI audio library
97 understand prototypes, right? */
98
99 extern void play_sound_file (char *, int);
100 extern void play_sound_data (unsigned char *, int, int);
101
102 /* Data structures */
103
104 /* an AudioContext describes everything we want to know about how a
105 particular sound snippet should be played. It is split into three
106 parts (device, port and buffer) for implementation reasons. The
107 device part corresponds to the state of the output device and must
108 be reverted after playing the samples. The port part corresponds
109 to an ALport; we want to allocate a minimal number of these since
110 there are only four of them system-wide, but on the other hand we
111 can't use the same port for mono and stereo. The buffer part
112 corresponds to the sound data itself. */
113
114 typedef struct _AudioContextRec * AudioContext;
115
116 typedef struct
117 {
118 long device;
119 int left_speaker_gain;
120 int right_speaker_gain;
121 long output_rate;
122 }
123 AudioDeviceRec, * AudioDevice;
124
125 /* supported sound data formats */
126
127 typedef enum
128 {
129 AFunknown,
130 #if HAVE_MULAW_8
131 AFmulaw8,
132 #endif
133 #if HAVE_LINEAR
134 AFlinear8,
135 AFlinear16,
136 AFlinear24,
137 #if HAVE_LINEAR_32
138 AFlinear32,
139 #endif
140 #endif
141 AFillegal
142 }
143 AudioFormat;
144
145 typedef struct
146 {
147 ALport port;
148 AudioFormat format;
149 unsigned nchan;
150 unsigned queue_size;
151 }
152 AudioPortRec, * AudioPort;
153
154 typedef struct
155 {
156 void * data;
157 unsigned long size;
158 void (* write_chunk_function) (void *, void *, AudioContext);
159 }
160 AudioBufferRec, * AudioBuffer;
161
162 typedef struct _AudioContextRec
163 {
164 AudioDeviceRec device;
165 AudioPortRec port;
166 AudioBufferRec buffer;
167 }
168 AudioContextRec;
169
170 #define ac_device device.device
171 #define ac_left_speaker_gain device.left_speaker_gain
172 #define ac_right_speaker_gain device.right_speaker_gain
173 #define ac_output_rate device.output_rate
174 #define ac_port port.port
175 #define ac_format port.format
176 #define ac_nchan port.nchan
177 #define ac_queue_size port.queue_size
178 #define ac_data buffer.data
179 #define ac_size buffer.size
180 #define ac_write_chunk_function buffer.write_chunk_function
181
182 /* Forward declarations */
183
184 static Lisp_Object close_sound_file (Lisp_Object);
185 static AudioContext audio_initialize (unsigned char *, int, int);
186 static void play_internal (unsigned char *, int, AudioContext);
187 static void drain_audio_port (AudioContext);
188 static void write_mulaw_8_chunk (void *, void *, AudioContext);
189 static void write_linear_chunk (void *, void *, AudioContext);
190 static void write_linear_32_chunk (void *, void *, AudioContext);
191 static Lisp_Object restore_audio_port (Lisp_Object);
192 static AudioContext initialize_audio_port (AudioContext);
193 static int open_audio_port (AudioContext, AudioContext);
194 static void adjust_audio_volume (AudioDevice);
195 static void get_current_volumes (AudioDevice);
196 static int set_channels (ALconfig, unsigned);
197 static int set_output_format (ALconfig, AudioFormat);
198 static int parse_snd_header (void*, long, AudioContext);
199
200 /* are we looking at an NeXT/Sun audio header? */
201 #define LOOKING_AT_SND_HEADER_P(address) \
202 (!strncmp(".snd", (char *)(address), 4))
203
204 static Lisp_Object
205 close_sound_file (Lisp_Object closure)
206 {
207 close (XINT (closure));
208 return Qnil;
209 }
210
211 void
212 play_sound_file (char *sound_file, int volume)
213 {
214 int count = specpdl_depth ();
215 int input_fd;
216 unsigned char buffer[CHUNKSIZE];
217 int bytes_read;
218 AudioContext ac = (AudioContext) 0;
219
220 input_fd = open (sound_file, O_RDONLY);
221 if (input_fd == -1)
222 /* no error message -- this can't happen
223 because Fplay_sound_file has checked the
224 file for us. */
225 return;
226
227 record_unwind_protect (close_sound_file, make_int (input_fd));
228
229 while ((bytes_read = read (input_fd, buffer, CHUNKSIZE)) > 0)
230 {
231 if (ac == (AudioContext) 0)
232 {
233 ac = audio_initialize (buffer, bytes_read, volume);
234 if (ac == 0)
235 return;
236 }
237 else
238 {
239 ac->ac_data = buffer;
240 ac->ac_size = bytes_read;
241 }
242 play_internal (buffer, bytes_read, ac);
243 }
244 drain_audio_port (ac);
245 unbind_to (count, Qnil);
246 }
247
248 static long
249 saved_device_state[] = {
250 AL_OUTPUT_RATE, 0,
251 AL_LEFT_SPEAKER_GAIN, 0,
252 AL_RIGHT_SPEAKER_GAIN, 0,
253 };
254
255 static Lisp_Object
256 restore_audio_port (Lisp_Object closure)
257 {
258 Lisp_Object * contents = XVECTOR_DATA (closure);
259 saved_device_state[1] = XINT (contents[0]);
260 saved_device_state[3] = XINT (contents[1]);
261 saved_device_state[5] = XINT (contents[2]);
262 ALsetparams (AL_DEFAULT_DEVICE, saved_device_state, 6);
263 return Qnil;
264 }
265
266 void
267 play_sound_data (unsigned char *data, int length, int volume)
268 {
269 int count = specpdl_depth ();
270 AudioContext ac;
271
272 ac = audio_initialize (data, length, volume);
273 if (ac == (AudioContext) 0)
274 return;
275 play_internal (data, length, ac);
276 drain_audio_port (ac);
277 unbind_to (count, Qnil);
278 }
279
280 static AudioContext
281 audio_initialize (unsigned char *data, int length, int volume)
282 {
283 Lisp_Object audio_port_state[3];
284 static AudioContextRec desc;
285 AudioContext ac;
286
287 desc.ac_right_speaker_gain
288 = desc.ac_left_speaker_gain
289 = volume * 256 / 100;
290 desc.ac_device = AL_DEFAULT_DEVICE;
291
292 #if HAVE_SND_FILES
293 if (LOOKING_AT_SND_HEADER_P (data))
294 {
295 if (parse_snd_header (data, length, & desc)==-1)
296 report_file_error ("decoding .snd header", Qnil);
297 }
298 else
299 #endif
300 {
301 desc.ac_data = data;
302 desc.ac_size = length;
303 desc.ac_output_rate = DEFAULT_SAMPLING_RATE;
304 desc.ac_nchan = DEFAULT_CHANNEL_COUNT;
305 desc.ac_format = DEFAULT_FORMAT;
306 desc.ac_write_chunk_function = write_mulaw_8_chunk;
307 }
308
309 /* Make sure that the audio port is reset to
310 its initial characteristics after exit */
311 ALgetparams (desc.ac_device, saved_device_state,
312 sizeof (saved_device_state) / sizeof (long));
313 audio_port_state[0] = make_int (saved_device_state[1]);
314 audio_port_state[1] = make_int (saved_device_state[3]);
315 audio_port_state[2] = make_int (saved_device_state[5]);
316 record_unwind_protect (restore_audio_port,
317 Fvector (3, &audio_port_state[0]));
318
319 ac = initialize_audio_port (& desc);
320 desc = * ac;
321 return ac;
322 }
323
324 static void
325 play_internal (unsigned char *data, int length, AudioContext ac)
326 {
327 unsigned char * limit;
328 if (ac == (AudioContext) 0)
329 return;
330
331 data = ac->ac_data;
332 limit = data + ac->ac_size;
333 while (data < limit)
334 {
335 unsigned char * chunklimit = data + CHUNKSIZE;
336
337 if (chunklimit > limit)
338 chunklimit = limit;
339
340 QUIT;
341
342 (* ac->ac_write_chunk_function) (data, chunklimit, ac);
343 data = chunklimit;
344 }
345 }
346
347 static void
348 drain_audio_port (AudioContext ac)
349 {
350 while (ALgetfilled (ac->ac_port) > 0)
351 sginap(1);
352 }
353
354 /* Methods to write a "chunk" from a buffer containing audio data to
355 an audio port. This may involve some conversion if the output
356 device doesn't directly support the format the audio data is in. */
357
358 #if HAVE_MULAW_8
359
360 #if USE_MULAW_DECODE_TABLE
361 #include "libst.h"
362 #else /* not USE_MULAW_DECODE_TABLE */
363 static int
364 st_ulaw_to_linear (int u)
365 {
366 static CONST short table[] = {0,132,396,924,1980,4092,8316,16764};
367 int u1 = ~u;
368 short exponent = (u1 >> 4) & 0x07;
369 int mantissa = u1 & 0x0f;
370 int unsigned_result = table[exponent]+(mantissa << (exponent+3));
371 return u1 & 0x80 ? -unsigned_result : unsigned_result;
372 }
373 #endif /* not USE_MULAW_DECODE_TABLE */
374
375 static void
376 write_mulaw_8_chunk (void *buffer, void *chunklimit, AudioContext ac)
377 {
378 unsigned char * data = (unsigned char *) buffer;
379 unsigned char * limit = (unsigned char *) chunklimit;
380 short * obuf, * bufp;
381 long n_samples = limit - data;
382
383 obuf = alloca_array (short, n_samples);
384 bufp = &obuf[0];
385
386 while (data < limit)
387 *bufp++ = st_ulaw_to_linear (*data++);
388 ALwritesamps (ac->ac_port, obuf, n_samples);
389 }
390 #endif /* HAVE_MULAW_8 */
391
392 #if HAVE_LINEAR
393 static void
394 write_linear_chunk (void *data, void *limit, AudioContext ac)
395 {
396 unsigned n_samples;
397
398 switch (ac->ac_format)
399 {
400 case AFlinear16: n_samples = (short *) limit - (short *) data; break;
401 case AFlinear8: n_samples = (char *) limit - (char *) data; break;
402 default: n_samples = (long *) limit - (long *) data; break;
403 }
404 ALwritesamps (ac->ac_port, data, (long) n_samples);
405 }
406
407 #if HAVE_LINEAR_32
408 static void
409 write_linear_32_chunk (void *buffer, void *chunklimit, AudioContext ac)
410 {
411 long * data = (long *) buffer;
412 long * limit = (long *) chunklimit;
413 long * obuf, * bufp;
414 long n_samples = limit-data;
415
416 obuf = alloca_array (long, n_samples);
417 bufp = &obuf[0];
418
419 while (data < limit)
420 *bufp++ = *data++ >> 8;
421 ALwritesamps (ac->ac_port, obuf, n_samples);
422 }
423 #endif /* HAVE_LINEAR_32 */
424 #endif /* HAVE_LINEAR */
425
426 static AudioContext
427 initialize_audio_port (AudioContext desc)
428 {
429 /* we can't use the same port for mono and stereo */
430 static AudioContextRec mono_port_state
431 = { { 0, 0, 0, 0 },
432 { (ALport) 0, AFunknown, 1, 0 },
433 { (void *) 0, (unsigned long) 0 } };
434 #if HAVE_STEREO
435 static AudioContextRec stereo_port_state
436 = { { 0, 0, 0, 0 },
437 { (ALport) 0, AFunknown, 2, 0 },
438 { (void *) 0, (unsigned long) 0 } };
439 static AudioContext return_ac;
440
441 switch (desc->ac_nchan)
442 {
443 case 1: return_ac = & mono_port_state; break;
444 case 2: return_ac = & stereo_port_state; break;
445 default: return (AudioContext) 0;
446 }
447 #else /* not HAVE_STEREO */
448 static AudioContext return_ac = & mono_port_state;
449 #endif /* not HAVE_STEREO */
450
451 return_ac->device = desc->device;
452 return_ac->buffer = desc->buffer;
453 return_ac->ac_format = desc->ac_format;
454 return_ac->ac_queue_size = desc->ac_queue_size;
455
456 if (return_ac->ac_port==(ALport) 0)
457 {
458 if ((open_audio_port (return_ac, desc))==-1)
459 {
460 report_file_error ("Open audio port", Qnil);
461 return (AudioContext) 0;
462 }
463 }
464 else
465 {
466 ALconfig config = ALgetconfig (return_ac->ac_port);
467 int changed = 0;
468 long params[2];
469
470 params[0] = AL_OUTPUT_RATE;
471 ALgetparams (return_ac->ac_device, params, 2);
472 return_ac->ac_output_rate = params[1];
473
474 if (return_ac->ac_output_rate != desc->ac_output_rate)
475 {
476 return_ac->ac_output_rate = params[1] = desc->ac_output_rate;
477 ALsetparams (return_ac->ac_device, params, 2);
478 }
479 if ((changed = set_output_format (config, return_ac->ac_format))==-1)
480 return (AudioContext) 0;
481 return_ac->ac_format = desc->ac_format;
482 if (changed)
483 ALsetconfig (return_ac->ac_port, config);
484 }
485 return_ac->ac_write_chunk_function = desc->ac_write_chunk_function;
486 get_current_volumes (& return_ac->device);
487 if (return_ac->ac_left_speaker_gain != desc->ac_left_speaker_gain
488 || return_ac->ac_right_speaker_gain != desc->ac_right_speaker_gain)
489 adjust_audio_volume (& desc->device);
490 return return_ac;
491 }
492
493 static int
494 open_audio_port (AudioContext return_ac, AudioContext desc)
495 {
496 ALconfig config = ALnewconfig();
497 long params[2];
498
499 adjust_audio_volume (& desc->device);
500 return_ac->ac_left_speaker_gain = desc->ac_left_speaker_gain;
501 return_ac->ac_right_speaker_gain = desc->ac_right_speaker_gain;
502 params[0] = AL_OUTPUT_RATE;
503 params[1] = desc->ac_output_rate;
504 ALsetparams (desc->ac_device, params, 2);
505 return_ac->ac_output_rate = desc->ac_output_rate;
506 if (set_channels (config, desc->ac_nchan)==-1)
507 return -1;
508 return_ac->ac_nchan = desc->ac_nchan;
509 if (set_output_format (config, desc->ac_format)==-1)
510 return -1;
511 return_ac->ac_format = desc->ac_format;
512 ALsetqueuesize (config, (long) CHUNKSIZE);
513 return_ac->ac_port = ALopenport("XEmacs audio output", "w", config);
514 ALfreeconfig (config);
515 if (return_ac->ac_port==0)
516 {
517 report_file_error ("Opening audio output port", Qnil);
518 return -1;
519 }
520 return 0;
521 }
522
523 static int
524 set_channels (ALconfig config, unsigned int nchan)
525 {
526 switch (nchan)
527 {
528 case 1: ALsetchannels (config, AL_MONO); break;
529 #if HAVE_STEREO
530 case 2: ALsetchannels (config, AL_STEREO); break;
531 #endif /* HAVE_STEREO */
532 default:
533 report_file_error ("Unsupported channel count",
534 Fcons (make_int (nchan), Qnil));
535 return -1;
536 }
537 return 0;
538 }
539
540 static int
541 set_output_format (ALconfig config, AudioFormat format)
542 {
543 long samplesize;
544 long old_samplesize;
545
546 switch (format)
547 {
548 #if HAVE_MULAW_8
549 case AFmulaw8:
550 #endif
551 #if HAVE_LINEAR
552 case AFlinear16:
553 #endif
554 #if HAVE_MULAW_8 || HAVE_LINEAR
555 samplesize = AL_SAMPLE_16;
556 break;
557 #endif
558 #if HAVE_LINEAR
559 case AFlinear8:
560 samplesize = AL_SAMPLE_8;
561 break;
562 case AFlinear24:
563 #if HAVE_LINEAR_32
564 case AFlinear32:
565 samplesize = AL_SAMPLE_24;
566 break;
567 #endif
568 #endif
569 default:
570 report_file_error ("Unsupported audio format",
571 Fcons (make_int (format), Qnil));
572 return -1;
573 }
574 old_samplesize = ALgetwidth (config);
575 if (old_samplesize==samplesize)
576 return 0;
577 ALsetwidth (config, samplesize);
578 return 1;
579 }
580
581 static void
582 adjust_audio_volume (AudioDevice device)
583 {
584 long params[4];
585 params[0] = AL_LEFT_SPEAKER_GAIN;
586 params[1] = device->left_speaker_gain;
587 params[2] = AL_RIGHT_SPEAKER_GAIN;
588 params[3] = device->right_speaker_gain;
589 ALsetparams (device->device, params, 4);
590 }
591
592 static void
593 get_current_volumes (AudioDevice device)
594 {
595 long params[4];
596 params[0] = AL_LEFT_SPEAKER_GAIN;
597 params[2] = AL_RIGHT_SPEAKER_GAIN;
598 ALgetparams (device->device, params, 4);
599 device->left_speaker_gain = params[1];
600 device->right_speaker_gain = params[3];
601 }
602
603 #if HAVE_SND_FILES
604
605 /* Parsing .snd (NeXT/Sun) headers */
606
607 typedef struct
608 {
609 int magic;
610 int dataLocation;
611 int dataSize;
612 int dataFormat;
613 int samplingRate;
614 int channelCount;
615 char info[4];
616 }
617 SNDSoundStruct;
618 #define SOUND_TO_HOST_INT(x) ntohl(x)
619
620 typedef enum
621 {
622 SND_FORMAT_FORMAT_UNSPECIFIED,
623 SND_FORMAT_MULAW_8,
624 SND_FORMAT_LINEAR_8,
625 SND_FORMAT_LINEAR_16,
626 SND_FORMAT_LINEAR_24,
627 SND_FORMAT_LINEAR_32,
628 SND_FORMAT_FLOAT,
629 SND_FORMAT_DOUBLE,
630 SND_FORMAT_INDIRECT,
631 SND_FORMAT_NESTED,
632 SND_FORMAT_DSP_CODE,
633 SND_FORMAT_DSP_DATA_8,
634 SND_FORMAT_DSP_DATA_16,
635 SND_FORMAT_DSP_DATA_24,
636 SND_FORMAT_DSP_DATA_32,
637 SND_FORMAT_DSP_unknown_15,
638 SND_FORMAT_DISPLAY,
639 SND_FORMAT_MULAW_SQUELCH,
640 SND_FORMAT_EMPHASIZED,
641 SND_FORMAT_COMPRESSED,
642 SND_FORMAT_COMPRESSED_EMPHASIZED,
643 SND_FORMAT_DSP_COMMANDS,
644 SND_FORMAT_DSP_COMMANDS_SAMPLES
645 }
646 SNDFormatCode;
647
648 static int
649 parse_snd_header (void *header, long length, AudioContext desc)
650 {
651 #define hp ((SNDSoundStruct *) (header))
652 long limit;
653
654 #if HAVE_LINEAR
655 desc->ac_write_chunk_function = write_linear_chunk;
656 #endif
657 switch ((SNDFormatCode) SOUND_TO_HOST_INT (hp->dataFormat))
658 {
659 #if HAVE_MULAW_8
660 case SND_FORMAT_MULAW_8:
661 desc->ac_format = AFmulaw8;
662 desc->ac_write_chunk_function = write_mulaw_8_chunk;
663 break;
664 #endif
665 #if HAVE_LINEAR
666 case SND_FORMAT_LINEAR_8:
667 desc->ac_format = AFlinear8;
668 break;
669 case SND_FORMAT_LINEAR_16:
670 desc->ac_format = AFlinear16;
671 break;
672 case SND_FORMAT_LINEAR_24:
673 desc->ac_format = AFlinear24;
674 break;
675 #endif
676 #if HAVE_LINEAR_32
677 case SND_FORMAT_LINEAR_32:
678 desc->ac_format = AFlinear32;
679 desc->ac_write_chunk_function = write_linear_32_chunk;
680 break;
681 #endif
682 default:
683 desc->ac_format = AFunknown;
684 }
685 desc->ac_output_rate = SOUND_TO_HOST_INT (hp->samplingRate);
686 desc->ac_nchan = SOUND_TO_HOST_INT (hp->channelCount);
687 desc->ac_data = (char *) header + SOUND_TO_HOST_INT (hp->dataLocation);
688 limit = (char *) header + length - (char *) desc->ac_data;
689 desc->ac_size = SOUND_TO_HOST_INT (hp->dataSize);
690 if (desc->ac_size > limit) desc->ac_size = limit;
691 return 0;
692 #undef hp
693 }
694 #endif /* HAVE_SND_FILES */
695