comparison src/sgiplay.c @ 0:376386a54a3c r19-14

Import from CVS: tag r19-14
author cvs
date Mon, 13 Aug 2007 08:45:50 +0200
parents
children 8eaf7971accc
comparison
equal deleted inserted replaced
-1:000000000000 0:376386a54a3c
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
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
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 = (vector_data (XVECTOR (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
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 (n_samples * sizeof (short));
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 (n_samples * sizeof (long));
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
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
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