Copyright (C) Kevin Larke 2009-2020

This file is part of libcm.

libcm is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

libcm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

See the GNU General Public License distributed with the libcm package or look here: .


cmMidiFile : MIDI file reader and writer.

MIDI file timing: Messages in the MIDI file are time tagged with a delta offset in ‘ticks’ from the previous message in the same track.

A ‘tick’ can be converted to microsends as follows:

microsecond per tick = micros per quarter note / ticks per quarter note

MpT = MpQN / TpQN

TpQN is given as a constant in the MIDI file header. MpQN is given as the value of the MIDI file tempo message.

See cmMidiFileSeekUSecs() for an example of converting ticks to milliseconds.

As part of the file reading process, the status byte of note-on messages with velocity=0 are is changed to a note-off message. See _cmMidiFileReadChannelMsg().


typedef cmHandle_t cmMidiFileH_t;
typedef unsigned   cmMfRC_t;

typedef struct
{
  cmMidiByte_t hr;
  cmMidiByte_t min;
  cmMidiByte_t sec;
  cmMidiByte_t frm;
  cmMidiByte_t sfr;
} cmMidiSmpte_t;

typedef struct
{
  cmMidiByte_t num;
  cmMidiByte_t den;
  cmMidiByte_t metro;
  cmMidiByte_t th2s;
} cmMidiTimeSig_t;

typedef struct
{
  cmMidiByte_t key;
  cmMidiByte_t scale;
} cmMidiKeySig_t;

struct cmMidiTrackMsg_str;

typedef struct
{
  cmMidiByte_t ch;
  cmMidiByte_t d0;
  cmMidiByte_t d1;
  unsigned     durMicros;   // note duration in microseconds (corrected for tempo changes)
  struct cmMidiTrackMsg_str* end;  // note-off or pedal-up message
} cmMidiChMsg_t;

enum
{
  kDropTrkMsgFl = 0x01
};

typedef struct cmMidiTrackMsg_str
{
  unsigned                   flags;    // see k???TrkMsgFl
  unsigned                   uid;      // uid's are unique among all msg's in the file
  unsigned                   dtick;    // delta ticks between events on this track (ticks between this event and the previous event on this track)
  unsigned long long         atick;    // global (all tracks interleaved) accumulated ticks
  unsigned long long         amicro;   // global (all tracks interleaved) accumulated microseconds adjusted for tempo changes
  cmMidiByte_t               status;   // ch msg's have the channel value removed (it is stored in u.chMsgPtr-&gtch)
  cmMidiByte_t               metaId;   
  unsigned short             trkIdx;   
  unsigned                   byteCnt;  // length of data pointed to by u.voidPtr (or any other pointer in the union)
  struct cmMidiTrackMsg_str* link;     // link to next record in this track
  
  union
  {
    cmMidiByte_t           bVal;
    unsigned               iVal;
    unsigned short         sVal;
    const char*            text;
    const void*            voidPtr;
    const cmMidiSmpte_t*   smptePtr;
    const cmMidiTimeSig_t* timeSigPtr;
    const cmMidiKeySig_t*  keySigPtr;
    const cmMidiChMsg_t*   chMsgPtr;
    const cmMidiByte_t*    sysExPtr;
  } u;
} cmMidiTrackMsg_t;

#define cmMidiFileIsNoteOn(m)         (cmMidiIsNoteOn((m)->status) && ((m)->u.chMsgPtr->d1>0))
#define cmMidiFileIsNoteOff(m)        (cmMidiIsNoteOff((m)->status,(m)->u.chMsgPtr->d1))

#define cmMidiFileIsPedalUp(m)        (cmMidiIsPedalUp(    (m)->status, (m)->u.chMsgPtr->d0, (m)->u.chMsgPtr->d1) )
#define cmMidiFileIsPedalDown(m)      (cmMidiIsPedalDown(  (m)->status, (m)->u.chMsgPtr->d0, (m)->u.chMsgPtr->d1) )

#define cmMidiFileIsSustainPedalUp(m)     (cmMidiIsSustainPedalUp(    (m)->status,(m)->u.chMsgPtr->d0,(m)->u.chMsgPtr->d1))
#define cmMidiFileIsSustainPedalDown(m)   (cmMidiIsSustainPedalDown(  (m)->status,(m)->u.chMsgPtr->d0,(m)->u.chMsgPtr->d1))

#define cmMidiFileIsSostenutoPedalUp(m)   (cmMidiIsSostenutoPedalUp(  (m)->status,(m)->u.chMsgPtr->d0,(m)->u.chMsgPtr->d1))
#define cmMidiFileIsSostenutoPedalDown(m) (cmMidiIsSostenutoPedalDown((m)->status,(m)->u.chMsgPtr->d0,(m)->u.chMsgPtr->d1))

enum
{
  kOkMfRC = cmOkRC,     // 0
  kFileFailMfRC,        // 1
  kNotAMidiFileMfRC,    // 2
  kMemAllocFailMfRC,    // 3
  kFileCorruptMfRC,     // 4
  kMissingEoxMfRC,      // 5 
  kUnknownMetaIdMfRC,   // 6
  kInvalidHandleMfRC,   // 7 
  kMissingNoteOffMfRC,  // 8
  kInvalidStatusMfRC,   // 9
  kSustainPedalMfRC,    // 10
  kSostenutoPedalMfRC,  // 11
  kLargeDeltaTickMfRC,  // 12 (a large delta tick value was filtered)
  kUidNotFoundMfRC,     // 13
  kUidNotANoteMsgMfRC,  // 14
  kInvalidTrkIndexMfRC, // 15
  kSvgFailMfRC,         // 16
  kMsgNotFoundMfRC,      // 17
  kEventTerminationMfRC  // 18
};

extern cmMidiFileH_t cmMidiFileNullHandle;

cmMfRC_t              cmMidiFileOpen( cmCtx_t* ctx, cmMidiFileH_t* h, const char* fn );
cmMfRC_t              cmMidiFileCreate( cmCtx_t* ctx, cmMidiFileH_t* hp, unsigned trkN, unsigned ticksPerQN );
cmMfRC_t              cmMidiFileClose( cmMidiFileH_t* hp );

cmMfRC_t              cmMidiFileWrite( cmMidiFileH_t h, const char* fn );

bool                  cmMidiFileIsValid( cmMidiFileH_t h );

// Returns track count or kInvalidCnt if 'h' is invalid.
unsigned              cmMidiFileTrackCount( cmMidiFileH_t h );

// 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.
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.
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.
cmMidiByte_t          cmMidiFileSmpteFormatId( cmMidiFileH_t h );

// Returns count of records in track 'trackIdx' or kInvalidCnt if 'h' is invalid.
unsigned              cmMidiFileTrackMsgCount( cmMidiFileH_t h, unsigned trackIdx );

// 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(). 
// Return kInvalidCnt if 'h' is invalid.
unsigned              cmMidiFileMsgCount( cmMidiFileH_t h );

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

// Set the velocity of a note-on/off msg identified by 'uid'.
cmMfRC_t             cmMidiFileSetVelocity( cmMidiFileH_t h, unsigned uid, cmMidiByte_t vel );


// Insert a MIDI message relative to the reference msg identified by 'uid'.
// 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 );

// Return a pointer to the first msg at or after 'usecsOffs' or kInvalidIdx if no
// msg exists after 'usecsOffs'.  Note that 'usecOffs' is an offset from the beginning
// of the file.
// On return *'msgUsecsPtr' is set to the actual time of the msg. 
// (which will be equal to or greater than 'usecsOffs').
unsigned              cmMidiFileSeekUsecs( cmMidiFileH_t h, unsigned long long usecsOffs, unsigned* msgUsecsPtr, unsigned* newMicrosPerTickPtr );

double                cmMidiFileDurSecs( cmMidiFileH_t h );

// Calculate Note Duration
enum { kWarningsMfFl=0x01, kDropReattacksMfFl=0x02 };
void                  cmMidiFileCalcNoteDurations( cmMidiFileH_t h, unsigned flags );

// Set the delay prior to the first non-zero msg.
void                  cmMidiFileSetDelay( cmMidiFileH_t h, unsigned ticks );

// This function packs a track msg into a single  consecutive 
// block of memory buf[ bufByteCnt ]. Call cmMidiFilePackTracMsgBufByteCount()
// to get the required buffer length for any given cmMidiTrackMsg_t instance.
cmMidiTrackMsg_t*     cmMidiFilePackTrackMsg( const cmMidiTrackMsg_t* m, void* buf, unsigned bufByteCnt );
unsigned              cmMidiFilePackTrackMsgBufByteCount( const cmMidiTrackMsg_t* m );

void                  cmMidiFilePrintMsgs( cmMidiFileH_t h, cmRpt_t* rpt );
void                  cmMidiFilePrintTrack( cmMidiFileH_t h, unsigned trkIdx, cmRpt_t* rpt );

typedef struct
{
  unsigned           uid;
  unsigned long long amicro;
  unsigned           density; 
} cmMidiFileDensity_t;

// Generate the note onset density measure for each note in the MIDI file.
// Delete the returned memory with a call to cmMemFree().
cmMidiFileDensity_t* cmMidiFileNoteDensity( cmMidiFileH_t h, unsigned* cntRef );

// Generate a piano-roll plot description file which can be displayed with cmXScore.m
cmMfRC_t             cmMidiFileGenPlotFile( cmCtx_t* ctx, const cmChar_t* midiFn, const cmChar_t* outFn );

cmMfRC_t             cmMidiFileGenSvgFile( cmCtx_t* ctx, const cmChar_t* midiFn, const cmChar_t* outSvgFn, const cmChar_t* cssFn, bool standAloneFl, bool panZoomFl );

// Generate a text file reportusing cmMIdiFilePrintMsgs()
cmMfRC_t             cmMidiFileReport(     cmCtx_t* ctx, const cmChar_t* midiFn, const cmChar_t* outTextFn );

void                  cmMidiFileTest( const char* fn, cmCtx_t* ctx );