249 lines
10 KiB
C++
249 lines
10 KiB
C++
//| Copyright: (C) 2020-2024 Kevin Larke <contact AT larke DOT org>
|
|
//| 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<struct file_str> 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
|