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