diff --git a/cwDynRefTbl.cpp b/cwDynRefTbl.cpp index 500183d..407c80d 100644 --- a/cwDynRefTbl.cpp +++ b/cwDynRefTbl.cpp @@ -13,10 +13,10 @@ namespace cw { typedef struct dyn_ref_tbl_str { - dyn_ref_t* dynRefA; - unsigned dynRefN; - unsigned* levelLookupA; - unsigned levelLookupN; + dyn_ref_t* dynRefA; // dynRefA[ dynRefN ] one entry per dyn. level + unsigned dynRefN; // + unsigned* levelLookupA; // levelLoopA[ levelLookupN ] - one entry per velocity value (0-127) + unsigned levelLookupN; // levelLooupN = kMidiVelCnt } dyn_ref_tbl_t; @@ -31,6 +31,7 @@ namespace cw std::for_each(p->dynRefA, p->dynRefA+p->dynRefN, relse ); mem::release(p->dynRefA); + mem::release(p->levelLookupA); mem::release(p); return rc; } @@ -39,13 +40,15 @@ namespace cw { for(midi::byte_t vel=0; vellevelLookupN; ++i) + for(unsigned i=0; idynRefN; ++i) if( p->dynRefA[i].velocity >= vel ) { midi::byte_t d0 = p->dynRefA[i].velocity - vel; midi::byte_t d1 = i>0 ? (vel - p->dynRefA[i-1].velocity) : d0; unsigned j = d0 <= d1 ? i : i-1; - + + assert( vel <= p->levelLookupN ); + p->levelLookupA[vel] = p->dynRefA[j].level; break; } diff --git a/cwObject.cpp b/cwObject.cpp index 518ca04..a84a6c8 100644 --- a/cwObject.cpp +++ b/cwObject.cpp @@ -746,10 +746,10 @@ cw::object_t* cw::newObject( const char* v, object_t* parent) { return _objCreateValueNode( parent, v ); } cw::object_t* cw::newDictObject( object_t* parent ) -{ return _objAllocate( kDictTId, parent); } +{ return _objAppendRightMostNode(parent, _objAllocate( kDictTId, parent) ); } cw::object_t* cw::newListObject( object_t* parent ) -{ return _objAllocate( kListTId, parent ); } +{ return _objAppendRightMostNode(parent, _objAllocate( kListTId, parent) ); } cw::object_t* cw::newPairObject( const char* label, object_t* value, object_t* parent) { diff --git a/cwObject.h b/cwObject.h index c69a848..67e6205 100644 --- a/cwObject.h +++ b/cwObject.h @@ -88,6 +88,9 @@ namespace cw } objType_t; + struct object_str* newPairObject( const char* label, struct object_str* v, struct object_str* parent ); + struct object_str* newListObject( struct object_str* parent ); + typedef struct object_str { objType_t* type = nullptr; @@ -246,6 +249,40 @@ namespace cw { return newPairObject(label, v, this); } + rc_t _putv() { return kOkRC; } + + // getv("label0",v0,"label1",v1, ... ) + template< typename T0, typename T1, typename... ARGS > + rc_t _putv( T0 label, const T1& val, ARGS&&... args ) + { + + insert_pair(label,val); + + _putv(std::forward(args)...); // ... recurse to find next label/value pair + + return kOkRC; + } + + + // getv("label0",v0,"label1",v1, ... ) + template< typename T0, typename T1, typename... ARGS > + rc_t putv( T0 label, const T1& val, ARGS&&... args ) + { return _putv(label,val,args...); } + + + template< typename T > + struct object_str* put_numeric_list( const char* label, const T* v, unsigned vN ) + { + struct object_str* pair = newPairObject(label,newListObject(nullptr),this)->parent; + struct object_str* list = pair->pair_value(); + for(unsigned i=0; i rc_t set( const char* label, const T& value ) { diff --git a/cwPerfMeas.cpp b/cwPerfMeas.cpp index 1332f62..90fd5b8 100644 --- a/cwPerfMeas.cpp +++ b/cwPerfMeas.cpp @@ -3,6 +3,7 @@ #include "cwCommonImpl.h" #include "cwMem.h" #include "cwText.h" +#include "cwFile.h" #include "cwObject.h" #include "cwMidi.h" #include "cwDynRefTbl.h" @@ -35,30 +36,49 @@ namespace cw struct calc_str; typedef struct section_str - { - + { const struct section_str* prev_section; // section prior to this section const sfscore::section_t* section; // score section this section_t represents struct calc_str* calc; // calc record for this section bool triggeredFl; // This section has already been triggered } section_t; + + typedef struct meas_info_str + { + unsigned missEvtN; // count of skipped events + unsigned missLocN; // count of missed locations + unsigned transposeLocN; // count of transposed locations + unsigned interpLocN; // count of interpolated locations + + unsigned vN; // vN=locN for even,tempo vN=evtN for dyn + + double* locSecV; // locSecV[vN] + unsigned* locSecNV; // locSecNV[vN] count of performed and interpolated notes (interpolated notes will have a False in the associated statusV[] location) + double* dSecV; // dSecV[vN] + bool* statusV; // statusV[vN] - true if this location was performed by at least one note + struct meas_info_str* link; + } meas_info_t; + + struct loc_str; typedef struct set_str { - const sfscore::set_t* set; // score set this set_t represents + struct loc_str* loc; // end location for this set + const sfscore::set_t* set; // score set this set_t represents + meas_info_t* meas_info; // unsigned lastPerfUpdateCnt; - double value; // The value associated with this set. DBL_MAX on initialization - struct calc_str* calc; // calc record to which this set belongs - struct set_str* alink; // perf_meas_t* links - struct set_str* slink; // loc_t.setL links - struct set_str* clink; // calc_t.setL links + double value; // The value associated with this set. DBL_MAX on initialization + struct calc_str* calc; // calc record to which this set belongs + struct set_str* alink; // perf_meas_t* links + struct set_str* slink; // loc_t.setL links + struct set_str* clink; // calc_t.setL links } set_t; // The 'calc_t' record hold pointers to all the sets assigned to a given section. // The record is different from a section record because it has a location assignment // prior to the section which it is represents. This allows all the sets for // a given section to be evaluated prior to the section being triggered. - // The 'calc' record is assigned to the location of the last event in it's latest set. + // The 'calc' record is assigned to the location of the last event in it's last set. typedef struct calc_str { set_t* setL; // list of sets that this calc is applied to (links via set_t.clink) @@ -66,7 +86,7 @@ namespace cw double value[ kValCnt ]; // Aggregate var values for this section } calc_t; - typedef struct + typedef struct loc_str { unsigned locId; // oloc location id and the index of this record into p->locA[] section_t* section; // section that begins on this location @@ -104,6 +124,22 @@ namespace cw return locId; } + + void _destroy_meas_info_records( set_t* set ) + { + meas_info_t* m = set->meas_info; + while( m!=nullptr ) + { + meas_info_t* m0 = m->link; + mem::release(m->locSecV); + mem::release(m->locSecNV); + mem::release(m->dSecV); + mem::release(m->statusV); + mem::release(m); + m = m0; + } + + } rc_t _destroy( perf_meas_t* p ) { @@ -115,6 +151,9 @@ namespace cw while(s!=nullptr) { set_t* s0 = s->slink; + + _destroy_meas_info_records(s); + mem::release(s); s = s0; } @@ -137,6 +176,29 @@ namespace cw cwLogError(kInvalidIdRC,"The setId '%i' is not valid.", setId ); return nullptr; } + + meas_info_t* _create_meas_info_record( set_t* set ) + { + meas_info_t* m = mem::allocZ(); + m->vN = set->set->varId==score_parse::kDynVarIdx ? set->set->evtCnt : set->set->locN; + m->locSecV = mem::allocZ( m->vN ); + m->locSecNV = mem::allocZ( m->vN ); + m->dSecV = mem::allocZ( m->vN ); + m->statusV = mem::allocZ( m->vN ); + + if( set->meas_info == nullptr ) + set->meas_info = m; + else + { + meas_info_t* m0 = set->meas_info; + while( m0->link != nullptr ) + m0 = m0->link; + + m0->link = m; + } + + return m; + } calc_t* _create_calc_record( perf_meas_t* p, sfscore::section_t* section ) { @@ -259,24 +321,28 @@ namespace cw const sfscore::event_t* e = set->set->evtArray[i]; if( e->perfFl ) { - double d = e->dynLevel>e->perfDynLevel ? e->dynLevel - e->perfDynLevel : e->perfDynLevel - e->dynLevel; - sum += d * d; + sum += e->dynLevel>e->perfDynLevel ? e->dynLevel - e->perfDynLevel : e->perfDynLevel - e->dynLevel; n += 1; } } - set->value = n==0 ? 0 : sqrt(sum/n); + set->value = n==0 ? 0 : sum/n; } // Calc chord onset time as the avg. onset over of all notes in the chord. - rc_t _calc_loc_sec( const sfscore::set_t* set, double* locV, unsigned* locNV, unsigned& evtSkipN_Ref ) + rc_t _calc_loc_sec( set_t* pm_set ) { rc_t rc = kOkRC; - evtSkipN_Ref = 0; + const sfscore::set_t* set = pm_set->set; + meas_info_t* m = pm_set->meas_info; - vop::zero(locV, set->locN); - vop::zero(locNV, set->locN); + m->missEvtN = 0; + + assert( set->locN == m->vN ); + + vop::zero(m->locSecV, set->locN); + vop::zero(m->locSecNV, set->locN); // Get the time for each location by taking the mean // of all events on the same location. @@ -287,7 +353,7 @@ namespace cw { if( !set->evtArray[i]->perfFl ) { - ++evtSkipN_Ref; + ++m->missEvtN; } else { @@ -303,94 +369,105 @@ namespace cw goto errLabel; } - locV[ li] += set->evtArray[i]->perfSec; - locNV[li] += 1; + m->locSecV[ li] += set->evtArray[i]->perfSec; + m->locSecNV[li] += 1; } } // Calc. mean. for(unsigned i=0; ilocN; ++i) - if( locNV[i] > 0 ) - locV[i] /= locNV[i]; + if( m->locSecNV[i] > 0 ) + m->locSecV[i] /= m->locSecNV[i]; errLabel: return rc; } - void _interpolate_time_of_missing_notes( perf_meas_t* p, const sfscore::set_t* set, double* locV, unsigned* locNV, bool* statusV, unsigned& bi_ref, unsigned& ei_ref, unsigned& insertN_ref ) + void _interpolate_time_of_missing_notes( perf_meas_t* p, set_t* pm_set, unsigned& bi_ref, unsigned& ei_ref ) { bi_ref = kInvalidIdx; ei_ref = kInvalidIdx; - insertN_ref = 0; + const sfscore::set_t* set = pm_set->set; + meas_info_t* m = pm_set->meas_info; unsigned missN = 0; unsigned ei = kInvalidIdx; unsigned bi = kInvalidIdx; + + assert( set->locN == m->vN ); - vop::fill(statusV, set->locN, false); + vop::fill(m->statusV, set->locN, false); // for each location for(unsigned i=0; ilocN; ++i) { - // if this location was missed or out of time order - if( locNV[i] == 0 || (ei != kInvalidIdx && (locV[i]-locV[ei])<0) ) + // if this location was missed + if( m->locSecNV[i] == 0 ) { missN += 1; + m->missLocN += 1; + continue; } - else - { - // if there are unplayed notes between this note - // and the last played note at 'ei' then - // fill in the missing note times by splitting - // the gap time evenly - note that this will - // bias the evenness std to be lower than it - // should be. - if( missN > 0 && ei!=kInvalidIdx ) - { - double dsec = (locV[i] - locV[ei])/(missN+1); - for(unsigned j=ei+1; jlocSecV[i]-m->locSecV[ei])<0 ) + { + missN += 1; + m->transposeLocN += 1; + continue; + } + + + // if there are unplayed notes between this note + // and the last played note at 'ei' then + // fill in the missing note times by splitting + // the gap time evenly - note that this will + // bias the evenness std to be lower than it + // should be. + if( missN > 0 && ei!=kInvalidIdx ) + { + double dsec = (m->locSecV[i] - m->locSecV[ei])/(missN+1); + for(unsigned j=ei+1; jlocSecV[j] = m->locSecV[j-1] + dsec; + m->interpLocN += 1; + } + } + + if( bi == kInvalidIdx ) + bi = i; - statusV[i] = true; - missN = 0; - ei = i; // ei=last valid location in locV[] - } + m->statusV[i] = true; + missN = 0; + ei = i; // ei=last valid location in m->locSecV[] + } bi_ref = bi; ei_ref = ei; + + //vop::print( m->locSecV, set->locN, "%f ", "locSecV:" ); + //vop::print( m->locSecNV, set->locN, "%i ", "locSecN:" ); + //vop::print( m->statusV, set->locN, "%i ", "ok:"); + } rc_t _eval_one_even_set(perf_meas_t* p, set_t* pm_set ) { - rc_t rc = kOkRC; - - const sfscore::set_t* set = pm_set->set; - - double locV[ set->locN ]; - unsigned locNV[ set->locN ]; - double stdV[ set->locN ]; - bool statusV[ set->locN ]; - - unsigned bi = kInvalidIdx; - unsigned ei = kInvalidIdx; - unsigned insertN = 0; - unsigned evtSkipN = 0; - unsigned locSkipN = 0; + rc_t rc = kOkRC; + const sfscore::set_t* set = pm_set->set; + unsigned bi = kInvalidIdx; + unsigned ei = kInvalidIdx; + meas_info_t* m = pm_set->meas_info; - if((rc = _calc_loc_sec(set,locV,locNV,evtSkipN)) != kOkRC ) + assert( set->locN == m->vN ); + + if((rc = _calc_loc_sec(pm_set)) != kOkRC ) goto errLabel; - _interpolate_time_of_missing_notes(p, set, locV, locNV, statusV, bi, ei, insertN ); + _interpolate_time_of_missing_notes(p, pm_set, bi, ei ); - vop::zero(stdV, set->locN); + vop::zero(m->dSecV, set->locN); // Calc the std. deviation of the note delta times // of all notes [bi:ei]. Note that if the notes @@ -404,17 +481,13 @@ namespace cw // calc the delta time for each time in locV[] unsigned stdN = 0; for(unsigned i=bi+1; i<=ei; ++i) - stdV[stdN++] = locV[i] - locV[i-1]; + m->dSecV[stdN++] = m->locSecV[i] - m->locSecV[i-1]; - printf("Skipped evt:%i Skipped locs:%i Insert:%i bi:%i ei:%i final N:%i\n",evtSkipN,locSkipN,insertN,bi,ei,stdN); - vop::print( locV, set->locN, "%f ", "locV:" ); - vop::print( locNV, set->locN, "%i ", "locN:" ); - vop::print( stdV, stdN, "%f ", "std:" ); - vop::print( statusV, set->locN, "%i ", "ok:"); - + //printf("Even: skipped evt:%i skipped locs:%i interp:%i bi:%i ei:%i final N:%i\n",m->missEvtN,m->missLocN,m->interpLocN,bi,ei,stdN); + //vop::print( m->dSecV, stdN, "%f ", "std:" ); - pm_set->value = vop::std(stdV, stdN ); + pm_set->value = vop::std(m->dSecV, stdN ); } errLabel: @@ -422,9 +495,42 @@ namespace cw } - void _eval_one_tempo_set(perf_meas_t* p, set_t* set ) + rc_t _eval_one_tempo_set(perf_meas_t* p, set_t* pm_set ) { - set->value = 3.0; + rc_t rc = kOkRC; + const sfscore::set_t* set = pm_set->set; + meas_info_t* m = pm_set->meas_info; + unsigned bi = kInvalidIdx; + unsigned ei = kInvalidIdx; + + assert( set->locN == m->vN ); + + if((rc = _calc_loc_sec(pm_set)) != kOkRC ) + goto errLabel; + + _interpolate_time_of_missing_notes(p, pm_set, bi, ei ); + + vop::zero(m->dSecV, set->locN); + + if( (ei - bi)+1 > 2 ) + { + // calc the delta time for each time in locV[] + unsigned dsecN = 0; + for(unsigned i=bi+1; i<=ei; ++i) + m->dSecV[dsecN++] = m->locSecV[i] - m->locSecV[i-1]; + + //printf("Tempo: skipped evt:%i skipped locs:%i interp:%i bi:%i ei:%i final N:%i\n",m->missEvtN,m->missLocN,m->interpLocN,bi,ei,dsecN); + //vop::print( m->dSecV, dsecN, "%f ", "dsecV:" ); + + double sec_per_beat = vop::mean(m->dSecV, dsecN ); + double bpm = 60.0/sec_per_beat; + + + pm_set->value = bpm; + } + + errLabel: + return rc; } void _aggregate_dynamic_meas_set_values( perf_meas_t* p, calc_t* calc ) @@ -442,7 +548,7 @@ namespace cw calc->value[ kTempoValIdx ] = _calc_set_list_mean( calc->setL, score_parse::kTempoVarIdx ); } - void _eval_cost_calc( perf_meas_t* p, calc_t* calc ) + void _aggregate_cost_meas_set_values( perf_meas_t* p, calc_t* calc ) { if( calc->section->prev_section != nullptr ) { @@ -462,7 +568,7 @@ namespace cw } - calc->value[ kMatchCostValIdx ] = sum / n; + calc->value[ kMatchCostValIdx ] = n==0 ? 0 : sum / n; } } @@ -535,6 +641,7 @@ namespace cw _aggregate_dynamic_meas_set_values(p,calc); _aggregate_even_meas_set_values(p,calc); _aggregate_tempo_meas_set_values(p,calc); + _aggregate_cost_meas_set_values(p,calc); } return rc; } @@ -586,6 +693,42 @@ namespace cw errLabel: return rc; } + + template< typename T > + rc_t _write_result_vector(file::handle_t fH, section_t* section, set_t* set, const char* opLabel, const char* fmt, const T* vect, unsigned locN) + { + rc_t rc; + + if((rc = file::printf(fH,"%s,%i,%s,%s,%i,", + section->section->label, + set==nullptr ? -1 : set->set->id, + set==nullptr ? "" : score_parse::var_index_to_char(set->set->varId), + opLabel, + locN)) != kOkRC ) + { + rc = cwLogError(rc,"Perf. measure vector header write failed."); + goto errLabel; + } + + for(const T* v=vect; v::max() ? -1.0 : x; } } } @@ -620,11 +763,14 @@ cw::rc_t cw::perf_meas::create( handle_t& hRef, sfscore::handle_t scoreH ) { // ... link the sets onto this location record set_t* set = mem::allocZ(); - set->set = s; + set->loc = p->locA + i; + set->set = s; set->slink = p->locA[i].setL; - p->locA[i].setL = set; + p->locA[i].setL = set; set->alink = p->setL; p->setL = set; + + _create_meas_info_record(set); } // if this location is the start of a new section @@ -802,4 +948,150 @@ void cw::perf_meas::report( handle_t h ) } } + + +cw::rc_t cw::perf_meas::write_result_json( handle_t h, + const char* player_name, + const char* perf_date, + unsigned perf_take_numb, + const char* out_fname ) +{ + rc_t rc = kOkRC; + perf_meas_t* p = _handleToPtr(h); + object_t* root = newDictObject(); + object_t* sectionL = root->insert_pair("sectionL",newListObject()); + + for(loc_t* loc=p->locA; loclocA+p->locN; ++loc) + if( loc->section != nullptr && loc->section->calc != nullptr ) + { + object_t* sect = newDictObject(sectionL); + const double* valueV = loc->section->calc->value; + object_t* setL = newListObject(); + + sect->putv("label",loc->section->section->label, + "player_name",player_name, + "perf_date",perf_date, + "perf_take_numb",perf_take_numb, + "locId",loc->locId, + "dyn",dbl_max_to_neg_one(valueV[kDynValIdx]), + "even",dbl_max_to_neg_one(valueV[kEvenValIdx]), + "tempo",dbl_max_to_neg_one(valueV[kTempoValIdx]), + "cost",dbl_max_to_neg_one(valueV[kMatchCostValIdx]), + "setL",setL); + + for(set_t* set=loc->section->calc->setL; set!=nullptr; set=set->clink) + { + object_t* set_o = newDictObject(setL); + + set_o->putv("type",score_parse::var_index_to_char(set->set->varId), + "setId",set->set->id, + "locId",set->loc->locId, + "value",dbl_max_to_neg_one(set->value), + "missEvtN",set->meas_info->missEvtN, + "missLocN",set->meas_info->missLocN, + "transposeLocN",set->meas_info->transposeLocN, + "interpLocN",set->meas_info->interpLocN); + + set_o->put_numeric_list("locSecV", set->meas_info->locSecV, set->meas_info->vN ); + set_o->put_numeric_list("locSecNV", set->meas_info->locSecNV, set->meas_info->vN ); + set_o->put_numeric_list("dSecV", set->meas_info->dSecV, set->meas_info->vN ); + set_o->put_numeric_list("statusV", set->meas_info->statusV, set->meas_info->vN ); + + } + } + + if((rc = objectToFile(out_fname,root)) != kOkRC ) + { + rc = cwLogError(rc,"Perf. measurement JSON file write failed."); + goto errLabel; + } + + errLabel: + root->free(); + + return rc; +} + +cw::rc_t cw::perf_meas::write_result_csv( handle_t h, const char* out_fname ) +{ + rc_t rc = kOkRC; + perf_meas_t* p = _handleToPtr(h); + file::handle_t fH; + + if((rc = file::open(fH,out_fname, file::kWriteFl)) != kOkRC ) + { + rc = cwLogError(rc,"Perf. measure result file create failed on '%s'.",cwStringNullGuard(out_fname)); + goto errLabel; + } + + if((rc = file::printf(fH,"section,set,type,op,value,missEvtN,missLocN,transposeLocN,interpLocN\n")) != kOkRC ) + { + rc = cwLogError(rc,"Perf. measure title write failed."); + goto errLabel; + } + + for(loc_t* loc=p->locA; loclocA+p->locN; ++loc) + if( loc->section != nullptr && loc->section->calc != nullptr ) + { + if((rc = _write_result_vector(fH,loc->section,nullptr,"section","%f,",loc->section->calc->value,kValCnt)) != kOkRC ) + { + rc = cwLogError(rc,"Perf. measure file write result section header failed."); + goto errLabel; + } + + for(set_t* set=loc->section->calc->setL; set!=nullptr; set=set->clink) + { + + if((rc = file::printf(fH,"%s,%i,%s,summary,%f,%i,%i,%i,%i\n", + loc->section->section->label, + set->set->id, + score_parse::var_index_to_char(set->set->varId), + set->value != std::numeric_limits::max() ? set->value : 0, + set->meas_info->missEvtN, + set->meas_info->missLocN, + set->meas_info->transposeLocN, + set->meas_info->interpLocN)) != kOkRC ) + { + rc = cwLogError(rc,"Perf. measure file write result failed."); + goto errLabel; + } + + if((rc = _write_result_vector(fH,loc->section,set,"locSecV","%f,",set->meas_info->locSecV,set->set->locN)) != kOkRC ) + { + rc = cwLogError(rc,"Perf.measure locSecV[] write result failed."); + goto errLabel; + } + + if((rc = _write_result_vector(fH,loc->section,set,"locSecNV","%i,",set->meas_info->locSecNV,set->set->locN)) != kOkRC ) + { + rc = cwLogError(rc,"Perf.measure locSecNV[] write result failed."); + goto errLabel; + } + + if((rc = _write_result_vector(fH,loc->section,set,"dSecV","%f,", set->meas_info->dSecV,set->set->locN)) != kOkRC ) + { + rc = cwLogError(rc,"Perf.measure dSecV[] write result failed."); + goto errLabel; + } + + if((rc = _write_result_vector(fH,loc->section,set,"statusV","%i,",set->meas_info->statusV,set->set->locN)) != kOkRC ) + { + rc = cwLogError(rc,"Perf.measure status[] write result failed."); + goto errLabel; + } + + } + } + + if((rc = file::close(fH)) != kOkRC ) + { + rc = cwLogError(rc,"Perf. meas. file close failed."); + goto errLabel; + } + + errLabel: + return rc; + +} + diff --git a/cwPerfMeas.h b/cwPerfMeas.h index 12008dc..57184f5 100644 --- a/cwPerfMeas.h +++ b/cwPerfMeas.h @@ -33,7 +33,9 @@ namespace cw void report( handle_t h ); - + rc_t write_result_csv( handle_t h, const char* out_fname ); + + rc_t write_result_json( handle_t h, const char* player_name, const char* perf_date, unsigned perf_take_numb, const char* out_fname ); } diff --git a/cwScoreFollowTest.cpp b/cwScoreFollowTest.cpp index 30d5de1..8813744 100644 --- a/cwScoreFollowTest.cpp +++ b/cwScoreFollowTest.cpp @@ -47,6 +47,9 @@ namespace cw bool meas_setup_report_fl; char* out_dir; + + bool write_perf_meas_json_fl; + const char* out_perf_meas_json_fname; bool write_svg_file_fl; const char* out_svg_fname; @@ -93,6 +96,10 @@ namespace cw "meas_setup_report_fl", p->meas_setup_report_fl, "out_dir", out_dir, + + "write_perf_meas_json_fl", p->write_perf_meas_json_fl, + "out_perf_meas_json_fname", p->out_perf_meas_json_fname, + "write_svg_file_fl", p->write_svg_file_fl, "out_svg_fname", p->out_svg_fname, @@ -133,17 +140,20 @@ namespace cw perf_meas::handle_t perfMeasH, unsigned perf_idx ) { - rc_t rc = kOkRC; - bool pre_test_fl = p->pre_test_fl; - bool enable_fl = true; - const char* perf_label = nullptr; - const char* midi_fname = nullptr; - char* out_dir = nullptr; - char* fname = nullptr; - unsigned start_loc = 0; - const object_t* perf = nullptr; - unsigned msgN = 0; - const midi::file::trackMsg_t** msgA = nullptr; + rc_t rc = kOkRC; + bool pre_test_fl = p->pre_test_fl; + bool enable_fl = true; + const char* perf_label = nullptr; + const char* player_name = nullptr; + const char* perf_date = nullptr; + unsigned perf_take_numb = kInvalidId; + const char* midi_fname = nullptr; + char* out_dir = nullptr; + char* fname = nullptr; + unsigned start_loc = 0; + const object_t* perf = nullptr; + unsigned msgN = 0; + const midi::file::trackMsg_t** msgA = nullptr; midi::file::handle_t mfH; // get the perf. record @@ -156,6 +166,9 @@ namespace cw // parse the performance record if((rc = perf->getv("label",perf_label, "enable_fl",enable_fl, + "player",player_name, + "perf_date",perf_date, + "take",perf_take_numb, "start_loc", start_loc, "midi_fname", midi_fname)) != kOkRC ) { @@ -236,7 +249,7 @@ namespace cw pre_test_fl = false; } - printf("%f %li %5i %3x %3i %3i\n",sec, smpIdx, m->uid, m->status, m->u.chMsgPtr->d0, m->u.chMsgPtr->d1); + //printf("%f %li %5i %3x %3i %3i\n",sec, smpIdx, m->uid, m->status, m->u.chMsgPtr->d0, m->u.chMsgPtr->d1); // send the note-on to the score follower if((rc = exec(sfH, sec, smpIdx, m->uid, m->status, m->u.chMsgPtr->d0, m->u.chMsgPtr->d1, newMatchFl )) != kOkRC ) @@ -293,6 +306,27 @@ namespace cw } } + if( p->write_perf_meas_json_fl ) + { + // create the JSON output filename + if((fname = filesys::makeFn(out_dir,p->out_perf_meas_json_fname,nullptr,nullptr)) == nullptr ) + { + cwLogError(kOpFailRC,"The output perf. meas. filename formation failed."); + goto errLabel; + } + + cwLogInfo("Writing JSON score-follow perf. meas. result to:%s",cwStringNullGuard(fname)); + + if((rc = write_result_json(perfMeasH,player_name,perf_date,perf_take_numb,fname)) != kOkRC ) + { + rc = cwLogError(rc,"Perf. meas. report file create failed."); + goto errLabel; + } + + mem::release(fname); + + } + // write the score following result SVG if( p->write_svg_file_fl ) diff --git a/cwScoreParse.cpp b/cwScoreParse.cpp index 38fd49e..d8574f0 100644 --- a/cwScoreParse.cpp +++ b/cwScoreParse.cpp @@ -635,7 +635,7 @@ namespace cw set_t* cur_set = nullptr; unsigned setId = 0; unsigned setNoteIdx = 0; - unsigned endLoc = kInvalidIdx; + unsigned endLocId = kInvalidIdx; for(unsigned vi=0; vieventN; ++ei) @@ -647,15 +647,16 @@ namespace cw // then the set is complete // (this handles the case where there are multiple events // on the same end set location) - if( endLoc != kInvalidIdx && (e->eLocId > endLoc || ei==p->eventN-1) ) + if( endLocId != kInvalidIdx && (e->eLocId > endLocId || ei==p->eventN-1) ) { cur_set->eventA = mem::allocZ(cur_set->eventN); setId += 1; setNoteIdx = 0; cur_set = nullptr; - endLoc = kInvalidIdx; + endLocId = kInvalidIdx; } - + + // if this event if( e->varA[vi].flags != 0 ) { @@ -667,11 +668,11 @@ namespace cw cur_set->eventN += 1; if( cwIsFlag(e->varA[vi].flags,kSetEndVarFl) ) - endLoc = e->eLocId; + endLocId = e->eLocId; } } } - + void _fill_sets( score_parse_t* p ) { for(unsigned ei=0; eieventN; ++ei) @@ -687,6 +688,44 @@ namespace cw } + unsigned _set_count( score_parse_t* p ) + { + unsigned n = 0; + for(set_t* s = p->begSetL; s!=nullptr; s=s->link) + ++n; + return n; + } + + void _order_set_ids_by_time( score_parse_t* p ) + { + typedef struct set_order_str + { + unsigned beg_evt_idx; + set_t* set; + } set_order_t; + + unsigned setAllocN = _set_count(p); + unsigned setN = 0; + set_order_t* setA = mem::allocZ(setAllocN); + + for(set_t* s=p->begSetL; s!=nullptr; s=s->link) + { + if( s->eventN > 0 ) + { + setA[setN].beg_evt_idx = s->eventA[0]->index; + setA[setN].set = s; + setN += 1; + } + } + + std::sort( setA, setA+setN, [](auto a, auto b){return a.beg_evt_idxid = set_id++; }); + + mem::release(setA); + } + rc_t _validate_sets( score_parse_t* p ) { rc_t rc = kOkRC; @@ -1037,6 +1076,8 @@ cw::rc_t cw::score_parse::create( handle_t& hRef, const char* fname, double srat _fill_sets(p); + _order_set_ids_by_time( p ); + if((rc = _validate_sets(p)) != kOkRC ) goto errLabel; @@ -1115,10 +1156,7 @@ const cw::score_parse::section_t* cw::score_parse::section_list( handle_t h ) unsigned cw::score_parse::set_count( handle_t h ) { score_parse_t* p = _handleToPtr(h); - unsigned n = 0; - for(set_t* s = p->begSetL; s!=nullptr; s=s->link) - ++n; - return n; + return _set_count(p); } const cw::score_parse::set_t* cw::score_parse::set_list( handle_t h ) diff --git a/cwSfScore.cpp b/cwSfScore.cpp index 6efbe6a..30210c1 100644 --- a/cwSfScore.cpp +++ b/cwSfScore.cpp @@ -960,7 +960,7 @@ cw::rc_t cw::sfscore::destroy( handle_t& hRef ) void cw::sfscore::clear_all_performance_data( handle_t h ) { sfscore_t* p = _handleToPtr(h); - std::for_each( p->eventA, p->eventA + p->eventN, [](event_t& e){ e.perfFl=false; e.perfVel=0, e.perfSec=0, e.perfMatchCost=std::numeric_limits::max(); } ); + std::for_each( p->eventA, p->eventA + p->eventN, [](event_t& e){ e.perfFl=false; e.perfCnt=0; e.perfVel=0, e.perfSec=0, e.perfMatchCost=std::numeric_limits::max(); } ); std::for_each( p->setA, p->setA + p->setN, [](set_t& s){ s.perfEventCnt=0; s.perfUpdateCnt=false; } ); } @@ -997,7 +997,8 @@ cw::rc_t cw::sfscore::set_perf( handle_t h, unsigned event_idx, double secs, uin } } } - + + e->perfCnt += 1; e->perfFl = true; e->perfVel = vel; e->perfSec = secs; diff --git a/cwSfScore.h b/cwSfScore.h index cf3479a..e0906bf 100644 --- a/cwSfScore.h +++ b/cwSfScore.h @@ -53,15 +53,17 @@ namespace cw unsigned varN; // Length of varA[] unsigned bpm; // beats per minute - double bpm_rval; + double bpm_rval; // double relTempo; // relative tempo (1=min tempo) bool perfFl; // has this event been performed + unsigned perfCnt; // count of time this event was performed (if perfCnt > 1 then the event was duplicated during performance) double perfSec; // performance event time uint8_t perfVel; // performance event velocity unsigned perfDynLevel; // performance dynamic level double perfMatchCost; // performance match cost (or DBL_MAX if not valid) + } event_t; // A 'set' is a collection of events that are grouped in time and all marked with a given attribute.