/////////////////////////////////////////////////////////////////////////////
// Copyright (C) 1998 by J�rg K�nig
// All rights reserved
//
// This file is part of the completely free tetris clone "CGTetris".
//
// This is free software.
// You may redistribute it by any means providing it is not sold for profit
// without the authors written consent.
//
// No warrantee of any kind, expressed or implied, is included with this
// software; use at your own risk, responsibility for damages (if any) to
// anyone resulting from the use of this software rests entirely with the
// user.
//
// Send bug reports, bug fixes, enhancements, requests, flames, etc., and
// I'll try to keep a version up to date. I can be reached as follows:
// J.Koenig@adg.de (company site)
// Joerg.Koenig@rhein-neckar.de (private site)
/////////////////////////////////////////////////////////////////////////////
// Midi.cpp
//
// The CMIDI class is based on a sample in the DirectX SDK (mstream)
#include "stdafx.h"
#include "Midi.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
#define MThd 0x6468544D // Start of file
#define MTrk 0x6B72544D // Start of track
#define BUFFER_TIME_LENGTH 60 // Amount to fill in milliseconds
// These structures are stored in MIDI files; they need to be byte aligned.
//
#pragma pack(1)
// Contents of MThd chunk.
struct MIDIFILEHDR
{
WORD wFormat; // Format (hi-lo)
WORD wTrackCount; // # tracks (hi-lo)
WORD wTimeDivision; // Time division (hi-lo)
};
#pragma pack() // End of need for byte-aligned structures
// Macros for swapping hi/lo-endian data
//
#define WORDSWAP(w) (((w) >> 8) | \
(((w) << 8) & 0xFF00))
#define DWORDSWAP(dw) (((dw) >> 24) | \
(((dw) >> 8) & 0x0000FF00) | \
(((dw) << 8) & 0x00FF0000) | \
(((dw) << 24) & 0xFF000000))
static char gteBadRunStat[] = "Reference to missing running status.";
static char gteRunStatMsgTrunc[]= "Running status message truncated";
static char gteChanMsgTrunc[] = "Channel message truncated";
static char gteSysExLenTrunc[] = "SysEx event truncated (length)";
static char gteSysExTrunc[] = "SysEx event truncated";
static char gteMetaNoClass[] = "Meta event truncated (no class byte)";
static char gteMetaLenTrunc[] = "Meta event truncated (length)";
static char gteMetaTrunc[] = "Meta event truncated";
static char gteNoMem[] = "Out of memory during malloc call";
//////////////////////////////////////////////////////////////////////
// CMIDI -- Construction/Destruction
//////////////////////////////////////////////////////////////////////
CMIDI::CMIDI()
: m_dwSoundSize(0)
, m_pSoundData(0)
, m_dwFormat(0)
, m_dwTrackCount(0)
, m_dwTimeDivision(0)
, m_bPlaying(FALSE)
, m_hStream(0)
, m_dwProgressBytes(0)
, m_bLooped(FALSE)
, m_tkCurrentTime(0)
, m_dwBufferTickLength(0)
, m_dwCurrentTempo(0)
, m_dwTempoMultiplier(100)
, m_bInsertTempo(FALSE)
, m_bBuffersPrepared(FALSE)
, m_nCurrentBuffer(0)
, m_uMIDIDeviceID(MIDI_MAPPER)
, m_nEmptyBuffers(0)
, m_bPaused(FALSE)
, m_uCallbackStatus(0)
, m_hBufferReturnEvent(0)
, m_ptsTrack(0)
, m_ptsFound(0)
, m_dwStatus(0)
, m_tkNext(0)
, m_dwMallocBlocks(0)
{
m_hBufferReturnEvent = ::CreateEvent(0, FALSE, FALSE, TEXT("Wait For Buffer Return"));
ASSERT(m_hBufferReturnEvent != 0);
}
CMIDI::~CMIDI()
{
Stop(FALSE);
if(m_hBufferReturnEvent)
::CloseHandle(m_hBufferReturnEvent);
}
BOOL CMIDI::Create(UINT uResID, CWnd * pWndParent /* = NULL */)
{
return Create(MAKEINTRESOURCE(uResID), pWndParent);
}
BOOL CMIDI::Create(LPCTSTR pszResID, CWnd * pWndParent /* = NULL */)
{
//////////////////////////////////////////////////////////////////
// load resource
HINSTANCE hApp = ::GetModuleHandle(0);
ASSERT(hApp);
HRSRC hResInfo = ::FindResource(hApp, pszResID, TEXT("MIDI"));
if(hResInfo == 0)
return FALSE;
HGLOBAL hRes = ::LoadResource(hApp, hResInfo);
if(hRes == 0)
return FALSE;
LPVOID pTheSound = ::LockResource(hRes);
if(pTheSound == 0)
return FALSE;
DWORD dwTheSound = ::SizeofResource(hApp, hResInfo);
return Create(pTheSound, dwTheSound, pWndParent);
}
BOOL CMIDI::Create(LPVOID pSoundData, DWORD dwSize, CWnd * pWndParent /* = NULL */)
{
if( m_pSoundData ) {
// already created
ASSERT(FALSE);
return FALSE;
}
ASSERT(pSoundData != 0);
ASSERT(dwSize > 0);
register LPBYTE p = LPBYTE(pSoundData);
// check header of MIDI
if(*(DWORD*)p != MThd) {
ASSERT(FALSE);
return FALSE;
}
p += sizeof(DWORD);
// check header size
DWORD dwHeaderSize = DWORDSWAP(*(DWORD*)p);
if( dwHeaderSize != sizeof(MIDIFILEHDR) ) {
ASSERT(FALSE);
return FALSE;
}
p += sizeof(DWORD);
// get header
MIDIFILEHDR hdr;
::CopyMemory(&hdr, p, dwHeaderSize);
m_dwFormat = DWORD(WORDSWAP(hdr.wFormat));
m_dwTrackCount = DWORD(WORDSWAP(hdr.wTrackCount));
m_dwTimeDivision = DWORD(WORDSWAP(hdr.wTimeDivision));
p += dwHeaderSize;
// create the array of tracks
m_Tracks.resize(m_dwTrackCount);
for(register DWORD i = 0; i < m_dwTrackCount; ++i) {
// check header of track
if(*(DWORD*)p != MTrk) {
ASSERT(FALSE);
return FALSE;
}
p += sizeof(DWORD);
m_Tracks[i].dwTrackLength = DWORDSWAP(*(DWORD*)p);
p += sizeof(DWORD);
m_Tracks[i].pTrackStart = m_Tracks[i].pTrackCurrent = p;
p += m_Tracks[i].dwTrackLength;
// Handle bozo MIDI files which contain empty track chunks
if( !m_Tracks[i].dwTrackLength ) {
m_Tracks[i].fdwTrack |= ITS_F_ENDOFTRK;
continue;
}
// We always preread the time from each track so the mixer code can
// determine which track has the next event with a minimum of work
if( !GetTrackVDWord( &m_Tracks[i], &m_Tracks[i].tkNextEventDue )) {
TRACE0("Error in MIDI data\n");
ASSERT(FALSE);
return FALSE;
}
}
m_pSoundData = pSoundData;
m_dwSoundSize = dwSize;
m_pWndParent = pWndParent;
// allocate volume channels and initialise them
m_Volumes.resize(NUM_CHANNELS, VOLUME_INIT);
if( ! StreamBufferSetup() ) {
ASSERT(FALSE);
return FALSE;
}
return TRUE;
}
BOOL CMIDI :: Play(BOOL bInfinite /* = FALSE */) {
if( IsPaused() ) {
Continue();
return TRUE;
}
// calling Play() while it is already playing will restart from scratch
if( IsPlaying() )
Stop();
// Clear the status of our callback so it will handle
// MOM_DONE callbacks once more
m_uCallbackStatus = 0;
MMRESULT mmResult;
if( (mmResult = midiStreamRestart(m_hStream)) != MMSYSERR_NOERROR ) {
MidiError(mmResult);
return FALSE;
}
m_bPlaying = TRUE;
m_bLooped = bInfinite;
return m_bPlaying;
}
BOOL CMIDI :: Stop(BOOL bReOpen /*=TRUE*/) {
MMRESULT mmrRetVal;
if( IsPlaying() || (m_uCallbackStatus != STATUS_CALLBACKDEAD) ) {
m_bPlaying = m_bPaused = FALSE;
if( m_uCallbackStatus != STATUS_CALLBACKDEAD && m_uCallbackStatus != STATUS_WAITINGFOREND )
m_uCallbackStatus = STATUS_KILLCALLBACK;
if( (mmrRetVal = midiStreamStop(m_hStream) ) != MMSYSERR_NOERROR ) {
MidiError(mmrRetVal);
return FALSE;
}
if( (mmrRetVal = midiOutReset((HMIDIOUT)m_hStream)) != MMSYSERR_NOERROR ) {
MidiError(mmrRetVal);
return FALSE;
}
// Wait for the callback thread to release this thread, which it will do by
// calling SetEvent() once all buffers are returned to it
if( WaitForSingleObject( m_hBufferReturnEvent, DEBUG_CALLBACK_TIMEOUT ) == WAIT_TIMEOUT ) {
// Note, this is a risky move because the callback may be genuinely busy, but
// when we're debugging, it's safer and faster than freezing the application,
// which leaves the MIDI device locked up and forces a system reset...
TRACE0("Timed out waiting for MIDI callback\n");
m_uCallbackStatus = STATUS