//| Copyright: (C) 2020-2024 Kevin Larke //| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file. #ifndef cwScoreMatch_h #define cwScoreMatch_h namespace cw { namespace sfmatch { enum { kSmMinIdx, // kSmSubIdx, // 'substitute' - may or may not match kSmDelIdx, // 'delete' - delete a MIDI note kSmInsIdx, // 'insert' - insert a space in the score kSmCnt }; enum { kSmMatchFl = 0x01, kSmTransFl = 0x02, kSmTruePosFl = 0x04, kSmFalsePosFl = 0x08, kSmBarFl = 0x10, kSmNoteFl = 0x20 }; // Dynamic Programming (DP) matrix element typedef struct { unsigned v[kSmCnt]; // cost for each operation unsigned flags; // cmSmMatchFl | cmSmTransFl unsigned scEvtIdx; } value_t; // List record used to track a path through the DP matrix p->m[,] typedef struct path_str { unsigned code; // kSmXXXIdx unsigned ri; // matrix row index unsigned ci; // matrix col index unsigned flags; // cmSmMatchFl | cmSmTransFl unsigned oLocId; // p->loc index or cmInvalidIdx unsigned scEvtIdx; // scScore event index struct path_str* next; // } path_t; typedef struct event_str { unsigned pitch; // unsigned scEvtIdx; // scScore event index } event_t; // Score location record. typedef struct { unsigned evtCnt; // count of score events at this location (i.e. a chord will have more than one event at a given location) event_t* evtV; // evtV[evtCnt] unsigned scLocIdx; // scH score location index int barNumb; // bar number of this location } loc_t; typedef struct { unsigned mni; // unique identifier for this MIDI note - used to recognize when the sfmatcher backtracks. unsigned muid; // MIDI file event msg unique id (See cmMidiTrackMsg_t.uid) double sec; // time stamp of this event in seconds unsigned smpIdx; // time stamp of this event in samples unsigned pitch; // MIDI note pitch unsigned vel; // " " velocity unsigned oLocId; // location assoc'd with this MIDI evt (kInvalidIdx if not a matching or non-matching 'substitute') unsigned scEvtIdx; // sfscore event index assoc'd with this event } midi_t; typedef struct sfmatch_str { sfscore::handle_t scH; // score handle unsigned locN; // Same as sfscore::loc_count() loc_t* loc; // loc[locN] One element for each score location // DP matrix unsigned mrn; // max m[] row count (midi) unsigned rn; // cur m[] row count unsigned mcn; // max m[] column count (score) unsigned cn; // cur m[] column count value_t* m; // m[mrn,mcn] DP matrix unsigned mmn; // max length of midiBuf[] (mrn-1) unsigned msn; // max length of score window (mcn-1) unsigned pn; // mrn+mcn path_t* p_mem; // pmem[ 2*pn ] - pre-allocated path memory path_t* p_avl; // available path record linked list path_t* p_cur; // current path linked list path_t* p_opt; // p_opt[pn] - current best alignment as a linked list double opt_cost; // last p_opt cost set by exec() } sfmatch_t; typedef handle handle_t; // This matcher determines the optimal alignment of a short list of MIDI notes // within a longer score window. It is designed to work for a single // limited size alignment. // // 1) This matcher cannot handle multiple instances of the same pitch occuring // at the same 'location'. // // 2) Because each note of a chord is spread out over multiple locations, and // there is no way to indicate that a note in the chord is already 'in-use'. // If a MIDI note which is part of the chord is repeated, in error, it will // appear to be correct (a positive match will be assigned to // the second (and possible successive notes)). // maxScWndN - max length of the score window (also the max value of 'locN' in subsequent calls to exec()). // maxMidiWndN - max length of the performance (MIDI) buffer to compare to the score window // (also the max value of 'midiN' in subsequent calls to exec()). rc_t create( handle_t& hRef, sfscore::handle_t scoreH, unsigned maxScWndN, unsigned maxMidiWndN ); rc_t destroy( handle_t& hRef ); // Locate the position in p->loc[oLocId:oLocId+locN-1] which bests matches midiV[0:midiN]. // The result of this function is to update p_opt[] // The optimal path p_opt[] will only be updated if the edit_cost associated 'midiV[0:midiN]'. // with the best match is less than 'min_cost'. // Set 'min_cost' to DBL_MAX to force p_opt[] to be updated. // Returns kEofRC if oLocId + locN > p->locN - note that this is not necessarily an error. rc_t exec( handle_t h, unsigned oLocId, unsigned locN, const midi_t* midiV, unsigned midiN, double min_cost ); // This function updates the midiBuf[] fields 'oLocId' and 'scEvtIdx' after an alignment // has been found via an earlier call to 'exec()'. // // Traverse the least cost path and: // // 1) Returns, esi, the score location index of the last MIDI note // which has a positive match with the score and assign // the internal score index to cp->oLocId. // // 2) Set cmScAlignPath_t.oLocId - index into p->loc[] associated // with each path element that is a 'substitute' or an 'insert'. // // 3) Set *missCnPtr: the count of trailing non-positive matches in midiBuf[]. // // i_opt is index into p->loc[] of p->p_opt. unsigned sync( handle_t h, unsigned i_opt, midi_t* midiBuf, unsigned midiN, unsigned* missCntPtr ); // Print a matched path. // cp - pointer to the first path element // bsi - score location index of the first element in the score window which was used to form the path. // midiV - pointer to the first element of the MIDI buffer used to form the path. void print_path( handle_t h, unsigned bsi, const midi_t* midiV ); // Given a loc value return the index into p->loc[p->locN].of the associated loc. unsigned loc_to_index( handle_t h, unsigned loc ); // Returns the index into loc->evtV[] of pitch. inline unsigned match_index( const loc_t* loc, unsigned pitch ) { for(unsigned i=0; ievtCnt; ++i) if( loc->evtV[i].pitch == pitch ) return i; return kInvalidIdx; } // Return true if 'pitch' occurs at loc. inline bool is_match( const loc_t* loc, unsigned pitch ) { return match_index(loc,pitch) != kInvalidIdx; } inline unsigned max_midi_wnd_count( handle_t h ) { return handleToPtr(h)->mmn; } inline unsigned max_score_wnd_count( handle_t h ) { return handleToPtr(h)->msn; } inline unsigned loc_count( handle_t h ) { return handleToPtr(h)->locN; } inline const cw::sfmatch::loc_t* loc_base( handle_t h ) { return handleToPtr(h)->loc; } inline double cost( handle_t h ) { return handleToPtr(h)->opt_cost; } inline const cw::sfmatch::path_t* optimal_path( handle_t h ) { return handleToPtr(h)->p_opt; } rc_t test( const object_t* cfg, sfscore::handle_t scoreH ); } } #endif