Initial git import
[sxemacs] / src / media / sound-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 SXEmacs
6
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.
11
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.
16
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/>. */
19
20
21 /* Synched up with: Not in FSF. */
22
23 #include <config.h>
24 #include "lisp.h"
25
26 #include <string.h>
27 #include <sys/file.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <fcntl.h>
31 #include <unistd.h>
32 #include <audio.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         long device;
117         int left_speaker_gain;
118         int right_speaker_gain;
119         long output_rate;
120 } AudioDeviceRec, *AudioDevice;
121
122 /* supported sound data formats */
123
124 typedef enum {
125         AFunknown,
126 #if HAVE_MULAW_8
127         AFmulaw8,
128 #endif
129 #if HAVE_LINEAR
130         AFlinear8,
131         AFlinear16,
132         AFlinear24,
133 #if HAVE_LINEAR_32
134         AFlinear32,
135 #endif
136 #endif
137         AFillegal
138 } AudioFormat;
139
140 typedef struct {
141         ALport port;
142         AudioFormat format;
143         unsigned nchan;
144         unsigned queue_size;
145 } AudioPortRec, *AudioPort;
146
147 typedef struct {
148         void *data;
149         unsigned long size;
150         void (*write_chunk_function) (void *, void *, AudioContext);
151 } AudioBufferRec, *AudioBuffer;
152
153 typedef struct _AudioContextRec {
154         AudioDeviceRec device;
155         AudioPortRec port;
156         AudioBufferRec buffer;
157 } AudioContextRec;
158
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
170 \f
171 /* Forward declarations */
172
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);
188
189 /* are we looking at an NeXT/Sun audio header? */
190 #define LOOKING_AT_SND_HEADER_P(address) \
191   (!strncmp(".snd", (char *)(address), 4))
192
193 static Lisp_Object close_sound_file(Lisp_Object closure)
194 {
195         close(XINT(closure));
196         return Qnil;
197 }
198
199 void play_sound_file(char *sound_file, int volume)
200 {
201         int count = specpdl_depth();
202         int input_fd;
203         unsigned char buffer[CHUNKSIZE];
204         int bytes_read;
205         AudioContext ac = (AudioContext) 0;
206
207         input_fd = open(sound_file, O_RDONLY);
208         if (input_fd == -1)
209                 /* no error message -- this can't happen
210                    because Fplay_sound_file has checked the
211                    file for us. */
212                 return;
213
214         record_unwind_protect(close_sound_file, make_int(input_fd));
215
216         while ((bytes_read = read(input_fd, buffer, CHUNKSIZE)) > 0) {
217                 if (ac == (AudioContext) 0) {
218                         ac = audio_initialize(buffer, bytes_read, volume);
219                         if (ac == 0)
220                                 return;
221                 } else {
222                         ac->ac_data = buffer;
223                         ac->ac_size = bytes_read;
224                 }
225                 play_internal(buffer, bytes_read, ac);
226         }
227         drain_audio_port(ac);
228         unbind_to(count, Qnil);
229 }
230
231 static long saved_device_state[] = {
232         AL_OUTPUT_RATE, 0,
233         AL_LEFT_SPEAKER_GAIN, 0,
234         AL_RIGHT_SPEAKER_GAIN, 0,
235 };
236
237 static Lisp_Object restore_audio_port(Lisp_Object closure)
238 {
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);
244         return Qnil;
245 }
246
247 void play_sound_data(unsigned char *data, int length, int volume)
248 {
249         int count = specpdl_depth();
250         AudioContext ac;
251
252         ac = audio_initialize(data, length, volume);
253         if (ac == (AudioContext) 0)
254                 return;
255         play_internal(data, length, ac);
256         drain_audio_port(ac);
257         unbind_to(count, Qnil);
258 }
259
260 static AudioContext
261 audio_initialize(unsigned char *data, int length, int volume)
262 {
263         Lisp_Object audio_port_state[3];
264         static AudioContextRec desc;
265         AudioContext ac;
266
267         desc.ac_right_speaker_gain
268             = desc.ac_left_speaker_gain = volume * 256 / 100;
269         desc.ac_device = AL_DEFAULT_DEVICE;
270
271 #if HAVE_SND_FILES
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);
275         } else
276 #endif
277         {
278                 desc.ac_data = data;
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;
284         }
285
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]));
295
296         ac = initialize_audio_port(&desc);
297         desc = *ac;
298         return ac;
299 }
300
301 static void play_internal(unsigned char *data, int length, AudioContext ac)
302 {
303         unsigned char *limit;
304         if (ac == (AudioContext) 0)
305                 return;
306
307         data = (unsigned char *)ac->ac_data;
308         limit = data + ac->ac_size;
309         while (data < limit) {
310                 unsigned char *chunklimit = data + CHUNKSIZE;
311
312                 if (chunklimit > limit)
313                         chunklimit = limit;
314
315                 QUIT;
316
317                 (*ac->ac_write_chunk_function) (data, chunklimit, ac);
318                 data = chunklimit;
319         }
320 }
321
322 static void drain_audio_port(AudioContext ac)
323 {
324         while (ALgetfilled(ac->ac_port) > 0)
325                 sginap(1);
326 }
327 \f
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. */
331
332 #if HAVE_MULAW_8
333
334 #if USE_MULAW_DECODE_TABLE
335 #include "libst.h"
336 #else                           /* not USE_MULAW_DECODE_TABLE */
337 static int st_ulaw_to_linear(int u)
338 {
339         static const short table[] =
340             { 0, 132, 396, 924, 1980, 4092, 8316, 16764 };
341         int u1 = ~u;
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;
346 }
347 #endif                          /* not USE_MULAW_DECODE_TABLE */
348
349 static void write_mulaw_8_chunk(void *buffer, void *chunklimit, AudioContext ac)
350 {
351         unsigned char *data = (unsigned char *)buffer;
352         unsigned char *limit = (unsigned char *)chunklimit;
353         short *obuf, *bufp;
354         long n_samples = limit - data;
355
356         obuf = alloca_array(short, n_samples);
357         bufp = &obuf[0];
358
359         while (data < limit)
360                 *bufp++ = st_ulaw_to_linear(*data++);
361         ALwritesamps(ac->ac_port, obuf, n_samples);
362 }
363 #endif                          /* HAVE_MULAW_8 */
364
365 #if HAVE_LINEAR
366 static void write_linear_chunk(void *data, void *limit, AudioContext ac)
367 {
368         unsigned n_samples;
369
370         switch (ac->ac_format) {
371         case AFlinear16:
372                 n_samples = (short *)limit - (short *)data;
373                 break;
374         case AFlinear8:
375                 n_samples = (char *)limit - (char *)data;
376                 break;
377         default:
378                 n_samples = (long *)limit - (long *)data;
379                 break;
380         }
381         ALwritesamps(ac->ac_port, data, (long)n_samples);
382 }
383
384 #if HAVE_LINEAR_32
385 static void
386 write_linear_32_chunk(void *buffer, void *chunklimit, AudioContext ac)
387 {
388         long *data = (long *)buffer;
389         long *limit = (long *)chunklimit;
390         long *obuf, *bufp;
391         long n_samples = limit - data;
392
393         obuf = alloca_array(long, n_samples);
394         bufp = &obuf[0];
395
396         while (data < limit)
397                 *bufp++ = *data++ >> 8;
398         ALwritesamps(ac->ac_port, obuf, n_samples);
399 }
400 #endif                          /* HAVE_LINEAR_32 */
401 #endif                          /* HAVE_LINEAR */
402 \f
403 static AudioContext initialize_audio_port(AudioContext desc)
404 {
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}
409         };
410 #if HAVE_STEREO
411         static AudioContextRec stereo_port_state = { {0, 0, 0, 0},
412         {(ALport) 0, AFunknown, 2, 0},
413         {(void *)0, (unsigned long)0}
414         };
415         static AudioContext return_ac;
416
417         switch (desc->ac_nchan) {
418         case 1:
419                 return_ac = &mono_port_state;
420                 break;
421         case 2:
422                 return_ac = &stereo_port_state;
423                 break;
424         default:
425                 return (AudioContext) 0;
426         }
427 #else                           /* not HAVE_STEREO */
428         static AudioContext return_ac = &mono_port_state;
429 #endif                          /* not HAVE_STEREO */
430
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;
435
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;
440                 }
441         } else {
442                 ALconfig config = ALgetconfig(return_ac->ac_port);
443                 int changed = 0;
444                 long params[2];
445
446                 params[0] = AL_OUTPUT_RATE;
447                 ALgetparams(return_ac->ac_device, params, 2);
448                 return_ac->ac_output_rate = params[1];
449
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);
454                 }
455                 if ((changed =
456                      set_output_format(config, return_ac->ac_format)) == -1)
457                         return (AudioContext) 0;
458                 return_ac->ac_format = desc->ac_format;
459                 if (changed)
460                         ALsetconfig(return_ac->ac_port, config);
461         }
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);
467         return return_ac;
468 }
469
470 static int open_audio_port(AudioContext return_ac, AudioContext desc)
471 {
472         ALconfig config = ALnewconfig();
473         long params[2];
474
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)
483                 return -1;
484         return_ac->ac_nchan = desc->ac_nchan;
485         if (set_output_format(config, desc->ac_format) == -1)
486                 return -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);
493                 return -1;
494         }
495         return 0;
496 }
497
498 static int set_channels(ALconfig config, unsigned int nchan)
499 {
500         switch (nchan) {
501         case 1:
502                 ALsetchannels(config, AL_MONO);
503                 break;
504 #if HAVE_STEREO
505         case 2:
506                 ALsetchannels(config, AL_STEREO);
507                 break;
508 #endif                          /* HAVE_STEREO */
509         default:
510                 report_file_error("Unsupported channel count",
511                                   Fcons(make_int(nchan), Qnil));
512                 return -1;
513         }
514         return 0;
515 }
516
517 static int set_output_format(ALconfig config, AudioFormat format)
518 {
519         long samplesize;
520         long old_samplesize;
521
522         switch (format) {
523 #if HAVE_MULAW_8
524         case AFmulaw8:
525 #endif
526 #if HAVE_LINEAR
527         case AFlinear16:
528 #endif
529 #if HAVE_MULAW_8 || HAVE_LINEAR
530                 samplesize = AL_SAMPLE_16;
531                 break;
532 #endif
533 #if HAVE_LINEAR
534         case AFlinear8:
535                 samplesize = AL_SAMPLE_8;
536                 break;
537         case AFlinear24:
538 #if HAVE_LINEAR_32
539         case AFlinear32:
540                 samplesize = AL_SAMPLE_24;
541                 break;
542 #endif
543 #endif
544         default:
545                 report_file_error("Unsupported audio format",
546                                   Fcons(make_int(format), Qnil));
547                 return -1;
548         }
549         old_samplesize = ALgetwidth(config);
550         if (old_samplesize == samplesize)
551                 return 0;
552         ALsetwidth(config, samplesize);
553         return 1;
554 }
555
556 static void adjust_audio_volume(AudioDevice device)
557 {
558         long params[4];
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);
564 }
565
566 static void get_current_volumes(AudioDevice device)
567 {
568         long params[4];
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];
574 }
575 \f
576 #if HAVE_SND_FILES
577
578 /* Parsing .snd (NeXT/Sun) headers */
579
580 typedef struct {
581         int magic;
582         int dataLocation;
583         int dataSize;
584         int dataFormat;
585         int samplingRate;
586         int channelCount;
587         char info[4];
588 } SNDSoundStruct;
589 #define SOUND_TO_HOST_INT(x) ntohl(x)
590
591 typedef enum {
592         SND_FORMAT_FORMAT_UNSPECIFIED,
593         SND_FORMAT_MULAW_8,
594         SND_FORMAT_LINEAR_8,
595         SND_FORMAT_LINEAR_16,
596         SND_FORMAT_LINEAR_24,
597         SND_FORMAT_LINEAR_32,
598         SND_FORMAT_FLOAT,
599         SND_FORMAT_DOUBLE,
600         SND_FORMAT_INDIRECT,
601         SND_FORMAT_NESTED,
602         SND_FORMAT_DSP_CODE,
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,
608         SND_FORMAT_DISPLAY,
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
615 } SNDFormatCode;
616
617 static int parse_snd_header(void *header, long length, AudioContext desc)
618 {
619 #define hp ((SNDSoundStruct *) (header))
620         long limit;
621
622 #if HAVE_LINEAR
623         desc->ac_write_chunk_function = write_linear_chunk;
624 #endif
625         switch ((SNDFormatCode) SOUND_TO_HOST_INT(hp->dataFormat)) {
626 #if HAVE_MULAW_8
627         case SND_FORMAT_MULAW_8:
628                 desc->ac_format = AFmulaw8;
629                 desc->ac_write_chunk_function = write_mulaw_8_chunk;
630                 break;
631 #endif
632 #if HAVE_LINEAR
633         case SND_FORMAT_LINEAR_8:
634                 desc->ac_format = AFlinear8;
635                 break;
636         case SND_FORMAT_LINEAR_16:
637                 desc->ac_format = AFlinear16;
638                 break;
639         case SND_FORMAT_LINEAR_24:
640                 desc->ac_format = AFlinear24;
641                 break;
642 #endif
643 #if HAVE_LINEAR_32
644         case SND_FORMAT_LINEAR_32:
645                 desc->ac_format = AFlinear32;
646                 desc->ac_write_chunk_function = write_linear_32_chunk;
647                 break;
648 #endif
649         default:
650                 desc->ac_format = AFunknown;
651         }
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;
659         return 0;
660 #undef hp
661 }
662 #endif                          /* HAVE_SND_FILES */