Microsoft-3D-Movie-Maker/kauai/SRC/MIDI.CPP
2022-05-03 16:31:19 -07:00

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);
}