2012-10-30 03:52:39 +00:00
# include "cmPrefix.h"
# include "cmGlobal.h"
# include "cmRpt.h"
# include "cmErr.h"
# include "cmCtx.h"
# include "cmFile.h"
# include "cmMem.h"
# include "cmMallocDebug.h"
# include "cmLinkedHeap.h"
2013-12-15 23:14:27 +00:00
# include "cmTime.h"
2012-10-30 03:52:39 +00:00
# include "cmMidi.h"
# include "cmMidiFile.h"
# ifdef cmBIG_ENDIAN
# define mfSwap16(v) (v)
# define mfSwap32(v) (v)
# else
# define mfSwap16(v) cmSwap16(v)
# define mfSwap32(v) cmSwap32(v)
# endif
typedef struct
{
unsigned cnt ; // count of track records
cmMidiTrackMsg_t * base ; // pointer to first track recd
cmMidiTrackMsg_t * last ; // pointer to last track recd
} _cmMidiTrack_t ;
typedef struct
{
cmErr_t err ; // this objects error object
cmLHeapH_t lhH ; // linked heap used for all dynamically alloc'd data space
2013-09-24 20:03:01 +00:00
cmFileH_t fh ; // cmFile handle (only used in fmMidiFileOpen() and cmMidiFileWrite())
2012-10-30 03:52:39 +00:00
unsigned short fmtId ; // midi file type id: 0,1,2
unsigned short ticksPerQN ; // ticks per quarter note or 0 if smpteFmtId is valid
cmMidiByte_t smpteFmtId ; // smpte format or 0 if ticksPerQN is valid
cmMidiByte_t smpteTicksPerFrame ; // smpte ticks per frame or 0 if ticksPerQN is valid
unsigned short trkN ; // track count
_cmMidiTrack_t * trkV ; // track vector
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
2013-09-26 06:43:59 +00:00
unsigned nextUid ; // next available msg uid
2012-10-30 03:52:39 +00:00
} _cmMidiFile_t ;
cmMidiFileH_t cmMidiFileNullHandle = cmSTATIC_NULL_HANDLE ;
_cmMidiFile_t * _cmMidiFileHandleToPtr ( cmMidiFileH_t h )
{
_cmMidiFile_t * p = ( _cmMidiFile_t * ) h . h ;
assert ( p ! = NULL ) ;
return p ;
}
void * _cmMidiFileMalloc ( _cmMidiFile_t * mfp , unsigned byteN )
{ return cmLHeapAllocZ ( mfp - > lhH , byteN ) ; }
cmMfRC_t _cmMidiFileRead8 ( _cmMidiFile_t * mfp , cmMidiByte_t * p )
{
if ( cmFileReadUChar ( mfp - > fh , p , 1 ) ! = kOkFileRC )
2013-09-24 20:03:01 +00:00
return cmErrMsg ( & mfp - > err , kFileFailMfRC , " MIDI byte read failed. " ) ;
2012-10-30 03:52:39 +00:00
return kOkMfRC ;
}
cmMfRC_t _cmMidiFileRead16 ( _cmMidiFile_t * mfp , unsigned short * p )
{
if ( cmFileReadUShort ( mfp - > fh , p , 1 ) ! = kOkFileRC )
2013-09-24 20:03:01 +00:00
return cmErrMsg ( & mfp - > err , kFileFailMfRC , " MIDI short read failed. " ) ;
2012-10-30 03:52:39 +00:00
* p = mfSwap16 ( * p ) ;
return kOkMfRC ;
}
cmMfRC_t _cmMidiFileRead24 ( _cmMidiFile_t * mfp , unsigned * p )
{
* p = 0 ;
int i = 0 ;
for ( ; i < 3 ; + + i )
{
unsigned char c ;
if ( cmFileReadUChar ( mfp - > fh , & c , 1 ) ! = kOkFileRC )
2013-09-24 20:03:01 +00:00
return cmErrMsg ( & mfp - > err , kFileFailMfRC , " MIDI 24 bit integer read failed. " ) ;
2012-10-30 03:52:39 +00:00
* p = ( * p < < 8 ) + c ;
}
//*p =mfSwap32(*p);
return kOkMfRC ;
}
cmMfRC_t _cmMidiFileRead32 ( _cmMidiFile_t * mfp , unsigned * p )
{
if ( cmFileReadUInt ( mfp - > fh , p , 1 ) ! = kOkFileRC )
2013-09-24 20:03:01 +00:00
return cmErrMsg ( & mfp - > err , kFileFailMfRC , " MIDI integer read failed. " ) ;
2012-10-30 03:52:39 +00:00
* p = mfSwap32 ( * p ) ;
return kOkMfRC ;
}
cmMfRC_t _cmMidiFileReadText ( _cmMidiFile_t * mfp , cmMidiTrackMsg_t * tmp , unsigned byteN )
{
if ( byteN = = 0 )
return kOkMfRC ;
char * t = ( char * ) _cmMidiFileMalloc ( mfp , byteN + 1 ) ;
t [ byteN ] = 0 ;
if ( cmFileReadChar ( mfp - > fh , t , byteN ) ! = kOkFileRC )
2013-09-24 20:03:01 +00:00
return cmErrMsg ( & mfp - > err , kFileFailMfRC , " MIDI read text failed. " ) ;
2012-10-30 03:52:39 +00:00
tmp - > u . text = t ;
tmp - > byteCnt = byteN ;
return kOkMfRC ;
}
cmMfRC_t _cmMidiFileReadRecd ( _cmMidiFile_t * mfp , cmMidiTrackMsg_t * tmp , unsigned byteN )
{
char * t = ( char * ) _cmMidiFileMalloc ( mfp , byteN ) ;
if ( cmFileReadChar ( mfp - > fh , t , byteN ) ! = kOkFileRC )
2013-09-24 20:03:01 +00:00
return cmErrMsg ( & mfp - > err , kFileFailMfRC , " MIDI read record failed. " ) ;
2012-10-30 03:52:39 +00:00
tmp - > byteCnt = byteN ;
tmp - > u . voidPtr = t ;
return kOkMfRC ;
}
cmMfRC_t _cmMidiFileReadVarLen ( _cmMidiFile_t * mfp , unsigned * p )
{
unsigned char c ;
if ( cmFileReadUChar ( mfp - > fh , & c , 1 ) ! = kOkFileRC )
2013-09-24 20:03:01 +00:00
return cmErrMsg ( & mfp - > err , kFileFailMfRC , " MIDI read variable length integer failed. " ) ;
2012-10-30 03:52:39 +00:00
if ( ! ( c & 0x80 ) )
* p = c ;
else
{
* p = c & 0x7f ;
do
{
if ( cmFileReadUChar ( mfp - > fh , & c , 1 ) ! = kOkFileRC )
2013-09-24 20:03:01 +00:00
return cmErrMsg ( & mfp - > err , kFileFailMfRC , " MIDI read variable length integer failed. " ) ;
2012-10-30 03:52:39 +00:00
* p = ( * p < < 7 ) + ( c & 0x7f ) ;
} while ( c & 0x80 ) ;
}
return kOkMfRC ;
}
cmMfRC_t _cmMidiFileAppendTrackMsg ( _cmMidiFile_t * mfp , unsigned short trkIdx , unsigned dtick , cmMidiByte_t status , cmMidiTrackMsg_t * * trkMsgPtrPtr )
{
cmMidiTrackMsg_t * tmp = ( cmMidiTrackMsg_t * ) _cmMidiFileMalloc ( mfp , sizeof ( cmMidiTrackMsg_t ) ) ;
// link new record onto track record chain
if ( mfp - > trkV [ trkIdx ] . base = = NULL )
mfp - > trkV [ trkIdx ] . base = tmp ;
else
mfp - > trkV [ trkIdx ] . last - > link = tmp ;
mfp - > trkV [ trkIdx ] . last = tmp ;
mfp - > trkV [ trkIdx ] . cnt + + ;
// set the generic track record fields
tmp - > dtick = dtick ;
tmp - > status = status ;
tmp - > metaId = kInvalidMetaMdId ;
tmp - > trkIdx = trkIdx ;
tmp - > byteCnt = 0 ;
* trkMsgPtrPtr = tmp ;
return kOkMfRC ;
}
cmMfRC_t _cmMidiFileReadSysEx ( _cmMidiFile_t * mfp , cmMidiTrackMsg_t * tmp , unsigned byteN )
{
cmMfRC_t rc = kOkMfRC ;
cmMidiByte_t b = 0 ;
if ( byteN = = cmInvalidCnt )
{
long offs ;
if ( cmFileTell ( mfp - > fh , & offs ) ! = kOkFileRC )
2013-09-24 20:03:01 +00:00
return cmErrMsg ( & mfp - > err , kFileFailMfRC , " MIDI File 'tell' failed. " ) ;
2012-10-30 03:52:39 +00:00
byteN = 0 ;
// get the length of the sys-ex msg
while ( ! cmFileEof ( mfp - > fh ) & & ( b ! = kSysComEoxMdId ) )
{
if ( ( rc = _cmMidiFileRead8 ( mfp , & b ) ) ! = kOkMfRC )
return rc ;
+ + byteN ;
}
// verify that the EOX byte was found
if ( b ! = kSysComEoxMdId )
2013-09-24 20:03:01 +00:00
return cmErrMsg ( & mfp - > err , kMissingEoxMfRC , " MIDI file missing 'end-of-sys-ex'. " ) ;
2012-10-30 03:52:39 +00:00
// rewind to the beginning of the msg
if ( cmFileSeek ( mfp - > fh , kBeginFileFl , offs ) ! = kOkFileRC )
2013-09-24 20:03:01 +00:00
return cmErrMsg ( & mfp - > err , kFileFailMfRC , " MIDI file seek failed on sys-ex read. " ) ;
2012-10-30 03:52:39 +00:00
}
// allocate memory to hold the sys-ex msg
cmMidiByte_t * mp = ( cmMidiByte_t * ) _cmMidiFileMalloc ( mfp , byteN ) ;
// read the sys-ex msg from the file into msg memory
if ( cmFileReadUChar ( mfp - > fh , mp , byteN ) ! = kOkFileRC )
2013-09-24 20:03:01 +00:00
return cmErrMsg ( & mfp - > err , kFileFailMfRC , " MIDI sys-ex read failed. " ) ;
2012-10-30 03:52:39 +00:00
tmp - > byteCnt = byteN ;
tmp - > u . sysExPtr = mp ;
return rc ;
}
cmMfRC_t _cmMidiFileReadChannelMsg ( _cmMidiFile_t * mfp , cmMidiByte_t * rsPtr , cmMidiByte_t status , cmMidiTrackMsg_t * tmp )
{
cmMfRC_t rc = kOkMfRC ;
cmMidiChMsg_t * p = ( cmMidiChMsg_t * ) _cmMidiFileMalloc ( mfp , sizeof ( cmMidiChMsg_t ) ) ;
2013-09-24 20:03:01 +00:00
unsigned useRsFl = status < = 0x7f ;
cmMidiByte_t statusCh = useRsFl ? * rsPtr : status ;
2012-10-30 03:52:39 +00:00
if ( useRsFl )
p - > d0 = status ;
else
* rsPtr = status ;
tmp - > byteCnt = sizeof ( cmMidiChMsg_t ) ;
tmp - > status = statusCh & 0xf0 ;
p - > ch = statusCh & 0x0f ;
2015-11-20 00:08:39 +00:00
p - > durMicros = 0 ;
2012-10-30 03:52:39 +00:00
unsigned byteN = cmMidiStatusToByteCount ( tmp - > status ) ;
if ( byteN = = kInvalidMidiByte | | byteN > 2 )
return cmErrMsg ( & mfp - > err , kInvalidStatusMfRC , " Invalid status:0x%x %i. " , tmp - > status , tmp - > status ) ;
unsigned i ;
for ( i = useRsFl ; i < byteN ; + + i )
{
cmMidiByte_t * b = i = = 0 ? & p - > d0 : & p - > d1 ;
if ( ( rc = _cmMidiFileRead8 ( mfp , b ) ) ! = kOkMfRC )
return rc ;
}
2012-11-15 04:03:30 +00:00
// convert note-on velocity=0 to note off
if ( tmp - > status = = kNoteOnMdId & & p - > d1 = = 0 )
tmp - > status = kNoteOffMdId ;
2012-10-30 03:52:39 +00:00
tmp - > u . chMsgPtr = p ;
return rc ;
}
cmMfRC_t _cmMidiFileReadMetaMsg ( _cmMidiFile_t * mfp , cmMidiTrackMsg_t * tmp )
{
cmMidiByte_t metaId ;
cmMfRC_t rc ;
unsigned byteN = 0 ;
if ( ( rc = _cmMidiFileRead8 ( mfp , & metaId ) ) ! = kOkMfRC )
return rc ;
if ( ( rc = _cmMidiFileReadVarLen ( mfp , & byteN ) ) ! = kOkMfRC )
return rc ;
//printf("mt: %i 0x%x n:%i\n",metaId,metaId,byteN);
switch ( metaId )
{
case kSeqNumbMdId : rc = _cmMidiFileRead16 ( mfp , & tmp - > u . sVal ) ; break ;
case kTextMdId : rc = _cmMidiFileReadText ( mfp , tmp , byteN ) ; break ;
case kCopyMdId : rc = _cmMidiFileReadText ( mfp , tmp , byteN ) ; break ;
case kTrkNameMdId : rc = _cmMidiFileReadText ( mfp , tmp , byteN ) ; break ;
case kInstrNameMdId : rc = _cmMidiFileReadText ( mfp , tmp , byteN ) ; break ;
case kLyricsMdId : rc = _cmMidiFileReadText ( mfp , tmp , byteN ) ; break ;
case kMarkerMdId : rc = _cmMidiFileReadText ( mfp , tmp , byteN ) ; break ;
case kCuePointMdId : rc = _cmMidiFileReadText ( mfp , tmp , byteN ) ; break ;
case kMidiChMdId : rc = _cmMidiFileRead8 ( mfp , & tmp - > u . bVal ) ; break ;
case kEndOfTrkMdId : break ;
case kTempoMdId : rc = _cmMidiFileRead24 ( mfp , & tmp - > u . iVal ) ; break ;
case kSmpteMdId : rc = _cmMidiFileReadRecd ( mfp , tmp , sizeof ( cmMidiSmpte_t ) ) ; break ;
case kTimeSigMdId : rc = _cmMidiFileReadRecd ( mfp , tmp , sizeof ( cmMidiTimeSig_t ) ) ; break ;
case kKeySigMdId : rc = _cmMidiFileReadRecd ( mfp , tmp , sizeof ( cmMidiKeySig_t ) ) ; break ;
case kSeqSpecMdId : rc = _cmMidiFileReadSysEx ( mfp , tmp , byteN ) ; break ;
default :
cmFileSeek ( mfp - > fh , kCurFileFl , byteN ) ;
2013-09-24 20:03:01 +00:00
rc = cmErrMsg ( & mfp - > err , kUnknownMetaIdMfRC , " Unknown meta status:0x%x %i. " , metaId , metaId ) ;
2012-10-30 03:52:39 +00:00
}
tmp - > metaId = metaId ;
return rc ;
}
cmMfRC_t _cmMidiFileReadTrack ( _cmMidiFile_t * mfp , unsigned short trkIdx )
{
cmMfRC_t rc = kOkMfRC ;
unsigned dticks = 0 ;
cmMidiByte_t status ;
cmMidiByte_t runstatus = 0 ;
bool contFl = true ;
while ( contFl & & ( rc = = kOkMfRC ) )
{
cmMidiTrackMsg_t * tmp = NULL ;
// read the tick count
if ( ( rc = _cmMidiFileReadVarLen ( mfp , & dticks ) ) ! = kOkMfRC )
return rc ;
// read the status byte
if ( ( rc = _cmMidiFileRead8 ( mfp , & status ) ) ! = kOkMfRC )
return rc ;
//printf("st:%i 0x%x\n",status,status);
// append a track msg
if ( ( rc = _cmMidiFileAppendTrackMsg ( mfp , trkIdx , dticks , status , & tmp ) ) ! = kOkMfRC )
return rc ;
// switch on status
switch ( status )
{
// handle sys-ex msg
case kSysExMdId :
rc = _cmMidiFileReadSysEx ( mfp , tmp , cmInvalidCnt ) ;
break ;
// handle meta msg
case kMetaStId :
rc = _cmMidiFileReadMetaMsg ( mfp , tmp ) ;
// ignore unknown meta messages
if ( rc = = kUnknownMetaIdMfRC )
rc = kOkMfRC ;
contFl = tmp - > metaId ! = kEndOfTrkMdId ;
break ;
default :
2012-11-27 07:03:35 +00:00
// handle channel msg
rc = _cmMidiFileReadChannelMsg ( mfp , & runstatus , status , tmp ) ;
2012-10-30 03:52:39 +00:00
}
}
return rc ;
}
cmMfRC_t _cmMidiFileReadHdr ( _cmMidiFile_t * mfp )
{
cmMfRC_t rc ;
unsigned fileId ;
unsigned chunkByteN ;
// read the file id
if ( ( rc = _cmMidiFileRead32 ( mfp , & fileId ) ) ! = kOkMfRC )
return rc ;
// verify the file id
if ( fileId ! = ' MThd ' )
2013-09-24 20:03:01 +00:00
return cmErrMsg ( & mfp - > err , kNotAMidiFileMfRC , " " ) ;
2012-10-30 03:52:39 +00:00
// read the file chunk byte count
if ( ( rc = _cmMidiFileRead32 ( mfp , & chunkByteN ) ) ! = kOkMfRC )
return rc ;
// read the format id
if ( ( rc = _cmMidiFileRead16 ( mfp , & mfp - > fmtId ) ) ! = kOkMfRC )
return rc ;
// read the track count
if ( ( rc = _cmMidiFileRead16 ( mfp , & mfp - > trkN ) ) ! = kOkMfRC )
return rc ;
// read the ticks per quarter note
if ( ( rc = _cmMidiFileRead16 ( mfp , & mfp - > ticksPerQN ) ) ! = kOkMfRC )
return rc ;
// if the division field was given in smpte
if ( mfp - > ticksPerQN & 0x8000 )
{
mfp - > smpteFmtId = ( mfp - > ticksPerQN & 0x7f00 ) > > 8 ;
mfp - > smpteTicksPerFrame = ( mfp - > ticksPerQN & 0xFF ) ;
mfp - > ticksPerQN = 0 ;
}
// allocate and zero the track array
if ( mfp - > trkN )
mfp - > trkV = _cmMidiFileMalloc ( mfp , sizeof ( _cmMidiTrack_t ) * mfp - > trkN ) ;
return rc ;
}
int _cmMidiFileSortFunc ( const void * p0 , const void * p1 )
{
2013-09-24 20:03:01 +00:00
if ( ( * ( cmMidiTrackMsg_t * * ) p0 ) - > atick = = ( * ( cmMidiTrackMsg_t * * ) p1 ) - > atick )
2012-10-30 03:52:39 +00:00
return 0 ;
2013-09-24 20:03:01 +00:00
return ( * ( cmMidiTrackMsg_t * * ) p0 ) - > atick < ( * ( cmMidiTrackMsg_t * * ) p1 ) - > atick ? - 1 : 1 ;
2012-10-30 03:52:39 +00:00
}
2016-04-13 19:43:20 +00:00
// Set the absolute accumulated ticks (atick) value of each track message.
// The absolute accumulated ticks gives a global time ordering for all
// messages in the file.
void _cmMidiFileSetAccumulateTicks ( _cmMidiFile_t * p )
{
cmMidiTrackMsg_t * nextTrkMsg [ p - > trkN ] ; // next msg in each track
unsigned atick = 0 ;
unsigned i ;
// iniitalize nextTrkTick[] and nextTrkMsg[].
for ( i = 0 ; i < p - > trkN ; + + i )
if ( ( nextTrkMsg [ i ] = p - > trkV [ i ] . base ) ! = NULL )
nextTrkMsg [ i ] - > atick = nextTrkMsg [ i ] - > dtick ;
while ( 1 )
{
unsigned k = cmInvalidIdx ;
// find the trk which has the next msg (min atick time)
for ( i = 0 ; i < p - > trkN ; + + i )
if ( nextTrkMsg [ i ] ! = NULL & & ( k = = cmInvalidIdx | | nextTrkMsg [ i ] - > atick < nextTrkMsg [ k ] - > atick ) )
k = i ;
// no next msg was found - we're done
if ( k = = cmInvalidIdx )
break ;
// store the current atick
atick = nextTrkMsg [ k ] - > atick ;
// advance the selected track to it's next message
nextTrkMsg [ k ] = nextTrkMsg [ k ] - > link ;
// set the selected tracks next atick time
if ( nextTrkMsg [ k ] ! = NULL )
nextTrkMsg [ k ] - > atick = atick + nextTrkMsg [ k ] - > dtick ;
}
}
void _cmMidiFileSetAbsoluteTime ( _cmMidiFile_t * mfp )
{
double microsPerQN = 60000000 / 120 ; // default tempo;
double amicro = 0 ;
double microsPerTick = microsPerQN / mfp - > ticksPerQN ;
double maxDMicro = 60000000 ;
bool fl = true ;
unsigned i ;
for ( i = 0 ; i < mfp - > msgN ; + + i )
{
cmMidiTrackMsg_t * mp = mfp - > msgV [ i ] ;
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 ;
}
// if this is the first msg with a dtick greater than zero
if ( fl & & mfp - > msgV [ i ] - > dtick > 0 )
{
fl = false ;
// if this mesg has a large offset
if ( microsPerTick * dtick > maxDMicro )
{
cmErrWarnMsg ( & mfp - > err , kLargeDeltaTickMfRC , " A message delta time of %f seconds was decreased to %f seconds. " , ( double ) microsPerTick * dtick / 1000000.0 , ( double ) maxDMicro / 1000000.0 ) ;
// change the dtick to 1 (so it will still be the first msg w/ a non-zero dtick)
mfp - > msgV [ i ] - > dtick = 1 ;
// dtick changed so the aticks need to be recalculated
_cmMidiFileSetAccumulateTicks ( mfp ) ;
// call this function recursively
return _cmMidiFileSetAbsoluteTime ( mfp ) ;
}
}
amicro + = microsPerTick * dtick ;
mp - > amicro = round ( amicro ) ;
// track tempo changes
if ( mp - > status = = kMetaStId & & mp - > metaId = = kTempoMdId )
microsPerTick = mp - > u . iVal / mfp - > ticksPerQN ;
}
}
2012-10-30 03:52:39 +00:00
cmMfRC_t _cmMidiFileClose ( _cmMidiFile_t * mfp )
{
cmMfRC_t rc = kOkMfRC ;
if ( mfp = = NULL )
return rc ;
cmMemPtrFree ( & mfp - > msgV ) ;
if ( cmFileIsValid ( mfp - > fh ) )
if ( cmFileClose ( & mfp - > fh ) ! = kOkFileRC )
2013-09-24 20:03:01 +00:00
rc = cmErrMsg ( & mfp - > err , kFileFailMfRC , " MIDI file close failed. " ) ;
2012-10-30 03:52:39 +00:00
if ( cmLHeapIsValid ( mfp - > lhH ) )
cmLHeapDestroy ( & mfp - > lhH ) ;
cmMemPtrFree ( & mfp ) ;
return rc ;
}
2016-02-17 22:14:43 +00:00
cmMfRC_t cmMidiFileOpen ( cmCtx_t * ctx , cmMidiFileH_t * hPtr , const char * fn )
2012-10-30 03:52:39 +00:00
{
2012-11-27 07:03:35 +00:00
cmMfRC_t rc = kOkMfRC ;
2012-10-30 03:52:39 +00:00
_cmMidiFile_t * mfp = NULL ;
unsigned short trkIdx = 0 ;
cmErr_t err ;
2016-04-13 19:43:20 +00:00
unsigned i , j ;
2012-10-30 03:52:39 +00:00
2012-11-27 07:03:35 +00:00
if ( cmMidiFileIsValid ( * hPtr ) )
if ( ( rc = _cmMidiFileClose ( _cmMidiFileHandleToPtr ( * hPtr ) ) ) ! = kOkMfRC )
return rc ;
2012-10-30 03:52:39 +00:00
cmErrSetup ( & err , & ctx - > rpt , " MIDI File " ) ;
// allocate the midi file object
if ( ( mfp = cmMemAllocZ ( _cmMidiFile_t , 1 ) ) = = NULL )
2013-09-24 20:03:01 +00:00
return rc = cmErrMsg ( & err , kMemAllocFailMfRC , " MIDI file memory allocation failed. " ) ;
2012-10-30 03:52:39 +00:00
cmErrClone ( & mfp - > err , & err ) ;
// allocate the linked heap
if ( cmLHeapIsValid ( mfp - > lhH = cmLHeapCreate ( 1024 , ctx ) ) = = false )
{
2013-09-24 20:03:01 +00:00
rc = cmErrMsg ( & err , kMemAllocFailMfRC , " MIDI heap allocation failed. " ) ;
2012-10-30 03:52:39 +00:00
goto errLabel ;
}
// open the file
if ( cmFileOpen ( & mfp - > fh , fn , kReadFileFl | kBinaryFileFl , mfp - > err . rpt ) ! = kOkFileRC )
{
2013-09-24 20:03:01 +00:00
rc = cmErrMsg ( & mfp - > err , kFileFailMfRC , " MIDI file open failed. " ) ;
2012-10-30 03:52:39 +00:00
goto errLabel ;
}
// read header and setup track array
if ( ( rc = _cmMidiFileReadHdr ( mfp ) ) ! = kOkMfRC )
goto errLabel ;
while ( ! cmFileEof ( mfp - > fh ) & & trkIdx < mfp - > trkN )
{
unsigned chkId = 0 , chkN = 0 ;
// read the chunk id
if ( ( rc = _cmMidiFileRead32 ( mfp , & chkId ) ) ! = kOkMfRC )
goto errLabel ;
// read the chunk size
if ( ( rc = _cmMidiFileRead32 ( mfp , & chkN ) ) ! = kOkMfRC )
goto errLabel ;
// if this is not a trk chunk then skip it
if ( chkId ! = ( unsigned ) ' MTrk ' )
{
//if( fseek( mfp->fp, chkN, SEEK_CUR) != 0 )
if ( cmFileSeek ( mfp - > fh , kCurFileFl , chkN ) ! = kOkFileRC )
{
2013-09-24 20:03:01 +00:00
rc = cmErrMsg ( & mfp - > err , kFileFailMfRC , " MIDI file seek failed. " ) ;
2012-10-30 03:52:39 +00:00
goto errLabel ;
}
}
else
{
if ( ( rc = _cmMidiFileReadTrack ( mfp , trkIdx ) ) ! = kOkMfRC )
goto errLabel ;
+ + trkIdx ;
}
}
// get the total trk msg count
mfp - > msgN = 0 ;
for ( trkIdx = 0 ; trkIdx < mfp - > trkN ; + + trkIdx )
mfp - > msgN + = mfp - > trkV [ trkIdx ] . cnt ;
// allocate the trk msg index vector: msgV[]
mfp - > msgV = cmMemAllocZ ( cmMidiTrackMsg_t * , mfp - > msgN ) ;
2013-09-26 06:43:59 +00:00
mfp - > nextUid = 0 ;
2015-11-20 00:08:39 +00:00
2016-04-13 19:43:20 +00:00
// store a pointer to every trk msg in msgV[] and set 'uid'
for ( i = 0 , j = 0 ; i < mfp - > trkN ; + + i )
{
cmMidiTrackMsg_t * m = mfp - > trkV [ i ] . base ;
for ( ; m ! = NULL ; m = m - > link )
{
assert ( j < mfp - > msgN ) ;
mfp - > msgV [ j + + ] = m ;
m - > uid = mfp - > nextUid + + ;
}
}
/*
2015-11-20 00:08:39 +00:00
double microsPerQN = 60000000 / 120 ; // default tempo;
double microsPerTick ;
2016-04-13 19:43:20 +00:00
double maxDMicro = 60.0 * 1000000.0 ; // max time between events (60 seconds)
2012-10-30 03:52:39 +00:00
unsigned i = 0 ;
for ( trkIdx = 0 ; trkIdx < mfp - > trkN ; + + trkIdx )
{
2016-04-13 19:43:20 +00:00
unsigned tick = 0 ;
2012-10-30 03:52:39 +00:00
cmMidiTrackMsg_t * tmp = mfp - > trkV [ trkIdx ] . base ;
2015-11-20 00:08:39 +00:00
microsPerTick = microsPerQN / mfp - > ticksPerQN ;
2016-04-07 23:02:47 +00:00
2012-10-30 03:52:39 +00:00
while ( tmp ! = NULL )
{
assert ( i < mfp - > msgN ) ;
2016-04-13 19:43:20 +00:00
// convert dtick to microseconds
unsigned dmicro = round ( tmp - > dtick * microsPerTick ) ;
if ( dmicro > maxDMicro )
{
tmp - > dtick = round ( maxDMicro / microsPerTick ) ;
cmErrWarnMsg ( & mfp - > err , kLargeDeltaTickMfRC , " A message delta time of %f seconds was decreased to %f seconds. " , ( double ) dmicro / 1000000.0 , ( double ) maxDMicro / 1000000.0 ) ;
}
tick + = tmp - > dtick ; // convert delta-ticks to absolute ticks
tmp - > atick = tick ;
tmp - > uid = mfp - > nextUid + + ; // assign the msg uid
tmp - > dmicro = dmicro ;
mfp - > msgV [ i ] = tmp ;
2015-11-20 00:08:39 +00:00
2016-04-13 19:43:20 +00:00
2015-11-20 00:08:39 +00:00
// track tempo changes
if ( tmp - > status = = kMetaStId & & tmp - > metaId = = kTempoMdId )
microsPerTick = tmp - > u . iVal / mfp - > ticksPerQN ;
2016-04-13 19:43:20 +00:00
2012-10-30 03:52:39 +00:00
tmp = tmp - > link ;
+ + i ;
}
}
2016-04-13 19:43:20 +00:00
*/
2012-10-30 03:52:39 +00:00
2016-04-13 19:43:20 +00:00
//
_cmMidiFileSetAccumulateTicks ( mfp ) ;
2013-09-26 06:43:59 +00:00
// sort msgV[] in ascending order on atick
2012-10-30 03:52:39 +00:00
qsort ( mfp - > msgV , mfp - > msgN , sizeof ( cmMidiTrackMsg_t * ) , _cmMidiFileSortFunc ) ;
2016-04-13 19:43:20 +00:00
_cmMidiFileSetAbsoluteTime ( mfp ) ;
/*
2015-11-20 00:08:39 +00:00
// set the amicro field of each midi message to the
// absolute time offset in microseconds
unsigned mi ;
2016-04-07 23:02:47 +00:00
double amicro = 0 ;
2015-11-20 00:08:39 +00:00
microsPerTick = microsPerQN / mfp - > ticksPerQN ;
for ( mi = 0 ; mi < mfp - > msgN ; + + mi )
{
cmMidiTrackMsg_t * mp = mfp - > msgV [ mi ] ;
// track tempo changes
if ( mp - > status = = kMetaStId & & mp - > metaId = = kTempoMdId )
microsPerTick = mp - > u . iVal / mfp - > ticksPerQN ;
unsigned dtick = 0 ;
if ( mi > 0 )
{
assert ( mp - > atick > = mfp - > msgV [ mi - 1 ] - > atick ) ;
dtick = mp - > atick - mfp - > msgV [ mi - 1 ] - > atick ;
}
2016-04-07 23:02:47 +00:00
amicro + = microsPerTick * dtick ;
mp - > amicro = round ( amicro ) ;
2015-11-20 00:08:39 +00:00
}
2016-04-13 19:43:20 +00:00
*/
2015-11-20 00:08:39 +00:00
2012-10-30 03:52:39 +00:00
//for(i=0; i<25; ++i)
// printf("%i 0x%x 0x%x\n",mfp->msgV[i]->tick,mfp->msgV[i]->status,mfp->msgV[i]->metaId);
mfp - > fn = _cmMidiFileMalloc ( mfp , strlen ( fn ) + 1 ) ;
assert ( mfp - > fn ! = NULL ) ;
strcpy ( mfp - > fn , fn ) ;
hPtr - > h = mfp ;
errLabel :
2012-11-27 07:03:35 +00:00
if ( cmFileClose ( & mfp - > fh ) ! = kOkFileRC )
2013-09-24 20:03:01 +00:00
rc = cmErrMsg ( & mfp - > err , kFileFailMfRC , " MIDI file close failed. " ) ;
2012-10-30 03:52:39 +00:00
2012-11-27 07:03:35 +00:00
if ( rc ! = kOkMfRC )
_cmMidiFileClose ( mfp ) ;
2012-10-30 03:52:39 +00:00
2012-11-27 07:03:35 +00:00
return rc ;
2012-10-30 03:52:39 +00:00
}
cmMfRC_t cmMidiFileClose ( cmMidiFileH_t * h )
{
cmMfRC_t rc ;
if ( cmMidiFileIsNull ( * h ) )
return kOkMfRC ;
if ( ( rc = _cmMidiFileClose ( _cmMidiFileHandleToPtr ( * h ) ) ) = = kOkMfRC )
return rc ;
h - > h = NULL ;
return rc ;
}
2013-09-24 20:03:01 +00:00
cmMfRC_t _cmMidiFileWrite8 ( _cmMidiFile_t * mfp , unsigned char v )
{
cmMfRC_t rc = kOkMfRC ;
if ( cmFileWriteUChar ( mfp - > fh , & v , 1 ) ! = kOkFileRC )
rc = cmErrMsg ( & mfp - > err , kFileFailMfRC , " MIDI file byte write failed. " ) ;
return rc ;
}
cmMfRC_t _cmMidiFileWrite16 ( _cmMidiFile_t * mfp , unsigned short v )
{
cmMfRC_t rc = kOkMfRC ;
v = mfSwap16 ( v ) ;
if ( cmFileWriteUShort ( mfp - > fh , & v , 1 ) ! = kOkFileRC )
rc = cmErrMsg ( & mfp - > err , kFileFailMfRC , " MIDI file short integer write failed. " ) ;
return rc ;
}
cmMfRC_t _cmMidiFileWrite24 ( _cmMidiFile_t * mfp , unsigned v )
{
cmMfRC_t rc = kOkMfRC ;
unsigned mask = 0xff0000 ;
int i ;
for ( i = 2 ; i > = 0 ; - - i )
{
unsigned char c = ( v & mask ) > > ( i * 8 ) ;
mask > > = 8 ;
if ( cmFileWriteUChar ( mfp - > fh , & c , 1 ) ! = kOkFileRC )
{
rc = cmErrMsg ( & mfp - > err , kFileFailMfRC , " MIDI file 24 bit integer write failed. " ) ;
goto errLabel ;
}
}
errLabel :
return rc ;
}
cmMfRC_t _cmMidiFileWrite32 ( _cmMidiFile_t * mfp , unsigned v )
{
cmMfRC_t rc = kOkMfRC ;
v = mfSwap32 ( v ) ;
if ( cmFileWriteUInt ( mfp - > fh , & v , 1 ) ! = kOkFileRC )
rc = cmErrMsg ( & mfp - > err , kFileFailMfRC , " MIDI file integer write failed. " ) ;
return rc ;
}
cmMfRC_t _cmMidiFileWriteRecd ( _cmMidiFile_t * mfp , const void * v , unsigned byteCnt )
{
cmMfRC_t rc = kOkMfRC ;
if ( cmFileWriteChar ( mfp - > fh , v , byteCnt ) ! = kOkFileRC )
rc = cmErrMsg ( & mfp - > err , kFileFailMfRC , " MIDI file write record failed. " ) ;
return rc ;
}
cmMfRC_t _cmMidiFileWriteVarLen ( _cmMidiFile_t * mfp , unsigned v )
{
cmMfRC_t rc = kOkMfRC ;
unsigned buf = v & 0x7f ;
while ( ( v > > = 7 ) > 0 )
{
buf < < = 8 ;
buf | = 0x80 ;
buf + = ( v & 0x7f ) ;
}
while ( 1 )
{
unsigned char c = ( unsigned char ) ( buf & 0xff ) ;
if ( cmFileWriteUChar ( mfp - > fh , & c , 1 ) ! = kOkFileRC )
{
rc = cmErrMsg ( & mfp - > err , kFileFailMfRC , " MIDI file variable length integer write failed. " ) ;
goto errLabel ;
}
if ( buf & 0x80 )
buf > > = 8 ;
else
break ;
}
errLabel :
return rc ;
}
cmMfRC_t _cmMidiFileWriteHdr ( _cmMidiFile_t * mfp )
{
cmMfRC_t rc ;
unsigned fileId = ' MThd ' ;
unsigned chunkByteN = 6 ;
// write the file id ('MThd')
if ( ( rc = _cmMidiFileWrite32 ( mfp , fileId ) ) ! = kOkMfRC )
return rc ;
// write the file chunk byte count (always 6)
if ( ( rc = _cmMidiFileWrite32 ( mfp , chunkByteN ) ) ! = kOkMfRC )
return rc ;
// write the MIDI file format id (0,1,2)
if ( ( rc = _cmMidiFileWrite16 ( mfp , mfp - > fmtId ) ) ! = kOkMfRC )
return rc ;
// write the track count
if ( ( rc = _cmMidiFileWrite16 ( mfp , mfp - > trkN ) ) ! = kOkMfRC )
return rc ;
unsigned short v = 0 ;
// if the ticks per quarter note field is valid ...
if ( mfp - > ticksPerQN )
v = mfp - > ticksPerQN ;
else
{
// ... otherwise the division field was given in smpte
v = mfp - > smpteFmtId < < 8 ;
v + = mfp - > smpteTicksPerFrame ;
}
if ( ( rc = _cmMidiFileWrite16 ( mfp , v ) ) ! = kOkMfRC )
return rc ;
return rc ;
}
cmMfRC_t _cmMidiFileWriteSysEx ( _cmMidiFile_t * mfp , cmMidiTrackMsg_t * tmp )
{
cmMfRC_t rc = kOkMfRC ;
if ( ( rc = _cmMidiFileWrite8 ( mfp , kSysExMdId ) ) ! = kOkMfRC )
goto errLabel ;
if ( cmFileWriteUChar ( mfp - > fh , tmp - > u . sysExPtr , tmp - > byteCnt ) ! = kOkFileRC )
rc = cmErrMsg ( & mfp - > err , kFileFailMfRC , " Sys-ex msg write failed. " ) ;
errLabel :
return rc ;
}
cmMfRC_t _cmMidiFileWriteChannelMsg ( _cmMidiFile_t * mfp , const cmMidiTrackMsg_t * tmp , cmMidiByte_t * runStatus )
{
cmMfRC_t rc = kOkMfRC ;
unsigned byteN = cmMidiStatusToByteCount ( tmp - > status ) ;
cmMidiByte_t status = tmp - > status + tmp - > u . chMsgPtr - > ch ;
if ( status ! = * runStatus )
{
* runStatus = status ;
if ( ( rc = _cmMidiFileWrite8 ( mfp , status ) ) ! = kOkMfRC )
goto errLabel ;
}
if ( byteN > = 1 )
if ( ( rc = _cmMidiFileWrite8 ( mfp , tmp - > u . chMsgPtr - > d0 ) ) ! = kOkMfRC )
goto errLabel ;
if ( byteN > = 2 )
if ( ( rc = _cmMidiFileWrite8 ( mfp , tmp - > u . chMsgPtr - > d1 ) ) ! = kOkMfRC )
goto errLabel ;
errLabel :
return rc ;
}
cmMfRC_t _cmMidiFileWriteMetaMsg ( _cmMidiFile_t * mfp , const cmMidiTrackMsg_t * tmp )
{
cmMfRC_t rc ;
if ( ( rc = _cmMidiFileWrite8 ( mfp , kMetaStId ) ) ! = kOkMfRC )
return rc ;
if ( ( rc = _cmMidiFileWrite8 ( mfp , tmp - > metaId ) ) ! = kOkMfRC )
return rc ;
switch ( tmp - > metaId )
{
case kSeqNumbMdId :
if ( ( rc = _cmMidiFileWrite8 ( mfp , sizeof ( tmp - > u . sVal ) ) ) = = kOkMfRC )
rc = _cmMidiFileWrite16 ( mfp , tmp - > u . sVal ) ;
break ;
case kTempoMdId :
if ( ( rc = _cmMidiFileWrite8 ( mfp , 3 ) ) = = kOkMfRC )
rc = _cmMidiFileWrite24 ( mfp , tmp - > u . iVal ) ;
break ;
case kSmpteMdId :
if ( ( rc = _cmMidiFileWrite8 ( mfp , sizeof ( cmMidiSmpte_t ) ) ) = = kOkMfRC )
rc = _cmMidiFileWriteRecd ( mfp , tmp - > u . smptePtr , sizeof ( cmMidiSmpte_t ) ) ;
break ;
case kTimeSigMdId :
if ( ( rc = _cmMidiFileWrite8 ( mfp , sizeof ( cmMidiTimeSig_t ) ) ) = = kOkMfRC )
rc = _cmMidiFileWriteRecd ( mfp , tmp - > u . timeSigPtr , sizeof ( cmMidiTimeSig_t ) ) ;
break ;
case kKeySigMdId :
if ( ( rc = _cmMidiFileWrite8 ( mfp , sizeof ( cmMidiKeySig_t ) ) ) = = kOkMfRC )
rc = _cmMidiFileWriteRecd ( mfp , tmp - > u . keySigPtr , sizeof ( cmMidiKeySig_t ) ) ;
break ;
case kSeqSpecMdId :
if ( ( rc = _cmMidiFileWriteVarLen ( mfp , sizeof ( tmp - > byteCnt ) ) ) = = kOkMfRC )
rc = _cmMidiFileWriteRecd ( mfp , tmp - > u . sysExPtr , tmp - > byteCnt ) ;
break ;
case kMidiChMdId :
if ( ( rc = _cmMidiFileWrite8 ( mfp , sizeof ( tmp - > u . bVal ) ) ) = = kOkMfRC )
rc = _cmMidiFileWrite8 ( mfp , tmp - > u . bVal ) ;
break ;
case kEndOfTrkMdId :
rc = _cmMidiFileWrite8 ( mfp , 0 ) ;
break ;
case kTextMdId :
case kCopyMdId :
case kTrkNameMdId :
case kInstrNameMdId :
case kLyricsMdId :
case kMarkerMdId :
case kCuePointMdId :
{
unsigned n = tmp - > u . text = = NULL ? 0 : strlen ( tmp - > u . text ) ;
if ( ( rc = _cmMidiFileWriteVarLen ( mfp , n ) ) = = kOkMfRC & & n > 0 )
rc = _cmMidiFileWriteRecd ( mfp , tmp - > u . text , n ) ;
}
break ;
default :
{
// ignore unknown meta messages
}
}
return rc ;
}
cmMfRC_t _cmMidiFileWriteTrack ( _cmMidiFile_t * mfp , unsigned trkIdx )
{
cmMfRC_t rc = kOkMfRC ;
cmMidiTrackMsg_t * tmp = mfp - > trkV [ trkIdx ] . base ;
cmMidiByte_t runStatus = 0 ;
for ( ; tmp ! = NULL ; tmp = tmp - > link )
{
// write the msg tick count
if ( ( rc = _cmMidiFileWriteVarLen ( mfp , tmp - > dtick ) ) ! = kOkMfRC )
return rc ;
// switch on status
switch ( tmp - > status )
{
// handle sys-ex msg
case kSysExMdId :
rc = _cmMidiFileWriteSysEx ( mfp , tmp ) ;
break ;
// handle meta msg
case kMetaStId :
rc = _cmMidiFileWriteMetaMsg ( mfp , tmp ) ;
break ;
default :
// handle channel msg
rc = _cmMidiFileWriteChannelMsg ( mfp , tmp , & runStatus ) ;
}
}
return rc ;
}
cmMfRC_t cmMidiFileWrite ( cmMidiFileH_t h , const char * fn )
{
cmMfRC_t rc = kOkMfRC ;
_cmMidiFile_t * mfp = _cmMidiFileHandleToPtr ( h ) ;
unsigned i ;
// create the output file
if ( cmFileOpen ( & mfp - > fh , fn , kWriteFileFl , mfp - > err . rpt ) ! = kOkFileRC )
return cmErrMsg ( & mfp - > err , kFileFailMfRC , " The MIDI file '%s' could not be created. " , cmStringNullGuard ( fn ) ) ;
// write the file header
if ( ( rc = _cmMidiFileWriteHdr ( mfp ) ) ! = kOkMfRC )
{
rc = cmErrMsg ( & mfp - > err , rc , " The file header write failed on the MIDI file '%s'. " , cmStringNullGuard ( fn ) ) ;
goto errLabel ;
}
for ( i = 0 ; i < mfp - > trkN ; + + i )
{
unsigned chkId = ' MTrk ' ;
long offs0 , offs1 ;
// write the track chunk id ('MTrk')
if ( ( rc = _cmMidiFileWrite32 ( mfp , chkId ) ) ! = kOkMfRC )
goto errLabel ;
cmFileTell ( mfp - > fh , & offs0 ) ;
// write the track chunk size as zero
if ( ( rc = _cmMidiFileWrite32 ( mfp , 0 ) ) ! = kOkMfRC )
goto errLabel ;
if ( ( rc = _cmMidiFileWriteTrack ( mfp , i ) ) ! = kOkMfRC )
goto errLabel ;
cmFileTell ( mfp - > fh , & offs1 ) ;
cmFileSeek ( mfp - > fh , kBeginFileFl , offs0 ) ;
_cmMidiFileWrite32 ( mfp , offs1 - offs0 - 4 ) ;
cmFileSeek ( mfp - > fh , kBeginFileFl , offs1 ) ;
}
errLabel :
cmFileClose ( & mfp - > fh ) ;
return rc ;
}
2012-11-27 07:03:35 +00:00
bool cmMidiFileIsValid ( cmMidiFileH_t h )
{ return ! cmMidiFileIsNull ( h ) ; }
2012-10-30 03:52:39 +00:00
unsigned cmMidiFileTrackCount ( cmMidiFileH_t h )
{
_cmMidiFile_t * mfp ;
if ( ( mfp = _cmMidiFileHandleToPtr ( h ) ) = = NULL )
return cmInvalidCnt ;
return mfp - > trkN ;
}
unsigned cmMidiFileType ( cmMidiFileH_t h )
{
_cmMidiFile_t * mfp ;
if ( ( mfp = _cmMidiFileHandleToPtr ( h ) ) = = NULL )
return cmInvalidId ;
return mfp - > fmtId ;
}
const char * cmMidiFileName ( cmMidiFileH_t h )
{
_cmMidiFile_t * mfp ;
if ( ( mfp = _cmMidiFileHandleToPtr ( h ) ) = = NULL )
return NULL ;
return mfp - > fn ;
}
unsigned cmMidiFileTicksPerQN ( cmMidiFileH_t h )
{
_cmMidiFile_t * mfp ;
if ( ( mfp = _cmMidiFileHandleToPtr ( h ) ) = = NULL )
return cmInvalidCnt ;
return mfp - > ticksPerQN ;
}
cmMidiByte_t cmMidiFileTicksPerSmpteFrame ( cmMidiFileH_t h )
{
_cmMidiFile_t * mfp ;
if ( ( mfp = _cmMidiFileHandleToPtr ( h ) ) = = NULL )
return kInvalidMidiByte ;
if ( mfp - > ticksPerQN ! = 0 )
return 0 ;
2012-11-27 07:03:35 +00:00
return mfp - > smpteTicksPerFrame ;
2012-10-30 03:52:39 +00:00
}
cmMidiByte_t cmMidiFileSmpteFormatId ( cmMidiFileH_t h )
{
_cmMidiFile_t * mfp ;
if ( ( mfp = _cmMidiFileHandleToPtr ( h ) ) = = NULL )
return kInvalidMidiByte ;
if ( mfp - > ticksPerQN ! = 0 )
return 0 ;
2012-11-27 07:03:35 +00:00
return mfp - > smpteFmtId ;
2012-10-30 03:52:39 +00:00
}
unsigned cmMidiFileTrackMsgCount ( cmMidiFileH_t h , unsigned trackIdx )
{
_cmMidiFile_t * mfp ;
if ( ( mfp = _cmMidiFileHandleToPtr ( h ) ) = = NULL )
return cmInvalidCnt ;
return mfp - > trkV [ trackIdx ] . cnt ;
}
const cmMidiTrackMsg_t * cmMidiFileTrackMsg ( cmMidiFileH_t h , unsigned trackIdx )
{
_cmMidiFile_t * mfp ;
if ( ( mfp = _cmMidiFileHandleToPtr ( h ) ) = = NULL )
return NULL ;
return mfp - > trkV [ trackIdx ] . base ;
}
unsigned cmMidiFileMsgCount ( cmMidiFileH_t h )
{
_cmMidiFile_t * mfp ;
if ( ( mfp = _cmMidiFileHandleToPtr ( h ) ) = = NULL )
return cmInvalidCnt ;
return mfp - > msgN ;
}
const cmMidiTrackMsg_t * * cmMidiFileMsgArray ( cmMidiFileH_t h )
{
_cmMidiFile_t * mfp ;
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 ;
}
unsigned cmMidiFileSeekUsecs ( cmMidiFileH_t h , unsigned offsUSecs , unsigned * msgUsecsPtr , unsigned * microsPerTickPtr )
{
_cmMidiFile_t * p ;
if ( ( p = _cmMidiFileHandleToPtr ( h ) ) = = NULL )
return cmInvalidIdx ;
if ( p - > msgN = = 0 )
return cmInvalidIdx ;
unsigned mi ;
2013-09-26 06:43:59 +00:00
double microsPerQN = 60000000.0 / 120.0 ;
double microsPerTick = microsPerQN / p - > ticksPerQN ;
double accUSecs = 0 ;
2012-10-30 03:52:39 +00:00
for ( mi = 0 ; mi < p - > msgN ; + + mi )
{
const cmMidiTrackMsg_t * mp = p - > msgV [ mi ] ;
2015-11-20 00:08:39 +00:00
/*
2012-10-30 03:52:39 +00:00
if ( mp - > status = = kMetaStId & & mp - > metaId = = kTempoMdId )
microsPerTick = mp - > u . iVal / p - > ticksPerQN ;
2015-11-20 00:08:39 +00:00
unsigned dtick = 0 ;
if ( mi > 0 )
{
assert ( mp - > atick > = p - > msgV [ mi - 1 ] - > atick )
dtick = mp - > atick - p - > msgV [ mi - 1 ] - > atick ;
}
accUSecs + = dtick * microsPerTick ;
2012-10-30 03:52:39 +00:00
if ( accUSecs > = offsUSecs )
break ;
2015-11-20 00:08:39 +00:00
*/
2012-10-30 03:52:39 +00:00
2015-11-20 00:08:39 +00:00
if ( mp - > amicro > = offsUSecs )
break ;
2012-10-30 03:52:39 +00:00
}
if ( mi = = p - > msgN )
return cmInvalidIdx ;
if ( msgUsecsPtr ! = NULL )
2013-09-26 06:43:59 +00:00
* msgUsecsPtr = round ( accUSecs - offsUSecs ) ;
2012-10-30 03:52:39 +00:00
if ( microsPerTickPtr ! = NULL )
2013-09-26 06:43:59 +00:00
* microsPerTickPtr = round ( microsPerTick ) ;
2012-10-30 03:52:39 +00:00
return mi ;
}
double cmMidiFileDurSecs ( cmMidiFileH_t h )
{
_cmMidiFile_t * mfp = _cmMidiFileHandleToPtr ( h ) ;
2015-11-20 00:08:39 +00:00
if ( mfp - > msgN = = 0 )
return 0 ;
2013-09-26 06:43:59 +00:00
2015-11-20 00:08:39 +00:00
return mfp - > msgV [ mfp - > msgN - 1 ] - > amicro / 1000000.0 ;
2012-11-18 01:32:21 +00:00
}
2012-10-30 03:52:39 +00:00
typedef struct _cmMidiVoice_str
{
const cmMidiTrackMsg_t * mp ;
2015-11-20 00:08:39 +00:00
unsigned durMicros ;
2012-10-30 03:52:39 +00:00
bool sustainFl ;
struct _cmMidiVoice_str * link ;
} _cmMidiVoice_t ;
2012-11-24 05:27:50 +00:00
2012-10-30 03:52:39 +00:00
void _cmMidFileCalcNoteDurationReleaseNote ( _cmMidiVoice_t * * listPtrPtr , _cmMidiVoice_t * pp , _cmMidiVoice_t * vp )
{
2012-11-24 05:27:50 +00:00
assert ( ( pp = = NULL & & vp = = * listPtrPtr ) | | pp - > link = = vp ) ;
2012-10-30 03:52:39 +00:00
// store the duration of the note into the track msg
// assoc'd with the note-on msg
cmMidiChMsg_t * cmp = ( cmMidiChMsg_t * ) vp - > mp - > u . chMsgPtr ; // cast away const
2015-11-20 00:08:39 +00:00
cmp - > durMicros = vp - > durMicros ;
2012-10-30 03:52:39 +00:00
2012-11-24 05:27:50 +00:00
_cmMidiVoice_t * np = vp - > link ;
// release the voice msg
cmMemFree ( vp ) ;
2012-10-30 03:52:39 +00:00
// unlink the active voice msg
if ( pp = = NULL )
2012-11-24 05:27:50 +00:00
* listPtrPtr = np ;
2012-10-30 03:52:39 +00:00
else
2012-11-24 05:27:50 +00:00
pp - > link = np ;
2012-10-30 03:52:39 +00:00
}
2014-03-17 17:03:37 +00:00
void _cmMidiFileCalcNoteDurationsAllocVoice ( _cmMidiVoice_t * * listPtrPtr , cmMidiTrackMsg_t * mp , bool sustainFl )
{
_cmMidiVoice_t * vp = cmMemAllocZ ( _cmMidiVoice_t , 1 ) ;
vp - > mp = mp ;
vp - > sustainFl = sustainFl ;
vp - > link = * listPtrPtr ;
* listPtrPtr = vp ;
}
2012-10-30 03:52:39 +00:00
void cmMidiFileCalcNoteDurations ( cmMidiFileH_t h )
{
_cmMidiFile_t * p ;
if ( ( p = _cmMidiFileHandleToPtr ( h ) ) = = NULL )
return ;
if ( p - > msgN = = 0 )
return ;
2015-02-24 23:39:38 +00:00
unsigned mi = cmInvalidId ;
_cmMidiVoice_t * list = NULL ; // list of active voices
_cmMidiVoice_t * vp = NULL ;
cmMidiTrackMsg_t * sustainPedalDownMsg = NULL ;
bool sustainFlagV [ kMidiChCnt ] ;
2012-10-30 03:52:39 +00:00
2012-11-24 05:27:50 +00:00
// clear the sustain pedal flag
2012-11-15 04:03:30 +00:00
for ( mi = 0 ; mi < kMidiChCnt ; + + mi )
sustainFlagV [ mi ] = false ;
2012-10-30 03:52:39 +00:00
for ( mi = 0 ; mi < p - > msgN ; + + mi )
{
2012-11-24 05:27:50 +00:00
cmMidiTrackMsg_t * mp = p - > msgV [ mi ] ;
2012-10-30 03:52:39 +00:00
2015-11-20 00:08:39 +00:00
unsigned d_amicro = 0 ;
if ( mi > 0 )
{
assert ( mp - > amicro > = p - > msgV [ mi - 1 ] - > amicro ) ;
d_amicro = mp - > amicro - p - > msgV [ mi - 1 ] - > amicro ;
}
2012-10-30 03:52:39 +00:00
// update the duration of the sounding notes
for ( vp = list ; vp ! = NULL ; vp = vp - > link )
2015-11-20 00:08:39 +00:00
vp - > durMicros + = d_amicro ;
2012-10-30 03:52:39 +00:00
2015-02-24 23:39:38 +00:00
// update the sustain pedal duration
if ( sustainPedalDownMsg ! = NULL )
2015-11-20 00:08:39 +00:00
( ( cmMidiChMsg_t * ) ( sustainPedalDownMsg - > u . chMsgPtr ) ) - > durMicros + = d_amicro ; // cast away const
2015-02-24 23:39:38 +00:00
2012-10-30 03:52:39 +00:00
//
// If this is sustain pedal msg
//
if ( mp - > status = = kCtlMdId & & mp - > u . chMsgPtr - > d0 = = kSustainCtlMdId )
{
2012-11-24 05:27:50 +00:00
unsigned chIdx = mp - > u . chMsgPtr - > ch ;
assert ( chIdx < kMidiChCnt ) ;
2012-10-30 03:52:39 +00:00
// set the state of the sustain pedal flags
2012-11-24 05:27:50 +00:00
sustainFlagV [ chIdx ] = mp - > u . chMsgPtr - > d1 > = 64 ;
2012-10-30 03:52:39 +00:00
2014-03-17 17:03:37 +00:00
// if the pedal went down ...
if ( sustainFlagV [ chIdx ] )
{
2015-02-24 23:39:38 +00:00
if ( sustainPedalDownMsg ! = NULL )
{
// TODO: the correct way to handle this is to maintain multiple sustain pedals
cmErrMsg ( & p - > err , kSustainPedalMfRC , " Sustain pedal down with no intervening sustain pedal up. " ) ;
}
else
{
sustainPedalDownMsg = mp ;
2015-11-20 00:08:39 +00:00
( ( cmMidiChMsg_t * ) ( sustainPedalDownMsg - > u . chMsgPtr ) ) - > durMicros = 0 ; // cast away const
2015-02-24 23:39:38 +00:00
}
2014-03-17 17:03:37 +00:00
_cmMidiFileCalcNoteDurationsAllocVoice ( & list , mp , true ) ;
}
else // ... if the pedal went up
2012-10-30 03:52:39 +00:00
{
2015-02-24 23:39:38 +00:00
sustainPedalDownMsg = NULL ;
2012-10-30 03:52:39 +00:00
// ... then release sustaining notes
_cmMidiVoice_t * pp = NULL ;
for ( vp = list ; vp ! = NULL ; )
{
_cmMidiVoice_t * np = vp - > link ;
2012-11-24 05:27:50 +00:00
if ( vp - > sustainFl & & ( vp - > mp - > u . chMsgPtr - > ch = = chIdx ) )
2012-10-30 03:52:39 +00:00
_cmMidFileCalcNoteDurationReleaseNote ( & list , pp , vp ) ;
else
pp = vp ;
vp = np ;
}
}
}
//
// if this is a note-on msg
//
if ( mp - > status = = kNoteOnMdId & & mp - > u . chMsgPtr - > d1 > 0 )
{
2014-03-17 17:03:37 +00:00
_cmMidiFileCalcNoteDurationsAllocVoice ( & list , mp , false ) ;
2012-10-30 03:52:39 +00:00
}
else
//
// if this is a note-off msg
//
if ( ( mp - > status = = kNoteOnMdId & & mp - > u . chMsgPtr - > d1 = = 0 ) | | ( mp - > status = = kNoteOffMdId ) )
{
_cmMidiVoice_t * pp = NULL ;
2012-11-24 05:27:50 +00:00
unsigned chIdx = mp - > u . chMsgPtr - > ch ;
assert ( chIdx < kMidiChCnt ) ;
2012-10-30 03:52:39 +00:00
// for each active voice
for ( vp = list ; vp ! = NULL ; vp = vp - > link )
{
// if this active voice ch/pitch matches the note-off msg ch pitch
2012-11-24 05:27:50 +00:00
if ( ( vp - > mp - > u . chMsgPtr - > d0 = = mp - > u . chMsgPtr - > d0 ) & & ( vp - > mp - > u . chMsgPtr - > ch = = chIdx ) )
2012-10-30 03:52:39 +00:00
{
2012-11-24 05:27:50 +00:00
// if the sustain pedal is down for this channel - then defer turning the note off
if ( sustainFlagV [ chIdx ] )
2012-10-30 03:52:39 +00:00
vp - > sustainFl = true ;
else
_cmMidFileCalcNoteDurationReleaseNote ( & list , pp , vp ) ;
break ;
}
pp = vp ;
2012-11-24 05:27:50 +00:00
} // end for
2012-10-30 03:52:39 +00:00
} // end if
} // end-for
// check for hung notes
_cmMidiVoice_t * np = NULL ;
vp = list ;
while ( vp ! = NULL )
{
np = vp - > link ;
2014-03-17 17:03:37 +00:00
if ( cmMidiIsNoteOn ( vp - > mp - > status ) = = false )
cmErrMsg ( & p - > err , kMissingNoteOffMfRC , " Missing note-off for note-on:%s " , cmMidiToSciPitch ( vp - > mp - > u . chMsgPtr - > d0 , NULL , 0 ) ) ;
2012-10-30 03:52:39 +00:00
cmMemFree ( vp ) ;
vp = np ;
}
}
void cmMidiFileSetDelay ( cmMidiFileH_t h , unsigned ticks )
{
_cmMidiFile_t * p ;
unsigned mi ;
if ( ( p = _cmMidiFileHandleToPtr ( h ) ) = = NULL )
return ;
if ( p - > msgN = = 0 )
return ;
for ( mi = 0 ; mi < p - > msgN ; + + mi )
{
cmMidiTrackMsg_t * mp = p - > msgV [ mi ] ;
// locate the first msg which has a non-zero delta tick
if ( mp - > dtick > 0 )
{
mp - > dtick = ticks ;
break ;
}
}
}
unsigned cmMidiFilePackTrackMsgBufByteCount ( const cmMidiTrackMsg_t * m )
{ return sizeof ( cmMidiTrackMsg_t ) + m - > byteCnt ; }
cmMidiTrackMsg_t * cmMidiFilePackTrackMsg ( const cmMidiTrackMsg_t * m , void * buf , unsigned bufByteCnt )
{
unsigned n = sizeof ( cmMidiTrackMsg_t ) + m - > byteCnt ;
if ( n < bufByteCnt )
{
assert ( 0 ) ;
return NULL ;
}
// copy the cmMidiTrackMsg_t into the buffer
memcpy ( buf , m , sizeof ( cmMidiTrackMsg_t ) ) ;
if ( m - > byteCnt > 0 )
{
// copy any linked data into the buffer
memcpy ( buf + sizeof ( cmMidiTrackMsg_t ) , m - > u . voidPtr , m - > byteCnt ) ;
// fixup the linked data ptr
cmMidiTrackMsg_t * mp = ( cmMidiTrackMsg_t * ) buf ;
mp - > u . voidPtr = buf + sizeof ( cmMidiTrackMsg_t ) ;
}
return ( cmMidiTrackMsg_t * ) buf ;
}
2015-11-18 23:56:18 +00:00
void _cmMidiFilePrintHdr ( const _cmMidiFile_t * mfp , cmRpt_t * rpt )
2012-10-30 03:52:39 +00:00
{
if ( mfp - > fn ! = NULL )
cmRptPrintf ( rpt , " %s " , mfp - > fn ) ;
cmRptPrintf ( rpt , " fmt:%i ticksPerQN:%i tracks:%i \n " , mfp - > fmtId , mfp - > ticksPerQN , mfp - > trkN ) ;
2016-04-07 23:02:47 +00:00
2016-04-13 19:43:20 +00:00
cmRptPrintf ( rpt , " UID dtick atick amicro type ch D0 D1 \n " ) ;
cmRptPrintf ( rpt , " ----- ---------- ---------- ---------- : ---- --- --- --- \n " ) ;
2016-04-07 23:02:47 +00:00
2015-11-18 23:56:18 +00:00
}
void _cmMidiFilePrintMsg ( cmRpt_t * rpt , const cmMidiTrackMsg_t * tmp )
{
2016-04-07 23:02:47 +00:00
cmRptPrintf ( rpt , " %5i %10u %10u %10u %10u : " ,
2016-03-10 22:49:54 +00:00
tmp - > uid ,
2016-02-25 00:08:49 +00:00
tmp - > dtick ,
tmp - > atick ,
tmp - > amicro ) ;
2015-11-18 23:56:18 +00:00
if ( tmp - > status = = kMetaStId )
2016-02-25 00:08:49 +00:00
{
cmRptPrintf ( rpt , " %s " , cmMidiMetaStatusToLabel ( tmp - > metaId ) ) ;
}
2015-11-18 23:56:18 +00:00
else
{
2016-02-25 00:08:49 +00:00
cmRptPrintf ( rpt , " %4s %3i %3i %3i " ,
cmMidiStatusToLabel ( tmp - > status ) ,
tmp - > u . chMsgPtr - > ch ,
tmp - > u . chMsgPtr - > d0 ,
tmp - > u . chMsgPtr - > d1 ) ;
2015-11-18 23:56:18 +00:00
}
2016-02-25 00:08:49 +00:00
if ( cmMidiIsChStatus ( tmp - > status ) & & cmMidiIsNoteOn ( tmp - > status ) & & ( tmp - > u . chMsgPtr - > d1 > 0 ) )
cmRptPrintf ( rpt , " %4s " , cmMidiToSciPitch ( tmp - > u . chMsgPtr - > d0 , NULL , 0 ) ) ;
2015-11-18 23:56:18 +00:00
cmRptPrintf ( rpt , " \n " ) ;
}
void cmMidiFilePrintMsgs ( cmMidiFileH_t h , cmRpt_t * rpt )
{
const _cmMidiFile_t * p = _cmMidiFileHandleToPtr ( h ) ;
unsigned mi ;
_cmMidiFilePrintHdr ( p , rpt ) ;
for ( mi = 0 ; mi < p - > msgN ; + + mi )
{
cmMidiTrackMsg_t * mp = p - > msgV [ mi ] ;
if ( mp ! = NULL )
_cmMidiFilePrintMsg ( rpt , mp ) ;
}
}
void cmMidiFilePrintTracks ( cmMidiFileH_t h , unsigned trkIdx , cmRpt_t * rpt )
{
const _cmMidiFile_t * mfp = _cmMidiFileHandleToPtr ( h ) ;
_cmMidiFilePrintHdr ( mfp , rpt ) ;
2012-10-30 03:52:39 +00:00
int i = trkIdx = = cmInvalidIdx ? 0 : trkIdx ;
int n = trkIdx = = cmInvalidIdx ? mfp - > trkN : trkIdx + 1 ;
for ( ; i < n ; + + i )
{
cmRptPrintf ( rpt , " Track:%i \n " , i ) ;
cmMidiTrackMsg_t * tmp = mfp - > trkV [ i ] . base ;
while ( tmp ! = NULL )
{
2015-11-18 23:56:18 +00:00
_cmMidiFilePrintMsg ( rpt , tmp ) ;
2012-10-30 03:52:39 +00:00
tmp = tmp - > link ;
}
}
}
bool cmMidiFileIsNull ( cmMidiFileH_t h )
{ return ( _cmMidiFile_t * ) h . h = = NULL ; }
void cmMidiFileTestPrint ( void * printDataPtr , const char * fmt , va_list vl )
{ vprintf ( fmt , vl ) ; }
2016-03-31 23:06:20 +00:00
cmMfRC_t cmMidiFileGenPlotFile ( cmCtx_t * ctx , const cmChar_t * midiFn , const cmChar_t * outFn )
{
cmMfRC_t rc = kOkMfRC ;
cmMidiFileH_t mfH = cmMidiFileNullHandle ;
cmFileH_t fH = cmFileNullHandle ;
unsigned i = 0 ;
const cmMidiTrackMsg_t * * m = NULL ;
unsigned mN = 0 ;
if ( ( rc = cmMidiFileOpen ( ctx , & mfH , midiFn ) ) ! = kOkMfRC )
return cmErrMsg ( & ctx - > err , rc , " The MIDI file object could not be opened from '%s'. " , cmStringNullGuard ( midiFn ) ) ;
_cmMidiFile_t * p = _cmMidiFileHandleToPtr ( mfH ) ;
if ( ( m = cmMidiFileMsgArray ( mfH ) ) = = NULL | | ( mN = cmMidiFileMsgCount ( mfH ) ) = = 0 )
{
rc = cmErrMsg ( & p - > err , kFileFailMfRC , " The MIDI file object appears to be empty. " ) ;
goto errLabel ;
}
cmMidiFileCalcNoteDurations ( mfH ) ;
if ( cmFileOpen ( & fH , outFn , kWriteFileFl , p - > err . rpt ) ! = kOkFileRC )
return cmErrMsg ( & p - > err , kFileFailMfRC , " Unable to create the file '%s'. " , cmStringNullGuard ( outFn ) ) ;
for ( i = 0 ; i < mN ; + + i )
if ( ( m [ i ] ! = NULL ) & & cmMidiIsChStatus ( m [ i ] - > status ) & & cmMidiIsNoteOn ( m [ i ] - > status ) & & ( m [ i ] - > u . chMsgPtr - > d1 > 0 ) )
cmFilePrintf ( fH , " n %f %f %i %s \n " , m [ i ] - > amicro / 1000000.0 , m [ i ] - > u . chMsgPtr - > durMicros / 1000000.0 , m [ i ] - > uid , cmMidiToSciPitch ( m [ i ] - > u . chMsgPtr - > d0 , NULL , 0 ) ) ;
errLabel :
cmMidiFileClose ( & mfH ) ;
cmFileClose ( & fH ) ;
return rc ;
}
2014-08-10 19:59:10 +00:00
void cmMidiFilePrintControlNumbers ( cmCtx_t * ctx , const char * fn )
{
cmMidiFileH_t h = cmMidiFileNullHandle ;
cmMfRC_t rc ;
2016-02-17 22:14:43 +00:00
if ( ( rc = cmMidiFileOpen ( ctx , & h , fn ) ) ! = kOkMfRC )
2014-08-10 19:59:10 +00:00
{
cmErrMsg ( & ctx - > err , rc , " MIDI file open failed on '%s'. " , fn ) ;
goto errLabel ;
}
const cmMidiTrackMsg_t * * mm ;
unsigned n = cmMidiFileMsgCount ( h ) ;
if ( ( mm = cmMidiFileMsgArray ( h ) ) ! = NULL )
{
unsigned j ;
for ( j = 0 ; j < n ; + + j )
{
const cmMidiTrackMsg_t * m = mm [ j ] ;
if ( m - > status = = kCtlMdId & & m - > u . chMsgPtr - > d0 = = 66 )
printf ( " %i %i \n " , m - > u . chMsgPtr - > d0 , m - > u . chMsgPtr - > d1 ) ;
}
}
errLabel :
cmMidiFileClose ( & h ) ;
}
2012-10-30 03:52:39 +00:00
void cmMidiFileTest ( const char * fn , cmCtx_t * ctx )
{
cmMfRC_t rc ;
2013-09-24 20:03:01 +00:00
cmMidiFileH_t h = cmMidiFileNullHandle ;
2012-10-30 03:52:39 +00:00
2016-02-17 22:14:43 +00:00
if ( ( rc = cmMidiFileOpen ( ctx , & h , fn ) ) ! = kOkMfRC )
2012-10-30 03:52:39 +00:00
{
printf ( " Error:%i Unable to open the cmMidi file: %s \n " , rc , fn ) ;
return ;
}
2016-04-07 23:02:47 +00:00
//cmMidiFileCalcNoteDurations( h );
2015-11-18 23:56:18 +00:00
if ( 1 )
{
//cmMidiFileTickToMicros( h );
2015-11-20 00:08:39 +00:00
//cmMidiFileTickToSamples(h,96000,false);
2015-11-18 23:56:18 +00:00
cmMidiFilePrintMsgs ( h , & ctx - > rpt ) ;
}
if ( 0 )
2013-09-24 20:03:01 +00:00
{
//cmMidiFilePrint(h,cmMidiFileTrackCount(h)-1,&ctx->rpt);
2014-08-10 19:59:10 +00:00
//cmMidiFilePrint(h,cmInvalidIdx,&ctx->rpt);
cmMidiFilePrintControlNumbers ( ctx , fn ) ;
2013-09-24 20:03:01 +00:00
}
if ( 0 )
{
printf ( " Tracks:%i \n " , cmMidiFileTrackCount ( h ) ) ;
unsigned i = 0 ;
for ( i = 0 ; i < cmMidiFileMsgCount ( h ) ; + + i )
{
cmMidiTrackMsg_t * tmp = ( cmMidiTrackMsg_t * ) cmMidiFileMsgArray ( h ) [ i ] ;
if ( tmp - > status = = kMetaStId & & tmp - > metaId = = kTempoMdId )
{
double bpm = 60000000.0 / tmp - > u . iVal ;
printf ( " Tempo:%i %f \n " , tmp - > u . iVal , bpm ) ;
tmp - > u . iVal = floor ( 60000000.0 / 69.0 ) ;
break ;
}
}
cmMidiFileWrite ( h , " /home/kevin/temp/test0.mid " ) ;
}
2012-10-30 03:52:39 +00:00
cmMidiFileClose ( & h ) ;
}