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