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.
5 This file is part of SXEmacs
7 SXEmacs is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
12 SXEmacs is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>. */
21 /* Synched up with: Not in FSF. */
28 #include <sys/types.h>
33 #include <netinet/in.h> /* for ntohl() etc. */
35 /* Configuration options */
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
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
48 #define HAVE_MULAW_8 1
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
54 #ifndef USE_MULAW_DECODE_TABLE
55 #define USE_MULAW_DECODE_TABLE 0
58 /* support for linear encoding -- useful if you want better quality.
59 This enables 8, 16 and 24 bit wide samples. */
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
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. */
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
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
95 /* all compilers on machines that have the SGI audio library
96 understand prototypes, right? */
98 extern void play_sound_file(char *, int);
99 extern void play_sound_data(unsigned char *, int, int);
101 /* Data structures */
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. */
113 typedef struct _AudioContextRec *AudioContext;
117 int left_speaker_gain;
118 int right_speaker_gain;
120 } AudioDeviceRec, *AudioDevice;
122 /* supported sound data formats */
145 } AudioPortRec, *AudioPort;
150 void (*write_chunk_function) (void *, void *, AudioContext);
151 } AudioBufferRec, *AudioBuffer;
153 typedef struct _AudioContextRec {
154 AudioDeviceRec device;
156 AudioBufferRec buffer;
159 #define ac_device device.device
160 #define ac_left_speaker_gain device.left_speaker_gain
161 #define ac_right_speaker_gain device.right_speaker_gain
162 #define ac_output_rate device.output_rate
163 #define ac_port port.port
164 #define ac_format port.format
165 #define ac_nchan port.nchan
166 #define ac_queue_size port.queue_size
167 #define ac_data buffer.data
168 #define ac_size buffer.size
169 #define ac_write_chunk_function buffer.write_chunk_function
171 /* Forward declarations */
173 static Lisp_Object close_sound_file(Lisp_Object);
174 static AudioContext audio_initialize(unsigned char *, int, int);
175 static void play_internal(unsigned char *, int, AudioContext);
176 static void drain_audio_port(AudioContext);
177 static void write_mulaw_8_chunk(void *, void *, AudioContext);
178 static void write_linear_chunk(void *, void *, AudioContext);
179 static void write_linear_32_chunk(void *, void *, AudioContext);
180 static Lisp_Object restore_audio_port(Lisp_Object);
181 static AudioContext initialize_audio_port(AudioContext);
182 static int open_audio_port(AudioContext, AudioContext);
183 static void adjust_audio_volume(AudioDevice);
184 static void get_current_volumes(AudioDevice);
185 static int set_channels(ALconfig, unsigned);
186 static int set_output_format(ALconfig, AudioFormat);
187 static int parse_snd_header(void *, long, AudioContext);
189 /* are we looking at an NeXT/Sun audio header? */
190 #define LOOKING_AT_SND_HEADER_P(address) \
191 (!strncmp(".snd", (char *)(address), 4))
193 static Lisp_Object close_sound_file(Lisp_Object closure)
195 close(XINT(closure));
199 void play_sound_file(char *sound_file, int volume)
201 int count = specpdl_depth();
203 unsigned char buffer[CHUNKSIZE];
205 AudioContext ac = (AudioContext) 0;
207 input_fd = open(sound_file, O_RDONLY);
209 /* no error message -- this can't happen
210 because Fplay_sound_file has checked the
214 record_unwind_protect(close_sound_file, make_int(input_fd));
216 while ((bytes_read = read(input_fd, buffer, CHUNKSIZE)) > 0) {
217 if (ac == (AudioContext) 0) {
218 ac = audio_initialize(buffer, bytes_read, volume);
222 ac->ac_data = buffer;
223 ac->ac_size = bytes_read;
225 play_internal(buffer, bytes_read, ac);
227 drain_audio_port(ac);
228 unbind_to(count, Qnil);
231 static long saved_device_state[] = {
233 AL_LEFT_SPEAKER_GAIN, 0,
234 AL_RIGHT_SPEAKER_GAIN, 0,
237 static Lisp_Object restore_audio_port(Lisp_Object closure)
239 Lisp_Object *contents = XVECTOR_DATA(closure);
240 saved_device_state[1] = XINT(contents[0]);
241 saved_device_state[3] = XINT(contents[1]);
242 saved_device_state[5] = XINT(contents[2]);
243 ALsetparams(AL_DEFAULT_DEVICE, saved_device_state, 6);
247 void play_sound_data(unsigned char *data, int length, int volume)
249 int count = specpdl_depth();
252 ac = audio_initialize(data, length, volume);
253 if (ac == (AudioContext) 0)
255 play_internal(data, length, ac);
256 drain_audio_port(ac);
257 unbind_to(count, Qnil);
261 audio_initialize(unsigned char *data, int length, int volume)
263 Lisp_Object audio_port_state[3];
264 static AudioContextRec desc;
267 desc.ac_right_speaker_gain
268 = desc.ac_left_speaker_gain = volume * 256 / 100;
269 desc.ac_device = AL_DEFAULT_DEVICE;
272 if (LOOKING_AT_SND_HEADER_P(data)) {
273 if (parse_snd_header(data, length, &desc) == -1)
274 report_file_error("decoding .snd header", Qnil);
279 desc.ac_size = length;
280 desc.ac_output_rate = DEFAULT_SAMPLING_RATE;
281 desc.ac_nchan = DEFAULT_CHANNEL_COUNT;
282 desc.ac_format = DEFAULT_FORMAT;
283 desc.ac_write_chunk_function = write_mulaw_8_chunk;
286 /* Make sure that the audio port is reset to
287 its initial characteristics after exit */
288 ALgetparams(desc.ac_device, saved_device_state,
289 sizeof(saved_device_state) / sizeof(long));
290 audio_port_state[0] = make_int(saved_device_state[1]);
291 audio_port_state[1] = make_int(saved_device_state[3]);
292 audio_port_state[2] = make_int(saved_device_state[5]);
293 record_unwind_protect(restore_audio_port,
294 Fvector(3, &audio_port_state[0]));
296 ac = initialize_audio_port(&desc);
301 static void play_internal(unsigned char *data, int length, AudioContext ac)
303 unsigned char *limit;
304 if (ac == (AudioContext) 0)
307 data = (unsigned char *)ac->ac_data;
308 limit = data + ac->ac_size;
309 while (data < limit) {
310 unsigned char *chunklimit = data + CHUNKSIZE;
312 if (chunklimit > limit)
317 (*ac->ac_write_chunk_function) (data, chunklimit, ac);
322 static void drain_audio_port(AudioContext ac)
324 while (ALgetfilled(ac->ac_port) > 0)
328 /* Methods to write a "chunk" from a buffer containing audio data to
329 an audio port. This may involve some conversion if the output
330 device doesn't directly support the format the audio data is in. */
334 #if USE_MULAW_DECODE_TABLE
336 #else /* not USE_MULAW_DECODE_TABLE */
337 static int st_ulaw_to_linear(int u)
339 static const short table[] =
340 { 0, 132, 396, 924, 1980, 4092, 8316, 16764 };
342 short exponent = (u1 >> 4) & 0x07;
343 int mantissa = u1 & 0x0f;
344 int unsigned_result = table[exponent] + (mantissa << (exponent + 3));
345 return u1 & 0x80 ? -unsigned_result : unsigned_result;
347 #endif /* not USE_MULAW_DECODE_TABLE */
349 static void write_mulaw_8_chunk(void *buffer, void *chunklimit, AudioContext ac)
351 unsigned char *data = (unsigned char *)buffer;
352 unsigned char *limit = (unsigned char *)chunklimit;
354 long n_samples = limit - data;
356 obuf = alloca_array(short, n_samples);
360 *bufp++ = st_ulaw_to_linear(*data++);
361 ALwritesamps(ac->ac_port, obuf, n_samples);
363 #endif /* HAVE_MULAW_8 */
366 static void write_linear_chunk(void *data, void *limit, AudioContext ac)
370 switch (ac->ac_format) {
372 n_samples = (short *)limit - (short *)data;
375 n_samples = (char *)limit - (char *)data;
378 n_samples = (long *)limit - (long *)data;
381 ALwritesamps(ac->ac_port, data, (long)n_samples);
386 write_linear_32_chunk(void *buffer, void *chunklimit, AudioContext ac)
388 long *data = (long *)buffer;
389 long *limit = (long *)chunklimit;
391 long n_samples = limit - data;
393 obuf = alloca_array(long, n_samples);
397 *bufp++ = *data++ >> 8;
398 ALwritesamps(ac->ac_port, obuf, n_samples);
400 #endif /* HAVE_LINEAR_32 */
401 #endif /* HAVE_LINEAR */
403 static AudioContext initialize_audio_port(AudioContext desc)
405 /* we can't use the same port for mono and stereo */
406 static AudioContextRec mono_port_state = { {0, 0, 0, 0},
407 {(ALport) 0, AFunknown, 1, 0},
408 {(void *)0, (unsigned long)0}
411 static AudioContextRec stereo_port_state = { {0, 0, 0, 0},
412 {(ALport) 0, AFunknown, 2, 0},
413 {(void *)0, (unsigned long)0}
415 static AudioContext return_ac;
417 switch (desc->ac_nchan) {
419 return_ac = &mono_port_state;
422 return_ac = &stereo_port_state;
425 return (AudioContext) 0;
427 #else /* not HAVE_STEREO */
428 static AudioContext return_ac = &mono_port_state;
429 #endif /* not HAVE_STEREO */
431 return_ac->device = desc->device;
432 return_ac->buffer = desc->buffer;
433 return_ac->ac_format = desc->ac_format;
434 return_ac->ac_queue_size = desc->ac_queue_size;
436 if (return_ac->ac_port == (ALport) 0) {
437 if ((open_audio_port(return_ac, desc)) == -1) {
438 report_file_error("Open audio port", Qnil);
439 return (AudioContext) 0;
442 ALconfig config = ALgetconfig(return_ac->ac_port);
446 params[0] = AL_OUTPUT_RATE;
447 ALgetparams(return_ac->ac_device, params, 2);
448 return_ac->ac_output_rate = params[1];
450 if (return_ac->ac_output_rate != desc->ac_output_rate) {
451 return_ac->ac_output_rate = params[1] =
452 desc->ac_output_rate;
453 ALsetparams(return_ac->ac_device, params, 2);
456 set_output_format(config, return_ac->ac_format)) == -1)
457 return (AudioContext) 0;
458 return_ac->ac_format = desc->ac_format;
460 ALsetconfig(return_ac->ac_port, config);
462 return_ac->ac_write_chunk_function = desc->ac_write_chunk_function;
463 get_current_volumes(&return_ac->device);
464 if (return_ac->ac_left_speaker_gain != desc->ac_left_speaker_gain
465 || return_ac->ac_right_speaker_gain != desc->ac_right_speaker_gain)
466 adjust_audio_volume(&desc->device);
470 static int open_audio_port(AudioContext return_ac, AudioContext desc)
472 ALconfig config = ALnewconfig();
475 adjust_audio_volume(&desc->device);
476 return_ac->ac_left_speaker_gain = desc->ac_left_speaker_gain;
477 return_ac->ac_right_speaker_gain = desc->ac_right_speaker_gain;
478 params[0] = AL_OUTPUT_RATE;
479 params[1] = desc->ac_output_rate;
480 ALsetparams(desc->ac_device, params, 2);
481 return_ac->ac_output_rate = desc->ac_output_rate;
482 if (set_channels(config, desc->ac_nchan) == -1)
484 return_ac->ac_nchan = desc->ac_nchan;
485 if (set_output_format(config, desc->ac_format) == -1)
487 return_ac->ac_format = desc->ac_format;
488 ALsetqueuesize(config, (long)CHUNKSIZE);
489 return_ac->ac_port = ALopenport("SXEmacs audio output", "w", config);
490 ALfreeconfig(config);
491 if (return_ac->ac_port == 0) {
492 report_file_error("Opening audio output port", Qnil);
498 static int set_channels(ALconfig config, unsigned int nchan)
502 ALsetchannels(config, AL_MONO);
506 ALsetchannels(config, AL_STEREO);
508 #endif /* HAVE_STEREO */
510 report_file_error("Unsupported channel count",
511 Fcons(make_int(nchan), Qnil));
517 static int set_output_format(ALconfig config, AudioFormat format)
529 #if HAVE_MULAW_8 || HAVE_LINEAR
530 samplesize = AL_SAMPLE_16;
535 samplesize = AL_SAMPLE_8;
540 samplesize = AL_SAMPLE_24;
545 report_file_error("Unsupported audio format",
546 Fcons(make_int(format), Qnil));
549 old_samplesize = ALgetwidth(config);
550 if (old_samplesize == samplesize)
552 ALsetwidth(config, samplesize);
556 static void adjust_audio_volume(AudioDevice device)
559 params[0] = AL_LEFT_SPEAKER_GAIN;
560 params[1] = device->left_speaker_gain;
561 params[2] = AL_RIGHT_SPEAKER_GAIN;
562 params[3] = device->right_speaker_gain;
563 ALsetparams(device->device, params, 4);
566 static void get_current_volumes(AudioDevice device)
569 params[0] = AL_LEFT_SPEAKER_GAIN;
570 params[2] = AL_RIGHT_SPEAKER_GAIN;
571 ALgetparams(device->device, params, 4);
572 device->left_speaker_gain = params[1];
573 device->right_speaker_gain = params[3];
578 /* Parsing .snd (NeXT/Sun) headers */
589 #define SOUND_TO_HOST_INT(x) ntohl(x)
592 SND_FORMAT_FORMAT_UNSPECIFIED,
595 SND_FORMAT_LINEAR_16,
596 SND_FORMAT_LINEAR_24,
597 SND_FORMAT_LINEAR_32,
603 SND_FORMAT_DSP_DATA_8,
604 SND_FORMAT_DSP_DATA_16,
605 SND_FORMAT_DSP_DATA_24,
606 SND_FORMAT_DSP_DATA_32,
607 SND_FORMAT_DSP_unknown_15,
609 SND_FORMAT_MULAW_SQUELCH,
610 SND_FORMAT_EMPHASIZED,
611 SND_FORMAT_COMPRESSED,
612 SND_FORMAT_COMPRESSED_EMPHASIZED,
613 SND_FORMAT_DSP_COMMANDS,
614 SND_FORMAT_DSP_COMMANDS_SAMPLES
617 static int parse_snd_header(void *header, long length, AudioContext desc)
619 #define hp ((SNDSoundStruct *) (header))
623 desc->ac_write_chunk_function = write_linear_chunk;
625 switch ((SNDFormatCode) SOUND_TO_HOST_INT(hp->dataFormat)) {
627 case SND_FORMAT_MULAW_8:
628 desc->ac_format = AFmulaw8;
629 desc->ac_write_chunk_function = write_mulaw_8_chunk;
633 case SND_FORMAT_LINEAR_8:
634 desc->ac_format = AFlinear8;
636 case SND_FORMAT_LINEAR_16:
637 desc->ac_format = AFlinear16;
639 case SND_FORMAT_LINEAR_24:
640 desc->ac_format = AFlinear24;
644 case SND_FORMAT_LINEAR_32:
645 desc->ac_format = AFlinear32;
646 desc->ac_write_chunk_function = write_linear_32_chunk;
650 desc->ac_format = AFunknown;
652 desc->ac_output_rate = SOUND_TO_HOST_INT(hp->samplingRate);
653 desc->ac_nchan = SOUND_TO_HOST_INT(hp->channelCount);
654 desc->ac_data = (char *)header + SOUND_TO_HOST_INT(hp->dataLocation);
655 limit = (char *)header + length - (char *)desc->ac_data;
656 desc->ac_size = SOUND_TO_HOST_INT(hp->dataSize);
657 if (desc->ac_size > limit)
658 desc->ac_size = limit;
662 #endif /* HAVE_SND_FILES */