cmMidiFile.h/c : Completed cmMidiFileInsertTrackMsg() and added _cmMidiFile_t.msgVDirtyFl and associated processing.
This commit is contained in:
parent
198aa41b98
commit
49943bd43e
150
cmMidiFile.c
150
cmMidiFile.c
@ -40,12 +40,14 @@ typedef struct
|
||||
char* fn; // file name or NULL if this object did not originate from a file
|
||||
unsigned msgN; // count of msg's in msgV[]
|
||||
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
|
||||
} _cmMidiFile_t;
|
||||
|
||||
|
||||
cmMidiFileH_t cmMidiFileNullHandle = cmSTATIC_NULL_HANDLE;
|
||||
|
||||
const cmMidiTrackMsg_t** _cmMidiFileMsgArray( _cmMidiFile_t* p );
|
||||
|
||||
_cmMidiFile_t* _cmMidiFileHandleToPtr( cmMidiFileH_t h )
|
||||
{
|
||||
@ -435,9 +437,9 @@ void _cmMidiFileSetAccumulateTicks( _cmMidiFile_t* p )
|
||||
unsigned i;
|
||||
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)
|
||||
if((nextTrkMsg[i] = p->trkV[i].base) != NULL )
|
||||
if((nextTrkMsg[i] = p->trkV[i].base) != NULL )
|
||||
nextTrkMsg[i]->atick = nextTrkMsg[i]->dtick;
|
||||
|
||||
while(1)
|
||||
@ -474,21 +476,23 @@ void _cmMidiFileSetAccumulateTicks( _cmMidiFile_t* p )
|
||||
|
||||
void _cmMidiFileSetAbsoluteTime( _cmMidiFile_t* mfp )
|
||||
{
|
||||
double microsPerQN = 60000000/120; // default tempo;
|
||||
double microsPerTick = microsPerQN / mfp->ticksPerQN;
|
||||
unsigned long long amicro = 0;
|
||||
unsigned i;
|
||||
const cmMidiTrackMsg_t** msgV = _cmMidiFileMsgArray(mfp);
|
||||
double microsPerQN = 60000000/120; // default tempo;
|
||||
double microsPerTick = microsPerQN / mfp->ticksPerQN;
|
||||
unsigned long long amicro = 0;
|
||||
unsigned 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;
|
||||
|
||||
if( i > 0 )
|
||||
{
|
||||
// atick must have already been set and sorted
|
||||
assert( mp->atick >= mfp->msgV[i-1]->atick );
|
||||
dtick = mp->atick - mfp->msgV[i-1]->atick;
|
||||
assert( mp->atick >= msgV[i-1]->atick );
|
||||
dtick = mp->atick - msgV[i-1]->atick;
|
||||
}
|
||||
|
||||
amicro += microsPerTick * dtick;
|
||||
@ -524,9 +528,13 @@ cmMfRC_t _cmMidiFileClose( _cmMidiFile_t* mfp )
|
||||
|
||||
}
|
||||
|
||||
|
||||
void _cmMidiFileLinearize( _cmMidiFile_t* mfp )
|
||||
{
|
||||
unsigned trkIdx,i,j;
|
||||
|
||||
if( mfp->msgVDirtyFl == false )
|
||||
return;
|
||||
|
||||
// get the total trk msg count
|
||||
mfp->msgN = 0;
|
||||
@ -558,9 +566,23 @@ void _cmMidiFileLinearize( _cmMidiFile_t* mfp )
|
||||
|
||||
// set the amicro value in each msg
|
||||
_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 rc = kOkMfRC;
|
||||
@ -644,10 +666,10 @@ cmMfRC_t cmMidiFileOpen( cmCtx_t* ctx, cmMidiFileH_t* hp, const char* fn )
|
||||
p->fn = cmLhAllocZ(p->lhH,char,strlen(fn)+1);
|
||||
assert( p->fn != NULL );
|
||||
strcpy(p->fn,fn);
|
||||
|
||||
|
||||
p->msgVDirtyFl = true;
|
||||
_cmMidiFileLinearize(p);
|
||||
|
||||
|
||||
errLabel:
|
||||
|
||||
if( cmFileClose(&p->fh) != kOkFileRC )
|
||||
@ -1133,6 +1155,7 @@ unsigned cmMidiFileMsgCount( cmMidiFileH_t h )
|
||||
return mfp->msgN;
|
||||
}
|
||||
|
||||
|
||||
const cmMidiTrackMsg_t** cmMidiFileMsgArray( cmMidiFileH_t h )
|
||||
{
|
||||
_cmMidiFile_t* mfp;
|
||||
@ -1140,18 +1163,18 @@ const cmMidiTrackMsg_t** cmMidiFileMsgArray( cmMidiFileH_t h )
|
||||
if((mfp = _cmMidiFileHandleToPtr(h)) == NULL )
|
||||
return NULL;
|
||||
|
||||
// this cast is needed to eliminate an apparently needless 'incompatible type' warning
|
||||
return (const cmMidiTrackMsg_t**)mfp->msgV;
|
||||
return _cmMidiFileMsgArray(mfp);
|
||||
}
|
||||
|
||||
|
||||
cmMidiTrackMsg_t* _cmMidiFileUidToMsg( _cmMidiFile_t* mfp, unsigned uid )
|
||||
{
|
||||
unsigned i;
|
||||
const cmMidiTrackMsg_t** msgV = _cmMidiFileMsgArray(mfp);
|
||||
|
||||
for(i=0; i<mfp->msgN; ++i)
|
||||
if( mfp->msgV[i]->uid == uid )
|
||||
return mfp->msgV[i];
|
||||
if( msgV[i]->uid == uid )
|
||||
return (cmMidiTrackMsg_t*)msgV[i];
|
||||
|
||||
return NULL;
|
||||
}
|
||||
@ -1272,18 +1295,12 @@ cmMfRC_t cmMidiFileInsertMsg( cmMidiFileH_t h, unsigned uid, int dtick, cmMidiBy
|
||||
|
||||
trk->cnt += 1;
|
||||
|
||||
_cmMidiFileLinearize(mfp);
|
||||
mfp->msgVDirtyFl = true;
|
||||
|
||||
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 )
|
||||
{
|
||||
_cmMidiFile_t* p = _cmMidiFileHandleToPtr(h);
|
||||
@ -1311,13 +1328,14 @@ cmMfRC_t cmMidiFileInsertTrackMsg( cmMidiFileH_t h, unsigned trkIdx, const cmMi
|
||||
memcpy((void*)m->u.voidPtr,msg->u.voidPtr,msg->byteCnt);
|
||||
}
|
||||
|
||||
cmMidiTrackMsg_t* m0 = NULL;
|
||||
cmMidiTrackMsg_t* m1 = p->trkV[trkIdx].base;
|
||||
cmMidiTrackMsg_t* m0 = NULL; // msg before insertion
|
||||
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)
|
||||
{
|
||||
if( m1->atick > m->atick )
|
||||
{
|
||||
{
|
||||
if( m0 == NULL )
|
||||
p->trkV[trkIdx].base = m;
|
||||
else
|
||||
@ -1326,17 +1344,42 @@ cmMfRC_t cmMidiFileInsertTrackMsg( cmMidiFileH_t h, unsigned trkIdx, const cmMi
|
||||
m->link = m1;
|
||||
break;
|
||||
}
|
||||
|
||||
// the new track record is the last msg
|
||||
if( m1 == NULL )
|
||||
{
|
||||
m1 = p->trkV[trkIdx].last;
|
||||
m1->link = m;
|
||||
p->trkV[trkIdx].last = m;
|
||||
if( p->trkV[trkIdx].base == NULL )
|
||||
p->trkV[trkIdx].base = m;
|
||||
|
||||
m0 = m1;
|
||||
}
|
||||
|
||||
// if the new track record was not inserted then it is the last msg
|
||||
if( m1 == NULL )
|
||||
{
|
||||
assert(m0 == p->trkV[trkIdx].last);
|
||||
|
||||
// link in the new msg
|
||||
if( m0 != NULL )
|
||||
m0->link = m;
|
||||
|
||||
// the new msg always becomes the last msg
|
||||
p->trkV[trkIdx].last = m;
|
||||
|
||||
// if the new msg is the first msg inserted in this track
|
||||
if( p->trkV[trkIdx].base == NULL )
|
||||
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;
|
||||
|
||||
@ -1387,14 +1430,15 @@ unsigned cmMidiFileSeekUsecs( cmMidiFileH_t h, unsigned long long offsUSecs, un
|
||||
if( p->msgN == 0 )
|
||||
return cmInvalidIdx;
|
||||
|
||||
unsigned mi;
|
||||
double microsPerQN = 60000000.0/120.0;
|
||||
double microsPerTick = microsPerQN / p->ticksPerQN;
|
||||
double accUSecs = 0;
|
||||
|
||||
unsigned mi;
|
||||
double microsPerQN = 60000000.0/120.0;
|
||||
double microsPerTick = microsPerQN / p->ticksPerQN;
|
||||
double accUSecs = 0;
|
||||
const cmMidiTrackMsg_t** msgV = _cmMidiFileMsgArray(p);
|
||||
|
||||
for(mi=0; mi<p->msgN; ++mi)
|
||||
{
|
||||
const cmMidiTrackMsg_t* mp = p->msgV[mi];
|
||||
const cmMidiTrackMsg_t* mp = msgV[mi];
|
||||
|
||||
if( mp->amicro >= offsUSecs )
|
||||
break;
|
||||
@ -1414,12 +1458,14 @@ unsigned cmMidiFileSeekUsecs( cmMidiFileH_t h, unsigned long long offsUSecs, un
|
||||
|
||||
double cmMidiFileDurSecs( cmMidiFileH_t h )
|
||||
{
|
||||
_cmMidiFile_t* mfp = _cmMidiFileHandleToPtr(h);
|
||||
_cmMidiFile_t* mfp = _cmMidiFileHandleToPtr(h);
|
||||
|
||||
if( mfp->msgN == 0 )
|
||||
return 0;
|
||||
|
||||
const cmMidiTrackMsg_t** msgV = _cmMidiFileMsgArray(mfp);
|
||||
|
||||
return mfp->msgV[ mfp->msgN-1 ]->amicro / 1000000.0;
|
||||
return msgV[ mfp->msgN-1 ]->amicro / 1000000.0;
|
||||
}
|
||||
|
||||
typedef struct _cmMidiVoice_str
|
||||
@ -1471,7 +1517,9 @@ void cmMidiFileCalcNoteDurations( cmMidiFileH_t h )
|
||||
int sustGateV[ kMidiChCnt]; // true if the associated sustain pedal is down
|
||||
int sostGateV[ kMidiChCnt]; // true if the associated sostenuto pedal is down
|
||||
unsigned i,j;
|
||||
|
||||
|
||||
const cmMidiTrackMsg_t** msgV = _cmMidiFileMsgArray(p);
|
||||
|
||||
// initialize the state tracking variables
|
||||
for(i=0; i<kMidiChCnt; ++i)
|
||||
{
|
||||
@ -1492,10 +1540,10 @@ void cmMidiFileCalcNoteDurations( cmMidiFileH_t h )
|
||||
// for each midi event
|
||||
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
|
||||
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
|
||||
if( !cmMidiIsChStatus( m->status ) )
|
||||
@ -1656,12 +1704,14 @@ void cmMidiFileSetDelay( cmMidiFileH_t h, unsigned ticks )
|
||||
if((p = _cmMidiFileHandleToPtr(h)) == NULL )
|
||||
return;
|
||||
|
||||
const cmMidiTrackMsg_t** msgV = _cmMidiFileMsgArray(p);
|
||||
|
||||
if( p->msgN == 0 )
|
||||
return;
|
||||
|
||||
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
|
||||
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 )
|
||||
{
|
||||
const _cmMidiFile_t* p = _cmMidiFileHandleToPtr(h);
|
||||
_cmMidiFile_t* p = _cmMidiFileHandleToPtr(h);
|
||||
unsigned mi;
|
||||
|
||||
_cmMidiFilePrintHdr(p,rpt);
|
||||
|
||||
const cmMidiTrackMsg_t** msgV = _cmMidiFileMsgArray(p);
|
||||
|
||||
for(mi=0; mi<p->msgN; ++mi)
|
||||
{
|
||||
cmMidiTrackMsg_t* mp = p->msgV[mi];
|
||||
const cmMidiTrackMsg_t* mp = msgV[mi];
|
||||
|
||||
if( mp != NULL )
|
||||
_cmMidiFilePrintMsg(rpt,mp);
|
||||
|
25
cmMidiFile.h
25
cmMidiFile.h
@ -136,17 +136,20 @@ extern "C" {
|
||||
// Return midi file format id (0,1,2) or kInvalidId if 'h' is invalid.
|
||||
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 );
|
||||
|
||||
// The file name used in an earlier call to midiFileOpen() or NULL if this
|
||||
// midi file did not originate from an actual file.
|
||||
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 );
|
||||
|
||||
// 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 );
|
||||
|
||||
// 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.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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 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 );
|
||||
|
Loading…
Reference in New Issue
Block a user