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