315 lines
8.9 KiB
C++
315 lines
8.9 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.
|
|
#include "cwCommon.h"
|
|
#include "cwLog.h"
|
|
#include "cwCommonImpl.h"
|
|
#include "cwTest.h"
|
|
#include "cwMem.h"
|
|
#include "cwText.h"
|
|
#include "cwObject.h"
|
|
#include "cwFileSys.h"
|
|
|
|
#include "cwCsv.h"
|
|
#include "cwMidi.h"
|
|
|
|
#include "cwDynRefTbl.h"
|
|
#include "cwScoreParse.h"
|
|
#include "cwSfScore.h"
|
|
#include "cwPerfMeas.h"
|
|
|
|
#include "cwPianoScore.h"
|
|
|
|
#include "cwPianoScore.h"
|
|
#include "cwScoreFollow2.h"
|
|
#include "cwScoreFollow2Test.h"
|
|
|
|
namespace cw
|
|
{
|
|
namespace score_follow_2
|
|
{
|
|
typedef struct midi_evt_str
|
|
{
|
|
unsigned uid;
|
|
uint8_t pitch;
|
|
uint8_t vel;
|
|
unsigned sample_idx;
|
|
double sec;
|
|
} midi_evt_t;
|
|
|
|
typedef struct midi_file_str
|
|
{
|
|
midi_evt_t* evtA;
|
|
unsigned evtN;
|
|
unsigned sampleN;
|
|
} midi_file_t;
|
|
|
|
rc_t parse_midi_file( const char* midi_csv_fname, double srate, midi_file_t& mf )
|
|
{
|
|
rc_t rc = kOkRC;
|
|
csv::handle_t csvH;
|
|
const char* titleA[] = { "UID","trk","amicro","type","ch","D0","D1","sci_pitch" };
|
|
unsigned titleN = sizeof(titleA)/sizeof(titleA[0]);
|
|
unsigned line_idx = 0;
|
|
unsigned lineN = 0;
|
|
|
|
if((rc = csv::create(csvH,midi_csv_fname,titleA,titleN)) != kOkRC )
|
|
{
|
|
rc = cwLogError(rc,"MIDI CSV file open failed.");
|
|
goto errLabel;
|
|
}
|
|
|
|
if((rc = line_count(csvH,lineN)) != kOkRC )
|
|
{
|
|
rc = cwLogError(rc,"MIDI CSV line count access failed.");
|
|
goto errLabel;
|
|
}
|
|
|
|
mf.evtA = mem::allocZ<midi_evt_t>(lineN);
|
|
mf.evtN = 0;
|
|
|
|
for(; (rc = next_line(csvH)) == kOkRC; ++line_idx )
|
|
{
|
|
unsigned uid;
|
|
unsigned amicro;
|
|
const char* type = nullptr;
|
|
unsigned ch;
|
|
unsigned d0;
|
|
unsigned d1;
|
|
|
|
if((rc = getv(csvH,"UID",uid,"amicro",amicro,"type",type,"ch",ch,"D0",d0,"D1",d1)) != kOkRC )
|
|
{
|
|
cwLogError(rc,"Error reading CSV line %i.",line_idx+1);
|
|
goto errLabel;
|
|
}
|
|
|
|
if(textIsEqual(type,"non"))
|
|
{
|
|
midi_evt_t& m = mf.evtA[ mf.evtN++ ];
|
|
|
|
assert( d0 <= 127 && d1 <= 127 );
|
|
|
|
m.pitch = (uint8_t)d0;
|
|
m.vel = (uint8_t)d1;
|
|
m.sec = amicro/1000000.0;
|
|
m.sample_idx = (unsigned)(m.sec * srate);
|
|
m.uid = uid;
|
|
|
|
mf.sampleN = m.sample_idx;
|
|
}
|
|
}
|
|
|
|
errLabel:
|
|
destroy(csvH);
|
|
|
|
if( rc == kEofRC )
|
|
rc = kOkRC;
|
|
|
|
if( rc != kOkRC )
|
|
rc = cwLogError(rc,"MIDI csv file parse failed on '%s'.",cwStringNullGuard(midi_csv_fname));
|
|
return rc;
|
|
}
|
|
|
|
rc_t run( perf_score::handle_t& scoreH,
|
|
double srate,
|
|
unsigned smp_per_cycle,
|
|
const char* midi_csv_fname,
|
|
const object_t* sf_cfg,
|
|
unsigned beg_loc_id,
|
|
unsigned end_loc_id,
|
|
score_follow_2::rpt_t& rpt_ref )
|
|
{
|
|
|
|
rc_t rc = kOkRC;
|
|
midi_file_t mf{ .evtA = nullptr, .evtN=0, .sampleN=0 };
|
|
score_follow_2::args_t sf_args{};
|
|
score_follow_2::handle_t sfH;
|
|
unsigned midi_evt_idx = 0;
|
|
|
|
// parse args
|
|
if((rc = score_follow_2::parse_args(sf_cfg,sf_args)) != kOkRC )
|
|
{
|
|
rc = cwLogError(rc,"SF2 parse arg. failed.");
|
|
goto errLabel;
|
|
}
|
|
|
|
// parse MIDI file into an array
|
|
if((rc = parse_midi_file( midi_csv_fname, srate, mf )) != kOkRC )
|
|
goto errLabel;
|
|
|
|
//cwLogInfo("Following: (%i notes found) sample count:%i %s.",mf.evtN,mf.sampleN,midi_csv_fname);
|
|
|
|
sf_args.scoreH = scoreH;
|
|
|
|
// create the score-follower
|
|
if((rc = score_follow_2::create(sfH,sf_args)) != kOkRC )
|
|
{
|
|
rc = cwLogError(rc,"Score follower create failed.");
|
|
goto errLabel;
|
|
}
|
|
|
|
if((rc = score_follow_2::reset(sfH,beg_loc_id,end_loc_id)) != kOkRC )
|
|
{
|
|
rc = cwLogError(rc,"Score follower reset failed.");
|
|
goto errLabel;
|
|
}
|
|
|
|
// for each cycle
|
|
for(unsigned smp_idx=0; smp_idx < mf.sampleN; smp_idx += smp_per_cycle)
|
|
{
|
|
while( smp_idx <= mf.evtA[midi_evt_idx].sample_idx && mf.evtA[midi_evt_idx].sample_idx < smp_idx + smp_per_cycle )
|
|
{
|
|
const midi_evt_t* e = mf.evtA + midi_evt_idx++;
|
|
unsigned loc_id;
|
|
|
|
//printf("%f pitch:%i vel:%i\n",e->sec,e->pitch,e->vel);
|
|
|
|
if((rc = on_new_note( sfH, e->uid, e->sec, e->pitch, e->vel, loc_id )) != kOkRC )
|
|
{
|
|
rc = cwLogError(rc,"SF2 note processing failed on note UID:%i.",e->uid);
|
|
goto errLabel;
|
|
}
|
|
}
|
|
|
|
if((rc = do_exec(sfH)) != kOkRC )
|
|
{
|
|
rc = cwLogError(rc,"SF2 exec processing failed.");
|
|
goto errLabel;
|
|
}
|
|
|
|
}
|
|
|
|
score_follow_2::report_summary(sfH,rpt_ref);
|
|
|
|
errLabel:
|
|
mem::release(mf.evtA);
|
|
destroy(sfH);
|
|
|
|
return rc;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
cw::rc_t cw::score_follow_2::test( const object_t* cfg )
|
|
{
|
|
rc_t rc = kOkRC;
|
|
double srate = 48000.0;
|
|
unsigned smp_per_cycle = 64;
|
|
unsigned limit_perf_N = -1;
|
|
const char* c_score_fname = nullptr;
|
|
char* score_fname = nullptr;
|
|
const object_t* fileL = nullptr;
|
|
const object_t* sf_cfg = nullptr;
|
|
|
|
unsigned tot_missN = 0;
|
|
unsigned tot_spuriousN = 0;
|
|
unsigned tot_passN = 0;
|
|
unsigned tot_failN = 0;
|
|
|
|
perf_score::handle_t scoreH;
|
|
|
|
if((rc = cfg->getv("srate",srate,
|
|
"smp_per_cycle",smp_per_cycle,
|
|
"limit_perf_N",limit_perf_N,
|
|
"score_fname",c_score_fname,
|
|
"fileL",fileL,
|
|
"follower",sf_cfg)) != kOkRC )
|
|
{
|
|
rc = cwLogError(rc,"SF2 test argument parsing failed.");
|
|
goto errLabel;
|
|
}
|
|
|
|
score_fname = filesys::expandPath( c_score_fname );
|
|
|
|
// create the score
|
|
if((rc = perf_score::create(scoreH,score_fname)) != kOkRC )
|
|
{
|
|
rc = cwLogError(rc,"Score create failed on '%s'.",cwStringNullGuard(score_fname));
|
|
goto errLabel;
|
|
}
|
|
|
|
// for each file
|
|
for(unsigned i=0; rc==kOkRC && i<fileL->child_count(); ++i)
|
|
{
|
|
unsigned beg_loc_id = kInvalidId;
|
|
unsigned end_loc_id = kInvalidId;
|
|
unsigned min_perf_noteN = 0;
|
|
unsigned max_spuriousN = 0;
|
|
const char* c_folder = nullptr;
|
|
char* folder = nullptr;
|
|
const object_t* takeL = nullptr;
|
|
|
|
score_follow_2::rpt_t sf_rpt;
|
|
|
|
// read the file record
|
|
if((rc = fileL->child_ele(i)->getv("beg_loc",beg_loc_id,
|
|
"end_loc",end_loc_id,
|
|
"min_perf_noteN",min_perf_noteN,
|
|
"max_spuriousN", max_spuriousN,
|
|
"folder",c_folder,
|
|
"takeL",takeL)) != kOkRC )
|
|
{
|
|
rc = cwLogError(rc,"Parse of SF2 test case at index %i.",i);
|
|
goto errLabel;
|
|
}
|
|
|
|
folder = filesys::expandPath( c_folder );
|
|
|
|
// for each take number
|
|
for(unsigned j=0; j<takeL->child_count(); ++j)
|
|
{
|
|
char* record_folder = nullptr;
|
|
unsigned take_num;
|
|
|
|
// read the take number
|
|
if((rc = takeL->child_ele(j)->value(take_num)) != kOkRC )
|
|
{
|
|
rc = cwLogError(rc,"Error parsing take number on test '%s' index '%i'.",cwStringNullGuard(c_folder),j);
|
|
break;
|
|
}
|
|
|
|
// form the record_### folder name
|
|
record_folder = mem::printf<char>(nullptr,"record_%i",take_num);
|
|
|
|
// form MIDI csv file name
|
|
char* midi_csv_fname = filesys::makeFn(folder,"fix_midi","csv", record_folder, nullptr );
|
|
|
|
if((rc = run(scoreH, srate, smp_per_cycle, midi_csv_fname, sf_cfg, beg_loc_id, end_loc_id, sf_rpt )) != kOkRC )
|
|
{
|
|
rc = cwLogError(rc,"SF2 test run failed on '%s'.",cwStringNullGuard(midi_csv_fname));
|
|
break;
|
|
}
|
|
|
|
cwLogInfo("Notes:%i Match:%i Miss:%i Spurious:%i : %s",sf_rpt.perfNoteN,sf_rpt.matchN,sf_rpt.missN,sf_rpt.spuriousN,cwStringNullGuard(midi_csv_fname));
|
|
|
|
if( sf_rpt.perfNoteN > min_perf_noteN && sf_rpt.spuriousN < max_spuriousN )
|
|
{
|
|
tot_passN += 1;
|
|
tot_missN += sf_rpt.missN;
|
|
tot_spuriousN += sf_rpt.spuriousN;
|
|
}
|
|
else
|
|
{
|
|
tot_failN += 1;
|
|
}
|
|
|
|
//printf("%s\n",fname);
|
|
|
|
mem::release(record_folder);
|
|
mem::release(midi_csv_fname);
|
|
|
|
}
|
|
|
|
mem::release(folder);
|
|
|
|
|
|
}
|
|
|
|
cwLogInfo("Total M+S:%i missed:%i spurious:%i pass:%i fail:%i",tot_missN+tot_spuriousN,tot_missN,tot_spuriousN,tot_passN,tot_failN);
|
|
|
|
errLabel:
|
|
destroy(scoreH);
|
|
mem::release(score_fname);
|
|
return rc;
|
|
}
|