1 //**************************************************************************
2 //**
3 //**	##   ##    ##    ##   ##   ####     ####   ###     ###
4 //**	##   ##  ##  ##  ##   ##  ##  ##   ##  ##  ####   ####
5 //**	 ## ##  ##    ##  ## ##  ##    ## ##    ## ## ## ## ##
6 //**	 ## ##  ########  ## ##  ##    ## ##    ## ##  ###  ##
7 //**	  ###   ##    ##   ###    ##  ##   ##  ##  ##       ##
8 //**	   #    ##    ##    #      ####     ####   ##       ##
9 //**
10 //**	$Id: snd_win32music.cpp 4122 2010-01-05 14:56:31Z firebrand_kh $
11 //**
12 //**	Copyright (C) 1999-2006 Jānis Legzdiņš
13 //**
14 //**	This program is free software; you can redistribute it and/or
15 //**  modify it under the terms of the GNU General Public License
16 //**  as published by the Free Software Foundation; either version 2
17 //**  of the License, or (at your option) any later version.
18 //**
19 //**	This program is distributed in the hope that it will be useful,
20 //**  but WITHOUT ANY WARRANTY; without even the implied warranty of
21 //**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22 //**  GNU General Public License for more details.
23 //**
24 //**************************************************************************
25 
26 // HEADER FILES ------------------------------------------------------------
27 
28 #include "winlocal.h"
29 #include <mmsystem.h>
30 #include "gamedefs.h"
31 #include "snd_local.h"
32 
33 // MACROS ------------------------------------------------------------------
34 
35 // TYPES -------------------------------------------------------------------
36 
37 class VMMSystemMidiDevice : public VMidiDevice
38 {
39 public:
40 	enum { MAX_TRACKS = 32 };
41 	enum { MAX_TICKS = 0xFFFFFFFF };
42 
43 	//	Number and size of playback buffers to keep around
44 	enum { C_MIDI_BUFFERS = 4 };
45 	enum { CB_MIDI_BUFFERS = 1024 };
46 
47 	enum
48 	{
49 		STATE_NoFile,
50 		STATE_Opened,
51 		STATE_Prerolling,
52 		STATE_Prerolled,
53 		STATE_Playing,
54 		STATE_Paused,
55 		STATE_Stopping,
56 		STATE_Reset,
57 	};
58 
59 	enum EResult
60 	{
61 		RES_Success,
62 		RES_InvalidFile,
63 		RES_EndOfFile
64 	};
65 
66 	//	Various flags.
67 	enum
68 	{
69 		TRACKF_Eot			= 0x00000001,
70 
71 		SEQF_Eof			= 0x00000001,
72 		SEQF_Waiting		= 0x00000002,
73 
74 		SMFF_Eof			= 0x00000001,
75 		SMFF_InsertSysEx	= 0x00000002
76 	};
77 
78 	//	Midi events.
79 	enum
80 	{
81 		MIDI_Msg		= 0x80,
82 		MIDI_Meta		= 0xFF,
83 		MIDI_SysEx		= 0xF0,
84 		MIDI_SysExEnd	= 0xF7,
85 
86 		MIDI_MetaEot	= 0x2F,
87 		MIDI_MetaTempo	= 0x51,
88 	};
89 
90 #pragma pack(1)
91 	struct FChunkHdr
92 	{
93 		char			Type[4];
94 		vuint32			Length;
95 	};
96 
97 	struct FMidiFileHdr
98 	{
99 		vuint16			Format;
100 		vuint16			Tracks;
101 		vuint16			Division;
102 	};
103 #pragma pack()
104 
105 	struct FEvent
106 	{
107 		DWORD			Delta;
108 		byte			Event[3];
109 		DWORD			ParmCount;
110 		const byte*		Parm;
111 	};
112 
113 	struct FTrack
114 	{
115 		DWORD			FileOffset;
116 		DWORD			TotalLength;
117 
118 		DWORD			Position;
119 		DWORD			BytesLeft;
120 		const byte*		ImagePtr;
121 		byte			RunningStatus;
122 
123 		DWORD			TrackFlags;
124 	};
125 
126 	bool			MusicPaused;
127 	float			MusVolume;
128 
129 	const byte*		MidiImage;
130 	DWORD			MidiImageSize;
131 
132 	int				State;				//	Sequencer state (SEQ_S_xxx)
133 	HMIDIOUT		hMidi;				//	Handle to open MIDI device
134 	byte			BufAlloc[(sizeof(MIDIHDR) + CB_MIDI_BUFFERS) * C_MIDI_BUFFERS];	//	Streaming buffers -- initial allocation
135 	LPMIDIHDR		FreeBuffers;		//	Streaming buffers -- free list
136 	int				BuffersInMMSYSTEM;	//	Streaming buffers -- in use
137 	DWORD			SeqFlags;			//	Various sequencer flags
138 
139 	DWORD			MidiPosition;
140 	DWORD			Format;
141 	DWORD			NumTracks;
142 	DWORD			TimeDivision;
143 	DWORD			SmfFlags;
144 
145 	DWORD			PendingUserEvent;
146 	DWORD			PendingUserEventCount;
147 	const byte*		PendingUserEvents;
148 
149 	FTrack			Tracks[MAX_TRACKS];
150 
151 	static const int ChanMsgLen[];
152 
153 	VMMSystemMidiDevice();
154 	void Init();
155 	void Shutdown();
156 	void SetVolume(float);
157 	void Tick(float);
158 	void Play(void*, int, const char*, bool);
159 	void Pause();
160 	void Resume();
161 	void Stop();
162 	bool IsPlaying();
163 
164 	MMRESULT Preroll();
165 	void Callback(UINT, DWORD);
166 	static void PASCAL StaticCallback(HMIDISTRM, UINT, DWORD, DWORD, DWORD);
167 
168 	EResult ReadEvents(LPMIDIHDR);
169 	bool InsertParmData(DWORD, LPMIDIHDR);
170 	void SeekStart();
171 	bool BuildFileIndex();
172 	EResult GetNextEvent(FEvent&);
173 	static DWORD GetVDword(const byte*, DWORD, DWORD&);
174 };
175 
176 // EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
177 
178 // PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
179 
180 // PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
181 
182 // EXTERNAL DATA DECLARATIONS ----------------------------------------------
183 
184 // PUBLIC DATA DEFINITIONS -------------------------------------------------
185 
186 IMPLEMENT_MIDI_DEVICE(VMMSystemMidiDevice, MIDIDRV_Default, "Default",
187 	"Windows multimedia system midi device", NULL);
188 
189 // PRIVATE DATA DEFINITIONS ------------------------------------------------
190 
191 const int VMMSystemMidiDevice::ChanMsgLen[] =
192 {
193 	0,                      /* 0x   not a status byte   */
194 	0,                      /* 1x   not a status byte   */
195 	0,                      /* 2x   not a status byte   */
196 	0,                      /* 3x   not a status byte   */
197 	0,                      /* 4x   not a status byte   */
198 	0,                      /* 5x   not a status byte   */
199 	0,                      /* 6x   not a status byte   */
200 	0,                      /* 7x   not a status byte   */
201 	3,                      /* 8x   Note off            */
202 	3,                      /* 9x   Note on             */
203 	3,                      /* Ax   Poly pressure       */
204 	3,                      /* Bx   Control change      */
205 	2,                      /* Cx   Program change      */
206 	2,                      /* Dx   Chan pressure       */
207 	3,                      /* Ex   Pitch bend change   */
208 	0,                      /* Fx   SysEx (see below)   */
209 } ;
210 
211 // CODE --------------------------------------------------------------------
212 
213 //==========================================================================
214 //
215 //	VMMSystemMidiDevice::VMMSystemMidiDevice
216 //
217 //==========================================================================
218 
VMMSystemMidiDevice()219 VMMSystemMidiDevice::VMMSystemMidiDevice()
220 : MidiImage(NULL)
221 , MusicPaused(false)
222 , MusVolume(-1)
223 {
224 }
225 
226 //==========================================================================
227 //
228 //	VMMSystemMidiDevice::Init
229 //
230 //==========================================================================
231 
Init()232 void VMMSystemMidiDevice::Init()
233 {
234 	guard(VMMSystemMidiDevice::Init);
235 	State = STATE_NoFile;
236 	FreeBuffers = NULL;
237 	hMidi = NULL;
238 	BuffersInMMSYSTEM = 0;
239 	SeqFlags = 0;
240 
241 	//	Initialise all MIDIHDR's and throw them into a free list
242 	LPBYTE Work = BufAlloc;
243 	for (int i = 0; i < C_MIDI_BUFFERS; i++)
244 	{
245 		((LPMIDIHDR)Work)->lpNext = FreeBuffers;
246 		((LPMIDIHDR)Work)->lpData = (char*)(Work + sizeof(MIDIHDR));
247 		((LPMIDIHDR)Work)->dwBufferLength = CB_MIDI_BUFFERS;
248 		((LPMIDIHDR)Work)->dwBytesRecorded = 0;
249 		((LPMIDIHDR)Work)->dwUser = 0;
250 		((LPMIDIHDR)Work)->dwFlags = 0;
251 
252 		FreeBuffers = (LPMIDIHDR)Work;
253 
254 		Work += sizeof(MIDIHDR) + CB_MIDI_BUFFERS;
255 	}
256 
257 	Initialised = true;
258 	unguard;
259 }
260 
261 //==========================================================================
262 //
263 //	VMMSystemMidiDevice::Shutdown
264 //
265 //==========================================================================
266 
Shutdown()267 void VMMSystemMidiDevice::Shutdown()
268 {
269 	guard(VMMSystemMidiDevice::Shutdown);
270 	if (Initialised)
271 	{
272 		Stop();
273 		Initialised = false;
274 	}
275 	unguard;
276 }
277 
278 //==========================================================================
279 //
280 //	VMMSystemMidiDevice::SetVolume
281 //
282 //==========================================================================
283 
SetVolume(float Volume)284 void VMMSystemMidiDevice::SetVolume(float Volume)
285 {
286 	guard(VMMSystemMidiDevice::SetVolume);
287 	if (Volume != MusVolume)
288 	{
289 		MusVolume = Volume;
290 		if (hMidi)
291 		{
292 			midiOutSetVolume(hMidi, (DWORD)(0xffff * MusVolume) |
293 				((DWORD)(0xffff * MusVolume) << 16));
294 		}
295 	}
296 	unguard;
297 }
298 
299 //==========================================================================
300 //
301 //	VMMSystemMidiDevice::Tick
302 //
303 //==========================================================================
304 
Tick(float)305 void VMMSystemMidiDevice::Tick(float)
306 {
307 }
308 
309 //==========================================================================
310 //
311 //	VMMSystemMidiDevice::Play
312 //
313 //==========================================================================
314 
Play(void * Data,int len,const char * song,bool loop)315 void VMMSystemMidiDevice::Play(void* Data, int len, const char* song, bool loop)
316 {
317 	guard(VMMSystemMidiDevice::Play);
318 	//	Stop, close, etc. if we're still playing
319 	Stop();
320 
321 	//	Open new file
322 	SmfFlags = 0;
323 	PendingUserEvent = 0;
324 	PendingUserEventCount = 0;
325 	PendingUserEvents = NULL;
326 
327 	MidiImage = (byte*)Data;
328 	MidiImageSize = len;
329 
330 	//	If the file exists, parse it just enough to pull out the header
331 	// and build a track index.
332 	if (!BuildFileIndex())
333 	{
334 		return;
335 	}
336 	State = STATE_Opened;
337 
338 	//	Preroll
339 	if (Preroll() != MMSYSERR_NOERROR)
340 	{
341 		return;
342 	}
343 
344 	//	Play
345 	if (hMidi)
346 	{
347 		State = STATE_Playing;
348 		midiStreamRestart((HMIDISTRM)hMidi);
349 
350 		//	Pause if needed.
351 		if (!MusVolume || MusicPaused)
352 		{
353 			Pause();
354 		}
355 		CurrSong = VName(song, VName::AddLower8);
356 		CurrLoop = loop;
357 	}
358 	unguard;
359 }
360 
361 //==========================================================================
362 //
363 //	VMMSystemMidiDevice::Pause
364 //
365 //==========================================================================
366 
Pause()367 void VMMSystemMidiDevice::Pause()
368 {
369 	guard(VMMSystemMidiDevice::Pause);
370 	if (State != STATE_Playing)
371 		return;
372 
373 	if (hMidi)
374 	{
375 		State = STATE_Paused;
376 		midiStreamPause((HMIDISTRM)hMidi);
377 		MusicPaused = true;
378 	}
379 	unguard;
380 }
381 
382 //==========================================================================
383 //
384 //	VMMSystemMidiDevice::Resume
385 //
386 //==========================================================================
387 
Resume()388 void VMMSystemMidiDevice::Resume()
389 {
390 	guard(VMMSystemMidiDevice::Resume);
391 	if (State != STATE_Paused)
392 		return;
393 
394 	if (hMidi)
395 	{
396 		State = STATE_Playing;
397 		midiStreamRestart((HMIDISTRM)hMidi);
398 		MusicPaused = false;
399 	}
400 	unguard;
401 }
402 
403 //==========================================================================
404 //
405 //	VMMSystemMidiDevice::IsPlaying
406 //
407 //	Is the song playing?
408 //
409 //==========================================================================
410 
IsPlaying()411 bool VMMSystemMidiDevice::IsPlaying()
412 {
413 	guard(VMMSystemMidiDevice::IsPlaying);
414 	return State == STATE_Playing || State == STATE_Paused;
415 	unguard;
416 }
417 
418 //==========================================================================
419 //
420 //  VMMSystemMidiDevice::Stop
421 //
422 //==========================================================================
423 
Stop()424 void VMMSystemMidiDevice::Stop()
425 {
426 	guard(VMMSystemMidiDevice::Stop);
427 	if (MidiImage)
428 	{
429 		if (hMidi != NULL)
430 		{
431 			if (MusicPaused)
432 			{
433 				Resume();
434 			}
435 			if (State != STATE_Playing && State != STATE_Paused)
436 			{
437 				SeqFlags &= ~SEQF_Waiting;
438 			}
439 			else
440 			{
441 				State = STATE_Stopping;
442 				SeqFlags |= SEQF_Waiting;
443 
444 				if (!hMidi || MMSYSERR_NOERROR != midiStreamStop((HMIDISTRM)hMidi))
445 				{
446 					SeqFlags &= ~SEQF_Waiting;
447 					return;
448 				}
449 
450 				while (BuffersInMMSYSTEM)
451 					Sleep(0);
452 			}
453 
454 			//	Close file.
455 			if (State == STATE_Opened)
456 			{
457 				//	If we were prerolled, need to clean up -- have an open MIDI
458 				// handle and buffers in the ready queue
459 				for (LPMIDIHDR lpmh = FreeBuffers; lpmh; lpmh = lpmh->lpNext)
460 					midiOutUnprepareHeader(hMidi, lpmh, sizeof(*lpmh));
461 			}
462 
463 			//	Close midi stream.
464 			if (hMidi)
465 			{
466 				midiStreamClose((HMIDISTRM)hMidi);
467 				hMidi = NULL;
468 			}
469 		}
470 		State = STATE_NoFile;
471 		Z_Free((void*)MidiImage);
472 		MidiImage = NULL;
473 		CurrSong = NAME_None;
474 	}
475 	unguard;
476 }
477 
478 //==========================================================================
479 //
480 //	VMMSystemMidiDevice::Preroll
481 //
482 //	Prepares the file for playback at the given position.
483 //
484 //==========================================================================
485 
Preroll()486 MMRESULT VMMSystemMidiDevice::Preroll()
487 {
488 	guard(VMMSystemMidiDevice::Preroll);
489 	EResult				smfrc;
490 	MMRESULT			mmrc = MMSYSERR_NOERROR;
491 	MIDIPROPTIMEDIV		mptd;
492 	LPMIDIHDR			lpmh = NULL;
493 	UINT				uDeviceID;
494 
495 	if (hMidi)
496 	{
497 		//	Recollect buffers from MMSYSTEM back into free queue
498 		State = STATE_Reset;
499 		midiOutReset(hMidi);
500 
501 		while (BuffersInMMSYSTEM)
502 			Sleep(0);
503 	}
504 
505 	BuffersInMMSYSTEM = 0;
506 	State = STATE_Prerolling;
507 
508 	//
509 	// We've successfully opened the file and all of the tracks; now
510 	// open the MIDI device and set the time division.
511 	//
512 	if (!hMidi)
513 	{
514 		uDeviceID = MIDI_MAPPER;
515 		if ((mmrc = midiStreamOpen((HMIDISTRM*)&hMidi, &uDeviceID, 1,
516 			(DWORD)StaticCallback, (DWORD)this, CALLBACK_FUNCTION)) != MMSYSERR_NOERROR)
517 		{
518 			hMidi = NULL;
519 			goto seq_Preroll_Cleanup;
520 		}
521 
522 		midiOutSetVolume(hMidi, (DWORD)(0xffff * MusVolume) |
523 			((DWORD)(0xffff * MusVolume) << 16));
524 
525 		mptd.cbStruct  = sizeof(mptd);
526 		mptd.dwTimeDiv = TimeDivision;
527 		if ((mmrc = midiStreamProperty((HMIDISTRM)hMidi,
528 			(LPBYTE)&mptd, MIDIPROP_SET | MIDIPROP_TIMEDIV)) != MMSYSERR_NOERROR)
529 		{
530 			midiStreamClose((HMIDISTRM)hMidi);
531 			hMidi = NULL;
532 			mmrc = MCIERR_DEVICE_NOT_READY;
533 			goto seq_Preroll_Cleanup;
534 		}
535 	}
536 
537 	mmrc = MMSYSERR_NOERROR;
538 
539 	SeekStart();
540 	smfrc = RES_Success;
541 
542 	SeqFlags &= ~SEQF_Eof;
543 	while (FreeBuffers)
544 	{
545 		lpmh = FreeBuffers;
546 		FreeBuffers = lpmh->lpNext;
547 
548 		smfrc = ReadEvents(lpmh);
549 		if (RES_Success != smfrc && RES_EndOfFile != smfrc)
550 		{
551 			GCon->Log("Preroll: Invalid file");
552 			mmrc = MCIERR_INVALID_FILE;
553 			goto seq_Preroll_Cleanup;
554 		}
555 
556 		if (MMSYSERR_NOERROR != (mmrc = midiOutPrepareHeader(hMidi, lpmh, sizeof(*lpmh))))
557 		{
558 			GCon->Log("Preroll: midiOutPrepareHeader failed");
559 			goto seq_Preroll_Cleanup;
560 		}
561 
562 		if (MMSYSERR_NOERROR != (mmrc = midiStreamOut((HMIDISTRM)hMidi, lpmh, sizeof(*lpmh))))
563 		{
564 			GCon->Log("Preroll: midiStreamOut failed");
565 			goto seq_Preroll_Cleanup;
566 		}
567 
568 		BuffersInMMSYSTEM++;
569 
570 		if (smfrc == RES_EndOfFile)
571 		{
572 			SeqFlags |= SEQF_Eof;
573 			break;
574 		}
575 	}
576 
577 seq_Preroll_Cleanup:
578 	if (MMSYSERR_NOERROR != mmrc)
579 	{
580 		State = STATE_Opened;
581 		SeqFlags &= ~SEQF_Waiting;
582 	}
583 	else
584 	{
585 		State = STATE_Prerolled;
586 	}
587 
588 	return mmrc;
589 	unguard;
590 }
591 
592 //==========================================================================
593 //
594 //	VMMSystemMidiDevice::Callback
595 //
596 //	Called by the system when a buffer is done.
597 //
598 //==========================================================================
599 
Callback(UINT uMsg,DWORD dw1)600 void VMMSystemMidiDevice::Callback(UINT uMsg, DWORD dw1)
601 {
602 	guard(VMMSystemMidiDevice::Callback);
603 	LPMIDIHDR		lpmh = (LPMIDIHDR)dw1;
604 
605 	if (uMsg != MOM_DONE)
606 		return;
607 
608 	BuffersInMMSYSTEM--;
609 	if (State == STATE_Reset)
610 	{
611 		//	We're recollecting buffers from MMSYSTEM
612 		lpmh->lpNext = FreeBuffers;
613 		FreeBuffers = lpmh;
614 		return;
615 	}
616 
617 	if ((State == STATE_Stopping) || (SeqFlags & SEQF_Eof))
618 	{
619 		//	Reached EOF, just put the buffer back on the free list
620 		lpmh->lpNext = FreeBuffers;
621 		FreeBuffers = lpmh;
622 
623 		midiOutUnprepareHeader(hMidi, lpmh, sizeof(*lpmh));
624 
625 		if (BuffersInMMSYSTEM == 0)
626 		{
627 			SeqFlags &= ~SEQF_Waiting;
628 			//	Restart stream if we are looping.
629 			if (CurrLoop && State != STATE_Stopping)
630 			{
631 				State = STATE_Opened;
632 				if (Preroll() == MMSYSERR_NOERROR)
633 				{
634 					State = STATE_Playing;
635 					midiStreamRestart((HMIDISTRM)hMidi);
636 				}
637 			}
638 			else
639 			{
640 				//	Totally done! Free device.
641 				midiStreamClose((HMIDISTRM)hMidi);
642 
643 				hMidi = NULL;
644 				State = STATE_Opened;
645 			}
646 		}
647 	}
648 	else
649 	{
650 		//	Not EOF yet; attempt to fill another buffer
651 		EResult smfrc = ReadEvents(lpmh);
652 
653 		switch(smfrc)
654 		{
655 		case RES_Success:
656 			break;
657 
658 		case RES_EndOfFile:
659 			SeqFlags |= SEQF_Eof;
660 			smfrc = RES_Success;
661 			break;
662 
663 		default:
664 			State = STATE_Stopping;
665 			break;
666 		}
667 
668 		if (smfrc == RES_Success)
669 		{
670 			BuffersInMMSYSTEM++;
671 			if (midiStreamOut((HMIDISTRM)hMidi, lpmh, sizeof(*lpmh)) != MMSYSERR_NOERROR)
672 			{
673 				BuffersInMMSYSTEM--;
674 				State = STATE_Stopping;
675 			}
676 		}
677 	}
678 	unguard;
679 }
680 
681 //==========================================================================
682 //
683 //	VMMSystemMidiDevice::StaticCallback
684 //
685 //==========================================================================
686 
StaticCallback(HMIDISTRM,UINT uMsg,DWORD inst,DWORD dw1,DWORD)687 void PASCAL VMMSystemMidiDevice::StaticCallback(HMIDISTRM,
688 	UINT uMsg, DWORD inst, DWORD dw1, DWORD)
689 {
690 	((VMMSystemMidiDevice*)inst)->Callback(uMsg, dw1);
691 }
692 
693 //==========================================================================
694 //
695 //	VMMSystemMidiDevice::ReadEvents
696 //
697 //	This function reads events from a track.
698 //
699 //==========================================================================
700 
ReadEvents(LPMIDIHDR lpmh)701 VMMSystemMidiDevice::EResult VMMSystemMidiDevice::ReadEvents(LPMIDIHDR lpmh)
702 {
703 	guard(VMMSystemMidiDevice::ReadEvents);
704 	EResult			smfrc;
705 	FEvent			event;
706 	LPDWORD			lpdw;
707 	DWORD			Tempo;
708 
709 	//	Read events from the track and pack them into the buffer in polymsg
710 	// format.
711 	//	If a SysEx or meta would go over a buffer boundry, split it.
712 	lpmh->dwBytesRecorded = 0;
713 	if (PendingUserEvent)
714 	{
715 		if (!InsertParmData(0, lpmh))
716 		{
717 			return RES_InvalidFile;
718 		}
719 	}
720 
721 	lpdw = (LPDWORD)(lpmh->lpData + lpmh->dwBytesRecorded);
722 
723 	if (SmfFlags & SMFF_Eof)
724 	{
725 		return RES_EndOfFile;
726 	}
727 
728 	while (1)
729 	{
730 		//	If we know ahead of time we won't have room for the event, just
731 		// break out now. We need 2 DWORD's for the terminator event and at
732 		// least 2 DWORD's for any event we might store - this will allow us
733 		// a full short event or the delta time and stub for a long event to
734 		// be split.
735 		if (lpmh->dwBufferLength - lpmh->dwBytesRecorded < 4 * sizeof(DWORD))
736 		{
737 			break;
738 		}
739 
740 		smfrc = GetNextEvent(event);
741 		if (RES_Success != smfrc)
742 		{
743 			break;
744 		}
745 
746 		if (MIDI_SysEx > event.Event[0])
747 		{
748 			*lpdw++ = (DWORD)event.Delta;
749 			*lpdw++ = 0;
750 			*lpdw++ = (((DWORD)MEVT_SHORTMSG) << 24) |
751 						((DWORD)event.Event[0]) |
752 						(((DWORD)event.Event[1]) << 8) |
753 						(((DWORD)event.Event[2]) << 16);
754 
755 			lpmh->dwBytesRecorded += 3 * sizeof(DWORD);
756 		}
757 		else if (MIDI_Meta == event.Event[0] &&
758 			MIDI_MetaEot == event.Event[1])
759 		{
760 			//	These are ignoreable since smfReadNextEvent()
761 			// takes care of track merging
762 		}
763 		else if (MIDI_Meta == event.Event[0] &&
764 			MIDI_MetaTempo == event.Event[1])
765 		{
766 			if (event.ParmCount != 3)
767 			{
768 				GCon->Log("ReadEvents: Corrupt tempo event");
769 				return RES_InvalidFile;
770 			}
771 
772 			Tempo = (((DWORD)MEVT_TEMPO) << 24)|
773 						(((DWORD)event.Parm[0]) << 16)|
774 						(((DWORD)event.Parm[1]) << 8)|
775 						((DWORD)event.Parm[2]);
776 
777 			*lpdw++ = (DWORD)event.Delta;
778 			*lpdw++ = 0;
779 			*lpdw++ = Tempo;
780 
781 			lpmh->dwBytesRecorded += 3 * sizeof(DWORD);
782 		}
783 		else if (MIDI_Meta != event.Event[0])
784 		{
785 			//	Must be F0 or F7 system exclusive or FF meta
786 			// that we didn't recognize
787 			PendingUserEventCount = event.ParmCount;
788 			PendingUserEvents = event.Parm;
789 			SmfFlags &= ~SMFF_InsertSysEx;
790 
791 			switch (event.Event[0])
792 			{
793 			case MIDI_SysEx:
794 				SmfFlags |= SMFF_InsertSysEx;
795 				PendingUserEventCount++;
796 				//	Falling through...
797 			case MIDI_SysExEnd:
798 				PendingUserEvent = ((DWORD)MEVT_LONGMSG) << 24;
799 				break;
800 			}
801 
802 			if (!InsertParmData(event.Delta, lpmh))
803 			{
804 				return RES_InvalidFile;
805 			}
806 
807 			lpdw = (LPDWORD)(lpmh->lpData + lpmh->dwBytesRecorded);
808 		}
809 	}
810 
811 	return (SmfFlags & SMFF_Eof) ? RES_EndOfFile : RES_Success;
812 	unguard;
813 }
814 
815 //==========================================================================
816 //
817 //	VMMSystemMidiDevice::InsertParmData
818 //
819 //	Inserts pending long data from a track into the given buffer.
820 //
821 //	Fills as much data as will fit while leaving room for the buffer
822 // terminator.
823 //
824 //	If the long data is depleted, resets PendingUserEvent so that the next
825 // event may be read.
826 //
827 //==========================================================================
828 
InsertParmData(DWORD Delta,LPMIDIHDR lpmh)829 bool VMMSystemMidiDevice::InsertParmData(DWORD Delta, LPMIDIHDR lpmh)
830 {
831 	guard(VMMSystemMidiDevice::InsertParmData);
832 	//	Can't fit 4 DWORD's? (Delta + stream-id + event + some data)
833 	// Can't do anything.
834 	if (lpmh->dwBufferLength - lpmh->dwBytesRecorded < 4 * sizeof(DWORD))
835 	{
836 		if (0 == Delta)
837 			return true;
838 
839 		//	If we got here with a real delta, that means ReadEvents screwed
840 		// up calculating left space and we should flag it somehow.
841 		GCon->Log("Can't fit initial piece of SysEx into buffer!");
842 		return false;
843 	}
844 
845 	LPDWORD pData = (LPDWORD)(lpmh->lpData + lpmh->dwBytesRecorded);
846 
847 	DWORD Length = lpmh->dwBufferLength - lpmh->dwBytesRecorded - 3 * sizeof(DWORD);
848 	Length = min(Length, PendingUserEventCount);
849 
850 	*pData++ = (DWORD)Delta;
851 	*pData++ = 0;
852 	*pData++ = (PendingUserEvent & 0xFF000000) | (Length & 0x00FFFFFF);
853 
854 	DWORD Rounded = (Length + 3) & (~3);
855 
856 	if (SmfFlags & SMFF_InsertSysEx)
857 	{
858 		*((LPBYTE)pData) = MIDI_SysEx;
859 		pData = (LPDWORD)(((LPBYTE)pData) + 1);
860 		SmfFlags &= ~SMFF_InsertSysEx;
861 		Length--;
862 		PendingUserEventCount--;
863 	}
864 
865 	if (Length & 0x80000000)
866 	{
867 		GCon->Logf("Length %08lX  dwBytesRecorded %08lX  dwBufferLength %08lX",
868 			Length, lpmh->dwBytesRecorded, lpmh->dwBufferLength);
869 		GCon->Logf("PendingUserEventCount %08lX  PendingUserEvent %08lX Rounded %08lX",
870 			PendingUserEventCount, PendingUserEvent, Rounded);
871 		GCon->Logf("Offset into MIDI image %08lX", (DWORD)(PendingUserEvents - MidiImage));
872 		GCon->Logf("!memcpy is about to fault");
873 	}
874 
875 	memcpy(pData, PendingUserEvents, Length);
876 	PendingUserEventCount -= Length;
877 	if (!PendingUserEventCount)
878 		PendingUserEvent = 0;
879 
880 	lpmh->dwBytesRecorded += 3*sizeof(DWORD) + Rounded;
881 
882 	return true;
883 	unguard;
884 }
885 
886 //==========================================================================
887 //
888 //	VMMSystemMidiDevice::SeekStart
889 //
890 //==========================================================================
891 
SeekStart()892 void VMMSystemMidiDevice::SeekStart()
893 {
894 	guard(VMMSystemMidiDevice::SeekStart);
895 	FTrack*					ptrk;
896 	DWORD                   idxTrack;
897 
898 	MidiPosition = 0;
899 	SmfFlags &= ~SMFF_Eof;
900 
901 	for (ptrk = Tracks, idxTrack = NumTracks; idxTrack--; ptrk++)
902 	{
903 		ptrk->Position = 0;
904 		ptrk->BytesLeft = ptrk->TotalLength;
905 		ptrk->ImagePtr = MidiImage + ptrk->FileOffset;
906 		ptrk->RunningStatus = 0;
907 		ptrk->TrackFlags = 0;
908 	}
909 	unguard;
910 }
911 
912 //==========================================================================
913 //
914 //	VMMSystemMidiDevice::BuildFileIndex
915 //
916 //	Preliminary parsing of a MIDI file.
917 //
918 //	This function validates the format of and existing MIDI file and builds
919 // the handle structure which will refer to it for the lifetime of the
920 // instance.
921 //
922 //	The file header information will be read and verified, and
923 // BuildTrackIndices will be called on every existing track
924 // to build keyframes and validate the track format.
925 //
926 //==========================================================================
927 
BuildFileIndex()928 bool VMMSystemMidiDevice::BuildFileIndex()
929 {
930 	guard(VMMSystemMidiDevice::BuildFileIndex);
931 	EResult					smfrc;
932 	const FChunkHdr*		pCh;
933 	const FMidiFileHdr*		pFh;
934 	DWORD					idx;
935 	FTrack*					pTrk;
936 	DWORD					dwLeft;
937 	const byte*				hpbImage;
938 	DWORD					idxTrack;
939 	FEvent					event;
940 	DWORD					dwLength;
941 
942 	//	Validate MIDI header
943 	dwLeft   = MidiImageSize;
944 	hpbImage = MidiImage;
945 
946 	if (dwLeft < sizeof(FChunkHdr))
947 		return false;
948 
949 	pCh = (FChunkHdr*)hpbImage;
950 
951 	dwLeft   -= sizeof(FChunkHdr);
952 	hpbImage += sizeof(FChunkHdr);
953 
954 	dwLength = BigLong(pCh->Length);
955 	if (dwLength < sizeof(FMidiFileHdr) || dwLength > dwLeft)
956 		return false;
957 
958 	pFh = (FMidiFileHdr*)hpbImage;
959 
960 	dwLeft -= dwLength;
961 	hpbImage += dwLength;
962 
963 	Format = (DWORD)BigShort(pFh->Format);
964 	NumTracks = (DWORD)BigShort(pFh->Tracks);
965 	TimeDivision = (DWORD)BigShort(pFh->Division);
966 
967 	if (NumTracks > MAX_TRACKS)
968 		return false;
969 
970 	//	We've successfully parsed the header. Now try to build the track index.
971 	//	We only check out the track header chunk here; the track will be
972 	// preparsed after we do a quick integretiy check.
973 	memset(Tracks, 0, MAX_TRACKS * sizeof(FTrack));
974 
975 	pTrk = Tracks;
976 
977 	for (idx = 0; idx < NumTracks; idx++)
978 	{
979 		if (dwLeft < sizeof(FChunkHdr))
980 			return false;
981 
982 		pCh = (FChunkHdr*)hpbImage;
983 
984 		dwLeft   -= sizeof(FChunkHdr);
985 		hpbImage += sizeof(FChunkHdr);
986 
987 		if (memcmp(pCh->Type, "MTrk", 4))
988 			return false;
989 
990 		pTrk->FileOffset = (DWORD)(hpbImage - MidiImage);
991 		pTrk->TotalLength = BigLong(pCh->Length);
992 
993 		if (pTrk->TotalLength > dwLeft)
994 		{
995 			GCon->Log("Track longer than file!");
996 			return false;
997 		}
998 
999 		dwLeft   -= pTrk->TotalLength;
1000 		hpbImage += pTrk->TotalLength;
1001 
1002 		pTrk++;
1003 	}
1004 
1005 	//	File looks OK. Now preparse, doing the following:
1006 	// (3) Validate all events in all tracks
1007 	MidiPosition = 0;
1008 	SmfFlags &= ~SMFF_Eof;
1009 
1010 	for (pTrk = Tracks, idxTrack = NumTracks; idxTrack--; pTrk++)
1011 	{
1012 		pTrk->Position = 0;
1013 		pTrk->BytesLeft = pTrk->TotalLength;
1014 		pTrk->ImagePtr = MidiImage + pTrk->FileOffset;
1015 		pTrk->RunningStatus = 0;
1016 		pTrk->TrackFlags = 0;
1017 	}
1018 
1019 	while (RES_Success == (smfrc = GetNextEvent(event)))
1020 	{
1021 	}
1022 
1023 	if (RES_EndOfFile == smfrc || RES_Success == smfrc)
1024 	{
1025 		smfrc = RES_Success;
1026 	}
1027 
1028 	return smfrc == RES_Success;
1029 	unguard;
1030 }
1031 
1032 //==========================================================================
1033 //
1034 //	VMMSystemMidiDevice::GetNextEvent
1035 //
1036 //	Read the next event from the given file.
1037 //
1038 //	This is the lowest level of parsing for a raw MIDI stream. The basic
1039 // information about one event in the file will be returned in Event.
1040 //
1041 //	Merging data from all tracks into one stream is performed here.
1042 //
1043 //	pEvent->Delta will contain the tick delta for the event.
1044 //
1045 // pEvent->Event will contain a description of the event.
1046 //  pevent->Event[0] will contain
1047 //    F0 or F7 for a System Exclusive message.
1048 //    FF for a MIDI file meta event.
1049 //    The status byte of any other MIDI message. (Running status will
1050 //    be tracked and expanded).
1051 //
1052 //	pEvent->ParmCount will contain the number of bytes of paramter data
1053 //   which is still in the file behind the event header already read.
1054 //   This data may be read with <f smfGetTrackEventData>. Any unread
1055 //   data will be skipped on the next call to <f smfGetNextTrackEvent>.
1056 //
1057 // Channel messages (0x8? - 0xE?) will always be returned fully in
1058 //   pevent->Event.
1059 //
1060 //  Meta events will contain the meta type in pevent->Event[1].
1061 //
1062 //  System exclusive events will contain only an 0xF0 or 0xF7 in
1063 //    pevent->Event[0].
1064 //
1065 //  The following fields in pTrk are used to maintain state and must
1066 //  be updated if a seek-in-track is performed:
1067 //
1068 //  bRunningStatus contains the last running status message or 0 if
1069 //   there is no valid running status.
1070 //
1071 //  hpbImage is a pointer into the file image of the first byte of
1072 //   the event to follow the event just read.
1073 //
1074 //  dwLeft contains the number of bytes from hpbImage to the end
1075 //   of the track.
1076 //
1077 // Get the next due event from all (in-use?) tracks
1078 //
1079 // For all tracks
1080 //   If not end-of-track
1081 //   decode event delta time without advancing through buffer
1082 //   event_absolute_time = track_tick_time + track_event_delta_time
1083 //   relative_time = event_absolute_time - last_stream_time
1084 //   if relative_time is lowest so far
1085 //    save this track as the next to pull from, along with times
1086 //
1087 // If we found a track with a due event
1088 //  Advance track pointer past event, saving ptr to parm data if needed
1089 //  track_tick_time += track_event_delta_time
1090 //  last_stream_time = track_tick_time
1091 // Else
1092 //  Mark and return end_of_file
1093 //
1094 //==========================================================================
1095 
GetNextEvent(FEvent & Event)1096 VMMSystemMidiDevice::EResult VMMSystemMidiDevice::GetNextEvent(FEvent& Event)
1097 {
1098 	guard(VMMSystemMidiDevice::GetNextEvent);
1099 	FTrack*		pTrk;
1100 	FTrack*		pTrkFound;
1101 	DWORD		idxTrack;
1102 	DWORD		tkEventDelta;
1103 	DWORD		tkRelTime;
1104 	DWORD		tkMinRelTime;
1105 	byte		bEvent;
1106 	DWORD		dwGotTotal;
1107 	DWORD		dwGot;
1108 	DWORD		cbEvent;
1109 
1110 	if (SmfFlags & SMFF_Eof)
1111 	{
1112 		return RES_EndOfFile;
1113 	}
1114 
1115 	pTrkFound = NULL;
1116 	tkMinRelTime = MAX_TICKS;
1117 
1118 	for (pTrk = Tracks, idxTrack = NumTracks; idxTrack--; pTrk++)
1119 	{
1120 		if (pTrk->TrackFlags & TRACKF_Eot)
1121 			continue;
1122 
1123 		if (!GetVDword(pTrk->ImagePtr, pTrk->BytesLeft, tkEventDelta))
1124 		{
1125 			GCon->Log("Hit end of track w/o end marker!");
1126 			return RES_InvalidFile;
1127 		}
1128 
1129 		tkRelTime = pTrk->Position + tkEventDelta - MidiPosition;
1130 
1131 		if (tkRelTime < tkMinRelTime)
1132 		{
1133 			tkMinRelTime = tkRelTime;
1134 			pTrkFound = pTrk;
1135 		}
1136 	}
1137 
1138 	if (!pTrkFound)
1139 	{
1140 		SmfFlags |= SMFF_Eof;
1141 		return RES_EndOfFile;
1142 	}
1143 
1144 	pTrk = pTrkFound;
1145 
1146 	pTrk->ImagePtr += (dwGot = GetVDword(pTrk->ImagePtr, pTrk->BytesLeft, tkEventDelta));
1147 	pTrk->BytesLeft -= dwGot;
1148 
1149 	//	We MUST have at least three bytes here (cause we haven't hit
1150 	// the end-of-track meta yet, which is three bytes long). Checking
1151 	// against three means we don't have to check how much is left
1152 	// in the track again for any short event, which is most cases.
1153 	if (pTrk->BytesLeft < 3)
1154 	{
1155 		return RES_InvalidFile;
1156 	}
1157 
1158 	pTrk->Position += tkEventDelta;
1159 	Event.Delta = pTrk->Position - MidiPosition;
1160 	MidiPosition = pTrk->Position;
1161 
1162 	bEvent = *pTrk->ImagePtr++;
1163 
1164 	if (MIDI_Msg > bEvent)
1165 	{
1166 		if (0 == pTrk->RunningStatus)
1167 		{
1168 			return RES_InvalidFile;
1169 		}
1170 
1171 		dwGotTotal = 1;
1172 		Event.Event[0] = pTrk->RunningStatus;
1173 		Event.Event[1] = bEvent;
1174 		if (3 == ChanMsgLen[(pTrk->RunningStatus >> 4) & 0x0F])
1175 		{
1176 			Event.Event[2] = *pTrk->ImagePtr++;
1177 			dwGotTotal++;
1178 		}
1179 	}
1180 	else if (MIDI_SysEx > bEvent)
1181 	{
1182 		pTrk->RunningStatus = bEvent;
1183 
1184 		dwGotTotal = 2;
1185 		Event.Event[0] = bEvent;
1186 		Event.Event[1] = *pTrk->ImagePtr++;
1187 		if (3 == ChanMsgLen[(bEvent >> 4) & 0x0F])
1188 		{
1189 			Event.Event[2] = *pTrk->ImagePtr++;
1190 			dwGotTotal++;
1191 		}
1192 	}
1193 	else
1194 	{
1195 		pTrk->RunningStatus = 0;
1196 		if (MIDI_Meta == bEvent)
1197 		{
1198 			Event.Event[0] = MIDI_Meta;
1199 			if (MIDI_MetaEot == (Event.Event[1] = *pTrk->ImagePtr++))
1200 			{
1201 				pTrk->TrackFlags |= TRACKF_Eot;
1202 			}
1203 
1204 			dwGotTotal = 2;
1205 		}
1206 		else if (MIDI_SysEx == bEvent || MIDI_SysExEnd == bEvent)
1207 		{
1208 			Event.Event[0] = bEvent;
1209 			dwGotTotal = 1;
1210 		}
1211 		else
1212 		{
1213 			return RES_InvalidFile;
1214 		}
1215 
1216 		if (0 == (dwGot = GetVDword(pTrk->ImagePtr, pTrk->BytesLeft - 2, cbEvent)))
1217 		{
1218 			return RES_InvalidFile;
1219 		}
1220 
1221 		pTrk->ImagePtr  += dwGot;
1222 		dwGotTotal      += dwGot;
1223 
1224 		if (dwGotTotal + cbEvent > pTrk->BytesLeft)
1225 		{
1226 			return RES_InvalidFile;
1227 		}
1228 
1229 		Event.ParmCount = cbEvent;
1230 		Event.Parm = pTrk->ImagePtr;
1231 
1232 		pTrk->ImagePtr += cbEvent;
1233 		dwGotTotal += cbEvent;
1234 	}
1235 
1236 	pTrk->BytesLeft -= dwGotTotal;
1237 
1238 	return RES_Success;
1239 	unguard;
1240 }
1241 
1242 //==========================================================================
1243 //
1244 //	VMMSystemMidiDevice::GetVDword
1245 //
1246 //	Reads a variable length DWORD from the given file.
1247 //
1248 //==========================================================================
1249 
GetVDword(const byte * ImagePtr,DWORD Left,DWORD & Out)1250 DWORD VMMSystemMidiDevice::GetVDword(const byte* ImagePtr, DWORD Left,
1251 	DWORD& Out)
1252 {
1253 	guard(VMMSystemMidiDevice::GetVDword);
1254 	byte		b;
1255 	DWORD		NumUsed = 0;
1256 
1257 	Out = 0;
1258 	do
1259 	{
1260 		if (!Left)
1261 		{
1262 			return 0;
1263 		}
1264 
1265 		b = *ImagePtr++;
1266 		Left--;
1267 		NumUsed++;
1268 
1269 		Out = (Out << 7) | (b & 0x7F);
1270 	} while (b & 0x80);
1271 
1272 	return NumUsed;
1273 	unguard;
1274 }
1275