cmMidiFile.h/c : Completed cmMidiFileInsertTrackMsg() and added _cmMidiFile_t.msgVDirtyFl and associated processing.

This commit is contained in:
kevin 2016-07-27 19:03:42 -04:00
parent 198aa41b98
commit 49943bd43e
2 changed files with 121 additions and 54 deletions

View File

@ -40,12 +40,14 @@ typedef struct
char* fn; // file name or NULL if this object did not originate from a file char* fn; // file name or NULL if this object did not originate from a file
unsigned msgN; // count of msg's in msgV[] unsigned msgN; // count of msg's in msgV[]
cmMidiTrackMsg_t** msgV; // sorted msg list cmMidiTrackMsg_t** msgV; // sorted msg list
bool msgVDirtyFl; // msgV[] needs to be refreshed from trkV[] because new msg's were inserted.
unsigned nextUid; // next available msg uid unsigned nextUid; // next available msg uid
} _cmMidiFile_t; } _cmMidiFile_t;
cmMidiFileH_t cmMidiFileNullHandle = cmSTATIC_NULL_HANDLE; cmMidiFileH_t cmMidiFileNullHandle = cmSTATIC_NULL_HANDLE;
const cmMidiTrackMsg_t** _cmMidiFileMsgArray( _cmMidiFile_t* p );
_cmMidiFile_t* _cmMidiFileHandleToPtr( cmMidiFileH_t h ) _cmMidiFile_t* _cmMidiFileHandleToPtr( cmMidiFileH_t h )
{ {
@ -435,9 +437,9 @@ void _cmMidiFileSetAccumulateTicks( _cmMidiFile_t* p )
unsigned i; unsigned i;
bool fl = true; bool fl = true;
// iniitalize nextTrkTick[] and nextTrkMsg[]. // iniitalize nextTrkTick[] and nextTrkMsg[] to the first msg in each track
for(i=0; i<p->trkN; ++i) for(i=0; i<p->trkN; ++i)
if((nextTrkMsg[i] = p->trkV[i].base) != NULL ) if((nextTrkMsg[i] = p->trkV[i].base) != NULL )
nextTrkMsg[i]->atick = nextTrkMsg[i]->dtick; nextTrkMsg[i]->atick = nextTrkMsg[i]->dtick;
while(1) while(1)
@ -474,21 +476,23 @@ void _cmMidiFileSetAccumulateTicks( _cmMidiFile_t* p )
void _cmMidiFileSetAbsoluteTime( _cmMidiFile_t* mfp ) void _cmMidiFileSetAbsoluteTime( _cmMidiFile_t* mfp )
{ {
double microsPerQN = 60000000/120; // default tempo; const cmMidiTrackMsg_t** msgV = _cmMidiFileMsgArray(mfp);
double microsPerTick = microsPerQN / mfp->ticksPerQN; double microsPerQN = 60000000/120; // default tempo;
unsigned long long amicro = 0; double microsPerTick = microsPerQN / mfp->ticksPerQN;
unsigned i; unsigned long long amicro = 0;
unsigned i;
for(i=0; i<mfp->msgN; ++i) for(i=0; i<mfp->msgN; ++i)
{ {
cmMidiTrackMsg_t* mp = mfp->msgV[i]; cmMidiTrackMsg_t* mp = (cmMidiTrackMsg_t*)msgV[i]; // cast away const
unsigned dtick = 0; unsigned dtick = 0;
if( i > 0 ) if( i > 0 )
{ {
// atick must have already been set and sorted // atick must have already been set and sorted
assert( mp->atick >= mfp->msgV[i-1]->atick ); assert( mp->atick >= msgV[i-1]->atick );
dtick = mp->atick - mfp->msgV[i-1]->atick; dtick = mp->atick - msgV[i-1]->atick;
} }
amicro += microsPerTick * dtick; amicro += microsPerTick * dtick;
@ -524,10 +528,14 @@ cmMfRC_t _cmMidiFileClose( _cmMidiFile_t* mfp )
} }
void _cmMidiFileLinearize( _cmMidiFile_t* mfp ) void _cmMidiFileLinearize( _cmMidiFile_t* mfp )
{ {
unsigned trkIdx,i,j; unsigned trkIdx,i,j;
if( mfp->msgVDirtyFl == false )
return;
// get the total trk msg count // get the total trk msg count
mfp->msgN = 0; mfp->msgN = 0;
for(trkIdx=0; trkIdx<mfp->trkN; ++trkIdx) for(trkIdx=0; trkIdx<mfp->trkN; ++trkIdx)
@ -559,6 +567,20 @@ void _cmMidiFileLinearize( _cmMidiFile_t* mfp )
// set the amicro value in each msg // set the amicro value in each msg
_cmMidiFileSetAbsoluteTime(mfp); _cmMidiFileSetAbsoluteTime(mfp);
mfp->msgVDirtyFl = false;
}
// Note that p->msgV[] should always be accessed through this function
// to guarantee that the p->msgVDirtyFl is checked and msgV[] is updated
// in case msgV[] is out of sync (due to inserted msgs (see cmMidiFileInsertTrackMsg())
// with trkV[].
const cmMidiTrackMsg_t** _cmMidiFileMsgArray( _cmMidiFile_t* p )
{
_cmMidiFileLinearize(p);
// this cast is needed to eliminate an apparently needless 'incompatible type' warning
return (const cmMidiTrackMsg_t**)p->msgV;
} }
cmMfRC_t _cmMidiFileCreate( cmCtx_t* ctx, cmMidiFileH_t* hp ) cmMfRC_t _cmMidiFileCreate( cmCtx_t* ctx, cmMidiFileH_t* hp )
@ -645,9 +667,9 @@ cmMfRC_t cmMidiFileOpen( cmCtx_t* ctx, cmMidiFileH_t* hp, const char* fn )
assert( p->fn != NULL ); assert( p->fn != NULL );
strcpy(p->fn,fn); strcpy(p->fn,fn);
p->msgVDirtyFl = true;
_cmMidiFileLinearize(p); _cmMidiFileLinearize(p);
errLabel: errLabel:
if( cmFileClose(&p->fh) != kOkFileRC ) if( cmFileClose(&p->fh) != kOkFileRC )
@ -1133,6 +1155,7 @@ unsigned cmMidiFileMsgCount( cmMidiFileH_t h )
return mfp->msgN; return mfp->msgN;
} }
const cmMidiTrackMsg_t** cmMidiFileMsgArray( cmMidiFileH_t h ) const cmMidiTrackMsg_t** cmMidiFileMsgArray( cmMidiFileH_t h )
{ {
_cmMidiFile_t* mfp; _cmMidiFile_t* mfp;
@ -1140,18 +1163,18 @@ const cmMidiTrackMsg_t** cmMidiFileMsgArray( cmMidiFileH_t h )
if((mfp = _cmMidiFileHandleToPtr(h)) == NULL ) if((mfp = _cmMidiFileHandleToPtr(h)) == NULL )
return NULL; return NULL;
// this cast is needed to eliminate an apparently needless 'incompatible type' warning return _cmMidiFileMsgArray(mfp);
return (const cmMidiTrackMsg_t**)mfp->msgV;
} }
cmMidiTrackMsg_t* _cmMidiFileUidToMsg( _cmMidiFile_t* mfp, unsigned uid ) cmMidiTrackMsg_t* _cmMidiFileUidToMsg( _cmMidiFile_t* mfp, unsigned uid )
{ {
unsigned i; unsigned i;
const cmMidiTrackMsg_t** msgV = _cmMidiFileMsgArray(mfp);
for(i=0; i<mfp->msgN; ++i) for(i=0; i<mfp->msgN; ++i)
if( mfp->msgV[i]->uid == uid ) if( msgV[i]->uid == uid )
return mfp->msgV[i]; return (cmMidiTrackMsg_t*)msgV[i];
return NULL; return NULL;
} }
@ -1272,18 +1295,12 @@ cmMfRC_t cmMidiFileInsertMsg( cmMidiFileH_t h, unsigned uid, int dtick, cmMidiBy
trk->cnt += 1; trk->cnt += 1;
_cmMidiFileLinearize(mfp); mfp->msgVDirtyFl = true;
return kOkMfRC; return kOkMfRC;
} }
// Only set
// atick - used to position the msg in the track
// status - this field is always set (Note that channel information must stripped from the status byte and included in the channel msg data)
// metaId - this field is optional depending on the msg type
// byteCnt - used to allocate storage for the data element in 'cmMidiTrackMsg_t.u'
// u - the message data
cmMfRC_t cmMidiFileInsertTrackMsg( cmMidiFileH_t h, unsigned trkIdx, const cmMidiTrackMsg_t* msg ) cmMfRC_t cmMidiFileInsertTrackMsg( cmMidiFileH_t h, unsigned trkIdx, const cmMidiTrackMsg_t* msg )
{ {
_cmMidiFile_t* p = _cmMidiFileHandleToPtr(h); _cmMidiFile_t* p = _cmMidiFileHandleToPtr(h);
@ -1311,11 +1328,12 @@ cmMfRC_t cmMidiFileInsertTrackMsg( cmMidiFileH_t h, unsigned trkIdx, const cmMi
memcpy((void*)m->u.voidPtr,msg->u.voidPtr,msg->byteCnt); memcpy((void*)m->u.voidPtr,msg->u.voidPtr,msg->byteCnt);
} }
cmMidiTrackMsg_t* m0 = NULL; cmMidiTrackMsg_t* m0 = NULL; // msg before insertion
cmMidiTrackMsg_t* m1 = p->trkV[trkIdx].base; cmMidiTrackMsg_t* m1 = p->trkV[trkIdx].base; // msg after insertion
// locate the track record before and after the new msg // locate the track record before and after the new msg based on 'atick' value
for(; m1!=NULL; m1=m1->link) for(; m1!=NULL; m1=m1->link)
{
if( m1->atick > m->atick ) if( m1->atick > m->atick )
{ {
if( m0 == NULL ) if( m0 == NULL )
@ -1327,16 +1345,41 @@ cmMfRC_t cmMidiFileInsertTrackMsg( cmMidiFileH_t h, unsigned trkIdx, const cmMi
break; break;
} }
// the new track record is the last msg m0 = m1;
}
// if the new track record was not inserted then it is the last msg
if( m1 == NULL ) if( m1 == NULL )
{ {
m1 = p->trkV[trkIdx].last; assert(m0 == p->trkV[trkIdx].last);
m1->link = m;
// link in the new msg
if( m0 != NULL )
m0->link = m;
// the new msg always becomes the last msg
p->trkV[trkIdx].last = m; p->trkV[trkIdx].last = m;
// if the new msg is the first msg inserted in this track
if( p->trkV[trkIdx].base == NULL ) if( p->trkV[trkIdx].base == NULL )
p->trkV[trkIdx].base = m; p->trkV[trkIdx].base = m;
} }
// set the dtick field of the new msg
if( m0 != NULL )
{
assert( m->atick >= m0->atick );
m->dtick = m->atick - m0->atick;
}
// update the dtick field of the msg following the new msg
if( m1 != NULL )
{
assert( m1->atick >= m->atick );
m1->dtick = m1->atick - m->atick;
}
p->msgVDirtyFl = true;
return kOkMfRC; return kOkMfRC;
@ -1387,14 +1430,15 @@ unsigned cmMidiFileSeekUsecs( cmMidiFileH_t h, unsigned long long offsUSecs, un
if( p->msgN == 0 ) if( p->msgN == 0 )
return cmInvalidIdx; return cmInvalidIdx;
unsigned mi; unsigned mi;
double microsPerQN = 60000000.0/120.0; double microsPerQN = 60000000.0/120.0;
double microsPerTick = microsPerQN / p->ticksPerQN; double microsPerTick = microsPerQN / p->ticksPerQN;
double accUSecs = 0; double accUSecs = 0;
const cmMidiTrackMsg_t** msgV = _cmMidiFileMsgArray(p);
for(mi=0; mi<p->msgN; ++mi) for(mi=0; mi<p->msgN; ++mi)
{ {
const cmMidiTrackMsg_t* mp = p->msgV[mi]; const cmMidiTrackMsg_t* mp = msgV[mi];
if( mp->amicro >= offsUSecs ) if( mp->amicro >= offsUSecs )
break; break;
@ -1414,12 +1458,14 @@ unsigned cmMidiFileSeekUsecs( cmMidiFileH_t h, unsigned long long offsUSecs, un
double cmMidiFileDurSecs( cmMidiFileH_t h ) double cmMidiFileDurSecs( cmMidiFileH_t h )
{ {
_cmMidiFile_t* mfp = _cmMidiFileHandleToPtr(h); _cmMidiFile_t* mfp = _cmMidiFileHandleToPtr(h);
if( mfp->msgN == 0 ) if( mfp->msgN == 0 )
return 0; return 0;
return mfp->msgV[ mfp->msgN-1 ]->amicro / 1000000.0; const cmMidiTrackMsg_t** msgV = _cmMidiFileMsgArray(mfp);
return msgV[ mfp->msgN-1 ]->amicro / 1000000.0;
} }
typedef struct _cmMidiVoice_str typedef struct _cmMidiVoice_str
@ -1472,6 +1518,8 @@ void cmMidiFileCalcNoteDurations( cmMidiFileH_t h )
int sostGateV[ kMidiChCnt]; // true if the associated sostenuto pedal is down int sostGateV[ kMidiChCnt]; // true if the associated sostenuto pedal is down
unsigned i,j; unsigned i,j;
const cmMidiTrackMsg_t** msgV = _cmMidiFileMsgArray(p);
// initialize the state tracking variables // initialize the state tracking variables
for(i=0; i<kMidiChCnt; ++i) for(i=0; i<kMidiChCnt; ++i)
{ {
@ -1492,10 +1540,10 @@ void cmMidiFileCalcNoteDurations( cmMidiFileH_t h )
// for each midi event // for each midi event
for(mi=0; mi<p->msgN; ++mi) for(mi=0; mi<p->msgN; ++mi)
{ {
cmMidiTrackMsg_t* m = p->msgV[mi]; cmMidiTrackMsg_t* m = (cmMidiTrackMsg_t*)msgV[mi]; // cast away const
// verify that time is also incrementing // verify that time is also incrementing
assert( mi==0 || (mi>0 && m->amicro >= p->msgV[mi-1]->amicro) ); assert( mi==0 || (mi>0 && m->amicro >= msgV[mi-1]->amicro) );
// ignore all non-channel messages // ignore all non-channel messages
if( !cmMidiIsChStatus( m->status ) ) if( !cmMidiIsChStatus( m->status ) )
@ -1656,12 +1704,14 @@ void cmMidiFileSetDelay( cmMidiFileH_t h, unsigned ticks )
if((p = _cmMidiFileHandleToPtr(h)) == NULL ) if((p = _cmMidiFileHandleToPtr(h)) == NULL )
return; return;
const cmMidiTrackMsg_t** msgV = _cmMidiFileMsgArray(p);
if( p->msgN == 0 ) if( p->msgN == 0 )
return; return;
for(mi=0; mi<p->msgN; ++mi) for(mi=0; mi<p->msgN; ++mi)
{ {
cmMidiTrackMsg_t* mp = p->msgV[mi]; cmMidiTrackMsg_t* mp = (cmMidiTrackMsg_t*)msgV[mi]; // cast away const
// locate the first msg which has a non-zero delta tick // locate the first msg which has a non-zero delta tick
if( mp->dtick > 0 ) if( mp->dtick > 0 )
@ -1742,14 +1792,16 @@ void _cmMidiFilePrintMsg( cmRpt_t* rpt, const cmMidiTrackMsg_t* tmp )
void cmMidiFilePrintMsgs( cmMidiFileH_t h, cmRpt_t* rpt ) void cmMidiFilePrintMsgs( cmMidiFileH_t h, cmRpt_t* rpt )
{ {
const _cmMidiFile_t* p = _cmMidiFileHandleToPtr(h); _cmMidiFile_t* p = _cmMidiFileHandleToPtr(h);
unsigned mi; unsigned mi;
_cmMidiFilePrintHdr(p,rpt); _cmMidiFilePrintHdr(p,rpt);
const cmMidiTrackMsg_t** msgV = _cmMidiFileMsgArray(p);
for(mi=0; mi<p->msgN; ++mi) for(mi=0; mi<p->msgN; ++mi)
{ {
cmMidiTrackMsg_t* mp = p->msgV[mi]; const cmMidiTrackMsg_t* mp = msgV[mi];
if( mp != NULL ) if( mp != NULL )
_cmMidiFilePrintMsg(rpt,mp); _cmMidiFilePrintMsg(rpt,mp);

View File

@ -136,17 +136,20 @@ extern "C" {
// Return midi file format id (0,1,2) or kInvalidId if 'h' is invalid. // Return midi file format id (0,1,2) or kInvalidId if 'h' is invalid.
unsigned cmMidiFileType( cmMidiFileH_t h ); unsigned cmMidiFileType( cmMidiFileH_t h );
// Returns ticks per quarter note or kInvalidMidiByte if 'h' is invalid or 0 if file uses SMPTE ticks per frame time base. // Returns ticks per quarter note or kInvalidMidiByte if 'h' is
// invalid or 0 if file uses SMPTE ticks per frame time base.
unsigned cmMidiFileTicksPerQN( cmMidiFileH_t h ); unsigned cmMidiFileTicksPerQN( cmMidiFileH_t h );
// The file name used in an earlier call to midiFileOpen() or NULL if this // The file name used in an earlier call to midiFileOpen() or NULL if this
// midi file did not originate from an actual file. // midi file did not originate from an actual file.
const char* cmMidiFileName( cmMidiFileH_t h ); const char* cmMidiFileName( cmMidiFileH_t h );
// Returns SMPTE ticks per frame or kInvalidMidiByte if 'h' is invalid or 0 if file uses ticks per quarter note time base. // Returns SMPTE ticks per frame or kInvalidMidiByte if 'h' is
// invalid or 0 if file uses ticks per quarter note time base.
cmMidiByte_t cmMidiFileTicksPerSmpteFrame( cmMidiFileH_t h ); cmMidiByte_t cmMidiFileTicksPerSmpteFrame( cmMidiFileH_t h );
// Returns SMPTE format or kInvalidMidiByte if 'h' is invalid or 0 if file uses ticks per quarter note time base. // Returns SMPTE format or kInvalidMidiByte if 'h' is invalid or 0
// if file uses ticks per quarter note time base.
cmMidiByte_t cmMidiFileSmpteFormatId( cmMidiFileH_t h ); cmMidiByte_t cmMidiFileSmpteFormatId( cmMidiFileH_t h );
// Returns count of records in track 'trackIdx' or kInvalidCnt if 'h' is invalid. // Returns count of records in track 'trackIdx' or kInvalidCnt if 'h' is invalid.
@ -155,11 +158,13 @@ extern "C" {
// Returns base of record chain from track 'trackIdx' or NULL if 'h' is invalid. // Returns base of record chain from track 'trackIdx' or NULL if 'h' is invalid.
const cmMidiTrackMsg_t* cmMidiFileTrackMsg( cmMidiFileH_t h, unsigned trackIdx ); const cmMidiTrackMsg_t* cmMidiFileTrackMsg( cmMidiFileH_t h, unsigned trackIdx );
// Returns the total count of records in the midi file and the number in the array returned by cmMidiFileMsgArray(). // Returns the total count of records in the midi file and the
// number in the array returned by cmMidiFileMsgArray().
// Return kInvalidCnt if 'h' is invalid. // Return kInvalidCnt if 'h' is invalid.
unsigned cmMidiFileMsgCount( cmMidiFileH_t h ); unsigned cmMidiFileMsgCount( cmMidiFileH_t h );
// Returns a pointer to the base of an array of pointers to each record in the file sorted in ascending time order. // Returns a pointer to the base of an array of pointers to all records
// in the file sorted in ascending time order.
// Returns NULL if 'h' is invalid. // Returns NULL if 'h' is invalid.
const cmMidiTrackMsg_t** cmMidiFileMsgArray( cmMidiFileH_t h ); const cmMidiTrackMsg_t** cmMidiFileMsgArray( cmMidiFileH_t h );
@ -170,6 +175,16 @@ extern "C" {
// If dtick is positive/negative then the new msg is inserted after/before the reference msg. // If dtick is positive/negative then the new msg is inserted after/before the reference msg.
cmMfRC_t cmMidiFileInsertMsg( cmMidiFileH_t h, unsigned uid, int dtick, cmMidiByte_t ch, cmMidiByte_t status, cmMidiByte_t d0, cmMidiByte_t d1 ); cmMfRC_t cmMidiFileInsertMsg( cmMidiFileH_t h, unsigned uid, int dtick, cmMidiByte_t ch, cmMidiByte_t status, cmMidiByte_t d0, cmMidiByte_t d1 );
//
// Insert a new cmMidiTrackMsg_t into the MIDI file on the specified track.
//
// Only the following fields need be set in 'msg'.
// atick - used to position the msg in the track
// status - this field is always set (Note that channel information must stripped from the status byte and included in the channel msg data)
// metaId - this field is optional depending on the msg type
// byteCnt - used to allocate storage for the data element in 'cmMidiTrackMsg_t.u'
// u - the message data
//
cmMfRC_t cmMidiFileInsertTrackMsg( cmMidiFileH_t h, unsigned trkIdx, const cmMidiTrackMsg_t* msg ); cmMfRC_t cmMidiFileInsertTrackMsg( cmMidiFileH_t h, unsigned trkIdx, const cmMidiTrackMsg_t* msg );
cmMfRC_t cmMidiFileInsertTrackChMsg( cmMidiFileH_t h, unsigned trkIdx, unsigned atick, cmMidiByte_t status, cmMidiByte_t d0, cmMidiByte_t d1 ); cmMfRC_t cmMidiFileInsertTrackChMsg( cmMidiFileH_t h, unsigned trkIdx, unsigned atick, cmMidiByte_t status, cmMidiByte_t d0, cmMidiByte_t d1 );
cmMfRC_t cmMidFileInsertTrackTempoMsg( cmMidiFileH_t h, unsigned trkIdx, unsigned atick, unsigned bpm ); cmMfRC_t cmMidFileInsertTrackTempoMsg( cmMidiFileH_t h, unsigned trkIdx, unsigned atick, unsigned bpm );