//| Copyright: (C) 2020-2024 Kevin Larke //| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file. #ifndef cwMidiFile_h #define cwMidiFile_h namespace cw { namespace midi { namespace file { // 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 seekUSecs() for an example of converting ticks to milliseconds. // // Notes: // 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 _readChannelMsg(). typedef struct { uint8_t hr; uint8_t min; uint8_t sec; uint8_t frm; uint8_t sfr; } smpte_t; typedef struct { uint8_t num; uint8_t den; uint8_t metro; uint8_t th2s; } timeSig_t; typedef struct { uint8_t key; uint8_t scale; } keySig_t; struct midiTrackMsg_str; typedef struct { uint8_t ch; uint8_t d0; uint8_t d1; unsigned durMicros; // note duration in microseconds (corrected for tempo changes) struct trackMsg_str* end; // note-off or pedal-up message } chMsg_t; enum { kDropTrkMsgFl = 0x01, kReleaseFl = 0x02, }; typedef struct trackMsg_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 uint8_t status; // ch msg's have the channel value removed (it is stored in u.chMsgPtr->ch) uint8_t metaId; // unsigned short trkIdx; // unsigned byteCnt; // length of data pointed to by u.voidPtr (or any other pointer in the union) struct trackMsg_str* link; // link to next record in this track union { uint8_t bVal; unsigned iVal; unsigned short sVal; const char* text; const void* voidPtr; const smpte_t* smptePtr; const timeSig_t* timeSigPtr; const keySig_t* keySigPtr; const chMsg_t* chMsgPtr; const uint8_t* sysExPtr; } u; } trackMsg_t; inline bool isNoteOn(const trackMsg_t* m) { return midi::isChStatus(m->status) ? midi::isNoteOn( m->status,m->u.chMsgPtr->d1) : false; } inline bool isNoteOff(const trackMsg_t* m) { return midi::isChStatus(m->status) ? midi::isNoteOff(m->status,m->u.chMsgPtr->d1) : false; } inline bool isPedalUp(const trackMsg_t* m) { return midi::isChStatus(m->status) ? midi::isPedalUp( m->status, m->u.chMsgPtr->d0, m->u.chMsgPtr->d1) : false; } inline bool isPedalDown(const trackMsg_t* m) { return midi::isChStatus(m->status) ? midi::isPedalDown( m->status, m->u.chMsgPtr->d0, m->u.chMsgPtr->d1) : false; } inline bool isSustainPedalUp(const trackMsg_t* m) { return midi::isChStatus(m->status) ? midi::isSustainPedalUp( m->status,m->u.chMsgPtr->d0,m->u.chMsgPtr->d1) : false; } inline bool isSustainPedalDown(const trackMsg_t* m) { return midi::isChStatus(m->status) ? midi::isSustainPedalDown( m->status,m->u.chMsgPtr->d0,m->u.chMsgPtr->d1) : false; } inline bool isSostenutoPedalUp(const trackMsg_t* m) { return midi::isChStatus(m->status) ? midi::isSostenutoPedalUp( m->status,m->u.chMsgPtr->d0,m->u.chMsgPtr->d1) : false; } inline bool isSostenutoPedalDown(const trackMsg_t* m) { return midi::isChStatus(m->status) ? midi::isSostenutoPedalDown(m->status,m->u.chMsgPtr->d0,m->u.chMsgPtr->d1) : false; } typedef handle handle_t; // Read a MIDI file. rc_t open( handle_t& hRef, const char* fn ); // Read from a CSV. // Columns: "uid","tpQN","bpm","dticks","ch","status","d0","d1" // tpQN = ticks per quarter note should be given on the first line. (Defaults to 1260). // bpm = beats per minute should be given on the first line. (Defaults to 60). rc_t open_csv( handle_t& hRef, const char* csv_fname ); // Create an empty MIDI file object. rc_t create( handle_t& hRef, unsigned trkN, unsigned ticksPerQN ); // Release all resources associated with this MIDI file object rc_t close( handle_t& hRef ); // Write this MIDI file to the specified file. rc_t write( handle_t h, const char* fn ); // Return midi file format id (0,1,2) or kInvalidId if 'h' is invalid. unsigned fileType( handle_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 ticksPerQN( handle_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* filename( handle_t h ); // Returns SMPTE ticks per frame or kInvalidMidiByte if 'h' is // invalid or 0 if file uses ticks per quarter note time base. uint8_t ticksPerSmpteFrame( handle_t h ); // Returns SMPTE format or kInvalidMidiByte if 'h' is invalid or 0 // if file uses ticks per quarter note time base. uint8_t smpteFormatId( handle_t h ); // Return the count of tracks in the file. unsigned trackCount( handle_t h ); // Returns count of records in track 'trackIdx' or kInvalidCnt if 'h' is invalid. unsigned trackMsgCount( handle_t h, unsigned trackIdx ); // Returns base of record chain from track 'trackIdx' or NULL if 'h' is invalid. const trackMsg_t* trackMsg( handle_t h, unsigned trackIdx ); // Returns the total count of records in the midi file and the // number in the array returned by msgArray(). // Return kInvalidCnt if 'h' is invalid. unsigned msgCount( handle_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 trackMsg_t** msgArray( handle_t h ); // Set the velocity of a note-on/off msg identified by 'uid'. rc_t setVelocity( handle_t h, unsigned uid, uint8_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. rc_t insertMsg( handle_t h, unsigned uid, int dtick, uint8_t ch, uint8_t status, uint8_t d0, uint8_t d1 ); // // Insert a new trackMsg_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 'trackMsg_t.u' // u - the message data // rc_t insertTrackMsg( handle_t h, unsigned trkIdx, const trackMsg_t* msg ); rc_t insertTrackChMsg( handle_t h, unsigned trkIdx, unsigned atick, uint8_t status, uint8_t d0, uint8_t d1 ); rc_t insertTrackTempoMsg( handle_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 seekUsecs( handle_t h, unsigned long long usecsOffs, unsigned* msgUsecsPtr, unsigned* newMicrosPerTickPtr ); double durSecs( handle_t h ); // Calculate Note Duration enum { kWarningsMfFl=0x01, kDropReattacksMfFl=0x02 }; void calcNoteDurations( handle_t h, unsigned flags ); // Set the delay prior to the first non-zero msg. void setDelay( handle_t h, unsigned ticks ); void printMsgs( handle_t h, log::handle_t logH ); void printTrack( handle_t h, unsigned trkIdx, log::handle_t logH ); typedef struct { unsigned uid; unsigned long long amicro; unsigned density; } density_t; // Generate the note onset density measure for each note in the MIDI file. // Delete the returned memory with a call to mem::release(). density_t* noteDensity( handle_t h, unsigned* cntRef ); // Generate a piano-roll plot description file which can be displayed with cmXScore.m rc_t genPlotFile( const char* midiFn, const char* outFn ); rc_t genSvgFile(const char* midiFn, const char* outSvgFn, const char* cssFn, bool standAloneFl, bool panZoomFl ); rc_t genCsvFile( const char* midiFn, const char* csvFn, bool printWarningsFl=true ); // Generate a text file report using printMsgs() rc_t report( const char* midiFn, log::handle_t logH ); rc_t report_begin_end( const char* midiFn, unsigned msg_cnt=10, bool pitch_only_fl=true, log::handle_t logH=log::handle_t() ); void printControlNumbers( const char* midiFileName ); rc_t test( const object_t* cfg ); } } } #endif