1 // Emacs style mode select   -*- C++ -*-
2 //-----------------------------------------------------------------------------
3 //
4 // $Id: i_sound.c 1471 2019-10-04 08:59:55Z wesleyjohnson $
5 //
6 // Copyright (C) 1993-1996 by id Software, Inc.
7 // Portions Copyright (C) 1998-2016 by DooM Legacy Team.
8 //
9 // This program is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU General Public License
11 // as published by the Free Software Foundation; either version 2
12 // of the License, or (at your option) any later version.
13 //
14 // This program is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 // GNU General Public License for more details.
18 //
19 //
20 // $Log: i_sound.c,v $
21 // Revision 1.13  2004/05/13 11:09:38  andyp
22 // Removed extern int errno references for Linux
23 //
24 // Revision 1.12  2004/04/17 12:55:27  hurdler
25 // now compile with gcc 3.3.3 under Linux
26 //
27 // Revision 1.11  2003/07/13 13:16:15  hurdler
28 // go RC1
29 //
30 // Revision 1.10  2003/01/19 21:24:26  bock
31 // Make sources buildable on FreeBSD 5-CURRENT.
32 //
33 // Revision 1.9  2002/07/01 19:59:59  metzgermeister
34 // *** empty log message ***
35 //
36 // Revision 1.8  2001/08/20 20:40:42  metzgermeister
37 // *** empty log message ***
38 //
39 // Revision 1.7  2001/05/16 22:33:35  bock
40 // Initial FreeBSD support.
41 //
42 // Revision 1.6  2000/08/11 19:11:07  metzgermeister
43 // *** empty log message ***
44 //
45 // Revision 1.5  2000/04/30 19:47:38  metzgermeister
46 // iwad support
47 //
48 // Revision 1.4  2000/03/28 16:18:42  linuxcub
49 // Added a command to the Linux sound-server which sets a master volume.
50 // Someone needs to check that this isn't too much of a performance drop
51 // on slow machines. (Works for me).
52 //
53 // Added code to the main parts of doomlegacy which uses this command to
54 // implement volume control for sound effects.
55 //
56 // Added code so the (really cool) cd music works for me. The volume didn't
57 // work for me (with a Teac 532E drive): It always started at max (31) no-
58 // matter what the setting in the config-file was. The added code "jiggles"
59 // the volume-control, and now it works for me :-)
60 // If this code is unacceptable, perhaps another solution is to periodically
61 // compare the cd_volume.value with an actual value _read_ from the drive.
62 // Ie. not trusting that calling the ioctl with the correct value actually
63 // sets the hardware-volume to the requested value. Right now, the ioctl
64 // is assumed to work perfectly, and the value in cd_volume.value is
65 // compared periodically with cdvolume.
66 //
67 // Updated the spec file, so an updated RPM can easily be built, with
68 // a minimum of editing. Where can I upload my pre-built (S)RPMS to ?
69 //
70 // Erling Jacobsen, linuxcub@email.dk
71 //
72 // Revision 1.3  2000/03/12 23:21:10  linuxcub
73 // Added consvars which hold the filenames and arguments which will be used
74 // when running the soundserver and musicserver (under Linux). I hope I
75 // didn't break anything ... Erling Jacobsen, linuxcub@email.dk
76 //
77 // Revision 1.2  2000/02/27 00:42:11  hurdler
78 // fix CR+LF problem
79 //
80 // Revision 1.1.1.1  2000/02/22 20:32:33  hurdler
81 // Initial import into CVS (v1.29 pr3)
82 //
83 //
84 // DESCRIPTION:
85 //      System interface for sound.
86 //
87 //-----------------------------------------------------------------------------
88 
89 #include "doomincl.h"
90   // stdio, stdlib
91 
92 #include <stdio.h>
93 #include <stdlib.h>
94 #include <stdarg.h>
95 
96 #include <math.h>
97 
98 #include <sys/time.h>
99 #include <sys/types.h>
100 
101 #if !defined(LINUX) && !defined(SCOOS5) && !defined(_AIX)
102 #include <sys/filio.h>
103 #endif
104 
105 #include <fcntl.h>
106 #include <unistd.h>
107 #include <sys/ioctl.h>
108 #include <sys/msg.h>
109 
110 // Linux voxware output.
111 #ifdef LINUX
112 #ifdef FREEBSD
113 #include <sys/soundcard.h>
114 #else
115 #include <linux/soundcard.h>
116 #endif
117 #endif
118 
119 // SCO OS5 and Unixware OSS sound output
120 #if defined(SCOOS5) || defined(SCOUW2) || defined(SCOUW7)
121 #include <sys/soundcard.h>
122 #endif
123 
124 // Timer stuff. Experimental.
125 #include <time.h>
126 #include <signal.h>
127 
128 // for IPC between xdoom and musserver
129 #include <sys/ipc.h>
130 
131 #include <errno.h>
132 
133 #include "doomstat.h"
134   // nomusic
135   // nosoundfx
136   // Flags for the -nosound and -nomusic options
137 
138 #include "i_system.h"
139 #include "i_sound.h"
140 #include "s_sound.h"
141 #include "m_argv.h"
142 #include "m_misc.h"
143 #include "w_wad.h"
144 
145 #include "searchp.h"
146 #include "d_main.h"
147 #include "z_zone.h"
148 
149 #include "musserv/musserver.h"
150 
151 
152 // #define DEBUG_SFX_PIPE
153 
154 // Master hardware sound volume.
155 static int hw_sndvolume = 31;
156 
157 // UNIX hack, to be removed.
158 #ifdef SNDSERV
159 #include "sndserv/soundsrv.h"
160 
161 static FILE *    sndserver = 0;
162 static uint16_t  handle_cnt = 0;
163 
164 #elif SNDINTR
165 
166 // Update all 30 millisecs, approx. 30fps synchronized.
167 // Linux resolution is allegedly 10 millisecs,
168 //  scale is microseconds.
169 #define SOUND_INTERVAL     10000
170 
171 #else
172 // None?
173 #endif
174 
175 // UNIX hack too, unlikely to be removed.
176 #ifdef MUSSERV
177 static int musserver = -1;
178 static int msg_id = -1;
179 #endif
180 
181 
182 // Flag to signal CD audio support to not play a title
183 //int playing_title;
184 
185 
186 // The number of internal mixing channels,
187 //  the samples calculated for each mixing step,
188 //  the size of the 16bit, 2 hardware channel (stereo)
189 //  mixing buffer, and the samplerate of the raw data.
190 
191 // Needed for calling the actual sound output.
192 #define NUM_CHANNELS            16
193 #define CHANNEL_NUM_MASK  (NUM_CHANNELS-1)
194 
195 
196 #ifndef SNDSERV
197 
198 #define SAMPLECOUNT             1024
199 #define SAMPLERATE              11025   // Hz
200 #define SAMPLESIZE              2       // 16bit
201 // It is 2 for 16bit, and 2 for two channels.
202 #define BUFMUL                  4
203 #define MIXBUFFERSIZE           (SAMPLECOUNT*BUFMUL)
204 
205 
206 // The actual output device and a flag for using 8bit samples.
207 static int audio_fd;
208 static byte audio_8bit_flag;
209 
210 
211 
212 typedef struct {
213     // the channel data, current position
214     byte *  data;  // NULL=inactive
215     // the channel data end pointer
216     byte *  data_end;
217     // the channel step amount
218     unsigned int step;
219     // 0.16 bit remainder of last step
220     unsigned int remainder;
221     // volumes
222     int  left_volume, right_volume;
223     // the channel volume lookup, modifed by master volume
224     int  * left_vol_tab, * right_vol_tab;
225     // Time/gametic that the channel started playing,
226     //  used to determine oldest, which automatically has lowest priority.
227     // In case number of active sounds exceeds available channels.
228     int  start_time;
229     // The channel handle, determined on registration,
230     //  might be used to unregister/stop/modify.
231     // Lowest bits are the channel num.
232     int  handle;
233     // SFX id of the playing sound effect.
234     // Used to catch duplicates (like chainsaw).
235     uint16_t  id;
236 
237 #ifdef SURROUND_SOUND
238     byte  invert_right;
239 #endif
240 
241 } channel_info_t;
242 
243 static channel_info_t   channel[NUM_CHANNELS];
244 
245 // The global mixing buffer.
246 // Basically, samples from all active internal channels
247 //  are modifed and added, and stored in the buffer
248 //  that is submitted to the audio device.
249 static int16_t  mixbuffer[MIXBUFFERSIZE];
250 
251 
252 // Pitch to stepping lookup, unused.
253 static int steptable[256];
254 
255 // Volume lookups.
256 static int vol_lookup[128][256];
257 
258 #endif
259 
260 #ifndef SNDSERV
261 //
262 // Safe ioctl, convenience.
263 //
myioctl(int fd,int command,int * arg)264 void myioctl(int fd, int command, int *arg)
265 {
266     static byte  ioctl_err_count = 0;
267     static byte  ioctl_err_off = 0;
268     int rc;
269 
270     if( ioctl_err_off )
271     {
272         ioctl_err_off--;
273         return;
274     }
275 
276     rc = ioctl(fd, command, arg);
277     if (rc < 0)
278     {
279         GenPrintf(EMSG_error, "ioctl(dsp,%d,arg) failed\n", command);
280         GenPrintf(EMSG_error, "errno=%d\n", errno);
281         // [WDJ] No unnecessary fatal exits, let the player savegame.
282         if( ioctl_err_count < 254 )
283             ioctl_err_count++;
284         if( ioctl_err_count > 10 )
285             ioctl_err_off = 20;
286 
287 //        exit(-1);
288     }
289 }
290 #endif
291 
292 
293 // Interface
294 // This function loads the sound data from the WAD lump,
295 //  for single sound.
296 //
I_GetSfx(sfxinfo_t * sfx)297 void I_GetSfx(sfxinfo_t * sfx)
298 {
299     byte *dssfx;
300     int size;
301 
302 
303     S_GetSfxLump( sfx ); // lump to sfx
304     // Linked sounds will reuse the data.
305     // Can set data to NULL, but cannot change its format.
306 
307     dssfx = (byte *) sfx->data;
308     if( ! dssfx )  return;
309 
310     // Sound data header format.
311     // 0,1: 03
312     // 2,3: sample rate (11,2B)=11025, (56,22)=22050
313     // 4,5: number of samples
314     // 6,7: 00
315     size = sfx->length;
316     if( size <= 8 )
317     {
318         size = 8;
319         GenPrintf( EMSG_warn, "GetSfx, short sound: %s\n", sfx->name );
320     }
321 
322 #ifdef SNDSERV
323     // write data to llsndserv 19990201 by Kin
324     if (sndserver)
325     {
326         server_load_sound_t  sls;
327         // Send sound data to server.
328         sls.flags = sfx->flags;
329         // The sound server does not need padded sound, and if it did
330         // it could more easily do that itself.
331         sls.snd_len = size - 8;
332         // [WDJ] No longer send volume with load, as it interferes with
333         // the automated volume update.
334         sls.id = sfx - S_sfx;
335 
336 #ifdef  DEBUG_SFX_PIPE
337         GenPrintf( EMSG_debug," Command L:  Sfx  size=%x\n", sfx->length );
338         GenPrintf( EMSG_debug," Load sound, sfx=%i, snd_len=%i  flagx=%x\n ", sls.id, sls.snd_len, sls.flags );
339 #endif
340         // sfx data loaded to sound server at sfx id.
341         fputc('l', sndserver);
342         fwrite((byte*)&sls, sizeof(sls), 1, sndserver);  // sfx id, flags, size
343         fwrite(&dssfx[8], 1, sls.snd_len, sndserver);
344         fflush(sndserver);
345     }
346 #else
347 #ifdef  HAVE_ALLEGRO
348     // convert raw data and header from Doom sfx to a SAMPLE for Allegro
349     // Linked sound will already be padded.
350     int sampsize = ((size - 8 + (SAMPLECOUNT - 1)) / SAMPLECOUNT) * SAMPLECOUNT;
351     int reqsize = sampsize + 8;
352     if( reqsize > size )
353     {
354         // Only reallocate when necessary.
355         byte *paddedsfx = (byte *) Z_Malloc(reqsize, PU_STATIC, 0);
356         memcpy(paddedsfx, dssfx, size);
357         for (i = size; i < reqsize; i++)
358             paddedsfx[i] = 128;
359         sfx->data = (void *) paddedsfx;
360         sfx->length = reqsize;
361         // Remove the cached lump.
362         Z_Free(dssfx);
363         dssfx = paddedsfx;
364     }
365     *((uint32_t *) dssfx) = sampsize;
366 #endif
367 #endif
368 }
369 
I_FreeSfx(sfxinfo_t * sfx)370 void I_FreeSfx(sfxinfo_t * sfx)
371 {
372     // normal free
373 }
374 
375 
376 #ifndef SNDSERV
377 //
378 // This function adds a sound to the
379 //  list of currently active sounds,
380 //  which is maintained as a given number
381 //  (eight, usually) of internal channels.
382 // Returns a handle.
383 //
384 //  vol : volume, 0..255
385 //  sep : separation, +/- 127, SURROUND_SEP special operation
386 static
addsfx_ch(int sfxid,int vol,int step,int sep)387 int addsfx_ch(int sfxid, int vol, int step, int sep)
388 {
389     channel_info_t * chp, * chp2;
390     int i, oldest;
391     int slot;
392     int leftvol, rightvol;
393 
394     // Chainsaw troubles.
395     // Play these sound effects only one at a time.
396     if (S_sfx[sfxid].flags & SFX_single)
397     {
398         // Loop all channels, check.
399         for( chp2 = &channel[0]; chp2 < &channel[cv_numChannels.value]; chp2++ )
400         {
401             // Active, and using the same SFX?
402             if (chp2->data && (chp2->id == sfxid))
403             {
404                 if( S_sfx[sfxid].flags & SFX_id_fin )
405                     return chp2->handle;  // already have one
406                 // Kill, Reset.
407                 chp2->data = NULL;  // close existing channel
408                 break;
409             }
410         }
411     }
412 
413     // Find inactive channel, or oldest channel.
414     chp = &channel[0];  // default
415     oldest = INT_MAX;
416     for ( chp2 = &channel[0]; chp2 < &channel[cv_numChannels.value]; chp2++ )
417     {
418         if( chp2->data == NULL )
419         {
420             chp = chp2;  // Inactive channel
421             break;
422         }
423         if( chp2->start_time < oldest )
424         {
425             chp = chp2;  // older channel
426             oldest = chp->start_time;
427         }
428     }
429 
430     // Okay, in the less recent channel,
431     //  we will handle the new SFX.
432     // Preserve sound SFX id,
433     //  e.g. for avoiding duplicates of chainsaw.
434     chp->id = sfxid;
435     // Set pointer to raw data.
436     chp->data = & S_sfx[sfxid].data[8];  // after header
437     // Set pointer to end of raw data.
438     chp->data_end = chp->data + S_sfx[sfxid].length - 8; // without header
439 
440     // Set stepping
441     // Kinda getting the impression this is never used.
442     chp->step = step;
443     chp->remainder = 0;
444     // Should be gametic, I presume.
445     chp->start_time = gametic;
446 
447     // Per left/right channel.
448     //  x^2 seperation,
449     //  adjust volume properly.
450 
451 #ifdef SURROUND_SOUND
452     chp->invert_right = 0;
453     if( sep == SURROUND_SEP )
454     {
455         // Use a normal sound data for the left channel (with pan left)
456         // and an inverted sound data for the right channel (with pan right)
457         leftvol = rightvol = (vol * (224 * 224)) >> 16;  // slight reduction going through panning
458         chp->invert_right = 1;  // invert right channel
459     }
460     else
461 #endif
462     {
463         // Separation, that is, orientation/stereo.
464         // sep : +/- 127, <0 is left, >0 is right
465         sep += 129;  // 129 +/- 127 ; ( 1 - 256 )
466         leftvol = vol - ((vol * sep * sep) >> 16);
467         sep = 258 - sep;  // 129 +/- 127
468         rightvol = vol - ((vol * sep * sep) >> 16);
469     }
470 
471     // Sanity check, clamp volume.
472     if (rightvol < 0 || rightvol > 127)
473     {
474         I_SoftError("rightvol out of bounds\n");
475         rightvol = ( rightvol < 0 ) ? 0 : 127;
476     }
477 
478     if (leftvol < 0 || leftvol > 127)
479     {
480         I_SoftError("leftvol out of bounds\n");
481         leftvol = ( leftvol < 0 ) ? 0 : 127;
482     }
483 
484     // Get the proper lookup table for this volume level.
485     chp->left_volume = leftvol;
486     chp->left_vol_tab = &vol_lookup[(leftvol * hw_sndvolume / 31)][0];
487     chp->right_volume = rightvol;
488     chp->right_vol_tab = &vol_lookup[(rightvol * hw_sndvolume / 31)][0];
489 
490     // Assign current handle number.
491     // Preserved so sounds could be stopped (unused).
492     chp->handle = slot | ((chp->handle + NUM_CHANNELS) & ~CHANNEL_NUM_MASK);
493     return chp->handle;
494 }
495 #endif
496 
497 #ifndef SNDSERV
498 //
499 // SFX API
500 // Note: this was called by S_Init.
501 // However, whatever they did in the old DPMS based DOS version, this
502 // were simply dummies in the Linux version.
503 // See soundserver initdata().
504 //
I_SetChannels()505 void I_SetChannels()
506 {
507     // Init internal lookups (raw data, mixing buffer, channels).
508     // This function sets up internal lookups used during
509     //  the mixing process.
510     int i;
511     int j;
512 
513 
514     int *steptablemid = steptable + 128;
515 
516     memset( channel, 0, sizeof(channel) );
517 
518     // This table provides step widths for pitch parameters.
519     // I fail to see that this is currently used.
520     for (i = -128; i < 128; i++)
521         steptablemid[i] = (int) (pow(2.0, (i / 64.0)) * 65536.0);
522 
523     // Generates volume lookup tables
524     //  which also turn the unsigned samples
525     //  into signed samples.
526     for (i = 0; i < 128; i++)
527     {
528         for (j = 0; j < 256; j++)
529         {
530             if (!audio_8bit_flag)
531                 vol_lookup[i][j] = (i * (j - 128) * 256) / 127;
532             else
533                 vol_lookup[i][j] = (i * (j - 128) * 256) / 127 * 4;
534         }
535     }
536 }
537 #endif
538 
539 
540 // new_volume : 0..31
I_SetSfxVolume(int volume)541 void I_SetSfxVolume(int volume)
542 {
543     // Identical to DOS.
544     // Basically, this should propagate the menu/config file setting
545     //  to the state variable used in the mixing.
546 
547 #ifdef  DEBUG_SFX_PIPE
548         GenPrintf( EMSG_debug," Testing:  volume=%i\n", hw_sndvolume );
549 #endif
550 #ifdef SNDSERV
551     hw_sndvolume = volume;
552 
553     if (sndserver)
554     {
555 #ifdef  DEBUG_SFX_PIPE
556         GenPrintf( EMSG_debug," Command V:  volume=%i\n", hw_sndvolume );
557 #endif
558         fputc('v', sndserver);
559         fputc((byte) hw_sndvolume, sndserver);
560         fflush(sndserver);
561     }
562 #else
563 
564     if( volume == hw_sndvolume )
565        return;
566 
567     if( volume > 31 )  volume = 31;
568     hw_sndvolume = volume;
569 
570     // Update existing channel volumes.
571     register channel_info_t * chp;
572     for( chp = &channel[0]; chp < &channel[cv_numChannels.value]; chp++ )
573     {
574         if( chp->data )
575         {
576             chp->left_vol_tab = &volume_lookup[(chp->left_volume * volume)/31][0];
577             chp->right_vol_tab = &volume_lookup[(chp->right_volume * volume)/31][0];
578         }
579     }
580 }
581 #endif
582 }
583 
584 
585 
586 //
587 // Starting a sound means adding it
588 //  to the current list of active sounds
589 //  in the internal channels.
590 // As the SFX info struct contains
591 //  e.g. a pointer to the raw data,
592 //  it is ignored.
593 // As our sound handling does not handle
594 //  priority, it is ignored.
595 // Pitching (that is, increased speed of playback)
596 //  is set, but currently not used by mixing.
597 //
598 // Starts a sound in a particular sound channel.
599 //  vol : 0..255
600 // Return a handle to the sound.
601 int I_StartSound ( sfxid_t sfxid, int vol, int sep, int pitch, int priority )
602 {
603     // UNUSED
604     priority = 0;
605     vol = vol >> 4;     // xdoom only accept 0-15 19990124 by Kin
606 
607     if (nosoundfx)
608         return 0;
609 
610 #ifdef SNDSERV
611     if (sndserver)
612     {
613         server_play_sound_t  sps;
614 
615         sps.sfxid = sfxid;
616         sps.vol = vol;
617         sps.pitch = pitch;
618         sps.sep = sep;
619         sps.handle = handle_cnt++;
620 
621 #ifdef  DEBUG_SFX_PIPE
622         GenPrintf( EMSG_debug," Command P:" );
623         GenPrintf( EMSG_debug," Play sound, sfx=%i, vol=%i, pitch=%i, sep=%i, handle=%i\n ", sps.sfxid, sps.vol, sps.pitch, sps.sep, sps.handle );
624 #endif
625         // play sound
626         fputc('p', sndserver);
627         fwrite((byte*)&sps, sizeof(sps), 1, sndserver);
628         fflush(sndserver);
629         return sps.handle;
630     }
631     return 0;
632 #else
633     // Debug.
634     //GenPrintf(EMSG_debug, "starting sound %d", id );
635 
636     // Returns a handle.
637     int handle = addsfx_ch(id, vol, steptable[pitch], sep);
638 
639     //GenPrintf(EMSG_debug, "/handle is %d\n", id );
640 
641     return handle;
642 #endif
643 }
644 
645 // You need the handle returned by StartSound.
646 void I_StopSound(int handle)
647 {
648 #ifdef SNDSERV
649     uint16_t  handle16 = handle;
650 
651 #ifdef  DEBUG_SFX_PIPE
652     GenPrintf( EMSG_debug," Command S:  Stop  handle=%i\n", handle );
653 #endif
654 
655     // Send stop sound.
656     fputc('s', sndserver);
657     fwrite(&handle16, sizeof(uint16_t), 1, sndserver);  // handle
658     fflush(sndserver);
659 #else
660     int slot = handle & CHANNEL_NUM_MASK;
661     if (channel[slot].handle == handle)
662     {
663         channel[i].data = NULL;
664     }
665 #endif
666 }
667 
668 int I_SoundIsPlaying(int handle)
669 {
670 #ifdef SNDSERV
671     return (handle_cnt - ((uint16_t)handle)) < 8;  // guess
672 #else
673     int slot = handle & CHANNEL_NUM_MASK;
674     if (channel[slot].handle == handle)
675     {
676 #if 1
677         return ( channel[chan].data != NULL );
678 
679 #else
680         // old code
681             return 1;
682 #endif
683     }
684     return 0;
685 #endif
686 }
687 
688 
689 
690 #ifdef SNDINTR
691 // Get the interrupt. Set duration in millisecs.
692 int I_SoundSetTimer(int duration_of_tick);
693 void I_SoundDelTimer(void);
694 #endif
695 
696 #ifndef SNDSERV
697 // A quick hack to establish a protocol between
698 // synchronous mix buffer updates and asynchronous
699 // audio writes. Probably redundant with gametic.
700 volatile static int mix_cnt = 0;
701 
702 //
703 // This function loops all active (internal) sound
704 //  channels, retrieves a given number of samples
705 //  from the raw sound data, modifies it according
706 //  to the current (internal) channel parameters,
707 //  mixes the per channel samples into the global
708 //  mixbuffer, clamping it to the allowed range,
709 //  and sets up everything for transferring the
710 //  contents of the mixbuffer to the (two)
711 //  hardware channels (left and right, that is).
712 //
713 void I_UpdateSound(void)
714 {
715     // Debug. Count buffer misses with interrupt.
716     static int misses = 0;
717 
718     // Flag. Will be set if the mixing buffer really gets updated.
719     byte updated = 0;
720 
721     channel_info_t * chp;
722 
723     // Mix current sound data.
724     // Data, from raw sound, for right and left.
725     register unsigned int sample;
726     register int dl, dr;
727     uint16_t  sdl, sdr;
728 
729     // Pointers in global mixbuffer, left, right, end.
730     int16_t * leftout;
731     int16_t * rightout;
732     int16_t * leftend;
733     byte * bothout;
734 
735     // Step in mixbuffer, left and right, thus two.
736     int step;
737 
738 
739     if (dedicated)
740         return;
741 
742     // Left and right channel
743     //  are in global mixbuffer, alternating.
744     leftout = mixbuffer;
745     rightout = mixbuffer + 1;
746     bothout = (byte *) mixbuffer;
747     step = 2;
748 
749     // Determine end, for left channel only
750     //  (right channel is implicit).
751     leftend = mixbuffer + SAMPLECOUNT * step;
752 
753     // Mix sounds into the mixing buffer.
754     // Loop over step*SAMPLECOUNT.
755     while (leftout != leftend)
756     {
757         // Reset left/right value.
758         dl = 0;
759         dr = 0;
760 
761         // Love thy L2 chache - made this a loop.
762         // Now more channels could be set at compile time
763         //  as well. Thus loop those  channels.
764         for( chp = &channel[0]; chp < &channel[cv_numChannels.value]; chp++ )
765         {
766             // Check channel, if active.
767             if (chp->data)
768             {
769                 // we are updating the mixer buffer, set flag
770                 updated = 1;
771                 // Get the raw data from the channel.
772                 sample = * chp->data;
773                 // Add left and right part for this channel (sound)
774                 //  to the current data.
775                 // Adjust volume accordingly.
776                 dl += chp->left_vol_tab[sample];
777 #ifdef SURROUND_SOUND
778                 if( chp->invert_right )
779                   dr -= chp->right_vol_tab[sample];
780                 else
781                   dr += chp->right_vol_tab[sample];
782 #else
783                 dr += chp->right_vol_tab[sample];
784 #endif
785                 // Increment fixed point index
786                 chp->remainder += chp->step;
787                 // MSB is next sample
788                 chp->data += chp->remainder >> 16;
789                 // Keep the fractional index part
790                 chp->remainder &= 0xFFFF;
791 
792                 // Check whether we are done.
793                 if (chp->data >= chp->data_end)
794                     chp->data = NULL;
795             }
796         }
797 
798         // Clamp to range. Left hardware channel.
799         // Has been char instead of int16.
800         // if (dl > 127) *leftout = 127;
801         // else if (dl < -128) *leftout = -128;
802         // else *leftout = dl;
803 
804         if (!audio_8bit_flag)
805         {
806             if (dl > 0x7fff)
807                 *leftout = 0x7fff;
808             else if (dl < -0x8000)
809                 *leftout = -0x8000;
810             else
811                 *leftout = dl;
812 
813             // Same for right hardware channel.
814             if (dr > 0x7fff)
815                 *rightout = 0x7fff;
816             else if (dr < -0x8000)
817                 *rightout = -0x8000;
818             else
819                 *rightout = dr;
820         }
821         else
822         {
823             if (dl > 0x7fff)
824                 dl = 0x7fff;
825             else if (dl < -0x8000)
826                 dl = -0x8000;
827             sdl = dl ^ 0xfff8000;
828 
829             if (dr > 0x7fff)
830                 dr = 0x7fff;
831             else if (dr < -0x8000)
832                 dr = -0x8000;
833             sdr = dr ^ 0xfff8000;
834 
835             *bothout++ = (((sdr + sdl) / 2) >> 8);
836         }
837 
838         // Increment current pointers in mixbuffer.
839         leftout += step;
840         rightout += step;
841     }
842 
843     if (updated)
844     {
845         // Debug check.
846         if (mix_cnt)
847         {
848             misses += mix_cnt;
849             mix_cnt = 0;
850         }
851 
852         if (misses > 10)
853         {
854             GenPrintf(EMSG_warn, "I_SoundUpdate: missed 10 buffer writes\n");
855             misses = 0;
856         }
857 
858         // Increment mix_cnt for update.
859         mix_cnt++;
860     }
861 }
862 #endif
863 
864 #ifdef SNDSERV
865 // [WDJ] Fix this in d_main.c
866 void I_SubmitSound(void)
867 {
868 }
869 #else
870 // This is used to write out the mixbuffer
871 //  during each game loop update.
872 //
873 void I_SubmitSound(void)
874 {
875     if (dedicated)
876         return;
877 
878     // Write it to DSP device.
879     if (mix_cnt)
880     {
881         if (!audio_8bit_flag)
882             write(audio_fd, mixbuffer, SAMPLECOUNT * BUFMUL);
883         else
884             write(audio_fd, mixbuffer, SAMPLECOUNT);
885         mix_cnt = 0;
886     }
887 }
888 #endif
889 
890 
891 
892 // Interface
893 void I_UpdateSoundParams(int handle, int vol, int sep, int pitch)
894 {
895     // I fail too see that this is used.
896     // Would be using the handle to identify
897     //  on which channel the sound might be active,
898     //  and resetting the channel parameters.
899 
900     // UNUSED.
901     handle = vol = sep = pitch = 0;
902 }
903 
904 
905 static
906 void LX_ShutdownSound(void)
907 {
908 #ifdef SNDSERV
909     if (sndserver)
910     {
911 #ifdef  DEBUG_SFX_PIPE
912         GenPrintf( EMSG_debug," Command Q:\n" );
913 #endif
914         // Send a "quit" command.
915         fputc('q', sndserver);
916         fflush(sndserver);
917     }
918 
919 #else
920 
921     // Wait till all pending sounds are finished.
922     int done = 0;
923     int i;
924 
925     if (nosoundfx)
926         return;
927 
928 #ifdef SNDINTR
929     I_SoundDelTimer();
930 #endif
931 
932     while (!done)
933     {
934         for (i = 0; i < cv_numChannels.value; i++)
935            if( channel[i].data ) break;  // any busy channel
936         if (i == cv_numChannels.value)
937             done++;
938         else
939         {
940             I_UpdateSound();
941             I_SubmitSound();
942         }
943     }
944 
945     // Cleaning up -releasing the DSP device.
946     close(audio_fd);
947 #endif
948 
949     // Done.
950     return;
951 }
952 
953 
954 static
955 void LX_InitSound()
956 {
957 #ifdef SNDSERV
958     char buffer[2048];
959     char *fn_snd;
960 
961     fn_snd = searchpath(cv_sndserver_cmd.string);
962 
963     // start sound process
964     if (!access(fn_snd, X_OK))
965     {
966         sprintf(buffer, "%s %s", fn_snd, cv_sndserver_arg.string);
967         sndserver = popen(buffer, "w");
968 #ifdef  DEBUG_SFX_PIPE
969         GenPrintf( EMSG_debug," Started Sound Server:\n" );
970 #endif
971     }
972     else
973         GenPrintf(EMSG_error, "Could not start sound server [%s]\n", fn_snd);
974 #else
975 
976     int i;
977 
978     if (nosoundfx)
979         return;
980 
981     // Secure and configure sound device first.
982     GenPrintf(EMSG_info, "LX_InitSound: ");
983 
984     audio_fd = open("/dev/dsp", O_WRONLY);
985     if (audio_fd < 0)
986     {
987         GenPrintf(EMSG_error, "Could not open /dev/dsp\n");
988         nosoundfx++;
989         return;
990     }
991 
992 #ifdef SOUND_RESET
993     myioctl(audio_fd, SNDCTL_DSP_RESET, 0);
994 #endif
995 
996     audio_8bit_flag = 1;  // default
997     if (getenv("DOOM_SOUND_SAMPLEBITS") == NULL)
998     {
999         myioctl(audio_fd, SNDCTL_DSP_GETFMTS, &i);
1000         if (i &= AFMT_S16_LE)
1001         {
1002             audio_8bit_flag = 0;
1003             myioctl(audio_fd, SNDCTL_DSP_SETFMT, &i);
1004             i = 11 | (2 << 16);
1005             myioctl(audio_fd, SNDCTL_DSP_SETFRAGMENT, &i);
1006             i = 1;
1007             myioctl(audio_fd, SNDCTL_DSP_STEREO, &i);
1008         }
1009     }
1010 
1011     if( audio_8bit_flag )  // default
1012     {
1013         i = AFMT_U8;
1014         myioctl(audio_fd, SNDCTL_DSP_SETFMT, &i);
1015         i = 10 | (2 << 16);
1016         myioctl(audio_fd, SNDCTL_DSP_SETFRAGMENT, &i);
1017     }
1018 
1019     i = SAMPLERATE;
1020     myioctl(audio_fd, SNDCTL_DSP_SPEED, &i);
1021 
1022     GenPrintf(EMSG_info, " configured %dbit audio device\n", (audio_8bit_flag) ? 8 : 16);
1023 
1024 #ifdef SNDINTR
1025     GenPrintf(EMSG_info, "I_SoundSetTimer: %d microsecs\n", SOUND_INTERVAL);
1026     I_SoundSetTimer(SOUND_INTERVAL);
1027 #endif
1028 
1029     // Initialize external data (all sounds) at start, keep static.
1030     GenPrintf(EMSG_info, "LX_InitSound: ");
1031 
1032     // Do we have a sound lump for the chaingun?
1033     if (W_CheckNumForName("dschgun") == -1)
1034     {
1035         // No, so link it to the pistol sound
1036         //S_sfx[sfx_chgun].link = &S_sfx[sfx_pistol];
1037         //S_sfx[sfx_chgun].pitch = 150;
1038         //S_sfx[sfx_chgun].volume = 0;
1039         //S_sfx[sfx_chgun].data = 0;
1040         GenPrintf(EMSG_info, "linking chaingun sound to pistol sound,");
1041     }
1042     else
1043     {
1044         GenPrintf(EMSG_info, "found chaingun sound,");
1045     }
1046 
1047     GenPrintf(EMSG_info, " pre-cached all sound data\n");
1048 
1049     // Now initialize mixbuffer with zero.
1050     for (i = 0; i < MIXBUFFERSIZE; i++)
1051         mixbuffer[i] = 0;
1052 
1053     // Finished initialization.
1054     GenPrintf(EMSG_info, "LX_InitSound: sound module ready\n");
1055 
1056 #endif
1057 }
1058 
1059 
1060 // --- Music
1061 //#define DEBUG_MUSSERV
1062 
1063 #ifdef MUSSERV
1064 mus_msg_t  msg_buffer;
1065 
1066 void send_val_musserver( char command, char sub_command, int val )
1067 {
1068     if( msg_id < 0 )  return;
1069 
1070     msg_buffer.mtype = 5;
1071     memset(msg_buffer.mtext, 0, MUS_MSG_MTEXT_LENGTH);
1072     snprintf(msg_buffer.mtext, MUS_MSG_MTEXT_LENGTH-1, "%c%c%i",
1073              command, sub_command, val);
1074     msg_buffer.mtext[MUS_MSG_MTEXT_LENGTH-1] = 0;
1075 #ifdef  DEBUG_MUSSERV
1076     GenPrintf( EMSG_debug, "Send musserver: %s\n", msg_buffer.mtext );
1077 #endif
1078     msgsnd(msg_id, MSGBUF(msg_buffer), 12, IPC_NOWAIT);
1079     usleep(2);  // just enough for musserver to respond promptly.
1080 }
1081 #endif
1082 
1083 // Music volume may be set before calling LX_InitMusic.
1084 static int music_volume = 0;
1085 
1086 //
1087 // MUSIC API.
1088 // Music done now, we'll use Michael Heasley's musserver.
1089 //
1090 static
1091 void LX_InitMusic(void)
1092 {
1093 #ifdef MUSSERV
1094     char buffer[MAX_WADPATH];
1095     char *fn_mus;
1096 
1097     fn_mus = searchpath(cv_musserver_cmd.string);
1098 
1099     // Try to start the music server process.
1100     if ( access(fn_mus, X_OK) < 0)
1101     {
1102         GenPrintf(EMSG_error, "Could not find music server [%s]\n", fn_mus);
1103         return;
1104     }
1105 
1106     // [WDJ] Use IPC for settings, not command line.
1107     snprintf(buffer, MAX_WADPATH-1, "%s %s &", fn_mus, cv_musserver_arg.string);
1108     buffer[MAX_WADPATH-1] = 0;
1109 
1110     GenPrintf( EMSG_info, "Starting music server [%s]\n", buffer);
1111     // Sys call "system()"  seems to work, and does not need \n.
1112     // It returns 0 on success.
1113     musserver = system(buffer);
1114     if( musserver < 0 )
1115     {
1116         GenPrintf( EMSG_error, "Could not start music server [%s]\n", fn_mus);
1117         return;
1118     }
1119 
1120     msg_id = msgget(MUSSERVER_MSG_KEY, IPC_CREAT | 0777);
1121     if( verbose > 1 )
1122         GenPrintf( EMSG_info, "Started Musicserver = %i, IPC = %i\n", musserver, msg_id );
1123     send_val_musserver( 'v', ' ', music_volume );
1124     // [WDJ] Starting with system() gives the process a PPID of 1, which is Init.
1125     // When DoomLegacy is killed, it is not detected by musserver.
1126     send_val_musserver( 'I', ' ', getpid() ); // our pid
1127     // Send this again because it was too early at configure time.
1128     I_SetMusicOption();
1129 #endif
1130 }
1131 
1132 void LX_ShutdownMusic(void)
1133 {
1134     if (nomusic)
1135         return;
1136 
1137 #ifdef MUSSERV
1138     // [WDJ] It is a race between the quit command and the queue destruction.
1139     // Rely upon one or the other.
1140 #if 1
1141     // send a "quit" command.
1142     send_val_musserver( 'Q', 'Q', 0 );
1143 #else
1144     if (musserver > -1)
1145     {
1146         // Close the queue.
1147         if (msg_id != -1)
1148             msgctl(msg_id, IPC_RMID, (struct msqid_ds *) NULL);
1149     }
1150 #endif
1151 #endif
1152 }
1153 
1154 
1155 // MUSIC API
1156 void I_SetMusicVolume(int volume)
1157 {
1158     // Internal state variable.
1159     music_volume = volume;
1160     // Now set volume on output device.
1161     // Whatever( snd_MusciVolume );
1162 
1163     if (nomusic)
1164         return;
1165 
1166 #ifdef MUSSERV
1167     send_val_musserver( 'v', ' ', volume );
1168 #endif
1169 }
1170 
1171 // MUSIC API
1172 
1173 char * mus_ipc_opt_tab[] = {
1174   "-dd", // Default
1175   "-da", // Search 1
1176   "-db", // Search 2
1177   "-dc", // Search 3
1178   "-dM", // Midi
1179   "-dT", // TiMidity
1180   "-dL", // FluidSynth
1181   "-dE", // Ext Midi
1182   "-dS", // Synth
1183   "-dF", // FM Synth
1184   "-dA", // Awe32 Synth
1185   "-dg", // Dev6
1186   "-dh", // Dev7
1187   "-dj", // Dev8
1188   "-dk"  // Dev9
1189 };
1190 
1191 void I_SetMusicOption(void)
1192 {
1193     byte bi = cv_musserver_opt.value;
1194 
1195     if( msg_id < 0 )  return;
1196     if( bi > 16 )  return;
1197 
1198     msg_buffer.mtype = 6;
1199     memset(msg_buffer.mtext, 0, MUS_MSG_MTEXT_LENGTH);
1200     snprintf(msg_buffer.mtext, MUS_MSG_MTEXT_LENGTH-1, "O%s", mus_ipc_opt_tab[bi] );
1201     msg_buffer.mtext[MUS_MSG_MTEXT_LENGTH-1] = 0;
1202 #ifdef  DEBUG_MUSSERV
1203     GenPrintf( EMSG_debug, "Send musserver option: %s\n", msg_buffer.mtext );
1204 #endif
1205     msg_buffer.mtext[MUS_MSG_MTEXT_LENGTH-1] = 0;
1206     msgsnd(msg_id, MSGBUF(msg_buffer), MUS_MSG_MTEXT_LENGTH, IPC_NOWAIT);
1207 }
1208 
1209 
1210 
1211 static byte music_looping = 0;
1212 static int music_dies = -1;
1213 
1214 
1215 
1216 void I_PauseSong(int handle)
1217 {
1218     if (nomusic)
1219         return;
1220 
1221 #ifdef MUSSERV
1222     send_val_musserver( 'P', 'P', 1 );
1223 #endif
1224     handle = 0;  // UNUSED
1225 }
1226 
1227 void I_ResumeSong(int handle)
1228 {
1229     if (nomusic)
1230         return;
1231 
1232 #ifdef MUSSERV
1233     send_val_musserver( 'P', 'R', 0 );
1234 #endif
1235     handle = 0;  // UNUSED
1236 }
1237 
1238 void I_StopSong(int handle)
1239 {
1240     if (nomusic)
1241         return;
1242 
1243 #ifdef MUSSERV
1244     send_val_musserver( 'X', 'X', 0 );
1245 #endif
1246     handle = 0; // UNUSED.
1247     music_looping = 0;
1248     music_dies = 0;
1249 }
1250 
1251 void I_UnRegisterSong(int handle)
1252 {
1253     handle = 0; // UNUSED.
1254 }
1255 
1256 
1257 #ifdef MUSSERV
1258 // Information for ports with music servers.
1259 //  name : name of song
1260 // Return handle
1261 int I_PlayServerSong( char * name, lumpnum_t lumpnum, byte looping )
1262 {
1263     if (nomusic)
1264     {
1265         return 1;
1266     }
1267 
1268     music_dies = gametic + (TICRATE * 30);
1269 
1270     music_looping = looping;
1271 
1272     if (msg_id != -1)
1273     {
1274         static  byte sent_genmidi = 0;
1275         wadfile_t * wadp;
1276 
1277         msg_buffer.mtype = 6;
1278         if( sent_genmidi == 0 )
1279         {
1280             sent_genmidi = 1;
1281             // Music server needs the GENMIDI lump, which may depend
1282             // upon the IWAD and PWAD order.
1283             lumpnum_t  genmidi_lumpnum = W_GetNumForName( "GENMIDI" );
1284             wadp = lumpnum_to_wad( genmidi_lumpnum );
1285             if( wadp )
1286             {
1287                 memset(msg_buffer.mtext, 0, MUS_MSG_MTEXT_LENGTH);
1288                 snprintf(msg_buffer.mtext, MUS_MSG_MTEXT_LENGTH-1, "W%s", wadp->filename);
1289                 msg_buffer.mtext[MUS_MSG_MTEXT_LENGTH-1] = 0;
1290 #ifdef  DEBUG_MUSSERV
1291                 GenPrintf( EMSG_debug, "Send musserver wad: %s\n", msg_buffer.mtext );
1292 #endif
1293                 msgsnd(msg_id, MSGBUF(msg_buffer), MUS_MSG_MTEXT_LENGTH, IPC_NOWAIT);
1294             }
1295             // Sending genmidi lumpnum to musserver.
1296             send_val_musserver( 'G', ' ', LUMPNUM(genmidi_lumpnum) );
1297         }
1298         // Send song name to musserver
1299         memset(msg_buffer.mtext, 0, MUS_MSG_MTEXT_LENGTH);
1300         sprintf(msg_buffer.mtext, "D %s", name);
1301 #ifdef  DEBUG_MUSSERV
1302         GenPrintf( EMSG_debug, "Send musserver song: %s\n", msg_buffer.mtext );
1303 #endif
1304         msgsnd(msg_id, MSGBUF(msg_buffer), 12, IPC_NOWAIT);
1305         // Song info
1306         wadp = lumpnum_to_wad( lumpnum );
1307         if( wadp )
1308         {
1309             // Send song wad information to server
1310             memset(msg_buffer.mtext, 0, MUS_MSG_MTEXT_LENGTH);
1311             snprintf(msg_buffer.mtext, MUS_MSG_MTEXT_LENGTH-1, "W%s", wadp->filename);
1312             msg_buffer.mtext[MUS_MSG_MTEXT_LENGTH-1] = 0;
1313 #ifdef  DEBUG_MUSSERV
1314             GenPrintf( EMSG_debug, "Send musserver wad: %s\n", msg_buffer.mtext );
1315 #endif
1316             msgsnd(msg_id, MSGBUF(msg_buffer), MUS_MSG_MTEXT_LENGTH, IPC_NOWAIT);
1317         }
1318         // Sending song lumpnum to musserver.
1319         send_val_musserver( 'S', (looping?'C':' '), LUMPNUM(lumpnum) );
1320     }
1321     return 1;
1322 }
1323 
1324 
1325 #else
1326 // not MUSSERV
1327 
1328 // Interface
1329 int I_RegisterSong( void* data, int len )
1330 {
1331     if (nomusic)
1332     {
1333         return 1;
1334     }
1335 
1336     data = NULL;
1337     return 1;
1338 }
1339 
1340 // Interface
1341 void I_PlaySong(int handle, int looping)
1342 {
1343     music_dies = gametic + (TICRATE * 30);
1344 
1345     if (nomusic)
1346         return;
1347 
1348     music_looping = looping;
1349     handle = 0;
1350 }
1351 #endif
1352 
1353 
1354 #if 0
1355 // Disabled call, no interface.
1356 // Is the song playing?
1357 int I_QrySongPlaying(int handle)
1358 {
1359     handle = 0;  // UNUSED
1360     return music_looping || (music_dies > gametic);
1361 }
1362 #endif
1363 
1364 
1365 //--- Sound system Interface
1366 // Interface, Start sound system.
1367 void I_StartupSound()
1368 {
1369    if( dedicated )
1370        return;
1371 
1372    if(! nosoundfx)
1373        LX_InitSound();
1374    if(! nomusic )
1375        LX_InitMusic();
1376 }
1377 
1378 // Interface, Shutdown sound system.
1379 void I_ShutdownSound(void)
1380 {
1381    LX_ShutdownSound();
1382    LX_ShutdownMusic();
1383 }
1384 
1385 
1386 #ifdef SNDINTR
1387 //
1388 // Experimental stuff.
1389 // A Linux timer interrupt, for asynchronous
1390 //  sound output.
1391 // I ripped this out of the Timer class in
1392 //  our Difference Engine, including a few
1393 //  SUN remains...
1394 //
1395 #ifdef sun
1396 typedef sigset_t tSigSet;
1397 #else
1398 typedef int tSigSet;
1399 #endif
1400 
1401 // We might use SIGVTALRM and ITIMER_VIRTUAL, if the process
1402 //  time independend timer happens to get lost due to heavy load.
1403 // SIGALRM and ITIMER_REAL doesn't really work well.
1404 // There are issues with profiling as well.
1405 
1406 //static int /*__itimer_which*/  itimer = ITIMER_REAL;
1407 static int /*__itimer_which*/ itimer = ITIMER_VIRTUAL;
1408 
1409 //static int sig = SIGALRM;
1410 static int sig = SIGVTALRM;
1411 
1412 // Interrupt handler.
1413 static void I_HandleSoundTimer(int ignore)
1414 {
1415     // Debug.
1416     //GenPrintf(EMSG_debug, "%c", '+' ); fflush( stderr );
1417 
1418     // Feed sound device if necesary.
1419     if (mix_cnt)
1420     {
1421         // See I_SubmitSound().
1422         // Write it to DSP device.
1423         if (!audio_8bit_flag)
1424             write(audio_fd, mixbuffer, SAMPLECOUNT * BUFMUL);
1425         else
1426             write(audio_fd, mixbuffer, SAMPLECOUNT);
1427 
1428         // Reset flag counter.
1429         mix_cnt = 0;
1430     }
1431     else
1432         return;
1433 
1434     // UNUSED, but required.
1435     ignore = 0;
1436     return;
1437 }
1438 
1439 // Get the interrupt. Set duration in millisecs.
1440 static int I_SoundSetTimer(int duration_of_tick)
1441 {
1442     // Needed for gametick clockwork.
1443     struct itimerval value;
1444     struct itimerval ovalue;
1445     struct sigaction act;
1446     struct sigaction oact;
1447 
1448     int res;
1449 
1450     // This sets to SA_ONESHOT and SA_NOMASK, thus we can not use it.
1451     //     signal( _sig, handle_SIG_TICK );
1452 
1453     // Now we have to change this attribute for repeated calls.
1454     act.sa_handler = I_HandleSoundTimer;
1455 #ifndef sun
1456     //ac  t.sa_mask = _sig;
1457 #endif
1458     act.sa_flags = SA_RESTART;
1459 
1460     sigaction(sig, &act, &oact);
1461 
1462     value.it_interval.tv_sec = 0;
1463     value.it_interval.tv_usec = duration_of_tick;
1464     value.it_value.tv_sec = 0;
1465     value.it_value.tv_usec = duration_of_tick;
1466 
1467     // Error is -1.
1468     res = setitimer(itimer, &value, &ovalue);
1469 
1470     // Debug.
1471     if (res == -1)
1472         GenPrintf(EMSG_debug, "I_SoundSetTimer: interrupt n.a.\n");
1473 
1474     return res;
1475 }
1476 
1477 // Remove the interrupt. Set duration to zero.
1478 static void I_SoundDelTimer()
1479 {
1480     // Debug.
1481     if (I_SoundSetTimer(0) == -1)
1482         GenPrintf(EMSG_debug, "I_SoundDelTimer: failed to remove interrupt. Doh!\n");
1483 }
1484 #endif
1485 
1486 #ifdef FMOD_SONG
1487 //Hurdler: TODO
1488 void I_StartFMODSong()
1489 {
1490     CONS_Printf("I_StartFMODSong: Not yet supported under Linux.\n");
1491 }
1492 
1493 void I_StopFMODSong()
1494 {
1495     CONS_Printf("I_StopFMODSong: Not yet supported under Linux.\n");
1496 }
1497 void I_SetFMODVolume(int volume)
1498 {
1499     CONS_Printf("I_SetFMODVolume: Not yet supported under Linux.\n");
1500 }
1501 #endif
1502