1 
2 /*
3  * Copyright 2001, 2002 David J. Hawkey Jr.
4  *
5  * Permission to use, copy, modify, and distribute this software and its
6  * documentation for any purpose and without fee is hereby granted, provided
7  * that the above copyright notice appear in all copies and that both that
8  * copyright notice and this permission notice appear in supporting
9  * documentation, and that the name of the copyright holder or the author not
10  * be used in advertising or publicity pertaining to distribution of the
11  * software without specific, written prior permission. The copyright holder
12  * and the author make no representations about the suitability of this
13  * software for any purpose. It is provided "as is" without express or
14  * implied warranty.
15  *
16  * THE COPYRIGHT HOLDER AND THE AUTHOR DISCLAIM ALL WARRANTIES WITH REGARD
17  * TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
18  * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDER OR THE AUTHOR BE LIABLE
19  * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
20  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
21  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
22  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23  */
24 
25 /*
26  * sound.c
27  *
28  * D. J. Hawkey Jr. - 6/22/01 8/16/01 11/15/02
29  */
30 
31 #include "twm.h"
32 
33 #ifdef SOUND_SUPPORT
34 
35 #include <X11/Xmu/CharSet.h>
36 #include <unistd.h>
37 #include <string.h>
38 #include <stdlib.h>
39 #include "gram.h"
40 #include "parse.h"
41 #include "sound.h"
42 #include <stdint.h>
43 #include <fcntl.h>
44 #include <stdio.h>
45 #ifdef HAVE_RPLAY
46 #include <rplay.h>
47 #endif
48 
49 #ifndef MAXHOSTNAMELEN
50 #define MAXHOSTNAMELEN	256
51 #endif
52 
53 #define AUDIO_DEV "/dev/dsp"
54 extern int parse_keyword();	/* in parse.c */
55 extern void twmrc_error_prefix();	/* in gram.y */
56 
57 typedef struct sound_keyword
58 {
59   char *name;
60   int value;
61 } sound_keyword;
62 
63 static sound_keyword sound_keywords[] =
64 {
65   {"(vtwm start)",	S_START},
66   {"(vtwm stop)",	S_STOP},
67   {"(client map)",	S_CMAP},
68   {"(client unmap)",	S_CUNMAP},
69   {"(menu map)",	S_MMAP},
70   {"(menu unmap)",	S_MUNMAP},
71   {"(info unmap)",	S_IUNMAP},
72   {"(autopan event)",	S_APAN},
73   {"(bell event)",	S_BELL}
74 };
75 
76 #define MAX_SOUNDKEYWORDS (sizeof(sound_keywords) / sizeof(sound_keyword))
77 
78 #define SOUND_NONE 0
79 #define SOUND_ACTIVE 1
80 #define SOUND_RPLAY 2
81 #define SOUND_ESD 4
82 #define SOUND_OSS 8
83 typedef struct sound_entry
84 {
85   int func;
86 #ifdef HAVE_RPLAY
87   RPLAY *rp;
88 #endif
89 #ifdef HAVE_ESD
90   int esd_sound_id;
91 #endif
92   char *filename;
93   int volume;
94 } sound_entry;
95 
96 #ifdef HAVE_OSS
97 /* Stuff Related to reading .au and .wav files for use with OSS */
98 typedef struct
99 {
100   uint32_t DataStart;
101   uint32_t DataSize;
102   uint32_t Format;
103   uint32_t SampleRate;
104   uint32_t Channels;
105 } AUHeader;
106 
107 typedef struct
108 {
109   uint32_t size;
110   char Format[4];
111 } RIFFHeader;
112 
113 typedef struct
114 {
115   char ID[4];
116   uint32_t size;
117   uint16_t AudioFormat;
118   uint16_t Channels;
119   uint32_t SampleRate;
120   uint32_t ByteRate;
121   uint16_t BlockAlign;
122   uint16_t BitsPerSample;
123 } WAVHeader;
124 
125 typedef struct
126 {
127   char ID[4];
128   uint32_t size;
129 } WAVData;
130 
131 #define SAMPLE_SIGNED 1
132 
133 #endif
134 
135 static sound_entry *sound_entries = NULL;
136 static int sound_count = 0;
137 static int sound_size = 0;
138 
139 static int sound_fd = -1;
140 static int sound_vol = 63;	/* 1/4 attenuation */
141 static int sound_state = 0;
142 static char sound_host[MAXHOSTNAMELEN + 1] = "";
143 
144 /* for qsort() */
145 static int
compare(const void * p,const void * q)146 compare(const void *p, const void *q)
147 {
148   sound_entry *pp = (sound_entry *) p, *qq = (sound_entry *) q;
149 
150   return (pp->func - qq->func);
151 }
152 
153 static int
adjustVolume(int volume)154 adjustVolume(int volume)
155 {
156   float vol;
157 
158   if (volume > 100)
159     volume = 100;
160 
161   /* volume for rplay is 1 to 255, not 1 to 100 */
162   vol = (float) volume / 100.0;
163   volume = vol * 255.0;
164 
165   return (volume);
166 }
167 
168 void
SetSoundHost(char * host)169 SetSoundHost(char *host)
170 {
171   strncpy(sound_host, host, MAXHOSTNAMELEN);
172   sound_host[MAXHOSTNAMELEN] = '\0';
173 }
174 
175 void
SetSoundVolume(int volume)176 SetSoundVolume(int volume)
177 {
178   if (volume < 0)
179     volume = 0;
180   sound_vol = adjustVolume(volume);
181 }
182 
183 int
ToggleSounds()184 ToggleSounds()
185 {
186   return ((sound_state ^= 1));
187 }
188 
189 #ifdef HAVE_RPLAY
190 
191 static int
RPlayOpenSound()192 RPlayOpenSound()
193 {
194   if (sound_fd < 0)
195   {
196     if (sound_host[0] == '\0')
197     {
198       strncpy(sound_host, rplay_default_host(), MAXHOSTNAMELEN);
199       sound_host[MAXHOSTNAMELEN] = '\0';
200     }
201 
202     if ((sound_fd = rplay_open(sound_host)) >= 0)
203       sound_state = SOUND_ACTIVE | SOUND_RPLAY;
204   }
205 
206   return (sound_fd);
207 }
208 
209 static void
RPlayCloseSound()210 RPlayCloseSound()
211 {
212   int i;
213 
214   for (i = 0; i < sound_count; i++)
215     rplay_destroy(sound_entries[i].rp);
216 
217   if (sound_entries != NULL)
218 
219     if (sound_fd >= 0)
220       rplay_close(sound_fd);
221 }
222 
223 static int
RPlayPlaySoundAdhoc(char * filename)224 RPlayPlaySoundAdhoc(char *filename)
225 {
226   RPLAY *rp;
227   int i;
228   if (sound_fd < 0 || (!(sound_state & SOUND_ACTIVE)))
229     return (1);	/* pretend success */
230 
231   if ((rp = rplay_create(RPLAY_PLAY)) == NULL)
232   {
233     twmrc_error_prefix();
234     fprintf(stderr, "unable to create sound \"%s\"\n", filename);
235     return (0);
236   }
237 
238   if ((i = rplay_set(rp, RPLAY_INSERT, 0, RPLAY_SOUND, filename,
239 		     RPLAY_VOLUME, sound_vol, NULL)) >= 0)
240     rplay(sound_fd, rp);
241 
242   rplay_destroy(rp);
243   if (i < 0)
244   {
245     twmrc_error_prefix();
246     fprintf(stderr, "unable to set sound \"%s\"\n", filename);
247     return (0);
248   }
249 
250   return (1);
251 }
252 
253 #endif /* HAVE_RPLAY */
254 
255 
256 #ifdef HAVE_ESD
257 
258 #include <esd.h>
259 
260 static int
ESDOpenSound()261 ESDOpenSound()
262 {
263   sound_state = SOUND_ACTIVE | SOUND_ESD;
264   return(sound_fd);
265 }
266 
267 static void
ESDCloseSound()268 ESDCloseSound()
269 {
270 
271   esd_audio_close();
272   close(sound_fd);
273 }
274 
275 static int
ESDSendFileData(char * SoundFilePath)276 ESDSendFileData(char *SoundFilePath)
277 {
278   char *Tempstr = NULL;
279   int id;
280 
281   if (sound_fd == -1)
282   {
283     if (strlen(sound_host) == 0)
284       sound_fd = esd_open_sound(NULL);
285     else
286       sound_fd = esd_open_sound(NULL);
287   }
288 
289   Tempstr = calloc(strlen(SoundFilePath)+6, sizeof(char));
290   strcpy(Tempstr, "vtwm:");
291   strcat(Tempstr, SoundFilePath);
292 
293   id = esd_file_cache(sound_fd, "vtwm", SoundFilePath);
294   free(Tempstr);
295   return(id);
296 }
297 
298 static int
ESDPlaySound(int id,int volume)299 ESDPlaySound(int id, int volume)
300 {
301   if (volume != -1)
302     esd_set_default_sample_pan(sound_fd, id, volume, volume);
303   esd_sample_play(sound_fd, id);
304   return(1);
305 }
306 
307 static int
ESDPlaySoundAdhoc(char * filename)308 ESDPlaySoundAdhoc(char *filename)
309 {
310   /*if (volume !=-1) esd_set_default_sample_pan(sound_fd,id,volume,volume);*/
311   esd_play_file("vtwm", filename,1);
312   return(1);
313 }
314 
315 #endif
316 
317 #ifdef HAVE_OSS
318 
319 #include <sys/soundcard.h>
320 #include <sys/ioctl.h>
321 
322 typedef struct
323 {
324   unsigned int Format;
325   unsigned int Channels;
326   unsigned int SampleRate;
327   unsigned int SampleSize;
328   unsigned int DataSize;
329 } TAudioInfo;
330 
331 static TAudioInfo *
ReadWAV(int fd)332 ReadWAV(int fd)
333 {
334   RIFFHeader Riff;
335   WAVHeader Wav;
336   WAVData Data;
337   TAudioInfo *AudioInfo;
338 
339   if (!(AudioInfo = (TAudioInfo *) calloc(1,sizeof(TAudioInfo))))
340     return(NULL);
341   if (read(fd, &Riff, sizeof(RIFFHeader)) != sizeof(RIFFHeader) ||
342       read(fd, &Wav, sizeof(WAVHeader)) != sizeof(WAVHeader) ||
343       read(fd, &Data, sizeof(WAVData)) != sizeof(WAVData))
344   {
345     free(AudioInfo);
346     return(NULL);
347   }
348   AudioInfo->Channels = Wav.Channels;
349   AudioInfo->SampleRate = Wav.SampleRate;
350   AudioInfo->DataSize = 0xFFFFFFFF;
351   if (Wav.BitsPerSample == 16)
352   {
353     AudioInfo->SampleSize = 2;
354     AudioInfo->Format = AFMT_S16_LE;
355   }
356   else
357   {
358     AudioInfo->SampleSize = 1;
359     AudioInfo->Format = AFMT_U8;
360   }
361   return(AudioInfo);
362 }
363 
364 static TAudioInfo *
ReadAU(int fd)365 ReadAU(int fd)
366 {
367   AUHeader Header;
368   TAudioInfo *AudioInfo;
369 
370   if (!(AudioInfo = (TAudioInfo *)calloc(1, sizeof(TAudioInfo))))
371     return(NULL);
372   if (read(fd, &Header, sizeof(AUHeader)) != sizeof(AUHeader))
373     goto error;
374   /* AU uses big endian */
375   Header.DataSize = htonl(Header.DataSize);
376   Header.DataStart = htonl(Header.DataStart);
377   Header.Channels = htonl(Header.Channels);
378   Header.SampleRate = htonl(Header.SampleRate);
379   Header.Format = htonl(Header.Format);
380   AudioInfo->Channels = Header.Channels;
381   AudioInfo->SampleRate = Header.SampleRate;
382   AudioInfo->DataSize = Header.DataSize;
383   switch(Header.Format)
384   {
385     case 1:
386       AudioInfo->Format = AFMT_MU_LAW;
387       break;
388     case 2:
389       AudioInfo->Format = AFMT_S8;
390       break;
391     case 3:
392       AudioInfo->Format = AFMT_S16_BE;
393       break;
394   }
395   if (lseek(fd, Header.DataStart, SEEK_SET) == -1)
396     goto error;
397 
398   return(AudioInfo);
399 
400  error:
401   if (AudioInfo)
402     free(AudioInfo);
403   return(NULL);
404 }
405 
406 static int
OSSOpenSound()407 OSSOpenSound()
408 {
409   if (access(AUDIO_DEV, W_OK) == 0)
410   {
411     sound_state = SOUND_ACTIVE | SOUND_OSS;
412     /*
413        this is a bit worrying, as sound_fd is now set to
414        be channel 1, (stdout), but we cannot hold the sound
415        device open under oss, as we will block other things
416        from accessing it
417      */
418     return(1);
419   }
420   return(-1);
421 }
422 
423 static void
OSSCloseSound()424 OSSCloseSound()
425 {
426 }
427 
428 static int
OSSPlaySound(char * filename,int volume)429 OSSPlaySound(char *filename, int volume)
430 {
431   int fd, mixfd, soundfilefd, i;
432   unsigned char *data = NULL;
433   int Flags, val, oldvol, result;
434   char FourCharacter[5];
435   TAudioInfo *AudioInfo = NULL;
436   int buflen = BUFSIZ;
437 
438   i = fork();
439   if (i == 0)
440   {
441     fd = open(AUDIO_DEV,O_WRONLY);
442     if (fd == -1)
443       _exit(0);
444     soundfilefd = open(filename, O_RDONLY);
445     if (soundfilefd == -1)
446       return(0);
447     if (read(soundfilefd, FourCharacter, 4) != 4)
448       goto error;
449     FourCharacter[4] = '\0';
450     if (strcmp(FourCharacter, ".snd") == 0)
451       AudioInfo = ReadAU(soundfilefd);
452     else if (strcmp(FourCharacter, "RIFF") == 0)
453       AudioInfo = ReadWAV(soundfilefd);
454     if (AudioInfo == NULL)
455     {
456     error:
457       close(soundfilefd);
458       _exit(0);
459     }
460     /* Tell the sound card that the sound about to be played is stereo. 0=mono 1=stereo */
461     val = AudioInfo->Channels-1;
462     if (val < 0)
463       val = 0;
464     if (ioctl(fd, SNDCTL_DSP_STEREO, &val) == -1)
465     {
466       perror("ioctl stereo");
467       _exit(0);
468     }
469     /* Inform the sound card of the audio format */
470     if (ioctl(fd, SNDCTL_DSP_SETFMT, &AudioInfo->Format) == -1)
471     {
472       perror("ioctl format");
473       _exit(0);
474     }
475 
476     /* Set the DSP playback rate, sampling rate of the raw PCM audio */
477     if (ioctl(fd, SNDCTL_DSP_SPEED, &AudioInfo->SampleRate) == -1)
478     {
479       perror("ioctl sample rate");
480       _exit(0);
481     }
482     mixfd = open(AUDIO_DEV, O_RDWR);
483     if (mixfd > -1)
484     {
485       ioctl(mixfd, SOUND_MIXER_READ_PCM, &oldvol);
486       result = sound_vol;
487       val = (result) | (result <<8);
488       ioctl(mixfd, SOUND_MIXER_WRITE_PCM, &val);
489     }
490     data = (char *)malloc(buflen);
491     result = read(soundfilefd, data, buflen);
492     val = 0;
493     while ((result > 0) && (val < AudioInfo->DataSize))
494     {
495       int written = 0;
496       while (result > written)
497       {
498 	if ((written = write(fd, data, result)) < 1)
499 	  break;
500       }
501       val += result;
502       result = read(soundfilefd, data, buflen);
503     }
504 
505     close(fd);
506     if (mixfd > -1)
507     {
508       ioctl(mixfd, SOUND_MIXER_WRITE_PCM, &oldvol);
509       close(mixfd);
510     }
511 
512     close(soundfilefd);
513     _exit(0);
514   }
515 
516 }
517 
518 static int
OSSPlaySoundAdhoc(char * filename)519 OSSPlaySoundAdhoc(char *filename)
520 {
521   return(OSSPlaySound(filename, sound_vol));
522 }
523 
524 #endif /* HAVE_OSS */
525 
526 int
OpenSound()527 OpenSound()
528 {
529   int fd = -1;
530 #ifdef HAVE_RPLAY
531   fd=RPlayOpenSound();
532 #endif
533 #ifdef HAVE_ESD
534   if (fd == -1)
535     fd = ESDOpenSound();
536 #endif
537 #ifdef HAVE_OSS
538   if (fd == -1)
539     fd = OSSOpenSound();
540 #endif
541   if (fd > -1)
542   {
543     qsort((void *)sound_entries, (size_t)sound_count,
544 	  (size_t)sizeof(sound_entry), compare);
545   }
546   return(fd);
547 }
548 
549 void
CloseSound()550 CloseSound()
551 {
552 #ifdef HAVE_RPLAY
553   if (sound_state & SOUND_RPLAY)
554     RPlayCloseSound();
555 #endif
556 #ifdef HAVE_ESD
557   if (sound_state & SOUND_ESD)
558     ESDCloseSound();
559 #endif
560 #ifdef HAVE_OSS
561   if (sound_state & SOUND_OSS)
562     OSSCloseSound();
563 #endif
564   free((void *) sound_entries);
565   sound_entries = NULL;
566   sound_count = 0;
567   sound_size = 0;
568   sound_fd = -1;
569   sound_vol = 63;
570   sound_state = 0;
571   sound_host[0] = '\0';
572 }
573 
574 int
SetSound(char * function,char * filename,int volume)575 SetSound(char *function, char *filename, int volume)
576 {
577   sound_entry *sptr;
578   int i, func, subfunc;
579 
580   func = parse_keyword(function, &subfunc);
581   if (func != FKEYWORD && func != FSKEYWORD)
582   {
583     XmuCopyISOLatin1Lowered(function, function);
584     for (i = 0; i < MAX_SOUNDKEYWORDS; i++)
585       if (strcmp(function, sound_keywords[i].name) == 0)
586       {
587 	func = FKEYWORD;
588 	subfunc = sound_keywords[i].value;
589 	break;
590       }
591   }
592   if (func == FKEYWORD || func == FSKEYWORD)
593   {
594     if (sound_count >= sound_size)
595     {
596       sound_size += 10;
597       sptr = (sound_entry *) realloc((sound_entry *) sound_entries,
598 				     sound_size * sizeof(sound_entry));
599       if (sptr == NULL)
600       {
601 	twmrc_error_prefix();
602 	fprintf(stderr,
603 		"unable to allocate %lu bytes for sound_entries\n",
604 		(unsigned long)sound_size * sizeof(sound_entry));
605 	Done(0);
606       }
607       else
608 	sound_entries = sptr;
609     }
610     sptr = &sound_entries[sound_count];
611     sptr->func = subfunc;
612     sptr->filename = strdup(filename);
613     if (volume < 0)
614       sptr->volume = sound_vol;
615     else
616       sptr->volume = adjustVolume(volume);
617 #ifdef HAVE_RPLAY
618     if ((sptr->rp = rplay_create(RPLAY_PLAY)) == NULL)
619     {
620       twmrc_error_prefix();
621       fprintf(stderr, "unable to add to sound list\n");
622       Done(0);
623     }
624     if (rplay_set(sptr->rp, RPLAY_INSERT, 0, RPLAY_SOUND, sptr->filename,
625 		  RPLAY_VOLUME, sptr->volume, NULL) < 0)
626     {
627       twmrc_error_prefix();
628       fprintf(stderr, "unable to set \"%s\" in sound list\n", filename);
629       Done(0);
630     }
631 #endif
632 #ifdef HAVE_ESD
633     sptr->esd_sound_id = ESDSendFileData(filename);
634     if (sptr->esd_sound_id < 0)
635     {
636       twmrc_error_prefix();
637       fprintf(stderr, "unable to set \"%s\" in sound list\n", filename);
638       Done(0);
639     }
640 #endif
641     sound_count++;
642     return (1);
643   }
644 
645   twmrc_error_prefix();
646   fprintf(stderr, "unknown function \"%s\" for sound_entry\n", function);
647   return (0);
648 }
649 
650 int
PlaySound(int function)651 PlaySound(int function)
652 {
653   register int i, low, mid, high;
654   int result;
655 
656   if (!(sound_state & SOUND_ACTIVE))
657     return (1);	/* pretend success */
658   low = 0;
659   high = sound_count - 1;
660   while (low <= high)
661   {
662     mid = (low + high) / 2;
663     i = sound_entries[mid].func - function;
664     if (i < 0)
665       low = mid + 1;
666     else if (i > 0)
667       high = mid - 1;
668     else
669     {
670 #ifdef HAVE_RPLAY
671       if (sound_state & SOUND_RPLAY)
672       {
673 	rplay(sound_fd, sound_entries[mid].rp);
674 	return(1);
675       }
676 #endif
677 #ifdef HAVE_ESD
678       if (sound_state & SOUND_ESD)
679 	return(ESDPlaySound(sound_entries[mid].esd_sound_id,
680 			    sound_entries[mid].volume));
681 #endif
682 #ifdef HAVE_OSS
683       if (sound_state & SOUND_OSS)
684 	return(OSSPlaySound(sound_entries[mid].filename,
685 			    sound_entries[mid].volume));
686 #endif
687       /* we found a file to play, but no player to send it to pretend success */
688       return(1);
689     }
690   }
691   return(0);
692 }
693 
694 
695 
696 int
PlaySoundAdhoc(char * filename)697 PlaySoundAdhoc(char *filename)
698 {
699 #ifdef HAVE_RPLAY
700   if ((sound_state & SOUND_RPLAY) && RPlayPlaySoundAdhoc(filename))
701     return(1);
702 #endif
703 #ifdef HAVE_ESD
704   if ((sound_state & SOUND_ESD) && ESDPlaySoundAdhoc(filename))
705     return(1);
706 #endif
707 #ifdef HAVE_OSS
708   if ((sound_state & SOUND_OSS) && OSSPlaySoundAdhoc(filename))
709     return(1);
710 #endif
711   return(0);
712 }
713 
714 #else	/* SOUND_SUPPORT */
715 
716 /* stub function for gram.y */
717 int
SetSound(char * function,char * filename,int volume)718 SetSound(char *function, char *filename, int volume)
719 {
720   return (1);
721 }
722 
723 #endif	/* SOUND_SUPPORT */
724 
725 /*
726    Local Variables:
727 mode:c
728 c-file-style:"GNU"
729 c-file-offsets:((substatement-open 0)(brace-list-open 0)(c-hanging-comment-ender-p . nil)(c-hanging-comment-beginner-p . nil)(comment-start . "// ")(comment-end . "")(comment-column . 48))
730 End:
731  */
732 /* vim: sw=2
733  */
734