1 // Emacs style mode select   -*- C++ -*-
2 //-----------------------------------------------------------------------------
3 //
4 // $Id: win_snd.c 1417 2019-01-29 08:00:14Z wesleyjohnson $
5 //
6 // Copyright (C) 1998-2016 by DooM Legacy Team.
7 //
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU General Public License
10 // as published by the Free Software Foundation; either version 2
11 // of the License, or (at your option) any later version.
12 //
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 // GNU General Public License for more details.
17 //
18 //
19 // $Log: win_snd.c,v $
20 // Revision 1.22  2007/01/29 06:16:03  chiphog
21 // Possible fix for playing raw MIDI lumps for the os2 and win32 builds.
22 //
23 // Revision 1.21  2004/07/27 08:19:39  exl
24 // New fmod, fs functions, bugfix or 2, patrol nodes
25 //
26 // Revision 1.20  2003/03/06 22:47:06  hurdler
27 // Add SSNTails fmod code
28 //
29 // Revision 1.18  2002/12/13 22:36:14  ssntails
30 // MP3/OGG support!
31 //
32 // Revision 1.17  2002/10/07 19:27:29  judgecutor
33 //
34 // Revision 1.16  2002/08/16 20:20:54  judgecutor
35 // Added sound pitching
36 //
37 // Revision 1.15  2002/01/21 23:20:12  judgecutor
38 // Temporary fixing MIDI bug.
39 // Added support for an extarnal sound driver
40 //
41 // Revision 1.14  2001/05/27 13:42:48  bpereira
42 // Revision 1.13  2001/04/08 10:15:54  bpereira
43 //
44 // Revision 1.12  2001/04/04 20:19:07  judgecutor
45 // Added support for the 3D Sound
46 //
47 // Revision 1.11  2001/01/25 22:15:45  bpereira
48 // added heretic support
49 //
50 // Revision 1.10  2001/01/21 04:33:35  judgecutor
51 //
52 // Revision 1.9  2000/10/27 20:38:21  judgecutor
53 // - Added the SurroundSound support
54 //
55 // Revision 1.8  2000/10/23 17:05:00  judgecutor
56 // Fixed old bug of midi stream
57 //
58 // Revision 1.7  2000/10/08 13:30:03  bpereira
59 // Revision 1.6  2000/09/28 20:57:22  bpereira
60 // Revision 1.5  2000/09/01 19:34:38  bpereira
61 // Revision 1.4  2000/08/10 19:58:05  bpereira
62 // Revision 1.3  2000/04/16 18:38:07  bpereira
63 // Revision 1.2  2000/02/27 00:42:12  hurdler
64 // Revision 1.1.1.1  2000/02/22 20:32:33  hurdler
65 // Initial import into CVS (v1.29 pr3)
66 //
67 //
68 // DESCRIPTION:
69 //      interface level code for sound
70 //      uses the midiStream* Win32 functions to play MIDI data with low latency and low
71 //      processor overhead.
72 //
73 //-----------------------------------------------------------------------------
74 
75 // Because of WINVER redefine, doomtype.h (via doomincl.h) is before any
76 // other include that might define WINVER
77 #include "doomincl.h"
78 
79 #include "win_main.h"
80 #include <mmsystem.h>
81 
82 // DirectX
83 #define DXVERSION
84 #ifdef __MINGW32__
85 // blocks duplicate definition of LPDIRECTFULLDUPLEX
86 #define _IDirectSoundFullDuplex_
87 #endif
88 #include <dsound.h>
89 
90 #include "command.h"
91 #include "i_sound.h"
92 #include "s_sound.h"
93 #include "i_system.h"
94 #include "m_argv.h"
95 #include "w_wad.h"
96 #include "z_zone.h"
97 #include "doomstat.h"
98 
99 #include "dx_error.h"
100 
101 #include "qmus2mid.h"
102 #include "mid2strm.h"
103 
104 #include "hardware/hw3sound.h"
105 
106 #include "win_dll.h"
107 
108 #include <windows.h>
109 #include <conio.h>
110 
111 #ifdef FMOD_SOUND
112 // TO COMPILE WIN32 EXECUTABLE YOU NOW NEED THE FMOD LIBRARY. GET IT AT WWW.FMOD.ORG
113 // SSNTails 12-13-2002
114 # include <fmod.h>
115 # include <fmod_errors.h>	/* optional */
116 // FIXME: for what VERSION was this written
117 #define FMOD_VERSION_NEEDED    1
118 #endif
119 
120 //#define TESTCODE            // remove this for release version
121 
122 #ifdef SURROUND_SOUND
123 #define SURROUND_SEP  1024
124 #endif
125 
126 // DirectSound3D mode
127 #define HWS_DS3D    1
128 
129 /* briefly described here for convenience:
130 typedef struct {
131     WORD  wFormatTag;       // WAVE_FORMAT_PCM is the only format accepted for DirectSound:
132                             // this tag indicates Pulse Code Modulation (PCM), an uncompressed format
133                             // in which each samples represents the amplitude of the signal at the time
134                             // of sampling.
135     WORD  nChannels;        // either one (mono) or two (stereo)
136     DWORD nSamplesPerSec;   // the sampling rate, or frequency, in hertz.
137                             //  Typical values are 11,025, 22,050, and 44,100
138     DWORD nAvgBytesPerSec;  // nAvgBytesPerSec is the product of nBlockAlign and nSamplesPerSec
139     WORD  nBlockAlign;      // the number of bytes required for each complete sample, for PCM formats
140                             // is equal to (wBitsPerSample * nChannels / 8).
141     WORD  wBitsPerSample;   // gives the size of each sample, generally 8 or 16 bits
142     WORD  cbSize;           // cbSize gives the size of any extra fields required to describe a
143                             // specialized wave format. This member is always zero for PCM formats.
144 } WAVEFORMATEX;
145 */
146 
147 #ifdef FMOD_SOUND
148 // SSNTails 12-13-2002
149 FSOUND_STREAM *fmus = NULL;
150 extern boolean  fmod_music;
151 #endif
152 
153 static void I_ShutdownMusic(void);
154 
155 byte    sound_started=0;
156 byte    music_started=0;
157 
158 #define NORMAL_PITCH   128
159 
160 // --------------------------------------------------------------------------
161 // DirectSound stuff
162 // --------------------------------------------------------------------------
163 LPDIRECTSOUND           DSnd = NULL;
164 LPDIRECTSOUNDBUFFER     DSndPrimary;
165 
166 // Stack sounds means sounds put on top of each other, since DirectSound can't play
167 // the same sound buffer at different locations at the same time, we need to dupli-
168 // cate existing buffers to play multiple instances of the same sound in the same
169 // time frame. A duplicate sound is freed when it is no more used. The priority that
170 // comes from the s_sound engine, is kept so that the lowest priority sounds are
171 // stopped to make place for the new sound, unless the new sound has a lower priority
172 // than all playing sounds, in which case the sound is not started.
173 #define MAXSTACKSOUNDS      32          // this is the absolute number of sounds that
174                                         // can play simultaneously, whatever the value
175                                         // of cv_numChannels
176 typedef struct {
177     LPDIRECTSOUNDBUFFER lpSndBuf;
178 #ifdef SURROUND_SOUND
179         // judgecutor:
180         // Need for produce surround sound
181     LPDIRECTSOUNDBUFFER lpSurround;
182 #endif
183     int16_t             priority;
184     boolean             duplicate;
185 } StackSound_t;
186 
187 static StackSound_t    StackSounds[MAXSTACKSOUNDS];
188 
189 
190 // --------------------------------------------------------------------------
191 // Fill the DirectSoundBuffer with data from a sample, made separate so that
192 // sound data can be reloaded if a sound buffer was lost.
193 // --------------------------------------------------------------------------
194 #ifdef WIN98
195 // win9x version only
CopySoundData(LPDIRECTSOUNDBUFFER dsbuffer,byte * data)196 static boolean CopySoundData (LPDIRECTSOUNDBUFFER dsbuffer, byte* data)
197 {
198     char *  reason;
199     LPVOID  lpvAudio1;              // receives address of lock start
200     DWORD   dwBytes1;               // receives number of bytes locked
201     LPVOID  lpvAudio2;              // receives address of lock start
202     DWORD   dwBytes2;               // receives number of bytes locked
203     HRESULT hr;
204 
205     // Obtain memory address of write block.
206     hr = dsbuffer->lpVtbl->Lock (dsbuffer, 0, 0, &lpvAudio1, &dwBytes1, &lpvAudio2, &dwBytes2, DSBLOCK_ENTIREBUFFER);
207 
208     // If DSERR_BUFFERLOST is returned, restore and retry lock.
209     if (hr == DSERR_BUFFERLOST)
210     {
211         hr = dsbuffer->lpVtbl->Restore (dsbuffer);
212         if( FAILED (hr) )
213         {
214 	    reason = "Restore fail";
215 	    goto errmsg;
216 	}
217         hr = dsbuffer->lpVtbl->Lock (dsbuffer, 0, 0, &lpvAudio1, &dwBytes1, NULL, NULL, DSBLOCK_ENTIREBUFFER);
218         if( FAILED (hr) )
219         {
220 	    reason = "Lock fail(2)";
221 	    goto errmsg;
222 	}
223     }
224     else
225     {
226         if( FAILED (hr) )
227         {
228 	    reason = "Lock fail(1)";
229 	    goto errmsg;
230 	}
231     }
232 
233     // copy wave data into the buffer (note: dwBytes1 should equal to dsbdesc->dwBufferBytes ...)
234     CopyMemory (lpvAudio1, data, dwBytes1);
235 
236     // finally, unlock the buffer
237     hr = dsbuffer->lpVtbl->Unlock (dsbuffer, lpvAudio1, dwBytes1, lpvAudio2, dwBytes2);
238 
239     if( FAILED (hr) )
240     {
241         reason = "Unlock fail";
242         goto errmsg;
243     }
244 
245     return true;
246 
247 errmsg:
248     I_SoftError("Copy Sound: %s on %x, %s\n", reason, dsbuffer, DXErrorToString(hr));
249     return false;
250 }
251 
252 #else
253 
254 // NT compatible version
CopySoundData(LPDIRECTSOUNDBUFFER dsbuffer,byte * data,int length)255 static boolean CopySoundData (LPDIRECTSOUNDBUFFER dsbuffer, byte* data, int length)
256 {
257     char *  reason;
258     LPVOID  lpvAudio1;              // receives address of lock start
259     DWORD   dwBytes1;               // receives number of bytes locked
260     LPVOID  lpvAudio2;              // receives address of lock start
261     DWORD   dwBytes2;               // receives number of bytes locked
262     HRESULT hr;
263 
264     // Obtain memory address of write block.
265     hr = dsbuffer->lpVtbl->Lock (dsbuffer, 0, length, &lpvAudio1, &dwBytes1, &lpvAudio2, &dwBytes2, 0);
266 
267     // If DSERR_BUFFERLOST is returned, restore and retry lock.
268     if (hr == DSERR_BUFFERLOST)
269     {
270         hr = dsbuffer->lpVtbl->Restore (dsbuffer);
271         if( FAILED (hr) )
272         {
273 	    reason = "Restore fail";
274 	    goto errmsg;
275 	}
276         hr = dsbuffer->lpVtbl->Lock (dsbuffer, 0, length, &lpvAudio1, &dwBytes1, NULL, NULL, 0);
277         if( FAILED (hr) )
278         {
279 	    reason = "Lock fail(2)";
280 	    goto errmsg;
281 	}
282     }
283     else
284     {
285         if( FAILED (hr) )
286         {
287 	    reason = "Lock fail(1)";
288 	    goto errmsg;
289 	}
290     }
291 
292     // copy wave data into the buffer (note: dwBytes1 should equal to dsbdesc->dwBufferBytes ...)
293     CopyMemory (lpvAudio1, data, dwBytes1);
294 
295     if ( dwBytes2 && lpvAudio2)
296          CopyMemory(lpvAudio2, data+dwBytes1, dwBytes2);
297 
298 
299     // finally, unlock the buffer
300     hr = dsbuffer->lpVtbl->Unlock (dsbuffer, lpvAudio1, dwBytes1, lpvAudio2, dwBytes2);
301 
302     if( FAILED (hr) )
303     {
304         reason = "Unlock fail";
305         goto errmsg;
306     }
307 
308     return true;
309 
310 errmsg:
311     I_SoftError("Copy Sound: %s on %x, %s\n", reason, dsbuffer, DXErrorToString(hr));
312     return false;
313 }
314 #endif
315 
316 #ifdef SURROUND_SOUND
317 // judgecutor:
318 // Hmmm... May be this function is not too good...
CopyAndInvertMemory(byte * dest,byte * src,int bytes)319 static void CopyAndInvertMemory(byte *dest, byte *src, int bytes)
320 {
321 #ifdef __GNUC__
322     while (bytes > 0)
323     {
324        *(dest++) = - *(src++);
325        bytes--;
326     }
327 #else
328     _asm
329     {
330         push esi
331         push edi
332         push ecx
333         mov  ecx,bytes
334         mov  esi,src
335         mov  edi,dest
336 a:
337         lodsb
338         neg  al
339         stosb
340         loop a
341         pop  ecx
342         pop  edi
343         pop  esi
344     }
345 #endif
346 }
347 
348 // judgecutor:
349 // Like normal CopySoundData but sound data will be inverted
CopyAndInvertSoundData(LPDIRECTSOUNDBUFFER dsbuffer,byte * data,int length)350 static boolean CopyAndInvertSoundData(LPDIRECTSOUNDBUFFER dsbuffer, byte* data, int length)
351 {
352     char *  reason;
353     LPVOID  lpvAudio1;              // receives address of lock start
354     DWORD   dwBytes1;               // receives number of bytes locked
355     LPVOID  lpvAudio2;
356     DWORD   dwBytes2;
357     HRESULT hr;
358 
359     // Obtain memory address of write block.
360     hr = dsbuffer->lpVtbl->Lock (dsbuffer, 0, length, &lpvAudio1, &dwBytes1, &lpvAudio2, &dwBytes2, 0);
361 
362     // If DSERR_BUFFERLOST is returned, restore and retry lock.
363     if (hr == DSERR_BUFFERLOST)
364     {
365         hr = dsbuffer->lpVtbl->Restore (dsbuffer);
366         if( FAILED (hr) )
367         {
368 	    reason = "Restore fail";
369 	    goto errmsg;
370 	}
371         hr = dsbuffer->lpVtbl->Lock (dsbuffer, 0, length, &lpvAudio1, &dwBytes1, NULL, NULL, 0);
372         if( FAILED (hr) )
373         {
374 	    reason = "Lock fail(2)";
375 	    goto errmsg;
376 	}
377     }
378     else
379     {
380         if( FAILED (hr) )
381         {
382 	    reason = "Lock fail(1)";
383 	    goto errmsg;
384 	}
385     }
386 
387 
388     // copy wave data into the buffer (note: dwBytes1 should equal to dsbdesc->dwBufferBytes ...)
389     CopyAndInvertMemory (lpvAudio1, data, dwBytes1);
390 
391     if ( dwBytes2 && lpvAudio2)
392         CopyAndInvertMemory(lpvAudio2, data+dwBytes1, dwBytes2);
393 
394 
395     hr = dsbuffer->lpVtbl->Unlock (dsbuffer, lpvAudio1, dwBytes1, lpvAudio2, dwBytes2);
396     if( FAILED (hr) )
397     {
398         reason = "Unlock fail";
399         goto errmsg;
400     }
401 
402     return true;
403 
404 errmsg:
405     I_SoftError("CopyAndInvert Sound: %s on %x, %s\n", reason, dsbuffer, DXErrorToString(hr));
406     return false;
407 }
408 #endif
409 
410 static DWORD sound_buffer_flags = DSBCAPS_CTRLPAN |
411                                     DSBCAPS_CTRLVOLUME |
412                                     DSBCAPS_STICKYFOCUS |
413                                     //DSBCAPS_LOCSOFTWARE |
414                                     DSBCAPS_STATIC
415                                     | DSBCAPS_CTRLFREQUENCY;
416 
417 // --------------------------------------------------------------------------
418 // raw2DS : convert a raw sound data, returns a LPDIRECTSOUNDBUFFER
419 // --------------------------------------------------------------------------
420 //   dsdata points a 4 unsigned short header:
421 //    +0 : value 3 what does it mean?
422 //    +2 : sample rate, either 11025 or 22050.
423 //    +4 : number of samples, each sample is a single byte since it's 8bit
424 //    +6 : value 0
425 //
426 #ifdef SURROUND_SOUND
427 // judgecutor:
428 // We need an another function definition for supporting the surround sound
429 // Invert just cause to copy an inverted sound data
raw2DS(byte * dsdata,int len,boolean invert)430 static LPDIRECTSOUNDBUFFER raw2DS( byte * dsdata, int len, boolean invert)
431 
432 #else
433 static LPDIRECTSOUNDBUFFER raw2DS( byte * dsdata, int len)
434 
435 #endif
436 {
437     HRESULT             hr;
438     WAVEFORMATEX        wfm;
439     DSBUFFERDESC        dsbdesc;
440     LPDIRECTSOUNDBUFFER dsbuffer;
441 
442     // initialise WAVEFORMATEX structure describing the wave format
443     ZeroMemory (&wfm, sizeof(WAVEFORMATEX));
444     wfm.wFormatTag = WAVE_FORMAT_PCM;
445     wfm.nChannels = 1;
446     wfm.nSamplesPerSec = *((unsigned short*)dsdata+1);      //mostly 11025, but some at 22050.
447     wfm.wBitsPerSample = 8;
448     wfm.nBlockAlign = wfm.wBitsPerSample / 8 * wfm.nChannels;
449     wfm.nAvgBytesPerSec = wfm.nSamplesPerSec * wfm.nBlockAlign;
450 
451     // Set up DSBUFFERDESC structure.
452     ZeroMemory (&dsbdesc, sizeof(DSBUFFERDESC) );
453     dsbdesc.dwSize = sizeof (DSBUFFERDESC);
454 /*    dsbdesc.dwFlags = DSBCAPS_CTRLPAN |
455                       DSBCAPS_CTRLVOLUME |
456                       DSBCAPS_STICKYFOCUS |
457                       //DSBCAPS_LOCSOFTWARE |
458                       DSBCAPS_STATIC
459                       | DSBCAPS_CTRLFREQUENCY;    // This one for pitching
460 */
461     dsbdesc.dwFlags = sound_buffer_flags;
462     dsbdesc.dwBufferBytes = len-8;
463     dsbdesc.lpwfxFormat = &wfm;             // pointer to WAVEFORMATEX structure
464 
465     // Create the sound buffer
466     hr = DSnd->lpVtbl->CreateSoundBuffer (DSnd, &dsbdesc, &dsbuffer, NULL);
467 
468     if ( hr == DSERR_CONTROLUNAVAIL )
469     {
470         CONS_Printf("\tSoundBufferCreate error - a buffer control is not available.\n\tTrying to disable frequency control.\n");
471 
472         sound_buffer_flags &= ~DSBCAPS_CTRLFREQUENCY;
473         dsbdesc.dwFlags = sound_buffer_flags;
474 
475         hr = DSnd->lpVtbl->CreateSoundBuffer (DSnd, &dsbdesc, &dsbuffer, NULL);
476     }
477 
478     if ( FAILED (hr) )
479         I_Error ("CreateSoundBuffer() FAILED: %s\n", DXErrorToString(hr));
480 
481 #ifdef SURROUND_SOUND
482 
483     if (invert)
484         // just invert a sound data for producing the surround sound
485         CopyAndInvertSoundData(dsbuffer, (byte*)dsdata + 8, dsbdesc.dwBufferBytes);
486     else
487         // Do a normal operation
488 #endif
489     // fill the DirectSoundBuffer waveform data
490     CopySoundData (dsbuffer, (byte*)dsdata + 8, dsbdesc.dwBufferBytes);
491 
492     return dsbuffer;
493 }
494 
495 
496 // --------------------------------------------------------------------------
497 // This function loads the sound data from the WAD lump, for single sound.
498 // --------------------------------------------------------------------------
I_GetSfx(sfxinfo_t * sfx)499 void I_GetSfx (sfxinfo_t*  sfx)
500 {
501     byte *  dssfx;
502     int     size;
503 
504     S_GetSfxLump( sfx );
505 
506 #ifdef HW3SOUND
507     if (hws_mode != HWS_DEFAULT_MODE)
508         return ;
509 #endif
510 
511     size = sfx->length;
512     dssfx = sfx->data;
513     // because the data is copied to the DIRECTSOUNDBUFFER, the one here will not be used
514 
515 #ifdef SURROUND_SOUND
516     // Make a normal (not inverted) sound buffer
517     sfx->data = (void*)raw2DS (dssfx, size, FALSE);
518 #else
519     // return the LPDIRECTSOUNDBUFFER, which will be stored in S_sfx[].data
520     sfx->data = (void *)raw2DS (dssfx, size);
521 #endif
522     Z_Free( dssfx );
523 }
524 
525 
526 // --------------------------------------------------------------------------
527 // Free all allocated resources for a single sound
528 // --------------------------------------------------------------------------
I_FreeSfx(sfxinfo_t * sfx)529 void I_FreeSfx (sfxinfo_t* sfx)
530 {
531     LPDIRECTSOUNDBUFFER dsbuffer;
532 
533     if( ! VALID_LUMP(sfx->lumpnum) )
534         return;
535 
536 #ifdef HW3SOUND
537     if (hws_mode != HWS_DEFAULT_MODE)
538     {
539         if (sfx->data)
540             Z_Free(sfx->data);
541     }
542     else
543 #endif
544     {
545         //debug_Printf ("I_FreeSfx(%d)\n", sfx->lumpnum);
546 
547         // free DIRECTSOUNDBUFFER
548         dsbuffer = (LPDIRECTSOUNDBUFFER) sfx->data;
549         if( dsbuffer )
550             dsbuffer->lpVtbl->Release (dsbuffer);
551     }
552     sfx->data = NULL;
553     sfx->lumpnum = NO_LUMP;
554 }
555 
556 
557 // --------------------------------------------------------------------------
558 // Set the global volume for sound effects
559 // --------------------------------------------------------------------------
I_SetSfxVolume(int volume)560 void I_SetSfxVolume(int volume)
561 {
562     int     vol;
563     HRESULT hr;
564     // Can set local var to volume, or use the global mix_sfxvolume.
565 
566     if (nosoundfx || !sound_started)
567         return;
568 
569     // use the last quarter of volume range
570     // mix_sfxvolume or volume : range 0..31
571     if (volume)
572         vol = (volume * ((DSBVOLUME_MAX-DSBVOLUME_MIN)/4)) / 31 +
573               (DSBVOLUME_MAX - ((DSBVOLUME_MAX-DSBVOLUME_MIN)/4));
574     else
575         vol = DSBVOLUME_MIN;    // make sure 0 is silence
576     //debug_Printf ("setvolume to %d\n", vol);
577     hr = DSndPrimary->lpVtbl->SetVolume (DSndPrimary, vol);
578     //if (FAILED(hr))
579     //    CONS_Printf ("setvolumne failed\n");
580 }
581 
582 
583 // --------------------------------------------------------------------------
584 // Update the volume for a secondary buffer, make sure it was created with
585 // DSBCAPS_CTRLVOLUME
586 // --------------------------------------------------------------------------
I_UpdateSoundVolume(LPDIRECTSOUNDBUFFER lpSnd,int volume)587 static void I_UpdateSoundVolume (LPDIRECTSOUNDBUFFER lpSnd, int volume)
588 {
589     HRESULT hr;
590     volume = (volume * ((DSBVOLUME_MAX-DSBVOLUME_MIN)/4)) / 256 +
591                         (DSBVOLUME_MAX - ((DSBVOLUME_MAX-DSBVOLUME_MIN)/4));
592     hr = lpSnd->lpVtbl->SetVolume (lpSnd, volume);
593     //if (FAILED(hr))
594     //    CONS_Printf ("\2SetVolume FAILED\n");
595 }
596 
597 
598 // --------------------------------------------------------------------------
599 // Update the panning for a secondary buffer, make sure it was created with
600 // DSBCAPS_CTRLPAN
601 // --------------------------------------------------------------------------
602 #define DSBPAN_RANGE    (DSBPAN_RIGHT-(DSBPAN_LEFT))
603 // Doom originally has sep range 1..256, but DoomLegacy is now using +/- 127.
604 // Doom sounds pan range +/- 127  (0 is centre)
605 #define SEP_RANGE       256
606 //  sep : +/- 127, 0 is center
I_UpdateSoundPanning(LPDIRECTSOUNDBUFFER lpSnd,int sep)607 static void I_UpdateSoundPanning (LPDIRECTSOUNDBUFFER lpSnd, int sep)
608 {
609     HRESULT hr;
610     // DirectSound sep has 0 as center.
611     hr = lpSnd->lpVtbl->SetPan (lpSnd, (sep * DSBPAN_RANGE)/SEP_RANGE);
612     //if (FAILED(hr))
613     //    CONS_Printf ("SetPan FAILED for sep %d pan %d\n", sep, (sep * DSBPAN_RANGE)/SEP_RANGE);
614 }
615 
616 // search a free slot in the stack, free it if needed
GetFreeStackNum(int16_t newpriority)617 static int GetFreeStackNum(int16_t  newpriority)
618 {
619     int16_t  lowestpri;
620     int  lowestprihandle;
621     int  i;
622     // DirectSound can't play multiple instances of the same sound buffer
623     // unless they are duplicated, so if the sound buffer is in use, make a duplicate
624     lowestpri = 0x3FFF;
625     lowestprihandle = 0;
626     for (i=0; i<MAXSTACKSOUNDS; i++)
627     {
628         // find a free 'playing sound slot' to use
629         if (StackSounds[i].lpSndBuf==NULL) {
630             //debug_Printf ("\t\tfound free slot %d\n", i);
631             return i;
632         }
633         else
634         {
635             // check for sounds that finished playing, and can be freed
636             if( !I_SoundIsPlaying(i) )
637             {
638                 //debug_Printf ("\t\tfinished sound in slot %d\n", i);
639                 //stop sound and free the 'slot'
640                 I_StopSound (i);
641                 // we can use this one since it's now freed
642                 return i;
643             }
644             else
645             //remember lowest priority sound
646             if (StackSounds[i].priority < lowestpri) {
647                 lowestpri = StackSounds[i].priority;
648                 lowestprihandle = i;
649             }
650 	}
651     }
652 
653     // the maximum of sounds playing at the same time is reached, if we have at least
654     // one sound playing with a lower priority, stop it and replace it with the new one
655 
656     //debug_Printf ("\t\tall slots occupied..");
657     if (newpriority >= lowestpri)
658     {
659         I_StopSound (lowestprihandle);
660         return lowestprihandle;
661             //debug_Printf (" kicking out lowest priority slot: %d pri: %d, my priority: %d\n",
662             //             handle, lowestpri, priority);
663     }
664 
665     return -1;
666 }
667 
668 #ifdef SURROUND_SOUND
CreateInvertedSound(int id)669 static LPDIRECTSOUNDBUFFER CreateInvertedSound(int id)
670 {
671     sfxinfo_t * sfx = &S_sfx[id];
672     byte  *dsdata;
673 
674     S_GetSfxLump( sfx );
675     dsdata = sfx->data;
676     if( ! dsdata )  return NULL;
677     return raw2DS(dsdata, sfx->length, TRUE);
678 }
679 #endif
680 
681 // Calculate internal pitch from Doom pitch
recalc_pitch(int doom_pitch)682 static float recalc_pitch(int doom_pitch)
683 {
684     return (doom_pitch < NORMAL_PITCH) ?
685         (float)(doom_pitch + NORMAL_PITCH) / (NORMAL_PITCH * 2)
686         :(float)doom_pitch / (float)NORMAL_PITCH;
687 }
688 
689 
690 
691 extern consvar_t cv_rndsoundpitch;
692 
693 // --------------------------------------------------------------------------
694 // Start the given S_sfx[id] sound with given properties (panning, volume..)
695 // FIXME: if a specific sound Id is already being played, another instance
696 //        of that sound should be created with DuplicateSound()
697 // --------------------------------------------------------------------------
698 //  sep : +/- 127, 0 is center, SURROUND_SEP as special operation
I_StartSound(int id,int vol,int sep,int pitch,int priority)699 int I_StartSound (int id, int vol, int sep, int pitch, int priority )
700 {
701     char *  reason;
702     sfxinfo_t * sfx = &S_sfx[id];
703     HRESULT     hr;
704     LPDIRECTSOUNDBUFFER     dsbuffer;
705     DWORD       dwStatus;
706     int         handle;
707     int         i;
708     DWORD       freq;
709 #ifdef SURROUND_SOUND
710     LPDIRECTSOUNDBUFFER     dssurround;
711 #endif
712 
713     if (nosoundfx)
714         goto ret_nothing;
715 
716     //debug_Printf ("I_StartSound:\n\t\tS_sfx[%d]\n", id);
717     // Heretic style signed priority, -10..2560, neg is lowest.
718     handle = GetFreeStackNum(priority);
719     if( handle<0 )
720         goto ret_nothing;
721 
722     //debug_Printf ("\t\tusing handle %d\n", handle);
723 
724     // if the original buffer is playing, duplicate it (DirectSound specific)
725     // else, use the original buffer
726     dsbuffer = (LPDIRECTSOUNDBUFFER) sfx->data;
727     dsbuffer->lpVtbl->GetStatus (dsbuffer, &dwStatus);
728     if (dwStatus & (DSBSTATUS_PLAYING | DSBSTATUS_LOOPING))
729     {
730         //debug_Printf ("\t\toriginal sound S_sfx[%d] is playing, duplicating.. ", id);
731         hr = DSnd->lpVtbl->DuplicateSoundBuffer(DSnd,  (LPDIRECTSOUNDBUFFER) S_sfx[id].data, &dsbuffer);
732         if (FAILED(hr))
733         {
734             //debug_Printf ("Cound't duplicate sound buffer\n");
735             // re-use the original then..
736             dsbuffer = (LPDIRECTSOUNDBUFFER) sfx->data;
737             // clean up stacksounds info
738             for (i=0; i<MAXSTACKSOUNDS; i++)
739 	    {
740                 if (handle != i &&
741                     StackSounds[i].lpSndBuf == dsbuffer)
742                 {
743                     StackSounds[i].lpSndBuf = NULL;
744                 }
745 	    }
746         }
747         // stop the duplicate or the re-used original
748         dsbuffer->lpVtbl->Stop (dsbuffer);
749     }
750 
751     //judgecutor: Sound pitching
752     if (cv_rndsoundpitch.value)
753     {
754         // At first reset the buffer back to original frequency
755         hr = IDirectSoundBuffer_SetFrequency(dsbuffer, DSBFREQUENCY_ORIGINAL);
756         if ( SUCCEEDED (hr) )
757         {
758             IDirectSoundBuffer_GetFrequency(dsbuffer, &freq);
759 
760             // Now pitch it
761             freq = freq * recalc_pitch(pitch);
762             IDirectSoundBuffer_SetFrequency(dsbuffer, freq);
763         }
764         else
765             cv_rndsoundpitch.value = 0;
766     }
767 
768     // store information on the playing sound
769     StackSounds[handle].lpSndBuf = dsbuffer;
770     StackSounds[handle].priority = priority;
771     StackSounds[handle].duplicate = (dsbuffer != (LPDIRECTSOUNDBUFFER)S_sfx[id].data);
772 
773     //debug_Printf ("StackSounds[%d].lpSndBuf is %s\n", handle, StackSounds[handle].lpSndBuf==NULL ? "Null":"valid");
774     //debug_Printf ("StackSounds[%d].priority is %d\n", handle, StackSounds[handle].priority);
775     //debug_Printf ("StackSounds[%d].duplicate is %s\n", handle, StackSounds[handle].duplicate ? "TRUE":"FALSE");
776 
777     I_UpdateSoundVolume (dsbuffer, vol);
778 
779 #ifdef SURROUND_SOUND
780     // Prepare the surround sound buffer
781     // Use a normal sound data for the left channel (with pan left (-127))
782     // and an inverted sound data for the right channel (with pan right (+127))
783 
784     dssurround = CreateInvertedSound(id);
785 
786     // Surround must be pitched too
787     if (cv_rndsoundpitch.value)
788         IDirectSoundBuffer_SetFrequency(dssurround, freq);
789 
790     if (sep == SURROUND_SEP)
791     {
792         I_UpdateSoundPanning(dssurround, 127);
793         I_UpdateSoundVolume(dssurround, vol);
794         I_UpdateSoundPanning(dsbuffer, -127);
795         dssurround->lpVtbl->SetCurrentPosition(dssurround, 0);
796     }
797     else
798         // Perform normal operation
799         I_UpdateSoundPanning (dsbuffer, sep);
800 #else
801     I_UpdateSoundPanning (dsbuffer, sep);
802 #endif
803 
804 
805     dsbuffer->lpVtbl->SetCurrentPosition (dsbuffer, 0);
806 
807     hr = dsbuffer->lpVtbl->Play (dsbuffer, 0, 0, 0);
808     if (hr == DSERR_BUFFERLOST)
809     {
810         //debug_Printf("buffer lost\n");
811         // restores the buffer memory and all other settings for the buffer
812         hr = dsbuffer->lpVtbl->Restore (dsbuffer);
813         if ( SUCCEEDED ( hr ) )
814         {
815             byte*   dsdata;
816             // reload sample data here
817 	    S_GetSfxLump( sfx );
818             dsdata = sfx->data;
819 
820             // Well... Data length must be -8!!!
821             CopySoundData (dsbuffer, (byte*)dsdata + 8, sfx->length - 8);
822 
823             // play
824             hr = dsbuffer->lpVtbl->Play (dsbuffer, 0, 0, 0);
825         }
826         else
827         {
828 	    reason = "Restore fail";
829 	    goto errmsg;
830 	}
831     }
832 
833 #ifdef SURROUND_SOUND
834     if (sep == SURROUND_SEP)
835     {
836         hr = dssurround->lpVtbl->Play (dssurround, 0, 0, 0);
837         //debug_Printf("Surround playback\n");
838         if (hr == DSERR_BUFFERLOST)
839         {
840             // restores the buffer memory and all other settings for the surround buffer
841             hr = dssurround->lpVtbl->Restore (dssurround);
842             if ( SUCCEEDED ( hr ) )
843             {
844                 byte*   dsdata;
845 
846                 S_GetSfxLump( sfx );
847                 dsdata = sfx->data;
848                 CopyAndInvertSoundData (dssurround, (byte*)dsdata + 8, sfx->length - 8);
849 
850                 hr = dssurround->lpVtbl->Play (dssurround, 0, 0, 0);
851             }
852             else
853 	    {
854 	        reason = "Restore fail";
855 	        goto errmsg;
856 	    }
857         }
858     }
859     StackSounds[handle].lpSurround = dssurround;
860 #endif
861 
862     // Returns a handle
863     return handle;
864 
865 errmsg:
866     I_SoftError ("StartSound : %s, %s", DXErrorToString(hr));
867 ret_nothing:
868     return -1;
869 }
870 
871 
872 // --------------------------------------------------------------------------
873 // Stop a sound if it is playing,
874 // free the corresponding 'playing sound slot' in StackSounds[]
875 // --------------------------------------------------------------------------
I_StopSound(int handle)876 void I_StopSound (int handle)
877 {
878     LPDIRECTSOUNDBUFFER dsbuffer;
879     HRESULT hr;
880 
881     if (nosoundfx || handle<0)
882         return;
883 
884     //debug_Printf ("I_StopSound (%d)\n", handle);
885 
886     dsbuffer = StackSounds[handle].lpSndBuf;
887     hr = dsbuffer->lpVtbl->Stop (dsbuffer);
888 
889     // free duplicates of original sound buffer (DirectSound hassles)
890     if (StackSounds[handle].duplicate) {
891         //debug_Printf ("\t\trelease a duplicate..\n");
892         dsbuffer->lpVtbl->Release (dsbuffer);
893     }
894 
895 #ifdef SURROUND_SOUND
896     // Stop and release the surround sound buffer
897     dsbuffer = StackSounds[handle].lpSurround;
898     if (dsbuffer != NULL)
899     {
900         dsbuffer->lpVtbl->Stop(dsbuffer);
901         dsbuffer->lpVtbl->Release(dsbuffer);
902     }
903     StackSounds[handle].lpSurround = NULL;
904 #endif
905 
906     StackSounds[handle].lpSndBuf = NULL;
907 }
908 
909 
910 // --------------------------------------------------------------------------
911 // Returns whether the sound is currently playing or not
912 // --------------------------------------------------------------------------
I_SoundIsPlaying(int handle)913 int I_SoundIsPlaying(int handle)
914 {
915     LPDIRECTSOUNDBUFFER dsbuffer;
916     DWORD   dwStatus;
917 
918     if (nosoundfx || handle == -1)
919         return FALSE;
920 
921     dsbuffer = StackSounds[handle].lpSndBuf;
922     if (dsbuffer) {
923         dsbuffer->lpVtbl->GetStatus (dsbuffer, &dwStatus);
924         if (dwStatus & (DSBSTATUS_PLAYING | DSBSTATUS_LOOPING))
925             return TRUE;
926     }
927 
928     return FALSE;
929 }
930 
931 
932 // --------------------------------------------------------------------------
933 // Update properties of a sound currently playing
934 // --------------------------------------------------------------------------
I_UpdateSoundParams(int handle,int vol,int sep,int pitch)935 void I_UpdateSoundParams(int handle, int vol, int sep, int pitch)
936 {
937     LPDIRECTSOUNDBUFFER     dsbuffer;
938 #ifdef SURROUND_SOUND
939     LPDIRECTSOUNDBUFFER     dssurround;
940     DWORD                   dwStatus;
941     DWORD                   pos;
942     boolean                 surround_inuse = FALSE;
943 #endif
944 
945     if (nosoundfx)
946         return;
947 
948     dsbuffer = StackSounds[handle].lpSndBuf;
949 
950 #ifdef SURROUND_SOUND
951     if (dsbuffer == NULL)
952         return;
953 
954     dssurround = StackSounds[handle].lpSurround;
955     if (dssurround)
956     {
957         dssurround->lpVtbl->GetStatus(dssurround, &dwStatus);
958         surround_inuse = (dwStatus & (DSBSTATUS_PLAYING | DSBSTATUS_LOOPING));
959     }
960         // If pan changed to stereo...
961     if (sep != SURROUND_SEP)
962     {
963         if (surround_inuse)
964         {
965             dssurround->lpVtbl->Stop(dssurround);
966             surround_inuse = FALSE;
967 
968         }
969     }
970     else
971     {
972         // Just update volumes and start the surround if need
973         if (!surround_inuse)
974         {
975             I_UpdateSoundVolume(dssurround, vol);
976             I_UpdateSoundPanning(dsbuffer, -127);
977             dsbuffer->lpVtbl->GetCurrentPosition(dsbuffer, &pos, NULL);
978             dssurround->lpVtbl->SetCurrentPosition(dssurround, pos);
979             dssurround->lpVtbl->Play(dssurround, 0, 0, 0);
980 
981             surround_inuse = TRUE;
982         }
983         else
984             I_UpdateSoundVolume(dssurround, vol);
985     }
986     I_UpdateSoundVolume(dsbuffer, vol);
987 
988     if (!surround_inuse)
989         I_UpdateSoundPanning(dsbuffer, sep);
990 
991 #else
992 
993     if (dsbuffer) {
994         I_UpdateSoundVolume (dsbuffer, vol);
995         I_UpdateSoundPanning (dsbuffer, sep);
996     }
997 #endif
998 }
999 
1000 
1001 //
1002 // Shutdown DirectSound
1003 //
1004 // Is also a registered exit func
I_DS_ShutdownSound(void)1005 static void I_DS_ShutdownSound(void)
1006 {
1007     int i;
1008 
1009     if( nosoundfx )
1010         return;
1011 
1012     CONS_Printf("I_DS_ShutdownSound: \n");
1013 
1014 #ifdef HW3SOUND
1015     if (hws_mode != HWS_DEFAULT_MODE)
1016     {
1017         HW3S_Shutdown();
1018         Shutdown3DSDriver();
1019         return;
1020     }
1021 #endif
1022     // release any temporary 'duplicated' secondary buffers
1023     for (i=0; i<MAXSTACKSOUNDS; i++)
1024     {
1025         if (StackSounds[i].lpSndBuf)
1026             // stops the sound and release it if it is a duplicate
1027             I_StopSound (i);
1028     }
1029 
1030     if (DSnd)
1031     {
1032         IDirectSound_Release(DSnd);
1033         DSnd = NULL;
1034     }
1035 }
1036 
1037 
1038 // ==========================================================================
1039 // Startup DirectSound
1040 // ==========================================================================
I_DS_StartupSound(void)1041 static void I_DS_StartupSound( void )
1042 {
1043     HRESULT             hr;
1044     LPDIRECTSOUNDBUFFER lpDsb;
1045     DSBUFFERDESC        dsbdesc;
1046     WAVEFORMATEX        wfm;
1047     int                 cooplevel;
1048     int                 frequency;
1049     int                 p;
1050 
1051 #ifdef HW3SOUND
1052     char                *sdrv_name;
1053     snddev_t            snddev;
1054 #endif
1055 
1056     // Secure and configure sound device first.
1057     CONS_Printf("I_DS_StartupSound: \n");
1058 
1059     // frequency of primary buffer may be set at cmd-line
1060     p = M_CheckParm ("-freq");
1061     if (p && p < myargc-1) {
1062         frequency = atoi(myargv[p+1]);
1063         CONS_Printf (" requested frequency of %d hz\n", frequency);
1064     }
1065     else
1066         frequency = 22050;
1067 
1068     // Set cooperative level
1069     // Cooperative sound with other applications can be requested at cmd-line
1070     if (M_CheckParm("-coopsound"))
1071         cooplevel = DSSCL_PRIORITY;
1072     else
1073         cooplevel = DSSCL_EXCLUSIVE;
1074 
1075 #ifdef HW3SOUND
1076     if (M_CheckParm("-ds3d"))
1077     {
1078         hws_mode = HWS_DS3D;
1079         sdrv_name = "s_ds3d.dll";
1080     }
1081     else
1082     {
1083         p = M_CheckParm("-sounddriver");
1084         if (p && p < myargc - 1)
1085         {
1086             hws_mode = HWS_DS3D;
1087             sdrv_name = myargv[p + 1];
1088         }
1089     }
1090 
1091     // There must be further sound drivers (such as A3D and EAX)!!!
1092 
1093     if (hws_mode != HWS_DEFAULT_MODE)
1094     {
1095         if (Init3DSDriver(sdrv_name))
1096         {
1097             //nosoundfx = true;
1098             I_AddExitFunc(I_DS_ShutdownSound);
1099             snddev.cooplevel = cooplevel;
1100             snddev.bps = 16;
1101             snddev.sample_rate = frequency;
1102             if (HW3S_Init(I_Error, &snddev))
1103             {
1104                 CONS_Printf("Using external sound driver %s\n", sdrv_name);
1105                 return;
1106             }
1107             // Falls back to default sound system
1108             HW3S_Shutdown();
1109             Shutdown3DSDriver();
1110         }
1111         hws_mode = HWS_DEFAULT_MODE;
1112     }
1113 #endif
1114 
1115     // Create DirectSound, use the default sound device
1116     hr = DirectSoundCreate( NULL, &DSnd, NULL);
1117     if ( FAILED( hr ) ) {
1118         CONS_Printf (" DirectSoundCreate FAILED\n"
1119                      " there is no sound device or the sound device is under\n"
1120                      " the control of another application\n" );
1121         nosoundfx = true;
1122         return;
1123     }
1124 
1125     // register exit code, now that we have at least DirectSound to close
1126     I_AddExitFunc (I_DS_ShutdownSound);
1127 
1128     hr = DSnd->lpVtbl->SetCooperativeLevel (DSnd, hWnd_main, cooplevel);
1129     if ( FAILED( hr ) ) {
1130         CONS_Printf (" SetCooperativeLevel FAILED\n");
1131         nosoundfx = true;
1132         return;
1133     }
1134 
1135     // Set up DSBUFFERDESC structure.
1136     ZeroMemory (&dsbdesc, sizeof(DSBUFFERDESC) );
1137     dsbdesc.dwSize        = sizeof(DSBUFFERDESC);
1138     dsbdesc.dwFlags = DSBCAPS_PRIMARYBUFFER |
1139                       DSBCAPS_CTRLVOLUME;
1140     dsbdesc.dwBufferBytes = 0;      // Must be 0 for primary buffer
1141     dsbdesc.lpwfxFormat = NULL;     // Must be NULL for primary buffer
1142 
1143     // Set up structure for the desired format
1144     ZeroMemory (&wfm, sizeof(WAVEFORMATEX));
1145     wfm.wFormatTag = WAVE_FORMAT_PCM;
1146     wfm.nChannels = 2;                              //STEREO SOUND!
1147     wfm.nSamplesPerSec = frequency;
1148     wfm.wBitsPerSample = 16;
1149     wfm.nBlockAlign = wfm.wBitsPerSample / 8 * wfm.nChannels;
1150     wfm.nAvgBytesPerSec = wfm.nSamplesPerSec * wfm.nBlockAlign;
1151 
1152     // Gain access to the primary buffer
1153     hr = DSnd->lpVtbl->CreateSoundBuffer (DSnd, &dsbdesc, &lpDsb, NULL);
1154     if ( FAILED( hr ) ) {
1155         CONS_Printf ("CreateSoundBuffer FAILED: %s (ErrNo %d)\n", DXErrorToString(hr), hr);
1156         nosoundfx = true;
1157         return;
1158     }
1159 
1160     // Set the primary buffer to the desired format,
1161     // but only if we are allowed to do it
1162     if (cooplevel >= DSSCL_PRIORITY)
1163     {
1164         if (SUCCEEDED ( hr ))
1165         {
1166             // Set primary buffer to the desired format. If this fails,
1167             // we'll just ignore and go with the default.
1168             hr = lpDsb->lpVtbl->SetFormat (lpDsb, &wfm);
1169             if (FAILED(hr))
1170                 CONS_Printf ("I_StartupSound :  couldn't set primary buffer format.\n");
1171         }
1172         // move any on-board sound memory into a contiguous block
1173         // to make the largest portion of free memory available.
1174 
1175         CONS_Printf (" Compacting onboard sound-memory...");
1176         hr = DSnd->lpVtbl->Compact (DSnd);
1177         CONS_Printf (" %s\n", SUCCEEDED(hr) ? "done" : "FAILED");
1178     }
1179 
1180     // set the primary buffer to play continuously, for performance
1181     // "... this method will ensure that the primary buffer is playing even when no secondary
1182     // buffers are playing; in that case, silence will be played. This can reduce processing
1183     // overhead when sounds are started and stopped in sequence, because the primary buffer
1184     // will be playing continuously rather than stopping and starting between secondary buffers."
1185     hr = lpDsb->lpVtbl->Play (lpDsb, 0, 0, DSBPLAY_LOOPING);
1186     if ( FAILED ( hr ) )
1187         CONS_Printf (" Primary buffer continuous play FAILED\n");
1188 
1189 #ifdef DEBUGSOUND
1190     {
1191     DSCAPS              DSCaps;
1192     DSCaps.dwSize = sizeof(DSCAPS);
1193     hr = DSnd->lpVtbl->GetCaps (DSnd, &DSCaps);
1194     if (SUCCEEDED (hr))
1195     {
1196         if (DSCaps.dwFlags & DSCAPS_CERTIFIED)
1197             CONS_Printf ("This driver has been certified by Microsoft\n");
1198         if (DSCaps.dwFlags & DSCAPS_EMULDRIVER)
1199             CONS_Printf ("No driver with DirectSound support installed (no hardware mixing)\n");
1200         if (DSCaps.dwFlags & DSCAPS_PRIMARY16BIT)
1201             CONS_Printf ("Supports 16-bit primary buffer\n");
1202         if (DSCaps.dwFlags & DSCAPS_PRIMARY8BIT)
1203             CONS_Printf ("Supports 8-bit primary buffer\n");
1204         if (DSCaps.dwFlags & DSCAPS_SECONDARY16BIT)
1205             CONS_Printf ("Supports 16-bit, hardware-mixed secondary buffers\n");
1206         if (DSCaps.dwFlags & DSCAPS_SECONDARY8BIT)
1207             CONS_Printf ("Supports 8-bit, hardware-mixed secondary buffers\n");
1208 
1209         CONS_Printf ("Maximum number of hardware buffers: %d\n", DSCaps.dwMaxHwMixingStaticBuffers);
1210         CONS_Printf ("Size of total hardware memory: %d\n", DSCaps.dwTotalHwMemBytes);
1211         CONS_Printf ("Size of free hardware memory= %d\n", DSCaps.dwFreeHwMemBytes);
1212         CONS_Printf ("Play Cpu Overhead (%% cpu cycles): %d\n", DSCaps.dwPlayCpuOverheadSwBuffers);
1213     }
1214     else
1215         CONS_Printf (" couldn't get sound device caps.\n");
1216     }
1217 #endif
1218 
1219     // save pointer to the primary DirectSound buffer for volume changes
1220     DSndPrimary = lpDsb;
1221 
1222     ZeroMemory (StackSounds, sizeof(StackSounds));
1223 
1224     CONS_Printf("sound initialised.\n");
1225     sound_started = true;
1226 }
1227 
1228 
1229 // ==========================================================================
1230 //
1231 // MUSIC API using MidiStream
1232 //
1233 // ==========================================================================
1234 
1235 #define MIDBUFFERSIZE   128*1024L       // buffer size for Mus2Midi conversion  (ugly code)
1236 #define SPECIAL_HANDLE_CLEANMIDI  -1999 // tell I_StopSong() to do a full (slow) midiOutReset() on exit
1237 
1238 static  byte*       MidiData_buf;       // buffer allocated at program start for Mus2Mid conversion
1239 
1240 static  UINT        uMIDIDeviceID, uCallbackStatus;
1241 static  HMIDISTRM   hStream;
1242 static  HANDLE      hBufferReturnEvent; // for synch between the callback thread and main program thread
1243                                         // (we need to synch when we decide to stop/free stream buffers)
1244 
1245 static  int         nCurrentBuffer = 0, nEmptyBuffers;
1246 
1247 static  boolean     buffers_prepared = FALSE;
1248 static  DWORD       dwVolCache[MAX_MIDI_IN_TRACKS];
1249         DWORD       dwVolumePercent;    // accessed by win_main.c
1250 
1251         // this is accessed by mid2strm.c conversion code
1252         boolean     midi_looped = FALSE, midi_playing = FALSE, midi_paused = FALSE;
1253         CONVERTINFO ciStreamBuffers[NUM_STREAM_BUFFERS];
1254 
1255 #define STATUS_KILLCALLBACK     100     // Signals that the callback should die
1256 #define STATUS_CALLBACKDEAD     200     // Signals callback is done processing
1257 #define STATUS_WAITINGFOREND    300     // Callback's waiting for buffers to play
1258 
1259 #define DEBUG_CALLBACK_TIMEOUT 2000         // Wait 2 seconds for callback
1260                                         // faB: don't freeze the main code if we debug..
1261 
1262 #define VOL_CACHE_INIT          127     // for dwVolCache[]
1263 
1264 static boolean  midi_can_set_volume;          // midi caps
1265 
1266 static void Mid2StreamFreeBuffers( void );
1267 static void CALLBACK MidiStreamCallback (HMIDIIN hMidi, UINT uMsg, DWORD dwInstance,
1268                                                  DWORD dwParam1, DWORD dwParam2 );
1269 static BOOL StreamBufferSetup( byte* MidiData, int MidiSize );
1270 
1271 // -------------------
1272 // MidiErrorMessageBox
1273 // Calls the midiOutGetErrorText() function and displays the text which
1274 // corresponds to a midi subsystem error code.
1275 // -------------------
MidiErrorMessageBox(MMRESULT mmr)1276 static void MidiErrorMessageBox(MMRESULT mmr)
1277 {
1278     char szTemp[256];
1279 
1280     /*szTemp[0] = '\2';   //white text to stand out*/
1281     midiOutGetErrorText (mmr, szTemp/*+1*/, sizeof(szTemp));
1282     CONS_Printf (szTemp);
1283     /*MessageBox (GetActiveWindow(), szTemp+1, "LEGACY",
1284                 MB_OK | MB_ICONSTOP );*/
1285     //wsprintf( szDebug, "Midi subsystem error: %s", szTemp );
1286 }
1287 
1288 
1289 // ----------------
1290 // I_InitAudioMixer
1291 // ----------------
1292 #ifdef TESTCODE
I_InitAudioMixer(void)1293 void I_InitAudioMixer (void)
1294 {
1295     UINT        cMixerDevs;
1296     cMixerDevs = mixerGetNumDevs();
1297     CONS_Printf ("%d mixer devices available\n", cMixerDevs);
1298 }
1299 #endif
1300 
1301 
1302 // -----------
1303 // I_InitMusic
1304 // Startup Midi device for streaming output
1305 // -----------
I_InitMusic(void)1306 static void I_InitMusic(void)
1307 {
1308     DWORD       idx;
1309     MMRESULT    mmrRetVal;
1310     UINT        cMidiDevs;
1311     MIDIOUTCAPS MidiOutCaps;
1312     char*       szTechnology;
1313 
1314     CONS_Printf("I_InitMusic: \n");
1315 
1316 #ifdef FMOD_SOUND
1317     if(fmod_music)
1318     {
1319         // SSNTails 12-13-2002
1320         if (FSOUND_GetVersion() < FMOD_VERSION_NEEDED)
1321         {
1322 	    I_SoftError("Error : You are using the wrong DLL version!  You should be using FMOD %.02f\n", FMOD_VERSION_NEEDED);
1323 	    fmod_music = 0;
1324 	    goto no_fmod;
1325 	}
1326 
1327         // INITIALIZE FMOD
1328         if (!FSOUND_Init(44100, 1, FSOUND_INIT_GLOBALFOCUS)) // Source data MUST be 44.1khz!
1329         {
1330 	    I_SoftError("%s\n", FMOD_ErrorString(FSOUND_GetError()));
1331 	    fmod_music = 0;
1332 	    goto no_fmod;
1333 	}
1334         return;
1335     }
1336 no_fmod:
1337 #endif
1338 
1339     // check out number of MIDI devices available
1340     //
1341     cMidiDevs = midiOutGetNumDevs();
1342     if (!cMidiDevs) {
1343         CONS_Printf ("No MIDI devices available, music is disabled\n");
1344         nomusic = true;
1345         return;
1346     }
1347 #ifdef DEBUGMIDISTREAM
1348     else {
1349         CONS_Printf ("%d MIDI devices available\n", cMidiDevs);
1350     }
1351 #endif
1352 
1353     if( M_CheckParm("-winmidi") )
1354         uMIDIDeviceID = atoi(M_GetNextParm());
1355     else
1356         uMIDIDeviceID = MIDI_MAPPER;
1357 
1358     // get MIDI device caps
1359     //
1360     if ((mmrRetVal = midiOutGetDevCaps (uMIDIDeviceID, &MidiOutCaps, sizeof(MIDIOUTCAPS))) !=
1361         MMSYSERR_NOERROR) {
1362         CONS_Printf ("midiOutGetCaps FAILED : \n");
1363         MidiErrorMessageBox (mmrRetVal);
1364     }
1365     else
1366     {
1367         CONS_Printf ("MIDI product name: %s\n", MidiOutCaps.szPname);
1368         switch (MidiOutCaps.wTechnology) {
1369         case MOD_FMSYNTH:   szTechnology = "FM Synth"; break;
1370         case MOD_MAPPER:    szTechnology = "Microsoft MIDI Mapper"; break;
1371         case MOD_MIDIPORT:  szTechnology = "MIDI hardware port"; break;
1372         case MOD_SQSYNTH:   szTechnology = "Square wave synthesizer"; break;
1373         case MOD_SYNTH:     szTechnology = "Synthesizer"; break;
1374         default:            szTechnology = "unknown"; break;
1375         }
1376         CONS_Printf ("MIDI technology: %s\n", szTechnology);
1377         CONS_Printf ("MIDI caps:\n");
1378         if (MidiOutCaps.dwSupport & MIDICAPS_CACHE)
1379             CONS_Printf ("-Patch caching\n");
1380         if (MidiOutCaps.dwSupport & MIDICAPS_LRVOLUME)
1381             CONS_Printf ("-Separate left and right volume control\n");
1382         if (MidiOutCaps.dwSupport & MIDICAPS_STREAM)
1383             CONS_Printf ("-Direct support for midiStreamOut()\n");
1384         if (MidiOutCaps.dwSupport & MIDICAPS_VOLUME)
1385             CONS_Printf ("-Volume control\n");
1386         midi_can_set_volume = ((MidiOutCaps.dwSupport & MIDICAPS_VOLUME)!=0);
1387     }
1388 
1389 #ifdef TESTCODE
1390     I_InitAudioMixer ();
1391 #endif
1392 
1393     // initialisation of midicard by I_StartupSound
1394     MidiData_buf = Z_Malloc (MIDBUFFERSIZE,PU_STATIC,NULL);
1395 
1396     // ----------------------------------------------------------------------
1397     // Midi2Stream initialization
1398     // ----------------------------------------------------------------------
1399 
1400     // create event for synch'ing the callback thread to main program thread
1401     // when we will need it
1402     hBufferReturnEvent = CreateEvent( NULL, FALSE, FALSE,
1403                          "DoomLegacy Midi Playback: Wait For Buffer Return" );
1404 
1405     if( !hBufferReturnEvent )
1406     {
1407         I_GetLastErrorMsgBox();
1408         nomusic = true;
1409         return;
1410     }
1411 
1412     if ((mmrRetVal = midiStreamOpen(&hStream,
1413                                     &uMIDIDeviceID,
1414                                     (DWORD)1, (DWORD)MidiStreamCallback/*NULL*/,
1415                                     (DWORD)0,
1416                                     CALLBACK_FUNCTION /*CALLBACK_NULL*/)) != MMSYSERR_NOERROR)
1417     {
1418         CONS_Printf ("I_RegisterSong: midiStreamOpen FAILED\n");
1419         MidiErrorMessageBox( mmrRetVal );
1420         nomusic = true;
1421         return;
1422     }
1423 
1424     // stream buffers are initially unallocated (set em NULL)
1425     for (idx = 0; idx < NUM_STREAM_BUFFERS; idx++ )
1426         ZeroMemory (&ciStreamBuffers[idx].mhBuffer, sizeof(MIDIHDR));
1427     // ----------------------------------------------------------------------
1428 
1429     // register exit code
1430     I_AddExitFunc (I_ShutdownMusic);
1431 
1432     music_started = true;
1433 }
1434 
1435 
1436 // ---------------
1437 // I_ShutdownMusic
1438 // ---------------
1439 // Is also a registered exit func
I_ShutdownMusic(void)1440 static void I_ShutdownMusic(void)
1441 {
1442     DWORD       idx;
1443     MMRESULT    mmrRetVal;
1444 
1445     if( nomusic )
1446         return;
1447 
1448     CONS_Printf("I_ShutdownMusic: \n");
1449 
1450 #ifdef FMOD_SOUND
1451     if(fmod_music)
1452     {
1453         FSOUND_Stream_Stop(fmus);
1454         FSOUND_Stream_Close(fmus);
1455         FSOUND_Close();
1456         remove("mus.tmp"); // Delete the temp file
1457         return;
1458     }
1459 #endif
1460 
1461     if (!music_started)
1462         return;
1463 
1464     if (hStream)
1465     {
1466         I_StopSong (SPECIAL_HANDLE_CLEANMIDI);
1467     }
1468 
1469     Mid2StreamConverterCleanup();
1470     Mid2StreamFreeBuffers();
1471 
1472     // Free our stream buffers
1473     for( idx = 0; idx < NUM_STREAM_BUFFERS; idx++ )
1474     {
1475         if( ciStreamBuffers[idx].mhBuffer.lpData )
1476         {
1477 	    // MinGW could not handle expr in GlobalFreePtr
1478 	    void * dp = ciStreamBuffers[idx].mhBuffer.lpData;
1479 	    // [WDJ] MinGW fix, GlobalFreePtr() without the test is an error
1480             if( GlobalFreePtr( dp ) )  // returns BOOL that MinGW insists that we handle
1481             {};
1482             ciStreamBuffers[idx].mhBuffer.lpData = NULL;
1483         }
1484     }
1485 
1486     if (hStream)
1487     {
1488         if(( mmrRetVal = midiStreamClose( hStream )) != MMSYSERR_NOERROR )
1489 	    MidiErrorMessageBox( mmrRetVal );
1490         hStream = NULL;
1491     }
1492 
1493     CloseHandle( hBufferReturnEvent );
1494 
1495     //free (MidiData_buf);
1496 
1497     music_started = false;
1498 }
1499 
1500 
1501 // --------------------
1502 // SetAllChannelVolumes
1503 // Given a percent in tenths of a percent, sets volume on all channels to
1504 // reflect the new value.
1505 // --------------------
SetAllChannelVolumes(DWORD dwVolumePercent)1506 static void SetAllChannelVolumes( DWORD dwVolumePercent )
1507 {
1508     DWORD       dwEvent, dwStatus, dwVol, idx;
1509     MMRESULT    mmrRetVal;
1510 
1511     if( !midi_playing )
1512         return;
1513 
1514     for( idx = 0, dwStatus = MIDI_CTRLCHANGE; idx < MAX_MIDI_IN_TRACKS; idx++, dwStatus++ )
1515     {
1516         dwVol = ( dwVolCache[idx] * dwVolumePercent ) / 1000;
1517         //CONS_Printf ("channel %d vol %d\n", idx, dwVol);
1518         dwEvent = dwStatus | ((DWORD)MIDICTRL_VOLUME << 8)
1519             | ((DWORD)dwVol << 16);
1520         if(( mmrRetVal = midiOutShortMsg( (HMIDIOUT)hStream, dwEvent ))
1521             != MMSYSERR_NOERROR )
1522         {
1523             MidiErrorMessageBox( mmrRetVal );
1524             return;
1525         }
1526     }
1527 }
1528 
1529 
1530 // ----------------
1531 // I_SetMusicVolume
1532 // Set the midi output volume
1533 // ----------------
I_SetMusicVolume(int volume)1534 void I_SetMusicVolume(int volume)
1535 {
1536     if (nomusic)
1537         return;
1538 
1539 #ifdef FMOD_SOUND
1540     if (fmod_music )
1541     {
1542         // left and right channels
1543         FSOUND_SetVolume(0, (volume<<3)+(volume>>2));
1544         return;
1545     }
1546 #endif
1547 
1548     if (midi_can_set_volume)
1549     {
1550         MMRESULT    mmrRetVal;
1551         // method A
1552         // current volume is 0-31, we need 0-0xFFFF in each word (left/right channel)
1553         int midi_volume = (volume << 11) | (volume << 27);
1554         if ((mmrRetVal = midiOutSetVolume ((HMIDIOUT)uMIDIDeviceID, midi_volume)) != MMSYSERR_NOERROR) {
1555             CONS_Printf ("I_SetMusicVolume: couldn't set volume\n");
1556             MidiErrorMessageBox(mmrRetVal);
1557         }
1558     }
1559     else
1560     {
1561         // method B
1562         dwVolumePercent = (volume * 1000) / 32;
1563         SetAllChannelVolumes (dwVolumePercent);
1564     }
1565 }
1566 
1567 
1568 void I_StartFMODSong (char* musicname, int looping);
1569 
1570 // ----------
1571 // I_PlaySong
1572 // Note: doesn't use the handle, would be useful to switch between mid's after
1573 //       some trigger (would do several RegisterSong, then PlaySong the chosen one)
1574 // ----------
I_PlaySong(int handle,int looping)1575 void I_PlaySong(int handle, int looping)
1576 {
1577     MMRESULT        mmrRetVal;
1578 
1579     if (nomusic)
1580         return;
1581 
1582 #ifdef FMOD_SOUND
1583     if (fmod_music)
1584     {
1585 #if 0
1586        	// FIXME: Need handle --> musicname
1587         I_StartFMODSong ( musicname, looping);
1588 #endif
1589     }
1590 #endif
1591 
1592 #ifdef DEBUGMIDISTREAM
1593     debug_Printf("I_PlaySong: looping %d\n", looping);
1594 #endif
1595 
1596     // unpause the song first if it was paused
1597     if( midi_paused )
1598         I_PauseSong( handle );
1599 
1600     // Clear the status of our callback so it will handle
1601     // MOM_DONE callbacks once more
1602     uCallbackStatus = 0;
1603     if(( mmrRetVal = midiStreamRestart( hStream )) != MMSYSERR_NOERROR )
1604     {
1605         MidiErrorMessageBox( mmrRetVal );
1606         Mid2StreamFreeBuffers();
1607         Mid2StreamConverterCleanup();
1608         I_SoftError ("I_PlaySong: midiStreamRestart error");
1609         return;
1610     }
1611     midi_playing = TRUE;
1612     midi_looped = looping;
1613 }
1614 
1615 
1616 // -----------
1617 // I_PauseSong
1618 // calls midiStreamPause() to pause the midi playback
1619 // -----------
I_PauseSong(int handle)1620 void I_PauseSong (int handle)
1621 {
1622 #ifdef FMOD_SOUND
1623     if(fmod_music)
1624     {
1625         if(FSOUND_IsPlaying(0)) // FMOD's so easy you almost lose brain
1626 	    FSOUND_SetPaused(0, true); // cells programming for it!
1627 
1628         return;
1629     }
1630 #endif
1631 
1632     if (nomusic)
1633         return;
1634 
1635 #ifdef DEBUGMIDISTREAM
1636     debug_Printf("I_PauseSong: \n");
1637 #endif
1638 
1639     if (!midi_paused) {
1640         midiStreamPause( hStream );
1641         midi_paused = true;
1642     }
1643 }
1644 
1645 
1646 // ------------
1647 // I_ResumeSong
1648 // un-pause the midi song with midiStreamRestart
1649 // ------------
I_ResumeSong(int handle)1650 void I_ResumeSong (int handle)
1651 {
1652 #ifdef FMOD_SOUND
1653     if(fmod_music)
1654     {
1655         if(FSOUND_GetPaused(0))
1656 	    FSOUND_SetPaused(0, false);
1657         return;
1658     }
1659 #endif
1660 
1661     if (nomusic)
1662         return;
1663 
1664 #ifdef DEBUGMIDISTREAM
1665     debug_Printf("I_ResumeSong: \n");
1666 #endif
1667 
1668     if( midi_paused )
1669     {
1670         midiStreamRestart( hStream );
1671         midi_paused = false;
1672     }
1673 }
1674 
1675 
1676 // ----------
1677 // I_StopSong
1678 // ----------
1679 // faB: -1999 is a special handle here, it means we stop the midi when exiting
1680 //      Legacy, this will do a midiOutReset() for a more 'sure' midi off.
I_StopSong(int handle)1681 void I_StopSong(int handle)
1682 {
1683     MMRESULT        mmrRetVal;
1684 
1685     if (nomusic)
1686         return;
1687 
1688 #ifdef FMOD_SOUND
1689     if (fmod_music)
1690     {
1691         if(FSOUND_IsPlaying(0))
1692 	    FSOUND_Stream_Stop(fmus);
1693     }
1694 #endif
1695 
1696 #ifdef DEBUGMIDISTREAM
1697     debug_Printf("I_StopSong: \n");
1698 #endif
1699 
1700     if (midi_playing || (uCallbackStatus != STATUS_CALLBACKDEAD) )
1701     {
1702         midi_playing = midi_paused = FALSE;
1703         if( uCallbackStatus != STATUS_CALLBACKDEAD &&
1704             uCallbackStatus != STATUS_WAITINGFOREND )
1705                     uCallbackStatus = STATUS_KILLCALLBACK;
1706 
1707         //debug_Printf ("a: %d\n",I_GetTime());
1708         if(( mmrRetVal = midiStreamStop( hStream )) != MMSYSERR_NOERROR )
1709         {
1710             MidiErrorMessageBox( mmrRetVal );
1711             return;
1712         }
1713 
1714         //faB: if we don't call midiOutReset() seems we have to stop the buffers
1715         //     ourselves (or it doesn't play anymore)
1716         if (!midi_paused && (handle != SPECIAL_HANDLE_CLEANMIDI))
1717         {
1718             midiStreamPause( hStream );
1719         }
1720         //debug_Printf ("b: %d\n",I_GetTime());
1721         else
1722         //faB: this damn call takes 1 second and a half !!! still do it on exit
1723         //     to be sure everything midi is cleaned as much as possible
1724         if (handle == SPECIAL_HANDLE_CLEANMIDI) {
1725             //
1726             if(( mmrRetVal = midiOutReset( (HMIDIOUT)hStream )) != MMSYSERR_NOERROR )
1727             {
1728                 MidiErrorMessageBox( mmrRetVal );
1729                 return;
1730             }
1731         }
1732         //debug_Printf ("c: %d\n",I_GetTime());
1733 
1734         // Wait for the callback thread to release this thread, which it will do by
1735         // calling SetEvent() once all buffers are returned to it
1736         if( WaitForSingleObject( hBufferReturnEvent, DEBUG_CALLBACK_TIMEOUT )
1737                                                             == WAIT_TIMEOUT )
1738         {
1739             // Note, this is a risky move because the callback may be genuinely busy, but
1740             // when we're debugging, it's safer and faster than freezing the application,
1741             // which leaves the MIDI device locked up and forces a system reset...
1742             CONS_Printf( "Timed out waiting for MIDI callback\n" );
1743             uCallbackStatus = STATUS_CALLBACKDEAD;
1744         }
1745         //debug_Printf ("d: %d\n",I_GetTime());
1746     }
1747 
1748     if( uCallbackStatus == STATUS_CALLBACKDEAD )
1749     {
1750         uCallbackStatus = 0;
1751         Mid2StreamConverterCleanup();
1752         Mid2StreamFreeBuffers();
1753         //faB: we could close the stream here and re-open later to avoid
1754         //     a little quirk in mmsystem (see DirectX6 mstream note)
1755         midiStreamClose(hStream);
1756         midiStreamOpen(&hStream, &uMIDIDeviceID, (DWORD)1,
1757                        (DWORD)MidiStreamCallback/*NULL*/,
1758                        (DWORD)0, CALLBACK_FUNCTION /*CALLBACK_NULL*/);
1759     }
1760 }
1761 
1762 
1763 // Is the song playing?
I_QrySongPlaying(int handle)1764 int I_QrySongPlaying (int handle)
1765 {
1766     if (nomusic)
1767         return 0;
1768 
1769 #ifdef DEBUGMIDISTREAM
1770     debug_Printf("I_QrySongPlaying: \n");
1771 #endif
1772 #ifdef FMOD_SOUND
1773     if (fmod_music)
1774     {
1775         return FSOUND_IsPlaying(0);
1776     }
1777 #endif
1778     return (midi_playing);
1779 }
1780 
1781 
I_UnRegisterSong(int handle)1782 void I_UnRegisterSong(int handle)
1783 {
1784     if (nomusic)
1785         return;
1786 
1787     //faB: we might free here whatever is allocated per-music
1788     //     (but we don't cause I hate malloc's)
1789     Mid2StreamConverterCleanup();
1790 
1791 #ifdef DEBUGMIDISTREAM
1792     debug_Printf("I_UnregisterSong: \n");
1793 #endif
1794 }
1795 
1796 #ifdef FMOD_SOUND
1797 // Special FMOD support SSNTails 12-13-2002
I_StartFMODSong(char * musicname,int looping)1798 void I_StartFMODSong (char* musicname, int looping)
1799 {
1800 	char filename[9];
1801 	void* data;
1802 	lumpnum_t lumpnum;
1803 
1804 	if(!fmod_music)
1805 		return;
1806 
1807 	if(fmus != NULL)
1808 	{
1809 		FSOUND_Stream_Stop(fmus);
1810 		FSOUND_Stream_Close(fmus);
1811 	}
1812 
1813 	// Create the filename we need
1814 	sprintf(filename, "o_%s", musicname);
1815 
1816 	lumpnum = W_CheckNumForName(filename);
1817 
1818 	if( ! VALID_LUMP(lumpnum) )
1819 	{
1820 		CONS_Printf("Music file %s not found!\n", filename);
1821 		fmus = NULL;
1822 		return; // No music found. Oh well! Just be silent instead.
1823 		        // Maybe someone (not me, heheh) would like to have it revert to
1824 		        // MIDI when a digital file isn't found?
1825 	}
1826 
1827 	data = W_CacheLumpName ( filename, PU_CACHE );
1828 
1829 	I_SaveMemToFile (data, W_LumpLength(W_GetNumForName (filename)), "mus.tmp");
1830 
1831 	Z_Free(data);
1832 
1833 	fmus = FSOUND_Stream_Open("mus.tmp", looping ? FSOUND_LOOP_NORMAL : 0, 0, 0);
1834 
1835 	if (!fmus)
1836 	{
1837 		CONS_Printf("%s:\n%s", FMOD_ErrorString(FSOUND_GetError()), filename);
1838 		return;
1839 	}
1840 
1841 	/*
1842 	    PLAY SONG
1843 	*/
1844 
1845 	FSOUND_Stream_Play(0, fmus);
1846 }
1847 #endif
1848 
1849 // --------------
1850 // I_RegisterSong
1851 // Prepare a song for playback
1852 // - if MUS, convert it to MIDI format
1853 // - setup midi stream buffers, and activate the callback procedure
1854 //   which will continually fill the buffers with new data
1855 // --------------
1856 
I_RegisterSong(void * data,int len)1857 int I_RegisterSong(void* data, int len)
1858 {
1859     int    err_code;
1860     byte*  MidiData = NULL;  // MIDI music buffer to be played or NULL
1861     unsigned long  MidiSize;  // size of Midi output data
1862 
1863     if (nomusic)
1864         return 1;
1865 
1866 #ifdef DEBUGMIDISTREAM
1867     debug_Printf("I_RegisterSong: \n");
1868 #endif
1869     if (!memcmp(data,"MUS",3))
1870     {
1871         // convert mus to mid with a wonderful function
1872         // thanks to S.Bacquet for the sources of qmus2mid
1873         // convert mus to mid and load it in memory
1874         err_code = qmus2mid((byte *)data, len, 89, 0,
1875 				MIDBUFFERSIZE,
1876 		   /*INOUT*/    MidiData_buf, &MidiSize );
1877         if(err_code != QM_success)
1878         {
1879             CONS_Printf("Cannot convert mus to mid, converterror :%d\n", err_code);
1880             return 0;
1881         }
1882         MidiData = MidiData_buf;
1883     }
1884     else if (!memcmp(data,"MThd",4))
1885     {
1886         // support mid file in WAD !!! (no conversion needed)
1887         MidiData = data;
1888         MidiSize = len;
1889     }
1890     else
1891     {
1892         CONS_Printf ("Music lump is not MID or MUS music format\n");
1893         return 0;
1894     }
1895 
1896     if (MidiData == NULL)
1897     {
1898         CONS_Printf ("Not a valid MIDI file : %c%c%c%c\n",
1899 		     (char)data[0], (char)data[1], (char)data[2], (char)data[3]);
1900         return 0;
1901     }
1902 #ifdef DEBUGMIDISTREAM
1903     else
1904     {
1905         I_SaveMemToFile (MidiData, MidiSize, "c:/temp/debug.mid");
1906     }
1907 #endif
1908 
1909     // setup midi stream buffer
1910     if (StreamBufferSetup(MidiData, MidiSize))
1911     {
1912         Mid2StreamConverterCleanup();
1913         I_SoftError ("I_RegisterSong: StreamBufferSetup FAILED\n");
1914         return 0;
1915     }
1916 
1917     return 1;
1918 }
1919 
1920 
1921 // -----------------
1922 // StreamBufferSetup
1923 // This function uses the filename stored in the global character array to
1924 // open a MIDI file. Then it goes about converting at least the first part of
1925 // that file into a midiStream buffer for playback.
1926 // -----------------
1927 
1928 //mid2strm.c - returns TRUE if an error occurs
1929 BOOL Mid2StreamConverterInit( byte* MidiData, ULONG MidiSize );
1930 void Mid2StreamConverterCleanup( void );
1931 
1932 
1933 // -----------------
1934 // StreamBufferSetup
1935 // - returns TRUE if a problem occurs
1936 // -----------------
StreamBufferSetup(byte * MidiData,int MidiSize)1937 static BOOL StreamBufferSetup( byte* MidiData, int MidiSize )
1938 {
1939     MMRESULT            mmrRetVal;
1940     MIDIPROPTIMEDIV     mptd;
1941     BOOL    bFoundEnd = FALSE;
1942     int     dwConvertFlag;
1943     int     nChkErr;
1944     int     idx;
1945 
1946 #ifdef DEBUGMIDISTREAM
1947     if (hStream == NULL)
1948         I_Error ("StreamBufferSetup: hStream is NULL!");
1949 #endif
1950 
1951     // pause midi stream before manipulate there buffers
1952     midiStreamPause(hStream);
1953 
1954     // allocate the stream buffers (only once)
1955     for (idx = 0; idx < NUM_STREAM_BUFFERS; idx++ )
1956     {
1957         ciStreamBuffers[idx].mhBuffer.dwBufferLength = OUT_BUFFER_SIZE;
1958         if( ciStreamBuffers[idx].mhBuffer.lpData == NULL )
1959         {
1960             if(( ciStreamBuffers[idx].mhBuffer.lpData = GlobalAllocPtr( GHND, OUT_BUFFER_SIZE )) == NULL )
1961             {
1962                 return (FALSE);
1963             }
1964         }
1965     }
1966 
1967     // returns TRUE in case of conversion error
1968     if (Mid2StreamConverterInit( MidiData, MidiSize ))
1969         return( TRUE );
1970 
1971     // Initialize the volume cache array to some pre-defined value
1972     for( idx = 0; idx < MAX_MIDI_IN_TRACKS; idx++ )
1973         dwVolCache[idx] = VOL_CACHE_INIT;
1974 
1975     mptd.cbStruct = sizeof(mptd);
1976     mptd.dwTimeDiv = ifs.dwTimeDivision;
1977     if(( mmrRetVal = midiStreamProperty(hStream,(LPBYTE)&mptd,
1978                                         MIDIPROP_SET | MIDIPROP_TIMEDIV ))
1979                     != MMSYSERR_NOERROR )
1980     {
1981         MidiErrorMessageBox( mmrRetVal );
1982         return( TRUE );
1983     }
1984 
1985     nEmptyBuffers = 0;
1986     dwConvertFlag = CONVERTF_RESET;
1987 
1988     for( nCurrentBuffer = 0; nCurrentBuffer < NUM_STREAM_BUFFERS; nCurrentBuffer++ )
1989     {
1990         // Tell the converter to convert up to one entire buffer's length of output
1991         // data. Also, set a flag so it knows to reset any saved state variables it
1992         // may keep from call to call.
1993         ciStreamBuffers[nCurrentBuffer].dwStartOffset = 0;
1994         ciStreamBuffers[nCurrentBuffer].dwMaxLength = OUT_BUFFER_SIZE;
1995         ciStreamBuffers[nCurrentBuffer].tkStart = 0;
1996         ciStreamBuffers[nCurrentBuffer].bTimesUp = FALSE;
1997 
1998         if(( nChkErr = Mid2StreamConvertToBuffer( dwConvertFlag,
1999                                                   &ciStreamBuffers[nCurrentBuffer] ))
2000                     != CONVERTERR_NOERROR )
2001         {
2002             if( nChkErr == CONVERTERR_DONE )
2003             {
2004                 bFoundEnd = TRUE;
2005             }
2006             else
2007             {
2008                 CONS_Printf( "StreamBufferSetup: initial conversion pass failed" );
2009                 return( TRUE );
2010             }
2011         }
2012         ciStreamBuffers[nCurrentBuffer].mhBuffer.dwBytesRecorded
2013             = ciStreamBuffers[nCurrentBuffer].dwBytesRecorded;
2014 
2015         if( !buffers_prepared ) {
2016             if(( mmrRetVal = midiOutPrepareHeader( (HMIDIOUT)hStream,
2017                 &ciStreamBuffers[nCurrentBuffer].mhBuffer,
2018                 sizeof(MIDIHDR))) != MMSYSERR_NOERROR )
2019             {
2020                 MidiErrorMessageBox( mmrRetVal );
2021                 return( TRUE );
2022             }
2023         }
2024         if(( mmrRetVal = midiStreamOut( hStream,
2025             &ciStreamBuffers[nCurrentBuffer].mhBuffer,
2026             sizeof(MIDIHDR))) != MMSYSERR_NOERROR )
2027         {
2028             MidiErrorMessageBox( mmrRetVal );
2029             break;
2030         }
2031         dwConvertFlag = 0;
2032 
2033         if( bFoundEnd )
2034             break;
2035     }
2036 
2037     buffers_prepared = TRUE;
2038     nCurrentBuffer = 0;
2039 
2040     // MIDI volume
2041     dwVolumePercent = (cv_musicvolume.value * 1000) / 32;
2042     if( hStream )
2043         SetAllChannelVolumes( dwVolumePercent );
2044 
2045     return( FALSE );
2046 }
2047 
2048 
2049 // ----------------
2050 // SetChannelVolume
2051 // Call here delayed by MIDI stream callback, to adapt the volume event of the
2052 // midi stream to our own set volume percentage.
2053 // ----------------
I_SetMidiChannelVolume(DWORD dwChannel,DWORD dwVolumePercent)2054 void I_SetMidiChannelVolume( DWORD dwChannel, DWORD dwVolumePercent )
2055 {
2056     DWORD       dwEvent, dwVol;
2057     MMRESULT    mmrRetVal;
2058 
2059     if( !midi_playing )
2060             return;
2061 
2062     dwVol = ( dwVolCache[dwChannel] * dwVolumePercent ) / 1000;
2063     //CONS_Printf ("setvolchannel %d vol %d\n", dwChannel, dwVol);
2064     dwEvent = MIDI_CTRLCHANGE | dwChannel | ((DWORD)MIDICTRL_VOLUME << 8)
2065                                           | ((DWORD)dwVol << 16);
2066     if(( mmrRetVal = midiOutShortMsg( (HMIDIOUT)hStream, dwEvent ))
2067                                                         != MMSYSERR_NOERROR )
2068     {
2069 #ifdef DEBUGMIDISTREAM
2070         MidiErrorMessageBox( mmrRetVal );
2071 #endif
2072         return;
2073     }
2074 }
2075 
2076 
2077 
2078 // ------------------
2079 // MidiStreamCallback
2080 // This is the callback handler which continually refills MIDI data buffers
2081 // as they're returned to us from the audio subsystem.
2082 // ------------------
MidiStreamCallback(HMIDIIN hMidi,UINT uMsg,DWORD dwInstance,DWORD dwParam1,DWORD dwParam2)2083 static void CALLBACK MidiStreamCallback (HMIDIIN hMidi, UINT uMsg, DWORD dwInstance,
2084                                                  DWORD dwParam1, DWORD dwParam2 )
2085 {
2086     //static int  nWaitingBuffers = 0;
2087     MMRESULT    mmrRetVal;
2088     int         nChkErr;
2089     MIDIEVENT   *pme;
2090     MIDIHDR     *pmh;
2091 
2092 
2093     switch( uMsg )
2094     {
2095         case MOM_DONE:
2096             //faB:  dwParam1 is LPMIDIHDR
2097 
2098             if( uCallbackStatus == STATUS_CALLBACKDEAD )
2099                 return;
2100 
2101             nEmptyBuffers++;
2102 
2103             //faB: we reached end of song, but we wait until all the buffers are returned
2104             if( uCallbackStatus == STATUS_WAITINGFOREND )
2105             {
2106                 if( nEmptyBuffers < NUM_STREAM_BUFFERS )
2107                 {
2108                     return;
2109                 }
2110                 else
2111                 {
2112                     // stop the song when end reached (was not looping)
2113                     uCallbackStatus = STATUS_CALLBACKDEAD;
2114                     SetEvent( hBufferReturnEvent );
2115                     I_StopSong(0);
2116                     return;
2117                 }
2118             }
2119 
2120             // This flag is set whenever the callback is waiting for all buffers to
2121             // come back.
2122             if( uCallbackStatus == STATUS_KILLCALLBACK )
2123             {
2124                 // Count NUM_STREAM_BUFFERS-1 being returned for the last time
2125                 if( nEmptyBuffers < NUM_STREAM_BUFFERS )
2126                 {
2127                     return;
2128                 }
2129                 // Then send a stop message when we get the last buffer back...
2130                 else
2131                 {
2132                     // Change the status to callback dead
2133                     uCallbackStatus = STATUS_CALLBACKDEAD;
2134                     SetEvent( hBufferReturnEvent );
2135                     return;
2136                 }
2137             }
2138 
2139             dwProgressBytes += ciStreamBuffers[nCurrentBuffer].mhBuffer.dwBytesRecorded;
2140 
2141         // -------------------------------------------------
2142         // Fill an available buffer with audio data again...
2143         // -------------------------------------------------
2144 
2145             if( midi_playing && nEmptyBuffers )
2146             {
2147                 ciStreamBuffers[nCurrentBuffer].dwStartOffset = 0;
2148                 ciStreamBuffers[nCurrentBuffer].dwMaxLength = OUT_BUFFER_SIZE;
2149                 ciStreamBuffers[nCurrentBuffer].tkStart = 0;
2150                 ciStreamBuffers[nCurrentBuffer].dwBytesRecorded = 0;
2151                 ciStreamBuffers[nCurrentBuffer].bTimesUp = FALSE;
2152 
2153                 if(( nChkErr = Mid2StreamConvertToBuffer( 0, &ciStreamBuffers[nCurrentBuffer] ))
2154                     != CONVERTERR_NOERROR )
2155                 {
2156                     if( nChkErr == CONVERTERR_DONE )
2157                     {
2158                         // Don't include this one in the count
2159                         //nWaitingBuffers = NUM_STREAM_BUFFERS - 1;
2160                         uCallbackStatus = STATUS_WAITINGFOREND;
2161                         return;
2162                     }
2163                     else
2164                     {
2165                         //faB: we're not in the main thread so we can't call I_Error() now
2166                         //     log the error message out, and post exit message.
2167                         CONS_Printf( "MidiStreamCallback(): conversion pass failed!" );
2168                         PostMessage(hWnd_main, WM_CLOSE, 0, 0);
2169                         return;
2170                     }
2171                 }
2172 
2173                 ciStreamBuffers[nCurrentBuffer].mhBuffer.dwBytesRecorded
2174                     = ciStreamBuffers[nCurrentBuffer].dwBytesRecorded;
2175 
2176                 if(( mmrRetVal = midiStreamOut( hStream,
2177                     &ciStreamBuffers[nCurrentBuffer].mhBuffer,
2178                     sizeof(MIDIHDR))) != MMSYSERR_NOERROR )
2179                 {
2180                     MidiErrorMessageBox( mmrRetVal );
2181                     Mid2StreamConverterCleanup();
2182                     return;
2183                 }
2184 
2185                 // May this line could resolve MIDI Bug issue?
2186                 ///////////////////////////////////////////
2187                 //I_SetMusicVolume( cv_musicvolume.value );
2188                 ///////////////////////////////////////////
2189 
2190                 nCurrentBuffer = ( nCurrentBuffer + 1 ) % NUM_STREAM_BUFFERS;
2191                 nEmptyBuffers--;
2192             }
2193 
2194             break;
2195 
2196         case MOM_POSITIONCB:
2197             pmh = (MIDIHDR *)dwParam1;
2198             pme = (MIDIEVENT *)(pmh->lpData + pmh->dwOffset);
2199             if( MIDIEVENT_TYPE( pme->dwEvent ) == MIDI_CTRLCHANGE )
2200             {
2201 #ifdef DEBUGMIDISTREAM
2202                 if( MIDIEVENT_DATA1( pme->dwEvent ) == MIDICTRL_VOLUME_LSB )
2203                 {
2204                     debug_Printf ( "Got an LSB volume event" );
2205                     PostMessage (hWnd_main, WM_CLOSE, 0, 0); //faB: can't I_Error() here
2206                     break;
2207                 }
2208 #endif
2209                 // this is meant to respond to our own intention, from mid2strm.c
2210                 if( MIDIEVENT_DATA1( pme->dwEvent ) != MIDICTRL_VOLUME )
2211                     break;
2212 
2213                 // Mask off the channel number and cache the volume data byte
2214                 //debug_Printf ( "dwvolcache %d = %d\n", MIDIEVENT_CHANNEL( pme->dwEvent ),  MIDIEVENT_VOLUME( pme->dwEvent ));
2215                 dwVolCache[ MIDIEVENT_CHANNEL( pme->dwEvent )] = MIDIEVENT_VOLUME( pme->dwEvent );
2216                 // call SetChannelVolume() later to adjust MIDI volume control message to our
2217                 // own current volume level.
2218                 PostMessage( hWnd_main, WM_MSTREAM_UPDATEVOLUME,
2219                              MIDIEVENT_CHANNEL( pme->dwEvent ), 0L );
2220             }
2221             break;
2222 
2223         default:
2224             break;
2225     }
2226 
2227     return;
2228 }
2229 
2230 
2231 // ---------------------
2232 // Mid2StreamFreeBuffers
2233 // This function unprepares and frees all our buffers -- something we must
2234 // do to work around a bug in MMYSYSTEM that prevents a device from playing
2235 // back properly unless it is closed and reopened after each stop.
2236 // ---------------------
Mid2StreamFreeBuffers(void)2237 static void Mid2StreamFreeBuffers( void )
2238 {
2239     DWORD       idx;
2240     MMRESULT    mmrRetVal;
2241 
2242     if( buffers_prepared )
2243     {
2244         for( idx = 0; idx < NUM_STREAM_BUFFERS; idx++ ) {
2245                 if(( mmrRetVal = midiOutUnprepareHeader( (HMIDIOUT)hStream,
2246                                         &ciStreamBuffers[idx].mhBuffer,
2247                                         sizeof(MIDIHDR)))
2248                                                 != MMSYSERR_NOERROR )
2249                 {
2250                     MidiErrorMessageBox( mmrRetVal );
2251                 }
2252         }
2253         buffers_prepared = FALSE;
2254     }
2255 
2256     //faB: I don't free the stream buffers here, but rather allocate them
2257     //      once at startup, and free'em at shutdown
2258 }
2259 
2260 
2261 //[WDJ] New init sound interface for sound effects and music combined
I_StartupSound(void)2262 void I_StartupSound(void)
2263 {
2264     sound_started = false;
2265     music_started = false;
2266 
2267     if ( ! nosoundfx )
2268     {
2269         I_DS_StartupSound();
2270     }
2271 
2272     if ( ! nomusic )
2273     {
2274         I_InitMusic();
2275     }
2276 }
2277 
I_ShutdownSound(void)2278 void I_ShutdownSound(void)
2279 {
2280     if ( ! nosoundfx )
2281     {
2282         I_DS_ShutdownSound();
2283     }
2284     if( ! nomusic)
2285     {
2286         I_ShutdownMusic();
2287     }
2288 }
2289 
2290 
I_SubmitSound(void)2291 void I_SubmitSound(void)
2292 {
2293 }
2294 
I_UpdateSound(void)2295 void I_UpdateSound(void)
2296 {
2297 }
2298