You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1801 lines
62 KiB
1801 lines
62 KiB
// 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( ×ig, 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*)×ig);
|
|
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*)×ig);
|
|
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;
|
|
}
|
|
|