Windows2003-3790/multimedia/directx/dmusic/dmime/midifile.cpp
2020-09-30 16:53:55 +02:00

1802 lines
62 KiB
C++

// Copyright (c) 1998-2001 Microsoft Corporation
//
// midifile.cpp
//
// original author: Dave Miller
// original project: AudioActive
// modified by: Mark Burton
// project: DirectMusic
//
#include <windows.h>
#include <mmsystem.h>
#include <dsoundp.h>
#include "debug.h"
#define ASSERT assert
#include "Template.h"
#include "dmusici.h"
#include "dmperf.h"
#include "dmusicf.h"
#include "..\dmusic\dmcollec.h"
#include "alist.h"
#include "tlist.h"
#include "dmime.h"
#include "..\dmband\dmbndtrk.h"
#include "..\dmband\bandinst.h"
typedef struct _DMUS_IO_BANKSELECT_ITEM
{
BYTE byLSB;
BYTE byMSB;
BYTE byPad[2];
} DMUS_IO_BANKSELECT_ITEM;
#define EVENT_VOICE 1 // Performance event
#define EVENT_REALTIME 2 // qevent() must invoke interrupt
#define EVENT_ONTIME 3 // event should be handled on time
/* MIDI status bytes ==================================================*/
#define MIDI_NOTEOFF 0x80
#define MIDI_NOTEON 0x90
#define MIDI_PTOUCH 0xA0
#define MIDI_CCHANGE 0xB0
#define MIDI_PCHANGE 0xC0
#define MIDI_MTOUCH 0xD0
#define MIDI_PBEND 0xE0
#define MIDI_SYSX 0xF0
#define MIDI_MTC 0xF1
#define MIDI_SONGPP 0xF2
#define MIDI_SONGS 0xF3
#define MIDI_EOX 0xF7
#define MIDI_CLOCK 0xF8
#define MIDI_START 0xFA
#define MIDI_CONTINUE 0xFB
#define MIDI_STOP 0xFC
#define MIDI_SENSE 0xFE
#define ET_NOTEOFF ( MIDI_NOTEOFF >> 4 ) // 0x08
#define ET_NOTEON ( MIDI_NOTEON >> 4 ) // 0x09
#define ET_PTOUCH ( MIDI_PTOUCH >> 4 ) // 0x0A
#define ET_CCHANGE ( MIDI_CCHANGE >> 4 ) // 0x0B
#define ET_PCHANGE ( MIDI_PCHANGE >> 4 ) // 0x0C
#define ET_MTOUCH ( MIDI_MTOUCH >> 4 ) // 0x0D
#define ET_PBEND ( MIDI_PBEND >> 4 ) // 0x0E
#define ET_SYSX ( MIDI_SYSX >> 4 ) // 0x0F
#define ET_PBCURVE 0x03
#define ET_CCCURVE 0x04
#define ET_MATCURVE 0x05
#define ET_PATCURVE 0x06
#define ET_TEMPOEVENT 0x01
#define ET_NOTDEFINED 0
#define NUM_MIDI_CHANNELS 16
struct FSEBlock;
/* FullSeqEvent is SeqEvent plus next pointers*/
typedef struct FullSeqEvent : DMUS_IO_SEQ_ITEM
{
struct FullSeqEvent* pNext;
struct FullSeqEvent* pTempNext; /* used in the compresseventlist routine */
long pos; /* used to keep track of the order of events in the file */
private:
DWORD dwPosInBlock;
static FSEBlock* sm_pBlockList;
public:
static void CleanUp();
void* operator new(size_t n);
void operator delete(void* p);
} FullSeqEvent;
#define BITMAPSPERBLOCK 8
struct FSEBlock
{
FSEBlock()
{
for(int i = 0 ; i < BITMAPSPERBLOCK ; ++i)
{
m_dwBitMap[i] = 0;
}
};
FSEBlock* m_pNext;
DWORD m_dwBitMap[BITMAPSPERBLOCK];
FullSeqEvent m_Event[BITMAPSPERBLOCK][32];
};
FSEBlock* FullSeqEvent::sm_pBlockList;
void FullSeqEvent::CleanUp()
{
FSEBlock* pBlock;
FSEBlock* pNext;
for(pBlock = sm_pBlockList ; pBlock != NULL ; pBlock = pNext)
{
#ifdef DEBUG
for(int i = 0 ; i < BITMAPSPERBLOCK ; ++i)
{
if(pBlock->m_dwBitMap[i] != 0)
{
DebugBreak();
}
}
#endif
pNext = pBlock->m_pNext;
delete pBlock;
}
sm_pBlockList = NULL;
}
void* FullSeqEvent::operator new(size_t n)
{
if(sm_pBlockList == NULL)
{
sm_pBlockList = new FSEBlock;
if(sm_pBlockList == NULL)
{
return NULL;
}
sm_pBlockList->m_pNext = NULL;
sm_pBlockList->m_dwBitMap[0] = 1;
sm_pBlockList->m_Event[0][0].dwPosInBlock = 0;
return &sm_pBlockList->m_Event[0][0];
}
FSEBlock* pBlock;
int i;
DWORD dw;
for(pBlock = sm_pBlockList ; pBlock != NULL ; pBlock = pBlock->m_pNext)
{
for(i = 0 ; i < BITMAPSPERBLOCK ; ++i)
{
if(pBlock->m_dwBitMap[i] != 0xffff)
{
break;
}
}
if(i < BITMAPSPERBLOCK)
{
break;
}
}
if(pBlock == NULL)
{
pBlock = new FSEBlock;
if(pBlock == NULL)
{
return NULL;
}
pBlock->m_pNext = sm_pBlockList;
sm_pBlockList = pBlock;
pBlock->m_dwBitMap[0] = 1;
pBlock->m_Event[0][0].dwPosInBlock = 0;
return &pBlock->m_Event[0][0];
}
for(dw = 0 ; (pBlock->m_dwBitMap[i] & (1 << dw)) != 0 ; ++dw);
pBlock->m_dwBitMap[i] |= (1 << dw);
pBlock->m_Event[i][dw].dwPosInBlock = (i << 6) | dw;
return &pBlock->m_Event[i][dw];
}
void FullSeqEvent::operator delete(void* p)
{
FSEBlock* pBlock;
int i;
DWORD dw;
FullSeqEvent* pEvent = (FullSeqEvent*)p;
dw = pEvent->dwPosInBlock & 0x1f;
i = pEvent->dwPosInBlock >> 6;
for(pBlock = sm_pBlockList ; pBlock != NULL ; pBlock = pBlock->m_pNext)
{
if(p == &pBlock->m_Event[i][dw])
{
pBlock->m_dwBitMap[i] &= ~(1 << dw);
return;
}
}
}
TList<StampedGMGSXG> gMidiModeList;
// One for each MIDI channel 0-15
DMUS_IO_BANKSELECT_ITEM gBankSelect[NUM_MIDI_CHANNELS];
DWORD gPatchTable[NUM_MIDI_CHANNELS];
long gPos; // Keeps track of order of events in the file
DWORD gdwLastControllerTime[NUM_MIDI_CHANNELS]; // Holds the time of the last CC event.
DWORD gdwControlCollisionOffset[NUM_MIDI_CHANNELS]; // Holds the index of the last CC.
DWORD gdwLastPitchBendValue[NUM_MIDI_CHANNELS]; // Holds the value of the last pbend event.
long glLastSysexTime;
void CreateChordFromKey(char cSharpsFlats, BYTE bMode, DWORD dwTime, DMUS_CHORD_PARAM& rChord);
void InsertMidiMode( TListItem<StampedGMGSXG>* pPair )
{
TListItem<StampedGMGSXG>* pScan = gMidiModeList.GetHead();
if( NULL == pScan )
{
gMidiModeList.AddHead(pPair);
}
else
{
if( pPair->GetItemValue().mtTime < pScan->GetItemValue().mtTime )
{
gMidiModeList.AddHead(pPair);
}
else
{
pScan = pScan->GetNext();
while( pScan )
{
if( pPair->GetItemValue().mtTime < pScan->GetItemValue().mtTime )
{
gMidiModeList.InsertBefore( pScan, pPair );
break;
}
pScan = pScan->GetNext();
}
if( NULL == pScan )
{
gMidiModeList.AddTail(pPair);
}
}
}
}
HRESULT LoadCollection(IDirectMusicCollection** ppIDMCollection,
IDirectMusicLoader* pIDMLoader)
{
// Any changes made to this function should also be made to CDirectMusicBand::LoadCollection
// in dmband.dll
assert(ppIDMCollection);
assert(pIDMLoader);
DMUS_OBJECTDESC desc;
memset(&desc, 0, sizeof(desc));
desc.dwSize = sizeof(desc);
desc.guidClass = CLSID_DirectMusicCollection;
desc.guidObject = GUID_DefaultGMCollection;
desc.dwValidData |= (DMUS_OBJ_CLASS | DMUS_OBJ_OBJECT);
HRESULT hr = pIDMLoader->GetObject(&desc,IID_IDirectMusicCollection, (void**)ppIDMCollection);
return hr;
}
// seeks to a 32-bit position in a stream.
HRESULT __inline StreamSeek( LPSTREAM pStream, long lSeekTo, DWORD dwOrigin )
{
LARGE_INTEGER li;
if( lSeekTo < 0 )
{
li.HighPart = -1;
}
else
{
li.HighPart = 0;
}
li.LowPart = lSeekTo;
return pStream->Seek( li, dwOrigin, NULL );
}
// this function gets a long that is formatted the correct way
// i.e. the motorola way as opposed to the intel way
BOOL __inline GetMLong( LPSTREAM pStream, DWORD& dw )
{
union uLong
{
unsigned char buf[4];
DWORD dw;
} u;
unsigned char ch;
if( S_OK != pStream->Read( u.buf, 4, NULL ) )
{
return FALSE;
}
#ifndef _MAC
// swap bytes
ch = u.buf[0];
u.buf[0] = u.buf[3];
u.buf[3] = ch;
ch = u.buf[1];
u.buf[1] = u.buf[2];
u.buf[2] = ch;
#endif
dw = u.dw;
return TRUE;
}
// this function gets a short that is formatted the correct way
// i.e. the motorola way as opposed to the intel way
BOOL __inline GetMShort( LPSTREAM pStream, short& n )
{
union uShort
{
unsigned char buf[2];
short n;
} u;
unsigned char ch;
if( S_OK != pStream->Read( u.buf, 2, NULL ) )
{
return FALSE;
}
#ifndef _MAC
// swap bytes
ch = u.buf[0];
u.buf[0] = u.buf[1];
u.buf[1] = ch;
#endif
n = u.n;
return TRUE;
}
static short snPPQN;
static IStream* gpTempoStream = NULL;
static IStream* gpSysExStream = NULL;
static IStream* gpTimeSigStream = NULL;
static DWORD gdwSizeTimeSigStream = 0;
static DWORD gdwSizeSysExStream = 0;
static DWORD gdwSizeTempoStream = 0;
static DMUS_IO_TIMESIGNATURE_ITEM gTimeSig; // holds the latest time sig
long glTimeSig = 1; // flag to see if we should be paying attention to time sigs.
// this is needed because we only care about the time sigs on the first track to
// contain them that we read
static IDirectMusicTrack* g_pChordTrack = NULL;
static DMUS_CHORD_PARAM g_Chord; // Holds the latest chord
static DMUS_CHORD_PARAM g_DefaultChord; // in case no chords are extracted from the track
static WORD GetVarLength( LPSTREAM pStream, DWORD& rfdwValue )
{
BYTE b;
WORD wBytes;
if( S_OK != pStream->Read( &b, 1, NULL ) )
{
rfdwValue = 0;
return 0;
}
wBytes = 1;
rfdwValue = b & 0x7f;
while( ( b & 0x80 ) != 0 )
{
if( S_OK != pStream->Read( &b, 1, NULL ) )
{
break;
}
++wBytes;
rfdwValue = ( rfdwValue << 7 ) + ( b & 0x7f );
}
return wBytes;
}
#ifdef _MAC
static DWORD ConvertTime( DWORD dwTime )
{
wide d;
long l; // storage for the remainder
if( snPPQN == DMUS_PPQ ) {
return dwTime;
}
WideMultiply( dwTime, DMUS_PPQ, &d );
return WideDivide( &d, snPPQN, &l );
}
#else
static DWORD ConvertTime( DWORD dwTime )
{
__int64 d;
if( snPPQN == DMUS_PPQ )
{
return dwTime;
}
d = dwTime;
d *= DMUS_PPQ;
d /= snPPQN;
return (DWORD)d;
}
#endif
static FullSeqEvent* ScanForDuplicatePBends( FullSeqEvent* lstEvent )
{
FullSeqEvent* pEvent;
FullSeqEvent* pNextEvent;
MUSIC_TIME mtCurrentTime = 0x7FFFFFFF; // We are scanning backwards in time, so start way in the future.
WORD wDupeBits = 0; // Keep a bit array of all channels that have active PBends at mtCurrentTime.
if( NULL == lstEvent ) return NULL;
// Scan through the list of events. This list is in backwards order, with the first item read at the end
// of the list. This makes it very easy to scan through and remove pitch bends that occur at the same time, since
// we can remove the latter events (which occured earlier in the midi file.)
for( pEvent = lstEvent ; pEvent ; pEvent = pNextEvent )
{
pNextEvent = pEvent->pNext;
if( pNextEvent )
{
// If the time is not the same as the last, reset.
if (pNextEvent->mtTime != mtCurrentTime)
{
// Reset the time.
mtCurrentTime = pNextEvent->mtTime;
// No duplicate pbends at this time.
wDupeBits = 0;
}
if ((pNextEvent->bStatus & 0xf0) == MIDI_PBEND)
{
DWORD dwChannel = pNextEvent->dwPChannel;
if (wDupeBits & (1 << dwChannel))
{
// There was a previous (therefore later in the file) pbend at this time. Delete this one.
pEvent->pNext = pNextEvent->pNext;
delete pNextEvent;
pNextEvent = pEvent;
}
else
{
// This is the last instance of a pbend on this channel at this time, so hang on to it.
wDupeBits |= (1 << dwChannel);
}
}
}
}
return lstEvent;
}
static FullSeqEvent* CompressEventList( FullSeqEvent* lstEvent )
{
static FullSeqEvent* paNoteOnEvent[16][128];
FullSeqEvent* pEvent;
FullSeqEvent* pPrevEvent;
FullSeqEvent* pNextEvent;
FullSeqEvent* pHoldEvent;
FullSeqEvent tempEvent;
int nChannel;
if( NULL == lstEvent ) return NULL;
memset( paNoteOnEvent, 0, sizeof( paNoteOnEvent ) );
pPrevEvent = NULL;
// add an event to the beginning of the list as a place holder
memset( &tempEvent, 0, sizeof(FullSeqEvent) );
tempEvent.mtTime = -1;
tempEvent.pNext = lstEvent;
lstEvent = &tempEvent;
// make sure that any events with the same time are sorted in order
// they were read
for( pEvent = lstEvent ; pEvent != NULL ; pEvent = pNextEvent )
{
pNextEvent = pEvent->pNext;
if( pNextEvent )
{
BOOL fSwap = TRUE;
// bubble sort
while( fSwap )
{
fSwap = FALSE;
pPrevEvent = pEvent;
pNextEvent = pEvent->pNext;
while( pNextEvent->pNext && ( pNextEvent->mtTime == pNextEvent->pNext->mtTime ))
{
if( pNextEvent->pNext->pos < pNextEvent->pos )
{
fSwap = TRUE;
pHoldEvent = pNextEvent->pNext;
pPrevEvent->pNext = pHoldEvent;
pNextEvent->pNext = pHoldEvent->pNext;
pHoldEvent->pNext = pNextEvent;
pPrevEvent = pHoldEvent;
continue;
}
pPrevEvent = pNextEvent;
pNextEvent = pNextEvent->pNext;
}
}
}
}
// remove the first, temporary event, added above
lstEvent = lstEvent->pNext;
pPrevEvent = NULL;
// combine note on and note offs
for( pEvent = lstEvent ; pEvent != NULL ; pEvent = pNextEvent )
{
pEvent->pTempNext = NULL;
pNextEvent = pEvent->pNext;
//nChannel = pEvent->bStatus & 0xf;
nChannel = pEvent->dwPChannel;
if( ( pEvent->bStatus & 0xf0 ) == MIDI_NOTEON )
{
// add this event to the end of the list of events based
// on the event's pitch. Keeping track of multiple events
// of the same pitch allows us to have overlapping notes
// of the same pitch, choosing that note on's and note off's
// follow in the same order.
if( NULL == paNoteOnEvent[nChannel][pEvent->bByte1] )
{
paNoteOnEvent[nChannel][pEvent->bByte1] = pEvent;
}
else
{
FullSeqEvent* pScan;
for( pScan = paNoteOnEvent[nChannel][pEvent->bByte1];
pScan->pTempNext != NULL; pScan = pScan->pTempNext );
pScan->pTempNext = pEvent;
}
}
else if( ( pEvent->bStatus & 0xf0 ) == MIDI_NOTEOFF )
{
if( paNoteOnEvent[nChannel][pEvent->bByte1] != NULL )
{
paNoteOnEvent[nChannel][pEvent->bByte1]->mtDuration =
pEvent->mtTime - paNoteOnEvent[nChannel][pEvent->bByte1]->mtTime;
paNoteOnEvent[nChannel][pEvent->bByte1] =
paNoteOnEvent[nChannel][pEvent->bByte1]->pTempNext;
}
if( pPrevEvent == NULL )
{
lstEvent = pNextEvent;
}
else
{
pPrevEvent->pNext = pNextEvent;
}
delete pEvent;
continue;
}
pPrevEvent = pEvent;
}
for( pEvent = lstEvent ; pEvent != NULL ; pEvent = pEvent->pNext )
{
pEvent->mtTime = pEvent->mtTime;
if( ( pEvent->bStatus & 0xf0 ) == MIDI_NOTEON )
{
pEvent->mtDuration = pEvent->mtDuration;
if( pEvent->mtDuration == 0 ) pEvent->mtDuration = 1;
}
}
return lstEvent;
}
static int CompareEvents( FullSeqEvent* pEvent1, FullSeqEvent* pEvent2 )
{
BYTE bEventType1 = static_cast<BYTE>( pEvent1->bStatus >> 4 );
BYTE bEventType2 = static_cast<BYTE>( pEvent2->bStatus >> 4 );
if( pEvent1->mtTime < pEvent2->mtTime )
{
return -1;
}
else if( pEvent1->mtTime > pEvent2->mtTime )
{
return 1;
}
else if( bEventType1 != ET_SYSX && bEventType2 != ET_SYSX )
{
BYTE bStatus1;
BYTE bStatus2;
bStatus1 = (BYTE)( pEvent1->bStatus & 0xf0 );
bStatus2 = (BYTE)( pEvent2->bStatus & 0xf0 );
if( bStatus1 == bStatus2 )
{
return 0;
}
else if( bStatus1 == MIDI_NOTEON )
{
return -1;
}
else if( bStatus2 == MIDI_NOTEON )
{
return 1;
}
else if( bStatus1 > bStatus2 )
{
return 1;
}
else if( bStatus1 < bStatus2 )
{
return -1;
}
}
return 0;
}
static FullSeqEvent* MergeEvents( FullSeqEvent* lstLeftEvent, FullSeqEvent* lstRightEvent )
{
FullSeqEvent anchorEvent;
FullSeqEvent* pEvent;
anchorEvent.pNext = NULL;
pEvent = &anchorEvent;
do
{
if( CompareEvents( lstLeftEvent, lstRightEvent ) < 0 )
{
pEvent->pNext = lstLeftEvent;
pEvent = lstLeftEvent;
lstLeftEvent = lstLeftEvent->pNext;
if( lstLeftEvent == NULL )
{
pEvent->pNext = lstRightEvent;
}
}
else
{
pEvent->pNext = lstRightEvent;
pEvent = lstRightEvent;
lstRightEvent = lstRightEvent->pNext;
if( lstRightEvent == NULL )
{
pEvent->pNext = lstLeftEvent;
lstLeftEvent = NULL;
}
}
} while( lstLeftEvent != NULL );
return anchorEvent.pNext;
}
static FullSeqEvent* SortEventList( FullSeqEvent* lstEvent )
{
FullSeqEvent* pMidEvent;
FullSeqEvent* pRightEvent;
if( lstEvent != NULL && lstEvent->pNext != NULL )
{
pMidEvent = lstEvent;
pRightEvent = pMidEvent->pNext->pNext;
if( pRightEvent != NULL )
{
pRightEvent = pRightEvent->pNext;
}
while( pRightEvent != NULL )
{
pMidEvent = pMidEvent->pNext;
pRightEvent = pRightEvent->pNext;
if( pRightEvent != NULL )
{
pRightEvent = pRightEvent->pNext;
}
}
pRightEvent = pMidEvent->pNext;
pMidEvent->pNext = NULL;
return MergeEvents( SortEventList( lstEvent ),
SortEventList( pRightEvent ) );
}
return lstEvent;
}
static DWORD ReadEvent( LPSTREAM pStream, DWORD dwTime, FullSeqEvent** plstEvent, DMUS_IO_PATCH_ITEM** pplstPatchEvent )
{
static BYTE bRunningStatus;
gPos++;
dwTime = ConvertTime(dwTime);
DWORD dwBytes;
DWORD dwLen;
FullSeqEvent* pEvent;
DMUS_IO_PATCH_ITEM* pPatchEvent;
DMUS_IO_SYSEX_ITEM* pSysEx;
BYTE b;
BYTE* pbSysExData = NULL;
if( FAILED( pStream->Read( &b, 1, NULL ) ) )
{
return 0;
}
if( b < 0x80 )
{
StreamSeek( pStream, -1, STREAM_SEEK_CUR );
b = bRunningStatus;
dwBytes = 0;
}
else
{
dwBytes = 1;
}
if( b < 0xf0 )
{
bRunningStatus = (BYTE)b;
switch( b & 0xf0 )
{
case MIDI_CCHANGE:
case MIDI_PTOUCH:
case MIDI_PBEND:
case MIDI_NOTEOFF:
case MIDI_NOTEON:
if( FAILED( pStream->Read( &b, 1, NULL ) ) )
{
return dwBytes;
}
++dwBytes;
pEvent = new FullSeqEvent;
if( pEvent == NULL )
{
return 0;
}
pEvent->mtTime = dwTime;
pEvent->nOffset = 0;
pEvent->pos = gPos;
pEvent->mtDuration = 0;
pEvent->bStatus = bRunningStatus & 0xf0;
pEvent->dwPChannel = bRunningStatus & 0xf;
pEvent->bByte1 = b;
if( FAILED( pStream->Read( &b, 1, NULL ) ) )
{
delete pEvent;
return dwBytes;
}
++dwBytes;
pEvent->bByte2 = b;
if( ( pEvent->bStatus & 0xf0 ) == MIDI_NOTEON &&
pEvent->bByte2 == 0 )
{
pEvent->bStatus = (BYTE)( MIDI_NOTEOFF );
}
/* If there are multiple controller events at the same time, seperate
them by clock ticks.
gdwLastControllerTime holds the time of the last CC event.
gdwControlCollisionOffset holds the number of colliding CCs.
*/
if ((pEvent->bStatus & 0xf0) == MIDI_CCHANGE)
{
DWORD dwChannel = pEvent->dwPChannel;
if (dwTime == gdwLastControllerTime[dwChannel])
{
pEvent->mtTime += ++gdwControlCollisionOffset[dwChannel];
}
else
{
gdwControlCollisionOffset[dwChannel] = 0;
gdwLastControllerTime[dwChannel] = dwTime;
}
}
if(((pEvent->bStatus & 0xf0) == MIDI_CCHANGE) && (pEvent->bByte1 == 0 || pEvent->bByte1 == 0x20))
{
// We have a bank select or its LSB either of which are not added to event list
if(pEvent->bByte1 == 0x20)
{
gBankSelect[pEvent->dwPChannel].byLSB = pEvent->bByte2;
}
else // pEvent->bByte1 == 0
{
gBankSelect[pEvent->dwPChannel].byMSB = pEvent->bByte2;
}
// We no longer need the event so we can free it
delete pEvent;
}
else // Add to event list
{
pEvent->pNext = *plstEvent;
*plstEvent = pEvent;
}
break;
case MIDI_PCHANGE:
if(FAILED(pStream->Read(&b, 1, NULL)))
{
return dwBytes;
}
++dwBytes;
pPatchEvent = new DMUS_IO_PATCH_ITEM;
if(pPatchEvent == NULL)
{
return 0;
}
memset(pPatchEvent, 0, sizeof(DMUS_IO_PATCH_ITEM));
pPatchEvent->lTime = dwTime - 1;
pPatchEvent->byStatus = bRunningStatus;
pPatchEvent->byPChange = b;
pPatchEvent->byMSB = gBankSelect[bRunningStatus & 0xF].byMSB;
pPatchEvent->byLSB = gBankSelect[bRunningStatus & 0xF].byLSB;
pPatchEvent->dwFlags |= DMUS_IO_INST_PATCH;
if((pPatchEvent->byMSB != 0xFF) && (pPatchEvent->byLSB != 0xFF))
{
pPatchEvent->dwFlags |= DMUS_IO_INST_BANKSELECT;
}
gPatchTable[bRunningStatus & 0xF] = 1;
pPatchEvent->pNext = *pplstPatchEvent;
pPatchEvent->pIDMCollection = NULL;
*pplstPatchEvent = pPatchEvent;
break;
case MIDI_MTOUCH:
if( FAILED( pStream->Read( &b, 1, NULL ) ) )
{
return dwBytes;
}
++dwBytes;
pEvent = new FullSeqEvent;
if( pEvent == NULL )
{
return 0;
}
pEvent->mtTime = dwTime;
pEvent->nOffset = 0;
pEvent->pos = gPos;
pEvent->mtDuration = 0;
pEvent->bStatus = bRunningStatus & 0xf0;
pEvent->dwPChannel = bRunningStatus & 0xf;
pEvent->bByte1 = b;
pEvent->pNext = *plstEvent;
*plstEvent = pEvent;
break;
default:
// this should NOT be possible - unknown midi note event type
ASSERT(FALSE);
break;
}
}
else
{
switch( b )
{
case 0xf0:
dwBytes += GetVarLength( pStream, dwLen );
pSysEx = new DMUS_IO_SYSEX_ITEM;
if( pSysEx != NULL )
{
pbSysExData = new BYTE[dwLen + 1];
if( pbSysExData != NULL )
{
MUSIC_TIME mt = dwTime;
if (mt == 0)
{
mt = glLastSysexTime++;
if (mt > 0) mt = 0;
}
pbSysExData[0] = 0xf0;
if( FAILED( pStream->Read( pbSysExData + 1, dwLen, NULL ) ) )
{
delete [] pbSysExData;
delete pSysEx;
return dwBytes;
}
if( pbSysExData[1] == 0x43 )
{
// check for XG files
BYTE abXG[] = { 0xF0, 0x43, 0x10, 0x4C, 0x00, 0x00, 0x7E, 0x00, 0xF7 };
int i;
for( i = 0; i < 8; i++ )
{
if( i == 2 )
{
if( ( pbSysExData[i] & 0xF0 ) != abXG[i] )
break;
}
else
{
if( pbSysExData[i] != abXG[i] )
break;
}
}
if( i == 8 ) // we have an XG!
{
TListItem<StampedGMGSXG>* pPair = new TListItem<StampedGMGSXG>;
if (!pPair) return dwBytes;
pPair->GetItemValue().mtTime = mt;
pPair->GetItemValue().dwMidiMode = DMUS_MIDIMODEF_XG;
InsertMidiMode(pPair);
}
}
else if( pbSysExData[1] == 0x41 )
{
// check for GS files
BYTE abGS[] = { 0xF0,0x41,0x00,0x42,0x12,0x40,0x00,0x7F,0x00,0x41,0xF7 };
int i;
for( i = 0; i < 10; i++ )
{
if( i != 2 )
{
if( pbSysExData[i] != abGS[i] )
break;
}
}
if( i == 10 ) // we have a GS!
{
TListItem<StampedGMGSXG>* pPair = new TListItem<StampedGMGSXG>;
if (!pPair) return dwBytes;
pPair->GetItemValue().mtTime = mt;
pPair->GetItemValue().dwMidiMode = DMUS_MIDIMODEF_GS;
InsertMidiMode(pPair);
}
}
else if (( pbSysExData[1] == 0x7E ) && (pbSysExData[3] == 0x09))
{
TListItem<StampedGMGSXG>* pPair = new TListItem<StampedGMGSXG>;
if (!pPair) return dwBytes;
pPair->GetItemValue().mtTime = mt;
pPair->GetItemValue().dwMidiMode = DMUS_MIDIMODEF_GM;
InsertMidiMode(pPair);
}
pSysEx->mtTime = mt;
pSysEx->dwPChannel = 0;
DWORD dwTempLen = dwLen + 1;
pSysEx->dwSysExLength = dwTempLen;
if( NULL == gpSysExStream )
{
// create a stream to hold sysex events
CreateStreamOnHGlobal( NULL, TRUE, &gpSysExStream );
if( gpSysExStream )
{
DWORD dwTemp;
// write the chunk header
dwTemp = DMUS_FOURCC_SYSEX_TRACK;
gpSysExStream->Write( &dwTemp, sizeof(DWORD), NULL );
// write the overall size. (Replace this later with the
// true overall size.)
dwTemp = sizeof(DMUS_IO_TIMESIGNATURE_ITEM);
// overall size (to be replaced later)
gpSysExStream->Write( &dwTemp, sizeof(DWORD), NULL );
}
}
if( gpSysExStream )
{
gpSysExStream->Write( &pSysEx->mtTime, sizeof(MUSIC_TIME), NULL );
gpSysExStream->Write( &pSysEx->dwPChannel, sizeof(DWORD), NULL );
gpSysExStream->Write( &pSysEx->dwSysExLength, sizeof(DWORD), NULL );
gpSysExStream->Write( pbSysExData, dwTempLen, NULL );
gdwSizeSysExStream += (sizeof(long) + sizeof(DWORD) + dwTempLen);
}
delete [] pbSysExData;
delete pSysEx;
}
else
{
StreamSeek( pStream, dwLen, STREAM_SEEK_CUR );
}
}
else
{
StreamSeek( pStream, dwLen, STREAM_SEEK_CUR );
}
dwBytes += dwLen;
break;
case 0xf7:
// ignore sysex f7 chunks
dwBytes += GetVarLength( pStream, dwLen );
StreamSeek( pStream, dwLen, STREAM_SEEK_CUR );
dwBytes += dwLen;
break;
case 0xff:
if( FAILED( pStream->Read( &b, 1, NULL ) ) )
{
return dwBytes;
}
++dwBytes;
dwBytes += GetVarLength( pStream, dwLen );
if( b == 0x51 ) // tempo change
{
DWORD dw = 0;
DMUS_IO_TEMPO_ITEM tempo;
while( dwLen > 0 )
{
if( FAILED( pStream->Read( &b, 1, NULL ) ) )
{
return dwBytes;
}
++dwBytes;
--dwLen;
dw <<= 8;
dw += b;
}
if (dw < 1) dw = 1;
tempo.dblTempo = 60000000.0 / ((double)dw);
tempo.lTime = dwTime;
if( NULL == gpTempoStream )
{
// create a stream to hold tempo events
CreateStreamOnHGlobal( NULL, TRUE, &gpTempoStream );
if( gpTempoStream )
{
DWORD dwTemp;
// write the chunk header
dwTemp = DMUS_FOURCC_TEMPO_TRACK;
gpTempoStream->Write( &dwTemp, sizeof(DWORD), NULL );
// write the overall size. (Replace this later with the
// true overall size.) Also write the size of the individual
// structure.
dwTemp = sizeof(DMUS_IO_TEMPO_ITEM);
// overall size (to be replaced later)
gpTempoStream->Write( &dwTemp, sizeof(DWORD), NULL );
// individual structure.
gpTempoStream->Write( &dwTemp, sizeof(DWORD), NULL );
}
}
if( gpTempoStream )
{
gpTempoStream->Write( &tempo, sizeof(DMUS_IO_TEMPO_ITEM), NULL );
gdwSizeTempoStream += sizeof(DMUS_IO_TEMPO_ITEM);
}
}
else if( b == 0x58 && glTimeSig )
{
// glTimeSig will be set to 0 inside the main calling function
// once we no longer care about time sigs.
DMUS_IO_TIMESIGNATURE_ITEM timesig;
if( FAILED( pStream->Read( &b, 1, NULL ) ) )
{
return dwBytes;
}
// set glTimeSig to 2 to signal to the main function that we've
// read a time sig on this track
glTimeSig = 2;
gTimeSig.lTime = timesig.lTime = dwTime;
gTimeSig.bBeatsPerMeasure = timesig.bBeatsPerMeasure = b;
++dwBytes;
if( FAILED( pStream->Read( &b, 1, NULL ) ) )
{
return dwBytes;
}
gTimeSig.bBeat = timesig.bBeat = (BYTE)( 1 << b ); // 0 means 256th note
gTimeSig.wGridsPerBeat = timesig.wGridsPerBeat = 4; // this is irrelavent for MIDI files
if( NULL == gpTimeSigStream )
{
CreateStreamOnHGlobal( NULL, TRUE, &gpTimeSigStream );
if( gpTimeSigStream )
{
DWORD dwTemp;
// write the chunk header
dwTemp = DMUS_FOURCC_TIMESIGNATURE_TRACK;
gpTimeSigStream->Write( &dwTemp, sizeof(DWORD), NULL );
// write the overall size. (Replace this later with the
// true overall size.) Also write the size of the individual
// structure.
dwTemp = sizeof(DMUS_IO_TIMESIGNATURE_ITEM);
// overall size (to be replaced later)
gpTimeSigStream->Write( &dwTemp, sizeof(DWORD), NULL );
// individual structure.
gpTimeSigStream->Write( &dwTemp, sizeof(DWORD), NULL );
gdwSizeTimeSigStream += sizeof(DWORD);
}
}
if( gpTimeSigStream )
{
gpTimeSigStream->Write( &timesig, sizeof(DMUS_IO_TIMESIGNATURE_ITEM), NULL );
gdwSizeTimeSigStream += sizeof(DMUS_IO_TIMESIGNATURE_ITEM);
}
++dwBytes;
StreamSeek( pStream, dwLen - 2, STREAM_SEEK_CUR );
dwBytes += ( dwLen - 2 );
}
else if( b == 0x59 )
{
// Read sharps/flats and major/minor bytes
if( FAILED( pStream->Read( &b, 1, NULL ) ) )
{
return dwBytes;
}
char cSharpsFlats = b;
++dwBytes;
if( FAILED( pStream->Read( &b, 1, NULL ) ) )
{
return dwBytes;
}
BYTE bMode = b;
++dwBytes;
// Create a chord (with one subchord) from the key info
CreateChordFromKey(cSharpsFlats, bMode, dwTime, g_Chord);
// If the chord track is empty, create it.
if (!g_pChordTrack)
{
HRESULT hr = CoCreateInstance(
CLSID_DirectMusicChordTrack, NULL, CLSCTX_INPROC,
IID_IDirectMusicTrack,
(void**)&g_pChordTrack );
if (!SUCCEEDED(hr)) return dwBytes;
// If dwTime > 0, use SetParam to insert the default chord at time 0
if (dwTime > 0)
{
g_pChordTrack->SetParam(GUID_ChordParam, 0, &g_DefaultChord);
}
}
// Use SetParam to insert the new chord into the chord track
g_pChordTrack->SetParam(GUID_ChordParam, dwTime, &g_Chord);
}
else
{
StreamSeek( pStream, dwLen, STREAM_SEEK_CUR );
dwBytes += dwLen;
}
break;
default:
break;
}
}
return dwBytes;
}
static void AddOffsets(FullSeqEvent* lstEvent, IDirectMusicTrack* pTimeSigTrack)
{
HRESULT hr;
MUSIC_TIME mtNext = 0;
DMUS_IO_TIMESIGNATURE_ITEM timesig;
timesig.bBeat = gTimeSig.bBeat ? gTimeSig.bBeat : 4;
timesig.bBeatsPerMeasure = gTimeSig.bBeatsPerMeasure ? gTimeSig.bBeatsPerMeasure : 4;
timesig.wGridsPerBeat = gTimeSig.wGridsPerBeat ? gTimeSig.wGridsPerBeat : 4;
timesig.lTime = 0;
short nClocksPerGrid = ((DMUS_PPQ * 4) / timesig.bBeat) / timesig.wGridsPerBeat;
if (pTimeSigTrack)
{
hr = pTimeSigTrack->GetParam(GUID_TimeSignature, 0, &mtNext, (void*)&timesig);
if (FAILED(hr))
{
mtNext = 0;
}
else
{
nClocksPerGrid = ((DMUS_PPQ * 4) / timesig.bBeat) / timesig.wGridsPerBeat;
}
}
for( FullSeqEvent* pEvent = lstEvent; pEvent; pEvent = pEvent->pNext )
{
if ( ( pEvent->bStatus & 0xf0 ) == MIDI_NOTEON )
{
if (mtNext && pTimeSigTrack && mtNext < pEvent->mtTime)
{
hr = pTimeSigTrack->GetParam(GUID_TimeSignature, mtNext, &mtNext, (void*)&timesig);
if (FAILED(hr))
{
mtNext = 0;
}
else
{
nClocksPerGrid = ((DMUS_PPQ * 4) / timesig.bBeat) / timesig.wGridsPerBeat;
}
}
ASSERT(nClocksPerGrid);
if( 0 == nClocksPerGrid ) nClocksPerGrid = 1; // this should never happen, but just in case.
pEvent->nOffset = (short) ((pEvent->mtTime - timesig.lTime) % nClocksPerGrid);
pEvent->mtTime -= pEvent->nOffset;
if (pEvent->nOffset > (nClocksPerGrid / 2))
{
// make it a negative offset and bump the time a corresponding amount
pEvent->nOffset -= nClocksPerGrid;
pEvent->mtTime += nClocksPerGrid;
}
}
}
}
/*
@method HRESULT | IDirectMusicPerformance | CreateSegmentFromMIDIStream |
Given a MIDI stream, creates a Segment that can be played via
<im IDirectMusicPerformance.PlaySegment>.
@parm LPSTREAM | pStream |
[in] The MIDI stream. It should be set to the correct seek to begin reading.
@parm IDirectMusicSegment* | pSegment |
[out] A pointer to contain the created Segment.
@rvalue DMUS_E_CANNOTREAD | There was an error attempting to read the MIDI file.
@rvalue S_OK
*/
HRESULT CreateSegmentFromMIDIStream(LPSTREAM pStream,
IDirectMusicSegment* pSegment)
{
if(pSegment == NULL || pStream == NULL)
{
return E_POINTER;
}
HRESULT hr = DMUS_E_CANNOTREAD;
DWORD dwID;
DWORD dwCurTime;
DWORD dwLength;
DWORD dwSize;
short nFormat;
short nNumTracks;
short nTracksRead;
FullSeqEvent* lstEvent;
DMUS_IO_PATCH_ITEM* lstPatchEvent;
FullSeqEvent* lstTrackEvent;
HRESULT hrGM = S_OK;
EnterCriticalSection(&g_CritSec);
gpTempoStream = NULL;
gpSysExStream = NULL;
gpTimeSigStream = NULL;
gdwSizeTimeSigStream = 0;
gdwSizeSysExStream = 0;
gdwSizeTempoStream = 0;
glTimeSig = 1; // flag to see if we should be paying attention to time sigs.
// this is needed because we only care about the time sigs on the first track to
// contain them that we read
g_pChordTrack = NULL;
lstEvent = NULL;
lstPatchEvent = NULL;
nNumTracks = nTracksRead = 0;
dwLength = 0;
gPos = 0;
gMidiModeList.CleanUp();
if (g_pChordTrack)
{
g_pChordTrack->Release();
g_pChordTrack = NULL;
}
CreateChordFromKey(0, 0, 0, g_Chord);
CreateChordFromKey(0, 0, 0, g_DefaultChord);
memset(&gBankSelect, 0xFF, (sizeof(DMUS_IO_BANKSELECT_ITEM) * NUM_MIDI_CHANNELS));
memset(&gPatchTable, 0, (sizeof(DWORD) * NUM_MIDI_CHANNELS));
memset(&gTimeSig, 0, sizeof(DMUS_IO_TIMESIGNATURE_ITEM));
memset(&gdwLastControllerTime, 0xFF, (sizeof(DWORD) * NUM_MIDI_CHANNELS));
memset(&gdwControlCollisionOffset, 0, (sizeof(DWORD) * NUM_MIDI_CHANNELS));
glLastSysexTime = -5;
if( ( S_OK != pStream->Read( &dwID, sizeof( FOURCC ), NULL ) ) ||
!GetMLong( pStream, dwSize ) )
{
Trace(1,"Error: Failure parsing MIDI file.\n");
LeaveCriticalSection(&g_CritSec);
return DMUS_E_CANNOTREAD;
}
// check for RIFF MIDI files
if( dwID == mmioFOURCC( 'R', 'I', 'F', 'F' ) )
{
StreamSeek( pStream, 12, STREAM_SEEK_CUR );
if( ( S_OK != pStream->Read( &dwID, sizeof( FOURCC ), NULL ) ) ||
!GetMLong( pStream, dwSize ) )
{
Trace(1,"Error: Failure parsing MIDI file.\n");
LeaveCriticalSection(&g_CritSec);
return DMUS_E_CANNOTREAD;
}
}
// check for normal MIDI files
if( dwID != mmioFOURCC( 'M', 'T', 'h', 'd' ) )
{
LeaveCriticalSection(&g_CritSec);
Trace(1,"Error: Failure parsing MIDI file - can't find a valid header.\n");
return DMUS_E_CANNOTREAD;
}
GetMShort( pStream, nFormat );
GetMShort( pStream, nNumTracks );
GetMShort( pStream, snPPQN );
if( dwSize > 6 )
{
StreamSeek( pStream, dwSize - 6, STREAM_SEEK_CUR );
}
pStream->Read( &dwID, sizeof( FOURCC ), NULL );
while( dwID == mmioFOURCC( 'M', 'T', 'r', 'k' ) )
{
GetMLong( pStream, dwSize );
dwCurTime = 0;
lstTrackEvent = NULL;
long lSize = (long)dwSize;
while( lSize > 0 )
{
long lReturn;
lSize -= GetVarLength( pStream, dwID );
dwCurTime += dwID;
if (lSize > 0)
{
lReturn = ReadEvent( pStream, dwCurTime, &lstTrackEvent, &lstPatchEvent );
if( lReturn )
{
lSize -= lReturn;
}
else
{
Trace(1,"Error: Failure parsing MIDI file.\n");
hr = DMUS_E_CANNOTREAD;
goto END;
}
}
}
dwSize = lSize;
if( glTimeSig > 1 )
{
// if glTimeSig is greater than 1, it means we've read some time sigs
// from this track (it was set to 2 inside ReadEvent.) This means that
// we no longer want ReadEvent to pay any attention to time sigs, so
// we set this to 0.
glTimeSig = 0;
}
if( dwCurTime > dwLength )
{
dwLength = dwCurTime;
}
lstTrackEvent = ScanForDuplicatePBends( lstTrackEvent );
lstTrackEvent = SortEventList( lstTrackEvent );
lstTrackEvent = CompressEventList( lstTrackEvent );
lstEvent = List_Cat( lstEvent, lstTrackEvent );
if( FAILED( pStream->Read( &dwID, sizeof( FOURCC ), NULL ) ) )
{
break;
}
}
dwLength = ConvertTime(dwLength);
lstEvent = SortEventList( lstEvent );
// if( lstEvent ) Removed: this might be just a band, or sysex data, or whatever.
{
if(pSegment)
{
IPersistStream* pIPSTrack;
IDirectMusicTrack* pDMTrack;
hr = S_OK;
if (!g_pChordTrack)
{
hr = CoCreateInstance(
CLSID_DirectMusicChordTrack, NULL, CLSCTX_INPROC,
IID_IDirectMusicTrack,
(void**)&g_pChordTrack );
if (SUCCEEDED(hr))
{
g_pChordTrack->SetParam(GUID_ChordParam, 0, &g_DefaultChord);
}
}
if (SUCCEEDED(hr))
{
pSegment->InsertTrack( g_pChordTrack, 1 );
g_pChordTrack->Release();
g_pChordTrack = NULL;
}
// Note: We could be checking to see if there are actually tempo events,
// sysex events, etc. to see if it's really necessary to create these
// tracks...
// Create a Tempo Track in which to store the tempo events
if( gpTempoStream )
{
if( SUCCEEDED( CoCreateInstance( CLSID_DirectMusicTempoTrack,
NULL, CLSCTX_INPROC, IID_IPersistStream,
(void**)&pIPSTrack )))
{
StreamSeek( gpTempoStream, sizeof(DWORD), STREAM_SEEK_SET );
gpTempoStream->Write( &gdwSizeTempoStream, sizeof(DWORD), NULL );
StreamSeek( gpTempoStream, 0, STREAM_SEEK_SET );
pIPSTrack->Load( gpTempoStream );
if( SUCCEEDED( pIPSTrack->QueryInterface( IID_IDirectMusicTrack,
(void**)&pDMTrack ) ) )
{
pSegment->InsertTrack( pDMTrack, 1 );
pDMTrack->Release();
}
pIPSTrack->Release();
}
}
// Add a patch event for each MIDI channel that does not have one
DMUS_IO_PATCH_ITEM* pPatchEvent = NULL;
for(DWORD i = 0; i < 16; i++)
{
if(gPatchTable[i] == 0)
{
pPatchEvent = new DMUS_IO_PATCH_ITEM;
if(pPatchEvent == NULL)
{
continue;
}
memset(pPatchEvent, 0, sizeof(DMUS_IO_PATCH_ITEM));
pPatchEvent->lTime = ConvertTime(0);
pPatchEvent->byStatus = 0xC0 + (BYTE)(i & 0xf);
pPatchEvent->dwFlags |= (DMUS_IO_INST_PATCH);
pPatchEvent->pIDMCollection = NULL;
pPatchEvent->fNotInFile = TRUE;
pPatchEvent->pNext = lstPatchEvent;
lstPatchEvent = pPatchEvent;
}
}
if(lstPatchEvent)
{
// Create Band Track in which to store patch change events
IDirectMusicBandTrk* pBandTrack;
if(SUCCEEDED(CoCreateInstance(CLSID_DirectMusicBandTrack,
NULL,
CLSCTX_INPROC,
IID_IDirectMusicBandTrk,
(void**)&pBandTrack)))
{
// Get the loader from stream so we can open a required collections
IDirectMusicGetLoader* pIDMGetLoader = NULL;
IDirectMusicLoader* pIDMLoader = NULL;
hr = pStream->QueryInterface(IID_IDirectMusicGetLoader, (void**)&pIDMGetLoader);
if( SUCCEEDED(hr) )
{
hr = pIDMGetLoader->GetLoader(&pIDMLoader);
pIDMGetLoader->Release();
}
// IStream needs a loader attached
assert(SUCCEEDED(hr));
// Populate the the Band Track with patch change events
for(DMUS_IO_PATCH_ITEM* pEvent = lstPatchEvent; pEvent; pEvent = lstPatchEvent)
{
// Remove instrument from head of list and give to band
DMUS_IO_PATCH_ITEM* temp = pEvent->pNext;
pEvent->pNext = NULL;
lstPatchEvent = temp;
// We will try to load the collection but if we can not we will continure
// and use the default GM on the card
if(pIDMLoader)
{
HRESULT hrTemp = LoadCollection(&pEvent->pIDMCollection, pIDMLoader);
if (FAILED(hrTemp))
{
hrGM = hrTemp;
}
}
hr = pBandTrack->AddBand(pEvent);
// Release reference to collection
if(pEvent->pIDMCollection)
{
(pEvent->pIDMCollection)->Release();
pEvent->pIDMCollection = NULL;
}
delete pEvent;
if(FAILED(hr))
{
break;
}
}
if(SUCCEEDED(hr))
{
TListItem<StampedGMGSXG>* pPair = gMidiModeList.GetHead();
if( NULL == pPair )
{
// if we had nothing, generate a GM one so the band knows
// it was loaded from a midi file
// since the first band is set to play at -1,
// this is when the default midi mode must occur.
pBandTrack->SetGMGSXGMode(-1, DMUS_MIDIMODEF_GM);
}
for ( ; pPair; pPair = pPair->GetNext() )
{
StampedGMGSXG& rPair = pPair->GetItemValue();
pBandTrack->SetGMGSXGMode(rPair.mtTime, rPair.dwMidiMode);
}
gMidiModeList.CleanUp();
if(SUCCEEDED(pBandTrack->QueryInterface(IID_IDirectMusicTrack,
(void**)&pDMTrack)))
{
pSegment->InsertTrack(pDMTrack, 1);
pDMTrack->Release();
}
}
if(pBandTrack)
{
pBandTrack->Release();
}
if(pIDMLoader)
{
pIDMLoader->Release();
}
}
}
if( gpTimeSigStream )
{
// Create a TimeSig Track to store the TimeSig events
if( SUCCEEDED( CoCreateInstance( CLSID_DirectMusicTimeSigTrack,
NULL, CLSCTX_INPROC, IID_IPersistStream,
(void**)&pIPSTrack )))
{
// set the overall size to the correct size
StreamSeek( gpTimeSigStream, sizeof(DWORD), STREAM_SEEK_SET );
gpTimeSigStream->Write( &gdwSizeTimeSigStream, sizeof(DWORD), NULL );
// reset to beginning and persist to track.
StreamSeek( gpTimeSigStream, 0, STREAM_SEEK_SET );
pIPSTrack->Load( gpTimeSigStream );
if( SUCCEEDED( pIPSTrack->QueryInterface( IID_IDirectMusicTrack,
(void**)&pDMTrack ) ) )
{
pSegment->InsertTrack( pDMTrack, 1 );
AddOffsets(lstEvent, pDMTrack);
pDMTrack->Release();
}
pIPSTrack->Release();
}
}
else
{
AddOffsets(lstEvent, NULL);
}
lstEvent = SortEventList( lstEvent );
// Create a Sequence Track in which to store the notes, curves,
// and SysEx events.
//
if( SUCCEEDED( CoCreateInstance( CLSID_DirectMusicSeqTrack,
NULL, CLSCTX_INPROC, IID_IPersistStream,
(void**)&pIPSTrack )))
{
// Create a stream in which to place the events so we can
// give it to the SeqTrack.Load.
IStream* pEventStream;
if( S_OK == CreateStreamOnHGlobal( NULL, TRUE, &pEventStream ) )
{
// angusg: The implementation of memory IStream interface on
// CE can be inefficient if the stream memory isn't allocated
// before. It will call LocalRealloc on every IStream->Write
// for the amount that is written (in this case a small amount)
// this is incredible inefficient here as Realloc can be called
// thousands of times....
// The solution is to pre calculate the size of the stream and
// call ISteam->SetSize(), which calls LocalAlloc, to alloc the
// memory in one call.
// calculate the size of the stream storage
DWORD dwStreamStorageSize;
FullSeqEvent* pEvent;
// add the size of the chunk id's written below
dwStreamStorageSize = 5 * sizeof(DWORD);
// now count how many events need to be stored in the stream
for( pEvent = lstEvent; pEvent; pEvent = pEvent->pNext )
{
dwStreamStorageSize += sizeof(DMUS_IO_SEQ_ITEM);
}
ULARGE_INTEGER liSize;
liSize.QuadPart = dwStreamStorageSize;
// make the stream allocate the complete amount of memory
pEventStream->SetSize(liSize);
// Save the events into the stream
ULONG cb, cbWritten;
// Save the chunk id
DWORD dwTemp = DMUS_FOURCC_SEQ_TRACK;
pEventStream->Write( &dwTemp, sizeof(DWORD), NULL );
// Save the overall size. Count the number of events to determine.
dwSize = 0;
for( pEvent = lstEvent; pEvent; pEvent = pEvent->pNext )
{
dwSize++;
}
dwSize *= sizeof(DMUS_IO_SEQ_ITEM);
// add 8 for the subchunk
dwSize += 8;
pEventStream->Write( &dwSize, sizeof(DWORD), NULL );
// Save the subchunk id
dwTemp = DMUS_FOURCC_SEQ_LIST;
pEventStream->Write( &dwTemp, sizeof(DWORD), NULL );
// Subtract the previously added 8
dwSize -= 8;
// Save the size of the subchunk
pEventStream->Write( &dwSize, sizeof(DWORD), NULL );
// Save the structure size.
dwTemp = sizeof(DMUS_IO_SEQ_ITEM);
pEventStream->Write( &dwTemp, sizeof(DWORD), NULL );
// Save the events.
cb = sizeof(DMUS_IO_SEQ_ITEM); // doesn't have the next pointers
for( pEvent = lstEvent; pEvent; pEvent = pEvent->pNext )
{
if( dwLength < (DWORD)(pEvent->mtTime + pEvent->mtDuration) )
{
dwLength = pEvent->mtTime + pEvent->mtDuration;
}
pEventStream->Write( pEvent, cb, &cbWritten );
if( cb != cbWritten ) // error!
{
pEventStream->Release();
pEventStream = NULL;
hr = DMUS_E_CANNOTREAD;
break;
}
}
if( pEventStream ) // may be NULL
{
StreamSeek( pEventStream, 0, STREAM_SEEK_SET );
pIPSTrack->Load( pEventStream );
pEventStream->Release();
}
}
if( SUCCEEDED( pIPSTrack->QueryInterface( IID_IDirectMusicTrack,
(void**)&pDMTrack ) ) )
{
pSegment->InsertTrack( pDMTrack, 1 );
pDMTrack->Release();
}
pIPSTrack->Release();
}
// set the length of the segment. Set it to the measure boundary
// past the last note.
DWORD dwResolvedLength = gTimeSig.lTime;
if( 0 == gTimeSig.bBeat ) gTimeSig.bBeat = 4;
if( 0 == gTimeSig.bBeatsPerMeasure ) gTimeSig.bBeatsPerMeasure = 4;
if( 0 == gTimeSig.wGridsPerBeat ) gTimeSig.wGridsPerBeat = 4;
while( dwResolvedLength < dwLength )
{
dwResolvedLength += (((DMUS_PPQ * 4) / gTimeSig.bBeat) * gTimeSig.bBeatsPerMeasure);
}
pSegment->SetLength( dwResolvedLength );
if( gpSysExStream )
{
// Create a SysEx Track in which to store the SysEx events
if( SUCCEEDED( CoCreateInstance( CLSID_DirectMusicSysExTrack,
NULL, CLSCTX_INPROC, IID_IPersistStream,
(void**)&pIPSTrack )))
{
// write overall length
StreamSeek( gpSysExStream, sizeof(DWORD), STREAM_SEEK_SET );
gpSysExStream->Write( &gdwSizeSysExStream, sizeof(DWORD), NULL );
// seek to beginning and persist to track
StreamSeek( gpSysExStream, 0, STREAM_SEEK_SET );
pIPSTrack->Load( gpSysExStream );
if( SUCCEEDED( pIPSTrack->QueryInterface( IID_IDirectMusicTrack,
(void**)&pDMTrack ) ) )
{
pSegment->InsertTrack( pDMTrack, 1 );
pDMTrack->Release();
}
pIPSTrack->Release();
}
}
}
else
{
hr = E_POINTER;
}
}
END:
List_Free( lstEvent );
List_Free( lstPatchEvent );
FullSeqEvent::CleanUp();
// release our hold on the streams
RELEASE( gpTempoStream );
RELEASE( gpSysExStream );
RELEASE( gpTimeSigStream );
gpTempoStream = NULL;
gpSysExStream = NULL;
gpTimeSigStream = NULL;
gdwSizeTimeSigStream = 0;
gdwSizeSysExStream = 0;
gdwSizeTempoStream = 0;
LeaveCriticalSection(&g_CritSec);
if (SUCCEEDED(hrGM) || hr != S_OK )
{
return hr;
}
else
{
return DMUS_S_PARTIALLOAD;
}
}
// Creates and returns (in rChord) a DMUS_CHORD_PARAM given the three input params.
// the new chord will have one subchord containing the root, third, fifth, and seventh
// of the key (as indicated by the sharps/flats and mode). Scale will be either
// major or minor, depending on the mode (mode is 0 if major, 1 if minor).
void CreateChordFromKey(char cSharpsFlats, BYTE bMode, DWORD dwTime, DMUS_CHORD_PARAM& rChord)
{
static DWORD dwMajorScale = 0xab5ab5; // 1010 1011 0101 1010 1011 0101
static DWORD dwMinorScale = 0x5ad5ad; // 0101 1010 1101 0101 1010 1101
static DWORD dwMajor7Chord = 0x891; // 1000 1001 0001
static DWORD dwMinor7Chord = 0x489; // 0100 1000 1001
BYTE bScaleRoot = 0;
switch (cSharpsFlats)
{
case 0: bScaleRoot = bMode ? 9 : 0; break;
case 1: bScaleRoot = bMode ? 4 : 7; break;
case 2: bScaleRoot = bMode ? 11 : 2; break;
case 3: bScaleRoot = bMode ? 6 : 9; break;
case 4: bScaleRoot = bMode ? 1 : 4; break;
case 5: bScaleRoot = bMode ? 8 : 11; break;
case 6: bScaleRoot = bMode ? 3 : 6; break;
case 7: bScaleRoot = bMode ? 10 : 1; break;
case -1: bScaleRoot = bMode ? 2 : 5; break;
case -2: bScaleRoot = bMode ? 7 : 10; break;
case -3: bScaleRoot = bMode ? 0 : 3; break;
case -4: bScaleRoot = bMode ? 5 : 8; break;
case -5: bScaleRoot = bMode ? 10 : 1; break;
case -6: bScaleRoot = bMode ? 3 : 6; break;
case -7: bScaleRoot = bMode ? 8 : 11; break;
}
if (bMode)
{
wcscpy(rChord.wszName, L"m7");
}
else
{
wcscpy(rChord.wszName, L"M7");
}
DMUS_IO_TIMESIGNATURE_ITEM timesig;
timesig.bBeat = gTimeSig.bBeat ? gTimeSig.bBeat : 4;
timesig.bBeatsPerMeasure = gTimeSig.bBeatsPerMeasure ? gTimeSig.bBeatsPerMeasure : 4;
timesig.wGridsPerBeat = gTimeSig.wGridsPerBeat ? gTimeSig.wGridsPerBeat : 4;
DWORD dwAbsBeat = dwTime / ((DMUS_PPQ * 4) / timesig.bBeat);
rChord.wMeasure = (WORD)(dwAbsBeat / timesig.bBeatsPerMeasure);
rChord.bBeat = (BYTE)(dwAbsBeat % timesig.bBeatsPerMeasure);
rChord.bSubChordCount = 1;
rChord.SubChordList[0].dwChordPattern = bMode ? dwMinor7Chord : dwMajor7Chord;
rChord.SubChordList[0].dwScalePattern = bMode ? dwMinorScale : dwMajorScale;
rChord.SubChordList[0].dwInversionPoints = 0xffffff; // inversions allowed everywhere
rChord.SubChordList[0].dwLevels = 0xffffffff; // supports all levels
rChord.SubChordList[0].bChordRoot = bScaleRoot;
rChord.SubChordList[0].bScaleRoot = bScaleRoot;
}