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