update.
[chise/xemacs-chise.git.1] / src / sgiplay.c
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 \f
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 \f
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 = (unsigned char *) 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 \f
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 \f
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 \f
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