XEmacs 21.2-b1
[chise/xemacs-chise.git.1] / src / nas.c
1 /* nas.c --- XEmacs support for the Network Audio System server.
2  *
3  * Author: Richard Caley <R.Caley@ed.ac.uk>
4  *
5  * Copyright 1994 Free Software Foundation, Inc.
6  * Copyright 1993 Network Computing Devices, Inc.
7  *
8  * Permission to use, copy, modify, distribute, and sell this software and
9  * its documentation for any purpose is hereby granted without fee, provided
10  * that the above copyright notice appear in all copies and that both that
11  * copyright notice and this permission notice appear in supporting
12  * documentation, and that the name Network Computing Devices, Inc. not be
13  * used in advertising or publicity pertaining to distribution of this 
14  * software without specific, written prior permission.
15  * 
16  * THIS SOFTWARE IS PROVIDED 'AS-IS'.  NETWORK COMPUTING DEVICES, INC.,
17  * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT
18  * LIMITATION ALL IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
19  * PARTICULAR PURPOSE, OR NONINFRINGEMENT.  IN NO EVENT SHALL NETWORK
20  * COMPUTING DEVICES, INC., BE LIABLE FOR ANY DAMAGES WHATSOEVER, INCLUDING
21  * SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES, INCLUDING LOSS OF USE, DATA,
22  * OR PROFITS, EVEN IF ADVISED OF THE POSSIBILITY THEREOF, AND REGARDLESS OF
23  * WHETHER IN AN ACTION IN CONTRACT, TORT OR NEGLIGENCE, ARISING OUT OF OR IN
24  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
25  */
26
27 /* Synched up with: Not in FSF. */
28
29 /* There are four compile-time options.
30  *
31  * XTOOLKIT     This will be part of an Xt program.
32  * 
33  * XTEVENTS     The playing will be supervised asynchronously by the Xt event
34  *              loop.  If not set, playing will be completed within the call
35  *              to play_file etc. 
36  *
37  * ROBUST_PLAY  Causes errors in nas to be caught.  This means that the
38  *              program will attempt not to die if the nas server does.
39  *
40  * CACHE_SOUNDS Causes the sounds to be played in buckets in the NAS
41  *              server.  They are named by their comment field, or if that is
42  *              empty by the filename, or for play_sound_data by a name made up
43  *              from the sample itself.
44  */
45
46 /* CHANGES:
47  *      10/8/94, rjc    Changed names from netaudio to nas
48  *                      Added back asynchronous play if nas library has
49  *                      correct error facilities.
50  *      4/11/94, rjc    Added wait_for_sounds to be called when user wants to
51  *                      be sure all play has finished.
52  */
53
54 #ifdef emacs
55 #include <config.h>
56 #include "lisp.h"
57 #endif
58
59 #if __STDC__ || defined (STDC_HEADERS)
60 #    include <stdlib.h>
61 #    include <stdarg.h>
62 #    include <string.h>
63 #endif
64
65 #ifdef HAVE_UNISTD_H
66 #include <unistd.h>
67 #endif
68
69 #include <stdio.h>
70 #include "syssignal.h"
71
72 #undef LITTLE_ENDIAN
73 #undef BIG_ENDIAN
74 #include <audio/audiolib.h>
75 #include <audio/soundlib.h>
76 #include <audio/snd.h>
77 #include <audio/fileutil.h>
78
79 #ifdef emacs
80
81 #    define XTOOLKIT
82 #    define XTEVENTS
83 #    define ROBUST_PLAY
84 #    define CACHE_SOUNDS
85
86     /*
87      * For old NAS libraries, force playing to be synchronous
88      * and declare the long jump point locally.
89      */
90
91 #    if defined (NAS_NO_ERROR_JUMP)
92
93 #       undef XTEVENTS
94
95 #       include <setjmp.h>
96         jmp_buf AuXtErrorJump;
97 #    endif
98
99      /* The GETTEXT is correct. --ben */
100 #    define warn(str) warn_when_safe (Qnas, Qwarning, "nas: %s ", GETTEXT (str))
101
102 #    define play_sound_file nas_play_sound_file
103 #    define play_sound_data nas_play_sound_data
104 #    define wait_for_sounds nas_wait_for_sounds
105 #    define init_play       nas_init_play
106 #    define close_down_play nas_close_down_play
107
108 #else /* !emacs */
109 #    define warn(str) fprintf (stderr, "%s\n", (str))
110 #    define CONST const
111 #endif /* emacs */
112
113 #ifdef XTOOLKIT
114 #    include <X11/Intrinsic.h>
115 #    include <audio/Xtutil.h>
116 #endif
117
118 #if defined (ROBUST_PLAY)
119 static AuBool CatchIoErrorAndJump (AuServer *aud);
120 static AuBool CatchErrorAndJump (AuServer *aud, AuErrorEvent *event);
121 SIGTYPE sigpipe_handle (int signo);
122 #endif
123
124 extern Lisp_Object Vsynchronous_sounds;
125
126 static Sound SoundOpenDataForReading (unsigned char *data, int length);
127
128 static AuServer       *aud;
129
130 /* count of sounds currently being played. */
131 static int sounds_in_play;
132
133
134 #ifdef XTOOLKIT
135 static Display *aud_server;
136 static XtInputId input_id;
137 #else
138 static char *aud_server;
139 #endif /* XTOOLKIT */
140
141 char *
142 init_play (
143 #ifdef XTOOLKIT
144            Display *display
145 #else
146            char *server
147 #endif
148            )
149 {
150   char *err_message;
151   SIGTYPE (*old_sigpipe) ();
152
153 #ifdef XTOOLKIT
154   char * server = DisplayString (display);
155   XtAppContext app_context = XtDisplayToApplicationContext (display);
156
157   aud_server = display;
158 #else
159
160   aud_server = server;
161 #endif
162
163 #ifdef ROBUST_PLAY
164   old_sigpipe = signal (SIGPIPE, sigpipe_handle);
165   if (setjmp (AuXtErrorJump))
166     {
167       signal (SIGPIPE, old_sigpipe);
168 #ifdef emacs
169       start_interrupts ();
170 #endif  
171       return "error in NAS";
172     }
173 #endif
174
175 #if defined (ROBUST_PLAY) && !defined (NAS_NO_ERROR_JUMP)
176   AuDefaultIOErrorHandler = CatchIoErrorAndJump;
177   AuDefaultErrorHandler = CatchErrorAndJump;
178 #endif
179
180 #ifdef emacs
181   stop_interrupts ();
182 #endif  
183   aud = AuOpenServer (server, 0, NULL, 0, NULL, &err_message);
184 #ifdef emacs
185   start_interrupts ();
186 #endif  
187   if (!aud)
188     {
189 #ifdef ROBUST_PLAY
190       signal (SIGPIPE, old_sigpipe);
191 #endif
192       if (err_message == NULL)
193         return "Can't connect to audio server";
194       else
195         return err_message;
196     }
197
198 #if defined (ROBUST_PLAY)
199 # if defined (NAS_NO_ERROR_JUMP)
200   aud->funcs.ioerror_handler = CatchIoErrorAndJump;
201   aud->funcs.error_handler = CatchErrorAndJump;
202 # else /* !NAS_NO_ERROR_JUMP */
203   AuDefaultIOErrorHandler = NULL;
204   AuDefaultErrorHandler = NULL;
205 # endif
206 #endif
207
208 #ifdef XTEVENTS
209   input_id = AuXtAppAddAudioHandler (app_context, aud); 
210 #endif
211
212 #ifdef CACHE_SOUNDS
213   AuSetCloseDownMode (aud, AuCloseDownRetainPermanent, NULL);
214 #endif
215
216 #ifdef ROBUST_PLAY
217   signal (SIGPIPE, old_sigpipe);
218 #endif
219
220   sounds_in_play = 0;
221
222   return NULL;
223 }
224
225 void
226 close_down_play (void)
227
228 {
229   AuCloseServer (aud);
230   warn ("disconnected from audio server");
231 }
232
233  /********************************************************************\
234  *                                                                    *
235  * Callback which is run when the sound finishes playing.             *
236  *                                                                    *
237  \********************************************************************/
238
239 static void
240 doneCB (AuServer       *aud,
241         AuEventHandlerRec *handler,
242         AuEvent        *ev,
243         AuPointer       data)
244 {
245   int         *in_play_p = (int *) data;
246
247   (*in_play_p) --;
248 }
249
250 #ifdef CACHE_SOUNDS
251
252  /********************************************************************\
253  *                                                                    *
254  * Play a sound by playing the relevant bucket, if any or             *
255  * downloading it if not.                                             *
256  *                                                                    *
257  \********************************************************************/
258
259 static void
260 do_caching_play (Sound s,
261                  int volume,
262                  unsigned char *buf)
263
264 {
265   AuBucketAttributes *list, b;
266   AuBucketID      id;
267   int n;
268
269   AuSetString (AuBucketDescription (&b),
270                AuStringLatin1, strlen (SoundComment (s)), SoundComment (s));
271
272   list = AuListBuckets (aud, AuCompCommonDescriptionMask, &b, &n, NULL);
273
274   if (list == NULL)
275     {
276       unsigned char *my_buf;
277
278       if (buf==NULL)
279         {
280           if ((my_buf=malloc (SoundNumBytes (s)))==NULL)
281             {
282               return;
283             }
284
285           if (SoundReadFile (my_buf, SoundNumBytes (s), s) != SoundNumBytes (s))
286             {
287               free (my_buf);
288               return;
289             }
290         }
291       else
292         my_buf=buf;
293
294       id = AuSoundCreateBucketFromData (aud, 
295                                         s,
296                                         my_buf,
297                                         AuAccessAllMasks, 
298                                         NULL,
299                                         NULL);
300       if (buf == NULL)
301         free (my_buf);
302     }
303   else /* found cached sound */
304     {
305       id = AuBucketIdentifier (list);
306       AuFreeBucketAttributes (aud, n, list);
307     }
308
309   sounds_in_play++;
310
311   AuSoundPlayFromBucket (aud, 
312                          id, 
313                          AuNone,
314                          AuFixedPointFromFraction (volume, 100), 
315                          doneCB, (AuPointer) &sounds_in_play,
316                          1,
317                          NULL, NULL,
318                          NULL, NULL);
319
320 }
321 #endif /* CACHE_SOUNDS */
322
323
324 void 
325 wait_for_sounds (void)
326
327 {
328   AuEvent         ev;
329
330   while (sounds_in_play>0)
331     {
332       AuNextEvent (aud, AuTrue, &ev);
333       AuDispatchEvent (aud, &ev);
334     }
335 }
336
337 int
338 play_sound_file (char *sound_file,
339                  int volume)
340 {
341   SIGTYPE (*old_sigpipe) ();
342
343 #ifdef ROBUST_PLAY
344   old_sigpipe=signal (SIGPIPE, sigpipe_handle);
345   if (setjmp (AuXtErrorJump))
346     {
347       signal (SIGPIPE, old_sigpipe);
348       return 0;
349     }
350 #endif
351
352   if (aud==NULL) {
353     if (aud_server != NULL)
354       {
355         char *m;
356         /* attempt to reconect */
357         if ((m=init_play (aud_server))!= NULL)
358           {
359
360 #ifdef ROBUST_PLAY
361             signal (SIGPIPE, old_sigpipe);
362 #endif
363             return 0;
364           }
365       }
366     else
367       {
368         warn ("Attempt to play with no audio init\n");
369 #ifdef ROBUST_PLAY
370         signal (SIGPIPE, old_sigpipe);
371 #endif
372         return 0;
373       }
374   }
375
376 #ifndef CACHE_SOUNDS
377   sounds_in_play++;
378   AuSoundPlayFromFile (aud,
379                        sound_file,
380                        AuNone,
381                        AuFixedPointFromFraction (volume,100),
382                        doneCB, (AuPointer) &sounds_in_play,
383                        NULL,
384                        NULL,
385                        NULL,
386                        NULL);
387 #else
388   /* Cache the sounds in buckets on the server */
389
390   {
391     Sound s;
392
393     if ((s = SoundOpenFileForReading (sound_file))==NULL)
394       {
395 #ifdef ROBUST_PLAY
396         signal (SIGPIPE, old_sigpipe);
397 #endif
398         return 0;
399       }
400
401     if (SoundComment (s) == NULL || SoundComment (s)[0] == '\0')
402       {
403         SoundComment (s) = FileCommentFromFilename (sound_file);
404       }
405
406     do_caching_play (s, volume, NULL);
407
408     SoundCloseFile (s);
409
410   }
411 #endif /* CACHE_SOUNDS */
412
413 #ifndef XTEVENTS
414   wait_for_sounds ();
415 #else
416   if (!NILP (Vsynchronous_sounds))
417     {
418       wait_for_sounds ();
419     }
420 #endif
421
422 #ifdef ROBUST_PLAY
423   signal (SIGPIPE, old_sigpipe);
424 #endif
425
426   return 1;
427 }
428
429 int
430 play_sound_data (unsigned char *data,
431                  int length, 
432                  int volume)
433 {
434   Sound s;
435   int offset;
436   SIGTYPE (*old_sigpipe) ();
437
438 #if !defined (XTEVENTS)
439   AuEvent         ev;
440 #endif
441
442 #ifdef ROBUST_PLAY
443   old_sigpipe = signal (SIGPIPE, sigpipe_handle);
444   if (setjmp (AuXtErrorJump) !=0)
445     {
446       signal (SIGPIPE, old_sigpipe);
447       return 0;
448     }
449 #endif
450
451
452   if (aud == NULL) {
453     if (aud_server != NULL)
454       {
455         char *m;
456         /* attempt to reconect */
457         if ((m = init_play (aud_server)) != NULL)
458           {
459 #ifdef ROBUST_PLAY
460             signal (SIGPIPE, old_sigpipe);
461 #endif
462             return 0;
463           }
464       }
465     else
466       {
467         warn ("Attempt to play with no audio init\n");
468 #ifdef ROBUST_PLAY
469         signal (SIGPIPE, old_sigpipe);
470 #endif
471         return 0;
472       }
473   }
474
475   if ((s=SoundOpenDataForReading (data, length))==NULL)
476     {
477       warn ("unknown sound type");
478 #ifdef ROBUST_PLAY
479       signal (SIGPIPE, old_sigpipe);
480 #endif
481       return 0;
482     }
483
484   if (SoundFileFormat (s) == SoundFileFormatSnd)
485     {
486       /* hack, hack */
487       offset = ((SndInfo *) (s->formatInfo))->h.dataOffset;
488     }
489   else
490     {
491       warn ("only understand snd files at the moment");
492       SoundCloseFile (s);
493 #ifdef ROBUST_PLAY
494       signal (SIGPIPE, old_sigpipe);
495 #endif
496       return 0;
497     }
498
499 #ifndef CACHE_SOUNDS
500   sounds_in_play++;
501   AuSoundPlayFromData (aud,
502                        s,
503                        data+offset,
504                        AuNone,
505                        AuFixedPointFromFraction (volume,100),
506                        doneCB, (AuPointer) &sounds_in_play,
507                        NULL,
508                        NULL,
509                        NULL,
510                        NULL);
511 #else
512   /* Cache the sounds in buckets on the server */
513
514   {
515     do_caching_play (s, volume, data+offset);
516   }
517 #endif /* CACHE_SOUNDS */
518
519
520 #ifndef XTEVENTS
521   wait_for_sounds ();
522 #else
523   if (!NILP (Vsynchronous_sounds))
524     {
525       wait_for_sounds ();
526     }
527 #endif
528
529   SoundCloseFile (s); 
530
531 #ifdef ROBUST_PLAY
532   signal (SIGPIPE, old_sigpipe);
533 #endif
534
535   return 1;
536 }
537
538 #if defined (ROBUST_PLAY)
539
540  /********************************************************************\
541  *                                                                    *
542  * Code to protect the client from server shutdowns.                  *
543  *                                                                    *
544  * This is unbelievably horrible.                                     *
545  *                                                                    *
546  \********************************************************************/
547
548 static AuBool
549 CatchIoErrorAndJump (AuServer *old_aud)
550 {
551   if (old_aud)
552     warn ("Audio Server connection broken"); 
553   else
554     warn ("Audio Server connection broken because of signal");
555
556 #ifdef XTEVENTS
557 #ifdef XTOOLKIT
558   {
559     AuXtAppRemoveAudioHandler (aud, input_id); 
560   }
561 #endif
562
563   if (aud)
564     AuCloseServer (aud);
565   aud = NULL;
566   sounds_in_play = 0;
567
568   longjmp (AuXtErrorJump, 1);
569
570 #else /* not XTEVENTS */
571
572   if (aud)
573     AuCloseServer (aud);
574   aud = NULL;
575   sounds_in_play = 0;
576   longjmp (AuXtErrorJump, 1);
577  
578 #endif /* XTEVENTS */
579 }
580
581 SIGTYPE
582 sigpipe_handle (int signo)
583 {
584   CatchIoErrorAndJump (NULL);
585 }
586
587 static AuBool
588 CatchErrorAndJump (AuServer *old_aud,
589                    AuErrorEvent *event)
590 {
591   return CatchIoErrorAndJump (old_aud);
592 }
593
594 #endif /* ROBUST_PLAY */
595
596  /********************************************************************\
597  *                                                                    *
598  * This code is here because the nas Sound library doesn't            *
599  * support playing from a file buffered in memory. It's a fairly      *
600  * direct translation of the file-based equivalent.                   *
601  *                                                                    *
602  * Since we don't have a filename, samples with no comment field      *
603  * are named by a section of their content.                           *
604  *                                                                    *
605  \********************************************************************/
606
607 /* Create a name from the sound. */
608
609 static char *
610 NameFromData (CONST unsigned char *buf,
611               int len)
612
613 {
614   unsigned char name[9];
615   int i;
616   char *s;
617
618   buf+=len/2;
619   len -= len/2;
620
621   i=0;
622   while (i<8 && len >0)
623     {
624       while (*buf < 32 && len>0)
625         {
626           buf++;
627           len--;
628         }
629       name[i]= *buf;
630       i++;
631       buf++;
632       len--;
633     }
634
635   name[i]='\0';
636
637   if (i==8)
638     {
639       strcpy (s=malloc (10), name);
640     }
641   else 
642     {
643       strcpy (s=malloc (15), "short sound");
644     }
645
646   return s;
647 }
648
649 /* Code to do a pseudo-open on a data buffer. Only for snd files at the
650    moment. 
651  */
652
653 static SndInfo *
654 SndOpenDataForReading (CONST char *data,
655                        int length)
656
657 {
658   SndInfo        *si;
659   int             size;
660
661   if (!(si = (SndInfo *) malloc (sizeof (SndInfo))))
662     return NULL;
663
664   si->comment = NULL;
665   si->writing = 0;
666
667   memcpy (&si->h, data, sizeof (SndHeader));
668
669   if (LITTLE_ENDIAN)
670     {
671       char            n;
672     
673       swapl (&si->h.magic, n);
674       swapl (&si->h.dataOffset, n);
675       swapl (&si->h.dataSize, n);
676       swapl (&si->h.format, n);
677       swapl (&si->h.sampleRate, n);
678       swapl (&si->h.tracks, n);
679     }
680
681   if (si->h.magic != SND_MAGIC_NUM)
682     {
683       free (si);
684       return NULL;
685     }
686
687   size = si->h.dataOffset - sizeof (SndHeader);
688
689   if (size)
690     {
691       if (!(si->comment = (char *) malloc (size + 1)))
692         {
693           free (si);
694           return NULL;
695         }
696
697       memcpy (si->comment,  data+sizeof (SndHeader), size);
698
699       *(si->comment + size) = 0;
700       if (*si->comment == '\0')
701         si->comment =
702           NameFromData (data+si->h.dataOffset, length-si->h.dataOffset);
703     }
704   else
705     si->comment = NameFromData (data+si->h.dataOffset, length-si->h.dataOffset);
706
707   si->h.dataSize = length-si->h.dataOffset;
708
709   si->fp=NULL;
710
711   return si;
712 }
713
714 static Sound
715 SoundOpenDataForReading (unsigned char *data,
716                          int length)
717
718 {
719   Sound s;
720
721   if (!(s = (Sound) malloc (sizeof (SoundRec))))
722     return NULL;
723
724   if ((s->formatInfo = SndOpenDataForReading (data, length))==NULL)
725     {
726       free (s);
727       return NULL;
728     }
729     
730
731   if (!(SoundFileInfo[SoundFileFormatSnd].toSound) (s))
732     {
733       SndCloseFile (s->formatInfo);
734       free (s);
735       return NULL;
736     }
737
738   return s;
739 }
740