mirror of
https://github.com/microsoft/Microsoft-3D-Movie-Maker.git
synced 2024-11-22 02:12:33 +01:00
712 lines
15 KiB
C++
712 lines
15 KiB
C++
/* Copyright (c) Microsoft Corporation.
|
|
Licensed under the MIT License. */
|
|
|
|
/***************************************************************************
|
|
Author: ShonK
|
|
Project: Kauai
|
|
Copyright (c) Microsoft Corporation
|
|
|
|
Midi stream and midi stream parser classes.
|
|
|
|
***************************************************************************/
|
|
#include "frame.h"
|
|
ASSERTNAME
|
|
|
|
|
|
RTCLASS(MSTP)
|
|
RTCLASS(MIDS)
|
|
|
|
|
|
/***************************************************************************
|
|
Constructor for a midi stream parser.
|
|
***************************************************************************/
|
|
MSTP::MSTP(void)
|
|
{
|
|
AssertBaseThis(0);
|
|
_pmids = pvNil;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Destructor for a midi stream parser.
|
|
***************************************************************************/
|
|
MSTP::~MSTP(void)
|
|
{
|
|
AssertThis(0);
|
|
|
|
if (pvNil != _pmids)
|
|
UnlockHq(_pmids->_hqrgb);
|
|
ReleasePpo(&_pmids);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Initialize this midi stream parser to the given midi stream and use the
|
|
given time as the start time.
|
|
***************************************************************************/
|
|
void MSTP::Init(PMIDS pmids, ulong tsStart, long lwTempo)
|
|
{
|
|
AssertThis(0);
|
|
AssertNilOrPo(pmids, 0);
|
|
|
|
if (_pmids != pmids)
|
|
{
|
|
if (pvNil != _pmids)
|
|
{
|
|
UnlockHq(_pmids->_hqrgb);
|
|
ReleasePpo(&_pmids);
|
|
}
|
|
_pmids = pmids;
|
|
if (pvNil == _pmids)
|
|
return;
|
|
_pmids->AddRef();
|
|
|
|
_prgb = (byte *)PvLockHq(_pmids->_hqrgb);
|
|
_pbLim = _prgb + CbOfHq(_pmids->_hqrgb);
|
|
}
|
|
else if (pvNil == _pmids)
|
|
return;
|
|
|
|
_pbCur = _prgb;
|
|
_tsCur = tsStart;
|
|
_lwTempo = lwTempo;
|
|
_bStatus = 0;
|
|
AssertThis(0);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Get the next midi event. If fAdvance is true, advance to the next event
|
|
after getting this one.
|
|
***************************************************************************/
|
|
bool MSTP::FGetEvent(PMIDEV pmidev, bool fAdvance)
|
|
{
|
|
AssertThis(0);
|
|
AssertNilOrVarMem(pmidev);
|
|
|
|
byte bT;
|
|
byte *pbCur;
|
|
MIDEV midev;
|
|
long cbT;
|
|
long ibSend;
|
|
ulong tsCur;
|
|
|
|
ClearPb(&midev, size(midev));
|
|
midev.lwTempo = _lwTempo;
|
|
if (pvNil == _pmids)
|
|
goto LFail;
|
|
|
|
tsCur = _tsCur;
|
|
for (pbCur = _pbCur; midev.cb == 0; )
|
|
{
|
|
// read the delta time
|
|
if (!_FReadVar(&pbCur, (long *)&midev.ts) || pbCur >= _pbLim)
|
|
goto LFail;
|
|
tsCur += midev.ts;
|
|
midev.ts = tsCur;
|
|
|
|
if ((bT = *pbCur) & 0x80)
|
|
{
|
|
// status byte
|
|
_bStatus = bT;
|
|
pbCur++;
|
|
}
|
|
|
|
// NOTE: we never return running status
|
|
midev.rgbSend[0] = _bStatus;
|
|
ibSend = 1;
|
|
|
|
switch (_bStatus & 0xF0)
|
|
{
|
|
default:
|
|
goto LFail;
|
|
|
|
case 0x80: // note off
|
|
case 0x90: // note on
|
|
case 0xA0: // key pressure
|
|
case 0xB0: // control change
|
|
case 0xE0: // pitch wheel
|
|
goto LTwoBytes;
|
|
|
|
case 0xC0: // program change
|
|
case 0xD0: // channel pressure
|
|
goto LOneByte;
|
|
|
|
case 0xF0:
|
|
switch (_bStatus & 0x0F)
|
|
{
|
|
default:
|
|
// unknown opcode
|
|
goto LFail;
|
|
|
|
case 0x02:
|
|
LTwoBytes:
|
|
// 2 bytes worth of parameters
|
|
if (pbCur + 2 > _pbLim)
|
|
goto LFail;
|
|
midev.rgbSend[ibSend++] = *pbCur++;
|
|
midev.rgbSend[ibSend++] = *pbCur++;
|
|
midev.cb = ibSend;
|
|
break;
|
|
|
|
case 0x01: // quarter frame
|
|
case 0x03: // song select
|
|
LOneByte:
|
|
// 1 byte worth of parameters
|
|
if (pbCur >= _pbLim)
|
|
goto LFail;
|
|
midev.rgbSend[ibSend++] = *pbCur++;
|
|
midev.cb = ibSend;
|
|
break;
|
|
|
|
case 0x06:
|
|
case 0x08:
|
|
case 0x0A:
|
|
case 0x0B:
|
|
case 0x0C:
|
|
case 0x0E:
|
|
// no parameters
|
|
_bStatus = 0; // can't use running status on this!
|
|
midev.cb = ibSend;
|
|
break;
|
|
|
|
case 0x00:
|
|
case 0x07:
|
|
// system exclusive messages - ignore them
|
|
// read the length
|
|
if (!_FReadVar(&pbCur, &cbT) ||
|
|
!FIn(cbT - 1, 0, _pbLim - pbCur))
|
|
{
|
|
goto LFail;
|
|
}
|
|
pbCur += cbT;
|
|
break;
|
|
|
|
case 0x0F:
|
|
// meta event
|
|
if (pbCur >= _pbLim)
|
|
goto LFail;
|
|
bT = *pbCur++;
|
|
if (!_FReadVar(&pbCur, &cbT) ||
|
|
!FIn(cbT, 0, _pbLim - pbCur + 1))
|
|
{
|
|
goto LFail;
|
|
}
|
|
|
|
switch (bT)
|
|
{
|
|
default:
|
|
pbCur += cbT;
|
|
break;
|
|
|
|
case 0x2F: // end of track
|
|
goto LFail;
|
|
|
|
case 0x51: // tempo change
|
|
if (cbT != 3)
|
|
goto LFail;
|
|
midev.lwTempo = LwFromBytes(0, pbCur[0], pbCur[1], pbCur[2]);
|
|
if (fAdvance)
|
|
_lwTempo = midev.lwTempo;
|
|
pbCur += 3;
|
|
goto LSend;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
LSend:
|
|
if (pvNil != pmidev)
|
|
*pmidev = midev;
|
|
if (fAdvance)
|
|
{
|
|
_pbCur = pbCur;
|
|
_tsCur = tsCur;
|
|
}
|
|
return fTrue;
|
|
|
|
LFail:
|
|
_pbCur = _pbLim;
|
|
TrashVar(pmidev);
|
|
return fFalse;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Read a variable length quantity.
|
|
***************************************************************************/
|
|
bool MSTP::_FReadVar(byte **ppbCur, long *plw)
|
|
{
|
|
AssertThis(0);
|
|
AssertVarMem(ppbCur);
|
|
|
|
byte bT;
|
|
|
|
*plw = 0;
|
|
do
|
|
{
|
|
if (*ppbCur >= _pbLim)
|
|
return fFalse;
|
|
bT = *(*ppbCur)++;
|
|
*plw = (*plw << 7) + (bT & 0x7F);
|
|
}
|
|
while (bT & 0x80);
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
/***************************************************************************
|
|
Assert the validity of a MSTP.
|
|
***************************************************************************/
|
|
void MSTP::AssertValid(ulong grf)
|
|
{
|
|
MSTP_PAR::AssertValid(0);
|
|
if (pvNil == _pmids)
|
|
return;
|
|
|
|
AssertPo(_pmids, 0);
|
|
Assert(_prgb == (byte *)QvFromHq(_pmids->_hqrgb), 0);
|
|
Assert(_pbLim == _prgb + CbOfHq(_pmids->_hqrgb), 0);
|
|
AssertIn(_pbCur - _prgb, 0, _pbLim - _prgb + 1);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Mark memory for the MSTP.
|
|
***************************************************************************/
|
|
void MSTP::MarkMem(void)
|
|
{
|
|
AssertValid(0);
|
|
MSTP_PAR::MarkMem();
|
|
MarkMemObj(_pmids);
|
|
}
|
|
#endif //DEBUG
|
|
|
|
|
|
/***************************************************************************
|
|
Constructor for a midi stream object.
|
|
***************************************************************************/
|
|
MIDS::MIDS(void)
|
|
{
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Destructor for a midi stream object.
|
|
***************************************************************************/
|
|
MIDS::~MIDS(void)
|
|
{
|
|
FreePhq(&_hqrgb);
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
/***************************************************************************
|
|
Assert the validity of a MIDS.
|
|
***************************************************************************/
|
|
void MIDS::AssertValid(ulong grf)
|
|
{
|
|
MIDS_PAR::AssertValid(0);
|
|
AssertHq(_hqrgb);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Mark memory for the MIDS.
|
|
***************************************************************************/
|
|
void MIDS::MarkMem(void)
|
|
{
|
|
AssertValid(0);
|
|
MIDS_PAR::MarkMem();
|
|
MarkHq(_hqrgb);
|
|
}
|
|
#endif //DEBUG
|
|
|
|
|
|
/***************************************************************************
|
|
A baco reader for a midi stream.
|
|
***************************************************************************/
|
|
bool MIDS::FReadMids(PCRF pcrf, CTG ctg, CNO cno, PBLCK pblck,
|
|
PBACO *ppbaco, long *pcb)
|
|
{
|
|
AssertPo(pcrf, 0);
|
|
AssertPo(pblck, fblckReadable);
|
|
AssertNilOrVarMem(ppbaco);
|
|
AssertVarMem(pcb);
|
|
PMIDS pmids;
|
|
|
|
*pcb = pblck->Cb(fTrue);
|
|
if (pvNil == ppbaco)
|
|
return fTrue;
|
|
|
|
if (!pblck->FUnpackData())
|
|
goto LFail;
|
|
*pcb = pblck->Cb();
|
|
|
|
if (pvNil == (pmids = PmidsRead(pblck)))
|
|
{
|
|
LFail:
|
|
TrashVar(ppbaco);
|
|
TrashVar(pcb);
|
|
return fFalse;
|
|
}
|
|
*ppbaco = pmids;
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Read a midi stream from the given block.
|
|
***************************************************************************/
|
|
PMIDS MIDS::PmidsRead(PBLCK pblck)
|
|
{
|
|
AssertPo(pblck, 0);
|
|
|
|
PMIDS pmids;
|
|
|
|
if (pvNil == (pmids = NewObj MIDS))
|
|
return pvNil;
|
|
|
|
if (!pblck->FUnpackData() || !pblck->FReadHq(&pmids->_hqrgb))
|
|
ReleasePpo(&pmids);
|
|
|
|
AssertNilOrPo(pmids, 0);
|
|
return pmids;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Read a native standard midi file and create a midi stream from it.
|
|
***************************************************************************/
|
|
PMIDS MIDS::PmidsReadNative(FNI *pfni)
|
|
{
|
|
AssertPo(pfni, ffniFile);
|
|
|
|
#pragma pack(1)
|
|
// Midi chunk header
|
|
struct MIDCHD
|
|
{
|
|
long lwSig;
|
|
long cb;
|
|
};
|
|
#define kbomMidchd 0xF0000000
|
|
|
|
// Midi file header chunk - should be first chunk
|
|
struct MIDHED
|
|
{
|
|
MIDCHD midchd;
|
|
short swFmt;
|
|
short ctrk;
|
|
short swDiv;
|
|
};
|
|
#define kbomMidhed 0xF5400000
|
|
#pragma pack()
|
|
|
|
struct MIDTR
|
|
{
|
|
PMSTP pmstp;
|
|
MIDEV midevCur;
|
|
};
|
|
|
|
FLO flo;
|
|
FP fp;
|
|
MIDHED midhed;
|
|
MIDCHD midchd;
|
|
MIDTR midtr;
|
|
bool fSmpte;
|
|
BSM bsm;
|
|
RAT ratTempo;
|
|
ulong tsTempo, tsRawTempo, tsLast, ts, dts;
|
|
byte rgbT[5];
|
|
long cbT;
|
|
bool fSeq;
|
|
long imidtr, imidtrMin;
|
|
ulong tsMin;
|
|
PMIDS pmids = pvNil;
|
|
PGL pglmidtr = pvNil;
|
|
|
|
// open the file and set up the source flo
|
|
if (pvNil == (flo.pfil = FIL::PfilOpen(pfni)))
|
|
return pvNil;
|
|
flo.fp = 0;
|
|
flo.cb = flo.pfil->FpMac();
|
|
|
|
// get the header chunk
|
|
if (flo.cb < size(MIDHED) || !flo.FReadRgb(&midhed, size(midhed), 0))
|
|
goto LFail;
|
|
|
|
// byte order is always big endian
|
|
#ifdef LITTLE_ENDIAN
|
|
SwapBytesBom(&midhed, kbomMidhed);
|
|
#endif //LITTLE_ENDIAN
|
|
|
|
// make sure it's a valid header chunk
|
|
if (midhed.midchd.lwSig != 'MThd' ||
|
|
!FIn(midhed.midchd.cb + size(midchd), size(midhed), flo.cb))
|
|
{
|
|
goto LFail;
|
|
}
|
|
|
|
// allocate the list of tracks to parse
|
|
if (pvNil == (pglmidtr = GL::PglNew(size(MIDTR))))
|
|
goto LFail;
|
|
|
|
// build the track list...
|
|
for (fp = midhed.midchd.cb + size(midchd); fp < flo.cb; )
|
|
{
|
|
// read the next midi chunk header
|
|
if (fp + size(midchd) > flo.cb ||
|
|
!flo.FReadRgb(&midchd, size(midchd), fp))
|
|
{
|
|
goto LFail;
|
|
}
|
|
fp += size(midchd);
|
|
|
|
#ifdef LITTLE_ENDIAN
|
|
SwapBytesBom(&midchd, kbomMidchd);
|
|
#endif //LITTLE_ENDIAN
|
|
|
|
// make sure the chunk length is valid
|
|
if (!FIn(midchd.cb, 0, flo.cb - fp + 1))
|
|
goto LFail;
|
|
|
|
// if it's not a track chunk ignore it
|
|
if (midchd.lwSig != 'MTrk' || midchd.cb == 0)
|
|
{
|
|
fp += midchd.cb;
|
|
continue;
|
|
}
|
|
|
|
// wrap a midi stream object around the track data
|
|
if (pvNil == (pmids = NewObj MIDS) ||
|
|
!flo.FReadHq(&pmids->_hqrgb, midchd.cb, fp))
|
|
{
|
|
goto LFail;
|
|
}
|
|
fp += midchd.cb;
|
|
|
|
// create the midi stream parser for this stream
|
|
if (pvNil == (midtr.pmstp = NewObj MSTP))
|
|
goto LFail;
|
|
midtr.pmstp->Init(pmids, 0, 500000 /* microseconds per beat */);
|
|
ReleasePpo(&pmids);
|
|
|
|
// get the first event - if there isn't one, just free
|
|
// the stream parser and continue
|
|
if (!midtr.pmstp->FGetEvent(&midtr.midevCur))
|
|
{
|
|
ReleasePpo(&midtr.pmstp);
|
|
continue;
|
|
}
|
|
|
|
// add the track to the list
|
|
if (!pglmidtr->FAdd(&midtr))
|
|
{
|
|
ReleasePpo(&midtr.pmstp);
|
|
goto LFail;
|
|
}
|
|
|
|
if (midhed.swFmt != 1 && midhed.swFmt != 2)
|
|
{
|
|
// we're only supposed to have one track and we have it,
|
|
// so don't bother looking for more.
|
|
break;
|
|
}
|
|
}
|
|
ReleasePpo(&flo.pfil);
|
|
|
|
// make sure we have at least one track
|
|
if (pglmidtr->IvMac() == 0)
|
|
goto LFail;
|
|
|
|
// set the amount to grow the bsm by
|
|
bsm.SetMinGrow(1024);
|
|
|
|
if (FPure(fSmpte = (midhed.swDiv < 0)))
|
|
{
|
|
// SMPTE time
|
|
long fps = (byte)(-BHigh(midhed.swDiv));
|
|
long ctickFrame = BLow(midhed.swDiv);
|
|
|
|
if (fps == 29)
|
|
fps = 30; // 29 is 30 drop frame - 30 is close enough for us
|
|
|
|
if (ctickFrame <= 0 || fps <= 0)
|
|
goto LFail;
|
|
ratTempo.Set(1000, LwMul(fps, ctickFrame));
|
|
}
|
|
else
|
|
{
|
|
// midhed.swDiv is the number of ticks per beat.
|
|
if (midhed.swDiv == 0)
|
|
goto LFail;
|
|
|
|
// assume 120 beats per minute - gives us 500 ms per beat
|
|
ratTempo.Set(500, midhed.swDiv);
|
|
}
|
|
|
|
// merge the tracks or play them in sequence (according to fSeq).
|
|
fSeq = pglmidtr->IvMac() == 1 || midhed.swFmt == 2;
|
|
|
|
tsTempo = tsRawTempo = 0;
|
|
tsLast = 0;
|
|
if (fSeq)
|
|
{
|
|
imidtrMin = 0;
|
|
pglmidtr->Get(0, &midtr);
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
if (!fSeq)
|
|
{
|
|
// find the track with the next event
|
|
imidtrMin = ivNil;
|
|
tsMin = kluMax;
|
|
for (imidtr = 0; imidtr < pglmidtr->IvMac(); imidtr++)
|
|
{
|
|
pglmidtr->Get(imidtr, &midtr);
|
|
if (midtr.midevCur.ts < tsMin)
|
|
{
|
|
tsMin = midtr.midevCur.ts;
|
|
imidtrMin = imidtr;
|
|
}
|
|
}
|
|
|
|
Assert(imidtrMin != ivNil, 0);
|
|
pglmidtr->Get(imidtrMin, &midtr);
|
|
}
|
|
|
|
// get the time of this event
|
|
ts = ratTempo.LwScale(midtr.midevCur.ts - tsRawTempo) + tsTempo;
|
|
|
|
if (midtr.midevCur.cb == 0)
|
|
{
|
|
// just a tempo change
|
|
if (!fSmpte)
|
|
{
|
|
RAT ratTempoNew(midtr.midevCur.lwTempo,
|
|
LwMul(1000, midhed.swDiv));
|
|
|
|
if (ratTempo != ratTempoNew)
|
|
{
|
|
ratTempo = ratTempoNew;
|
|
tsRawTempo = midtr.midevCur.ts;
|
|
tsTempo = ts;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// write the time out - in variable format
|
|
Assert(midtr.midevCur.cb > 0, 0);
|
|
dts = ts - tsLast;
|
|
cbT = _CbEncodeLu(dts, rgbT);
|
|
if (!bsm.FReplace(rgbT, cbT, bsm.IbMac(), 0))
|
|
goto LFail;
|
|
|
|
if (!bsm.FReplace(midtr.midevCur.rgbSend,
|
|
midtr.midevCur.cb, bsm.IbMac(), 0))
|
|
{
|
|
goto LFail;
|
|
}
|
|
}
|
|
tsLast = ts;
|
|
|
|
if (midtr.pmstp->FGetEvent(&midtr.midevCur))
|
|
{
|
|
if (!fSeq)
|
|
pglmidtr->Put(imidtrMin, &midtr);
|
|
}
|
|
else
|
|
{
|
|
// imidtrMin is empty
|
|
ReleasePpo(&midtr.pmstp);
|
|
pglmidtr->Delete(imidtrMin);
|
|
if (0 == pglmidtr->IvMac())
|
|
break;
|
|
if (fSeq)
|
|
{
|
|
tsTempo = tsLast;
|
|
tsRawTempo = 0;
|
|
pglmidtr->Get(0, &midtr);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pvNil != (pmids = NewObj MIDS) &&
|
|
FAllocHq(&pmids->_hqrgb, bsm.IbMac(), fmemNil, mprNormal))
|
|
{
|
|
bsm.FetchRgb(0, bsm.IbMac(), PvLockHq(pmids->_hqrgb));
|
|
UnlockHq(pmids->_hqrgb);
|
|
}
|
|
else
|
|
{
|
|
LFail:
|
|
ReleasePpo(&pmids);
|
|
}
|
|
|
|
// clean up stuff
|
|
ReleasePpo(&flo.pfil);
|
|
if (pvNil != pglmidtr)
|
|
{
|
|
while (pglmidtr->FPop(&midtr))
|
|
ReleasePpo(&midtr.pmstp);
|
|
ReleasePpo(&pglmidtr);
|
|
}
|
|
|
|
AssertNilOrPo(pmids, 0);
|
|
return pmids;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Static method to convert a long to its midi file variable length
|
|
equivalent.
|
|
***************************************************************************/
|
|
long MIDS::_CbEncodeLu(ulong lu, byte *prgb)
|
|
{
|
|
AssertNilOrVarMem(prgb);
|
|
|
|
long ib;
|
|
|
|
if (pvNil != prgb)
|
|
prgb[0] = (byte)(lu & 0x7F);
|
|
for (ib = 1; (lu >>= 7) > 0; ib++)
|
|
{
|
|
if (pvNil != prgb)
|
|
prgb[ib] = (byte)((lu & 0x7F) | 0x80);
|
|
}
|
|
if (pvNil != prgb)
|
|
ReversePb(prgb, ib);
|
|
return ib;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Write a midi stream to the given block.
|
|
***************************************************************************/
|
|
bool MIDS::FWrite(PBLCK pblck)
|
|
{
|
|
AssertThis(0);
|
|
AssertPo(pblck, 0);
|
|
|
|
return pblck->FWriteHq(_hqrgb, 0);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Return the length of this midi stream on file.
|
|
***************************************************************************/
|
|
long MIDS::CbOnFile(void)
|
|
{
|
|
AssertThis(0);
|
|
|
|
return CbOfHq(_hqrgb);
|
|
}
|
|
|
|
|