2024-12-01 19:35:24 +00:00
|
|
|
//| Copyright: (C) 2020-2024 Kevin Larke <contact AT larke DOT org>
|
|
|
|
//| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file.
|
2022-11-11 18:13:35 +00:00
|
|
|
#include "cwCommon.h"
|
|
|
|
#include "cwLog.h"
|
|
|
|
#include "cwCommonImpl.h"
|
2024-05-29 16:36:57 +00:00
|
|
|
#include "cwTest.h"
|
2022-11-11 18:13:35 +00:00
|
|
|
#include "cwMem.h"
|
|
|
|
#include "cwText.h"
|
|
|
|
#include "cwObject.h"
|
|
|
|
#include "cwMidi.h"
|
2023-11-26 20:32:11 +00:00
|
|
|
#include "cwFile.h"
|
2023-01-31 00:38:11 +00:00
|
|
|
#include "cwFileSys.h"
|
|
|
|
#include "cwMidi.h"
|
|
|
|
#include "cwMidiFile.h"
|
2023-09-13 00:24:50 +00:00
|
|
|
|
|
|
|
#include "cwDynRefTbl.h"
|
|
|
|
#include "cwScoreParse.h"
|
|
|
|
#include "cwSfScore.h"
|
|
|
|
#include "cwSfMatch.h"
|
|
|
|
#include "cwSfTrack.h"
|
|
|
|
#include "cwPerfMeas.h"
|
|
|
|
|
2023-05-25 20:11:34 +00:00
|
|
|
#include "cwScoreFollowerPerf.h"
|
2022-11-11 18:13:35 +00:00
|
|
|
#include "cwScoreFollower.h"
|
2023-05-13 11:55:34 +00:00
|
|
|
#include "cwMidiState.h"
|
2022-11-11 18:13:35 +00:00
|
|
|
|
2023-05-13 11:55:34 +00:00
|
|
|
#include "cwSvgScoreFollow.h"
|
|
|
|
|
2023-09-13 00:24:50 +00:00
|
|
|
|
2022-11-11 18:13:35 +00:00
|
|
|
namespace cw
|
|
|
|
{
|
|
|
|
namespace score_follower
|
|
|
|
{
|
2023-09-13 00:24:50 +00:00
|
|
|
typedef struct meas_set_str
|
|
|
|
{
|
|
|
|
unsigned vsi;
|
|
|
|
unsigned nsi;
|
|
|
|
} meas_set_t;
|
|
|
|
|
2022-11-11 18:13:35 +00:00
|
|
|
typedef struct score_follower_str
|
|
|
|
{
|
2023-06-27 21:26:32 +00:00
|
|
|
bool enableFl;
|
2023-01-31 00:38:11 +00:00
|
|
|
double srate;
|
|
|
|
unsigned search_area_locN;
|
|
|
|
unsigned key_wnd_locN;
|
2023-09-13 00:24:50 +00:00
|
|
|
char* score_csv_fname;
|
2023-05-01 01:17:31 +00:00
|
|
|
|
2023-09-13 00:24:50 +00:00
|
|
|
bool deleteScoreFl;
|
|
|
|
sfscore::handle_t scoreH;
|
|
|
|
sftrack::handle_t trackH;
|
|
|
|
//perf_meas::handle_t measH;
|
2023-05-01 01:17:31 +00:00
|
|
|
|
2023-11-26 20:32:11 +00:00
|
|
|
unsigned* result_idxA; //
|
|
|
|
unsigned result_idx_allocN; //
|
|
|
|
unsigned result_idx_curN; //
|
2023-05-13 11:55:34 +00:00
|
|
|
|
2023-11-26 20:32:11 +00:00
|
|
|
ssf_note_on_t* perfA; // perfA[ perfN ] stored performance
|
|
|
|
unsigned perfN; //
|
|
|
|
unsigned perf_idx;
|
2023-09-13 00:24:50 +00:00
|
|
|
|
|
|
|
dyn_ref_tbl::handle_t dynRefH;
|
|
|
|
|
|
|
|
unsigned track_flags;
|
2023-05-13 11:55:34 +00:00
|
|
|
|
2022-11-11 18:13:35 +00:00
|
|
|
} score_follower_t;
|
|
|
|
|
|
|
|
score_follower_t* _handleToPtr( handle_t h )
|
|
|
|
{ return handleToPtr<handle_t,score_follower_t>(h); }
|
|
|
|
|
2023-05-01 01:17:31 +00:00
|
|
|
|
|
|
|
// parse the score follower parameter record
|
2022-11-11 18:13:35 +00:00
|
|
|
rc_t _parse_cfg( score_follower_t* p, const object_t* cfg )
|
|
|
|
{
|
2023-09-13 00:24:50 +00:00
|
|
|
rc_t rc = kOkRC;
|
|
|
|
const char* score_csv_fname = nullptr;
|
|
|
|
const object_t* dyn_ref_dict = nullptr;
|
|
|
|
bool track_print_fl = false;
|
|
|
|
bool track_results_backtrack_fl = false;
|
2022-11-11 18:13:35 +00:00
|
|
|
|
2023-09-13 00:24:50 +00:00
|
|
|
if((rc = cfg->getv("enable_flag", p->enableFl,
|
|
|
|
"score_csv_fname", score_csv_fname,
|
|
|
|
"search_area_locN", p->search_area_locN,
|
|
|
|
"key_wnd_locN", p->key_wnd_locN,
|
|
|
|
"dyn_ref", dyn_ref_dict,
|
|
|
|
"track_print_fl", track_print_fl,
|
|
|
|
"track_results_backtrack_fl", track_results_backtrack_fl)) != kOkRC )
|
2022-11-11 18:13:35 +00:00
|
|
|
{
|
|
|
|
rc = cwLogError(kInvalidArgRC, "Score follower argument parsing failed.");
|
|
|
|
goto errLabel;
|
|
|
|
}
|
|
|
|
|
2023-09-13 00:24:50 +00:00
|
|
|
if((p->score_csv_fname = filesys::expandPath( score_csv_fname )) == nullptr )
|
2022-11-11 18:13:35 +00:00
|
|
|
{
|
|
|
|
rc = cwLogError(kOpFailRC,"Score follower score file expansion failed.");
|
|
|
|
goto errLabel;
|
|
|
|
}
|
|
|
|
|
2023-09-13 00:24:50 +00:00
|
|
|
if((rc = dyn_ref_tbl::create( p->dynRefH, dyn_ref_dict )) != kOkRC )
|
2023-01-31 00:38:11 +00:00
|
|
|
{
|
2023-09-13 00:24:50 +00:00
|
|
|
rc = cwLogError(rc,"Dynamics reference array parse failed.");
|
2023-05-01 01:17:31 +00:00
|
|
|
goto errLabel;
|
|
|
|
}
|
|
|
|
|
2023-09-13 00:24:50 +00:00
|
|
|
p->track_flags += track_print_fl ? sftrack::kPrintFl : 0;
|
|
|
|
p->track_flags += track_results_backtrack_fl ? sftrack::kBacktrackResultsFl : 0;
|
2023-05-01 01:17:31 +00:00
|
|
|
|
2023-09-13 00:24:50 +00:00
|
|
|
errLabel:
|
2023-05-01 01:17:31 +00:00
|
|
|
return rc;
|
2023-05-13 11:55:34 +00:00
|
|
|
}
|
2023-09-13 00:24:50 +00:00
|
|
|
|
2023-05-13 11:55:34 +00:00
|
|
|
rc_t _update_midi_state( score_follower_t* p, midi_state::handle_t msH )
|
|
|
|
{
|
|
|
|
rc_t rc;
|
|
|
|
for(unsigned i=0; i<p->perf_idx; ++i)
|
|
|
|
{
|
|
|
|
if((rc = setMidiMsg( msH, p->perfA[i].sec, i, 0, midi::kNoteOnMdId, p->perfA[i].pitch, p->perfA[i].vel )) != kOkRC )
|
|
|
|
{
|
|
|
|
rc = cwLogError(rc,"midi_state update failed.");
|
|
|
|
goto errLabel;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
errLabel:
|
|
|
|
return rc;
|
|
|
|
}
|
2023-05-01 01:17:31 +00:00
|
|
|
|
2023-12-17 13:56:56 +00:00
|
|
|
bool _processPedal(const char* pedalLabel, unsigned barNumb, bool curPedalDownStateFl, uint8_t d1)
|
|
|
|
{
|
|
|
|
bool newStateFl = midi::isPedalDown(d1);
|
|
|
|
/*
|
|
|
|
if( newStateFl == curPedalDownStateFl )
|
|
|
|
{
|
|
|
|
const char* upDownLabel = newStateFl ? "down" : "up";
|
|
|
|
cwLogWarning("Double %s pedal %s in bar number:%i.",pedalLabel,upDownLabel,barNumb);
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
|
|
|
return newStateFl;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-11-11 18:13:35 +00:00
|
|
|
rc_t _destroy( score_follower_t* p)
|
|
|
|
{
|
2023-09-13 00:24:50 +00:00
|
|
|
destroy(p->dynRefH);
|
|
|
|
mem::release(p->score_csv_fname);
|
2023-05-13 11:55:34 +00:00
|
|
|
mem::release(p->perfA);
|
2023-09-13 00:24:50 +00:00
|
|
|
mem::release(p->result_idxA);
|
|
|
|
destroy(p->trackH);
|
|
|
|
if( p->deleteScoreFl)
|
|
|
|
destroy(p->scoreH);
|
|
|
|
//destroy(p->measH);
|
2023-04-11 12:20:18 +00:00
|
|
|
mem::release(p);
|
2022-11-11 18:13:35 +00:00
|
|
|
return kOkRC;
|
|
|
|
}
|
2023-01-31 00:38:11 +00:00
|
|
|
|
2023-09-13 00:24:50 +00:00
|
|
|
void _score_follower_cb( void* arg, sftrack::result_t* r )
|
2023-01-31 00:38:11 +00:00
|
|
|
{
|
|
|
|
score_follower_t* p = (score_follower_t*)arg;
|
|
|
|
|
2023-09-13 00:24:50 +00:00
|
|
|
//printf("%4i %4i %4i %3i %3i %4s : ", r->oLocId, r->mni, r->muid, r->flags, r->pitch, midi::midiToSciPitch( r->pitch, nullptr, 0 ));
|
2023-04-11 12:20:18 +00:00
|
|
|
|
2023-09-13 00:24:50 +00:00
|
|
|
if( r->scEvtIdx == kInvalidIdx )
|
2023-01-31 00:38:11 +00:00
|
|
|
{
|
2023-05-01 01:17:31 +00:00
|
|
|
//cwLogInfo("Score Follower: MISS");
|
2023-01-31 00:38:11 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2023-09-13 00:24:50 +00:00
|
|
|
const sfscore::event_t* score_event;
|
|
|
|
|
|
|
|
// get a pointer to the matched score event
|
|
|
|
if((score_event = event( p->scoreH, r->scEvtIdx )) == nullptr )
|
2023-01-31 00:38:11 +00:00
|
|
|
{
|
2023-04-11 12:20:18 +00:00
|
|
|
cwLogError(kInvalidStateRC,"cm Score event index (%i) reported by the score follower is invalid.",r->scEvtIdx );
|
2023-01-31 00:38:11 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2023-09-13 00:24:50 +00:00
|
|
|
// verify that the matched event buffer has available space
|
|
|
|
if( p->result_idx_curN >= p->result_idx_allocN )
|
2023-04-11 12:20:18 +00:00
|
|
|
{
|
|
|
|
cwLogError(kInvalidStateRC,"The score follower match id array is full.");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2023-09-13 00:24:50 +00:00
|
|
|
assert( score_event->index == r->scEvtIdx );
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
// store the performance data in the score
|
|
|
|
set_perf( p->scoreH, score_event->index, r->sec, r->pitch, r->vel, r->cost );
|
|
|
|
|
|
|
|
perf_meas::result_t pmr = {0};
|
|
|
|
|
|
|
|
// Call performance measurement unit
|
|
|
|
if( perf_meas::exec( p->measH, score_event, pmr ) == kOkRC && pmr.loc != kInvalidIdx && pmr.valueA != nullptr )
|
|
|
|
{
|
|
|
|
double v[ perf_meas::kValCnt ];
|
|
|
|
for(unsigned i=0; i<perf_meas::kValCnt; ++i)
|
|
|
|
v[i] = pmr.valueA[i] == std::numeric_limits<double>::max() ? -1 : pmr.valueA[i];
|
|
|
|
|
|
|
|
cwLogInfo("Section '%s' triggered loc:%i : dyn:%f even:%f tempo:%f cost:%f",
|
|
|
|
cwStringNullGuard(pmr.sectionLabel),
|
|
|
|
pmr.sectionLoc,
|
|
|
|
v[ perf_meas::kDynValIdx ],
|
|
|
|
v[ perf_meas::kEvenValIdx ],
|
|
|
|
v[ perf_meas::kTempoValIdx ],
|
|
|
|
v[ perf_meas::kMatchCostValIdx ] );
|
|
|
|
}
|
2023-04-11 12:20:18 +00:00
|
|
|
|
2023-09-13 00:24:50 +00:00
|
|
|
*/
|
2023-04-11 12:20:18 +00:00
|
|
|
|
2023-09-13 00:24:50 +00:00
|
|
|
|
|
|
|
// store a pointer to the m matched sfscore event
|
|
|
|
p->result_idxA[ p->result_idx_curN++ ] = r->index; //score_event->index;
|
2023-04-11 12:20:18 +00:00
|
|
|
}
|
2023-01-31 00:38:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-11-11 18:13:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-13 00:24:50 +00:00
|
|
|
cw::rc_t cw::score_follower::create( handle_t& hRef, const args_t& args )
|
2022-11-11 18:13:35 +00:00
|
|
|
{
|
2023-01-31 00:38:11 +00:00
|
|
|
rc_t rc = kOkRC;
|
2022-11-11 18:13:35 +00:00
|
|
|
|
|
|
|
if((rc = destroy(hRef)) != kOkRC )
|
|
|
|
return rc;
|
|
|
|
|
2023-01-31 00:38:11 +00:00
|
|
|
score_follower_t* p = mem::allocZ<score_follower_t>();
|
2022-11-11 18:13:35 +00:00
|
|
|
|
2023-09-13 00:24:50 +00:00
|
|
|
unsigned track_flags = 0;
|
2022-11-11 18:13:35 +00:00
|
|
|
|
2023-09-13 00:24:50 +00:00
|
|
|
track_flags += args.trackPrintFl ? sftrack::kPrintFl : 0;
|
|
|
|
track_flags += args.trackResultsBacktrackFl ? sftrack::kBacktrackResultsFl : 0;
|
|
|
|
|
|
|
|
p->scoreH = args.scoreH;
|
|
|
|
|
|
|
|
// create the score tracker
|
|
|
|
if((rc = sftrack::create( p->trackH, args.scoreH, args.scoreWndLocN, args.midiWndLocN, track_flags, _score_follower_cb, p )) != kOkRC )
|
2023-01-31 00:38:11 +00:00
|
|
|
{
|
2023-09-13 00:24:50 +00:00
|
|
|
cwLogError(kOpFailRC,"The score follower create failed.");
|
2023-01-31 00:38:11 +00:00
|
|
|
goto errLabel;
|
|
|
|
}
|
2023-05-01 01:17:31 +00:00
|
|
|
|
2023-09-13 00:24:50 +00:00
|
|
|
p->srate = sample_rate(p->scoreH);
|
|
|
|
p->result_idx_allocN = event_count( p->scoreH )*2; // give plenty of extra space for the result_idxA[]
|
|
|
|
p->result_idxA = mem::allocZ<unsigned>(p->result_idx_allocN);
|
|
|
|
|
|
|
|
p->perfN = event_count(p->scoreH)*2;
|
|
|
|
p->perfA = mem::allocZ<ssf_note_on_t>( p->perfN );
|
|
|
|
p->perf_idx = 0;
|
|
|
|
p->enableFl = args.enableFl;
|
|
|
|
hRef.set(p);
|
|
|
|
|
|
|
|
|
|
|
|
errLabel:
|
|
|
|
if(rc != kOkRC )
|
|
|
|
_destroy(p);
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
cw::rc_t cw::score_follower::create( handle_t& hRef, const object_t* cfg, double srate )
|
|
|
|
{
|
|
|
|
rc_t rc = kOkRC;
|
|
|
|
|
|
|
|
if((rc = destroy(hRef)) != kOkRC )
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
score_follower_t* p = mem::allocZ<score_follower_t>();
|
|
|
|
|
|
|
|
if((rc = _parse_cfg(p,cfg)) != kOkRC )
|
2023-01-31 00:38:11 +00:00
|
|
|
goto errLabel;
|
2023-09-13 00:24:50 +00:00
|
|
|
|
|
|
|
// create the score
|
|
|
|
if((rc = sfscore::create( p->scoreH, p->score_csv_fname, srate, p->dynRefH )) != kOkRC )
|
|
|
|
{
|
|
|
|
rc = cwLogError(kOpFailRC,"The score could not be initialized from '%s'. cmRC:%i.",cwStringNullGuard(p->score_csv_fname));
|
|
|
|
goto errLabel;
|
2023-01-31 00:38:11 +00:00
|
|
|
}
|
2023-09-13 00:24:50 +00:00
|
|
|
|
|
|
|
p->deleteScoreFl = true;
|
2022-11-11 18:13:35 +00:00
|
|
|
|
2023-09-13 00:24:50 +00:00
|
|
|
// create the score tracker
|
|
|
|
if((rc = sftrack::create( p->trackH, p->scoreH, p->search_area_locN, p->key_wnd_locN, p->track_flags, _score_follower_cb, p )) != kOkRC )
|
2023-05-01 01:17:31 +00:00
|
|
|
{
|
2023-09-13 00:24:50 +00:00
|
|
|
cwLogError(kOpFailRC,"The score follower create failed.");
|
2023-05-01 01:17:31 +00:00
|
|
|
goto errLabel;
|
|
|
|
}
|
2023-09-13 00:24:50 +00:00
|
|
|
/*
|
|
|
|
if((rc = perf_meas::create( p->measH, p->scoreH )) != kOkRC )
|
2023-05-01 01:17:31 +00:00
|
|
|
{
|
2023-09-13 00:24:50 +00:00
|
|
|
cwLogError(kOpFailRC,"The perf. measure object create failed.");
|
|
|
|
goto errLabel;
|
2023-05-01 01:17:31 +00:00
|
|
|
}
|
2023-09-13 00:24:50 +00:00
|
|
|
*/
|
2023-05-01 01:17:31 +00:00
|
|
|
p->srate = srate;
|
2023-09-13 00:24:50 +00:00
|
|
|
p->result_idx_allocN = event_count( p->scoreH )*2; // give plenty of extra space for the result_idxA[]
|
|
|
|
p->result_idxA = mem::allocZ<unsigned>(p->result_idx_allocN);
|
2023-05-13 11:55:34 +00:00
|
|
|
|
2023-09-13 00:24:50 +00:00
|
|
|
p->perfN = event_count(p->scoreH)*2;
|
2023-05-13 11:55:34 +00:00
|
|
|
p->perfA = mem::allocZ<ssf_note_on_t>( p->perfN );
|
|
|
|
p->perf_idx = 0;
|
2023-09-13 00:24:50 +00:00
|
|
|
|
2022-11-11 18:13:35 +00:00
|
|
|
hRef.set(p);
|
2023-09-13 00:24:50 +00:00
|
|
|
|
2022-11-11 18:13:35 +00:00
|
|
|
errLabel:
|
|
|
|
if( rc != kOkRC )
|
|
|
|
{
|
|
|
|
_destroy(p);
|
|
|
|
cwLogError(rc,"Score follower create failed.");
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
cw::rc_t cw::score_follower::destroy( handle_t& hRef )
|
|
|
|
{
|
|
|
|
rc_t rc = kOkRC;
|
|
|
|
score_follower_t* p = nullptr;
|
|
|
|
|
|
|
|
if( !hRef.isValid() )
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
p = _handleToPtr(hRef);
|
|
|
|
|
|
|
|
if((rc = _destroy(p)) != kOkRC )
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
hRef.clear();
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2023-06-27 21:26:32 +00:00
|
|
|
bool cw::score_follower::is_enabled( handle_t h )
|
|
|
|
{
|
2023-11-26 20:32:11 +00:00
|
|
|
score_follower_t* p = _handleToPtr(h);
|
2023-06-27 21:26:32 +00:00
|
|
|
return p->enableFl;
|
|
|
|
}
|
|
|
|
|
2023-11-26 20:32:11 +00:00
|
|
|
void cw::score_follower::enable( handle_t h, bool enable_fl )
|
|
|
|
{
|
|
|
|
score_follower_t* p = _handleToPtr(h);
|
|
|
|
p->enableFl = enable_fl;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-09-13 00:24:50 +00:00
|
|
|
cw::rc_t cw::score_follower::reset( handle_t h, unsigned locId )
|
2022-11-11 18:13:35 +00:00
|
|
|
{
|
2023-04-11 12:20:18 +00:00
|
|
|
rc_t rc = kOkRC;
|
|
|
|
score_follower_t* p = _handleToPtr(h);
|
2023-05-01 01:17:31 +00:00
|
|
|
|
2023-09-13 00:24:50 +00:00
|
|
|
if( locId != kInvalidId )
|
2023-04-11 12:20:18 +00:00
|
|
|
{
|
2023-05-01 01:17:31 +00:00
|
|
|
|
2023-11-26 20:32:11 +00:00
|
|
|
cwLogInfo("SF Reset: loc:%i",locId);
|
2023-09-13 00:24:50 +00:00
|
|
|
|
|
|
|
if((rc = reset( p->trackH, locId )) != kOkRC )
|
2023-05-01 01:17:31 +00:00
|
|
|
{
|
2023-09-13 00:24:50 +00:00
|
|
|
rc = cwLogError(rc,"The score follower reset failed.");
|
2023-05-01 01:17:31 +00:00
|
|
|
goto errLabel;
|
|
|
|
}
|
2023-05-13 11:55:34 +00:00
|
|
|
|
2023-09-13 00:24:50 +00:00
|
|
|
/*
|
|
|
|
if((rc = reset( p->measH, locId )) != kOkRC )
|
2023-05-01 01:17:31 +00:00
|
|
|
{
|
2023-09-13 00:24:50 +00:00
|
|
|
rc = cwLogError(rc,"The measurement unit reset failed.");
|
2023-05-01 01:17:31 +00:00
|
|
|
goto errLabel;
|
|
|
|
}
|
2023-09-13 00:24:50 +00:00
|
|
|
*/
|
|
|
|
|
2023-05-13 11:55:34 +00:00
|
|
|
p->perf_idx = 0;
|
2023-09-13 00:24:50 +00:00
|
|
|
clear_result_index_array(h);
|
|
|
|
clear_all_performance_data(p->scoreH);
|
|
|
|
|
2023-04-11 12:20:18 +00:00
|
|
|
}
|
2023-05-01 01:17:31 +00:00
|
|
|
|
2023-04-11 12:20:18 +00:00
|
|
|
errLabel:
|
2022-11-11 18:13:35 +00:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2023-09-13 00:24:50 +00:00
|
|
|
cw::rc_t cw::score_follower::exec( handle_t h,
|
|
|
|
double sec,
|
|
|
|
unsigned smpIdx,
|
|
|
|
unsigned muid,
|
|
|
|
unsigned status,
|
|
|
|
uint8_t d0,
|
|
|
|
uint8_t d1,
|
|
|
|
bool& newMatchFlRef )
|
2022-11-11 18:13:35 +00:00
|
|
|
{
|
2023-09-13 00:24:50 +00:00
|
|
|
rc_t rc = kOkRC;
|
|
|
|
score_follower_t* p = _handleToPtr(h);
|
|
|
|
unsigned scLocIdx = kInvalidIdx;
|
|
|
|
unsigned pre_result_idx_curN = p->result_idx_curN;
|
2023-01-31 00:38:11 +00:00
|
|
|
|
2023-06-27 21:26:32 +00:00
|
|
|
if( !p->enableFl )
|
|
|
|
return cwLogError(kInvalidStateRC,"The score follower is not enabled.");
|
|
|
|
|
2023-01-31 00:38:11 +00:00
|
|
|
newMatchFlRef = false;
|
2023-05-13 11:55:34 +00:00
|
|
|
|
2024-03-06 14:27:31 +00:00
|
|
|
// This call results in a callback to: _score_follower_cb()
|
2023-05-13 11:55:34 +00:00
|
|
|
// Note: pass p->perf_idx as 'muid' to the score follower
|
2023-09-13 00:24:50 +00:00
|
|
|
rc = exec( p->trackH, sec, smpIdx, p->perf_idx, status, d0, d1, &scLocIdx );
|
2023-05-01 01:17:31 +00:00
|
|
|
|
2023-09-13 00:24:50 +00:00
|
|
|
switch( rc )
|
2023-01-31 00:38:11 +00:00
|
|
|
{
|
2023-09-13 00:24:50 +00:00
|
|
|
case kOkRC:
|
|
|
|
newMatchFlRef = p->result_idx_curN != pre_result_idx_curN;
|
2023-05-01 01:17:31 +00:00
|
|
|
//printf("NM_FL:%i\n",newMatchFlRef);
|
|
|
|
break;
|
2023-01-31 00:38:11 +00:00
|
|
|
|
2023-09-13 00:24:50 +00:00
|
|
|
case kEofRC:
|
2023-05-01 01:17:31 +00:00
|
|
|
rc = cwLogInfo("Score match complete.");
|
|
|
|
break;
|
2023-01-31 00:38:11 +00:00
|
|
|
|
2023-09-13 00:24:50 +00:00
|
|
|
case kInvalidArgRC:
|
2023-05-01 01:17:31 +00:00
|
|
|
rc = cwLogError(kInvalidStateRC,"Score follower state is invalid.");
|
|
|
|
break;
|
2023-01-31 00:38:11 +00:00
|
|
|
|
2023-09-13 00:24:50 +00:00
|
|
|
case kOpFailRC:
|
2023-05-01 01:17:31 +00:00
|
|
|
rc = cwLogError(kOpFailRC,"The score follower failed during a resync attempt.");
|
|
|
|
break;
|
2023-01-31 00:38:11 +00:00
|
|
|
|
2023-05-01 01:17:31 +00:00
|
|
|
default:
|
2023-09-13 00:24:50 +00:00
|
|
|
rc = cwLogError(rc,"The score follower failed with an the error:%i",rc);
|
2023-01-31 00:38:11 +00:00
|
|
|
}
|
2023-05-13 11:55:34 +00:00
|
|
|
|
|
|
|
// store note-on messages
|
|
|
|
if( p->perf_idx < p->perfN && midi::isNoteOn(status,(unsigned)d1) )
|
|
|
|
{
|
|
|
|
ssf_note_on_t* pno = p->perfA + p->perf_idx;
|
2023-09-13 00:24:50 +00:00
|
|
|
pno->sec = sec;
|
|
|
|
pno->muid = muid;
|
|
|
|
pno->pitch = d0;
|
|
|
|
pno->vel = d1;
|
2023-05-13 11:55:34 +00:00
|
|
|
p->perf_idx += 1;
|
|
|
|
if( p->perf_idx >= p->perfN )
|
|
|
|
cwLogWarning("The cw score follower performance cache is full.");
|
|
|
|
}
|
2023-05-01 01:17:31 +00:00
|
|
|
|
2022-11-11 18:13:35 +00:00
|
|
|
return rc;
|
|
|
|
}
|
2023-01-31 00:38:11 +00:00
|
|
|
|
2023-09-13 00:24:50 +00:00
|
|
|
const unsigned* cw::score_follower::current_result_index_array( handle_t h, unsigned& cur_result_idx_array_cnt_ref )
|
2023-01-31 00:38:11 +00:00
|
|
|
{
|
|
|
|
score_follower_t* p = _handleToPtr(h);
|
2023-09-13 00:24:50 +00:00
|
|
|
cur_result_idx_array_cnt_ref = p->result_idx_curN;
|
|
|
|
return p->result_idxA;
|
2023-01-31 00:38:11 +00:00
|
|
|
}
|
|
|
|
|
2023-09-13 00:24:50 +00:00
|
|
|
void cw::score_follower::clear_result_index_array( handle_t h )
|
2023-01-31 00:38:11 +00:00
|
|
|
{
|
|
|
|
score_follower_t* p = _handleToPtr(h);
|
2023-09-13 00:24:50 +00:00
|
|
|
p->result_idx_curN = 0;
|
2023-01-31 00:38:11 +00:00
|
|
|
}
|
2023-09-13 00:24:50 +00:00
|
|
|
/*
|
2023-05-21 16:41:50 +00:00
|
|
|
cw::rc_t cw::score_follower::cw_loc_range( handle_t h, unsigned& minLocRef, unsigned& maxLocRef )
|
|
|
|
{
|
|
|
|
rc_t rc = kOkRC;
|
|
|
|
score_follower_t* p = _handleToPtr(h);
|
|
|
|
|
2023-09-13 00:24:50 +00:00
|
|
|
minLocRef = 0;
|
|
|
|
maxLocRef = loc_count(p->scoreH);
|
2023-05-21 16:41:50 +00:00
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool cw::score_follower::is_loc_in_range( handle_t h, unsigned loc )
|
|
|
|
{
|
|
|
|
rc_t rc;
|
|
|
|
unsigned minLoc = 0;
|
|
|
|
unsigned maxLoc = 0;
|
|
|
|
|
|
|
|
if((rc = cw_loc_range(h,minLoc,maxLoc)) != kOkRC )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return minLoc <= loc && loc <= maxLoc;
|
|
|
|
}
|
2023-09-13 00:24:50 +00:00
|
|
|
*/
|
2023-05-21 16:41:50 +00:00
|
|
|
unsigned cw::score_follower::has_stored_performance( handle_t h )
|
|
|
|
{
|
|
|
|
score_follower_t* p = _handleToPtr(h);
|
2023-09-13 00:24:50 +00:00
|
|
|
return p->perf_idx > 0 && result_count(p->trackH) > 0;
|
2023-05-25 20:11:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
cw::rc_t cw::score_follower::sync_perf_to_score( handle_t h )
|
|
|
|
{
|
2023-09-13 00:24:50 +00:00
|
|
|
rc_t rc = kOkRC;
|
|
|
|
score_follower_t* p = _handleToPtr(h);
|
|
|
|
unsigned resultN = 0;
|
|
|
|
const sftrack::result_t* resultA = nullptr;
|
|
|
|
|
2023-05-25 20:11:34 +00:00
|
|
|
if( !has_stored_performance(h) )
|
|
|
|
{
|
|
|
|
rc = cwLogError(kInvalidStateRC,"No performance to sync.");
|
|
|
|
goto errLabel;
|
|
|
|
}
|
2023-09-13 00:24:50 +00:00
|
|
|
|
|
|
|
resultN = result_count(p->trackH);
|
|
|
|
resultA = result_base(p->trackH);
|
2023-05-25 20:11:34 +00:00
|
|
|
|
2023-09-13 00:24:50 +00:00
|
|
|
for(unsigned i=0; i<resultN; ++i)
|
2023-05-25 20:11:34 +00:00
|
|
|
{
|
2023-09-13 00:24:50 +00:00
|
|
|
unsigned perf_idx = resultA[i].muid;
|
2023-05-25 20:11:34 +00:00
|
|
|
|
|
|
|
// the matcher result 'muid' is the perf. array index of the matching perf. record
|
|
|
|
if( perf_idx >= p->perf_idx )
|
|
|
|
{
|
|
|
|
rc = cwLogError(kInvalidStateRC,"Inconsistent match to perf. map: index mismatch.");
|
|
|
|
goto errLabel;
|
|
|
|
}
|
|
|
|
|
|
|
|
// verify the pitch of the matching records
|
2023-09-13 00:24:50 +00:00
|
|
|
if( p->perfA[ perf_idx ].pitch != resultA[i].pitch )
|
2023-05-25 20:11:34 +00:00
|
|
|
{
|
2023-09-13 00:24:50 +00:00
|
|
|
rc = cwLogError(kInvalidStateRC,"Inconsistent match to perf. map: pitch mismatch %i != %i.",p->perfA[ perf_idx ].pitch ,resultA[i].pitch);
|
2023-05-25 20:11:34 +00:00
|
|
|
goto errLabel;
|
|
|
|
}
|
|
|
|
|
2023-09-13 00:24:50 +00:00
|
|
|
//assert( resultA[i].scEvtIdx == kInvalidIdx || resultA[i].scEvtIdx < p->cmLocToCwLocN );
|
|
|
|
|
|
|
|
// if( resultA[i].scEvtIdx != kInvalidIdx )
|
|
|
|
// p->perfA[ perf_idx ].loc = p->cmLocToCwLocA[ resultA[i].scEvtIdx ];
|
2023-05-25 20:11:34 +00:00
|
|
|
|
2023-09-13 00:24:50 +00:00
|
|
|
if( resultA[i].scEvtIdx != kInvalidIdx )
|
|
|
|
p->perfA[ perf_idx ].loc = resultA[i].oLocId;
|
2023-05-25 20:11:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
errLabel:
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2023-09-13 00:24:50 +00:00
|
|
|
unsigned cw::score_follower::track_result_count( handle_t h )
|
|
|
|
{
|
|
|
|
score_follower_t* p = _handleToPtr(h);
|
|
|
|
return result_count(p->trackH);
|
|
|
|
}
|
|
|
|
|
|
|
|
const cw::sftrack::result_t* cw::score_follower::track_result( handle_t h )
|
|
|
|
{
|
|
|
|
score_follower_t* p = _handleToPtr(h);
|
|
|
|
return result_base(p->trackH);
|
|
|
|
}
|
|
|
|
|
2023-05-25 20:11:34 +00:00
|
|
|
unsigned cw::score_follower::perf_count( handle_t h )
|
|
|
|
{
|
|
|
|
score_follower_t* p = _handleToPtr(h);
|
2023-09-13 00:24:50 +00:00
|
|
|
return p->perf_idx;
|
2023-05-21 16:41:50 +00:00
|
|
|
}
|
|
|
|
|
2023-05-25 20:11:34 +00:00
|
|
|
const cw::score_follower::ssf_note_on_t* cw::score_follower::perf_base( handle_t h )
|
|
|
|
{
|
|
|
|
score_follower_t* p = _handleToPtr(h);
|
|
|
|
return p->perfA;
|
|
|
|
}
|
2023-05-21 16:41:50 +00:00
|
|
|
|
2023-05-16 13:14:20 +00:00
|
|
|
cw::rc_t cw::score_follower::write_svg_file( handle_t h, const char* out_fname, bool show_muid_fl )
|
2023-05-13 11:55:34 +00:00
|
|
|
{
|
|
|
|
score_follower_t* p = _handleToPtr(h);
|
|
|
|
|
2023-09-13 00:24:50 +00:00
|
|
|
return svgScoreFollowWrite( p->scoreH, p->trackH, p->perfA, p->perf_idx, out_fname, show_muid_fl );
|
2023-05-13 11:55:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void cw::score_follower::score_report( handle_t h, const char* out_fname )
|
|
|
|
{
|
|
|
|
score_follower_t* p = _handleToPtr(h);
|
2023-09-13 00:24:50 +00:00
|
|
|
report(p->scoreH,out_fname);
|
2023-05-13 11:55:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
cw::rc_t cw::score_follower::midi_state_rt_report( handle_t h, const char* out_fname )
|
|
|
|
{
|
|
|
|
score_follower_t* p = _handleToPtr(h);
|
|
|
|
midi_state::config_t msCfg = midi_state::default_config();
|
|
|
|
rc_t rc = kOkRC;
|
|
|
|
midi_state::handle_t msH;
|
|
|
|
|
|
|
|
if((rc = midi_state::create(msH,nullptr,nullptr,&msCfg)) != kOkRC )
|
|
|
|
{
|
|
|
|
rc = cwLogError(rc,"midi_state create failed.");
|
|
|
|
goto errLabel;
|
|
|
|
}
|
|
|
|
|
|
|
|
if((rc = _update_midi_state(p,msH)) != kOkRC )
|
|
|
|
goto errLabel;
|
|
|
|
|
|
|
|
if((rc = report_events(msH,out_fname)) != kOkRC )
|
|
|
|
goto errLabel;
|
|
|
|
|
|
|
|
errLabel:
|
|
|
|
if( rc != kOkRC )
|
|
|
|
cwLogError(rc,"Score follower midi_state_rt_report() failed.");
|
|
|
|
|
|
|
|
midi_state::destroy(msH);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2023-11-26 20:32:11 +00:00
|
|
|
cw::rc_t cw::score_follower::write_sync_perf_csv( handle_t h, const char* out_fname, const midi::file::trackMsg_t** msgA, unsigned msgN )
|
|
|
|
{
|
2023-12-17 13:56:56 +00:00
|
|
|
score_follower_t* p = _handleToPtr(h);
|
|
|
|
rc_t rc = kOkRC;
|
|
|
|
unsigned resultN = result_count(p->trackH);
|
|
|
|
auto resultA = result_base(p->trackH);
|
|
|
|
bool dampPedalDownFl = false;
|
|
|
|
bool sostPedalDownFl = false;
|
|
|
|
bool softPedalDownFl = false;
|
|
|
|
unsigned curBarNumb = 1;
|
2023-11-26 20:32:11 +00:00
|
|
|
file::handle_t fH;
|
|
|
|
|
|
|
|
if( msgN == 0 )
|
|
|
|
{
|
|
|
|
cwLogWarning("Nothing to write.");
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( resultN == 0 )
|
|
|
|
{
|
|
|
|
cwLogWarning("The score follower does not have any score sync. info.");
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
// open the file
|
|
|
|
if((rc = file::open(fH,out_fname,file::kWriteFl)) != kOkRC )
|
|
|
|
{
|
|
|
|
rc = cwLogError(kOpenFailRC,"Unable to create the file '%s'.",cwStringNullGuard(out_fname));
|
|
|
|
goto errLabel;
|
|
|
|
}
|
|
|
|
|
|
|
|
// write the header line
|
2023-12-17 13:56:56 +00:00
|
|
|
file::printf(fH,"meas,index,voice,loc,tick,sec,dur,rval,dots,sci_pitch,dmark,dlevel,status,d0,d1,bar,section,bpm,grace,damp_down_fl,soft_down_fl,sost_down_fl\n");
|
2023-11-26 20:32:11 +00:00
|
|
|
|
|
|
|
for(unsigned i=0; i<msgN; ++i)
|
|
|
|
{
|
|
|
|
const midi::file::trackMsg_t* m = msgA[i];
|
|
|
|
|
|
|
|
double secs = (msgA[i]->amicro - msgA[0]->amicro)/1000000.0;
|
|
|
|
|
|
|
|
// write the event line
|
|
|
|
if( midi::isChStatus(m->status) )
|
|
|
|
{
|
|
|
|
uint8_t d0 = m->u.chMsgPtr->d0;
|
|
|
|
uint8_t d1 = m->u.chMsgPtr->d1;
|
2023-12-17 13:56:56 +00:00
|
|
|
|
2023-11-26 20:32:11 +00:00
|
|
|
|
|
|
|
if( midi::isNoteOn(m->status,d1) )
|
2023-12-17 13:56:56 +00:00
|
|
|
{
|
2024-02-08 16:09:40 +00:00
|
|
|
unsigned bar = 0;
|
|
|
|
const char* sectionLabel = "";
|
|
|
|
unsigned loc = score_parse::kInvalidLocId;
|
|
|
|
unsigned dlevel = -1;
|
2023-12-17 13:56:56 +00:00
|
|
|
char sciPitch[ midi::kMidiSciPitchCharCnt + 1 ];
|
2023-11-26 20:32:11 +00:00
|
|
|
|
2023-12-17 13:56:56 +00:00
|
|
|
midi::midiToSciPitch( d0, sciPitch, midi::kMidiSciPitchCharCnt );
|
|
|
|
|
|
|
|
// locate score matching record for this performed note
|
2023-11-26 20:32:11 +00:00
|
|
|
for(unsigned i=0; i<resultN; ++i)
|
|
|
|
{
|
2023-12-17 13:56:56 +00:00
|
|
|
const sfscore::event_t* e;
|
2023-11-26 20:32:11 +00:00
|
|
|
// FIX THIS:
|
|
|
|
// THE perfA[] INDEX IS STORED IN resultA[i].muid
|
2023-12-17 13:56:56 +00:00
|
|
|
// this isn't right.
|
2023-11-26 20:32:11 +00:00
|
|
|
assert( resultA[i].muid != kInvalidIdx && resultA[i].muid < p->perfN );
|
|
|
|
|
2023-12-17 13:56:56 +00:00
|
|
|
if( p->perfA[resultA[i].muid].muid == m->uid && resultA[i].scEvtIdx != kInvalidIdx)
|
2023-11-26 20:32:11 +00:00
|
|
|
{
|
|
|
|
assert( resultA[i].pitch == d0 );
|
2023-12-17 13:56:56 +00:00
|
|
|
|
|
|
|
if((e = event( p->scoreH, resultA[i].scEvtIdx )) == nullptr )
|
|
|
|
{
|
|
|
|
cwLogError(kInvalidStateRC,"The performed, and matched, note with muid %i does not have a valid score event index.",m->uid);
|
|
|
|
goto errLabel;
|
|
|
|
}
|
|
|
|
|
2024-02-08 16:09:40 +00:00
|
|
|
bar = e->barNumb;
|
|
|
|
sectionLabel = e->section != nullptr ? e->section->label : "";
|
|
|
|
curBarNumb = std::max(bar,curBarNumb);
|
|
|
|
dlevel = e->dynLevel;
|
2024-03-25 14:50:11 +00:00
|
|
|
loc = resultA[i].oLocId == kInvalidId ? (unsigned)score_parse::kInvalidLocId : resultA[i].oLocId;
|
2023-11-26 20:32:11 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2023-12-17 13:56:56 +00:00
|
|
|
|
|
|
|
|
2023-11-26 20:32:11 +00:00
|
|
|
|
2024-02-08 16:09:40 +00:00
|
|
|
rc = file::printf(fH, "%i,%i,%i,%i,0,%f,0.0,0.0,0,%s,,%i,%i,%i,%i,,%s,,,%i,%i,%i\n",
|
|
|
|
bar,i,1,loc,secs,sciPitch,dlevel,m->status,d0,d1,sectionLabel,dampPedalDownFl,softPedalDownFl,sostPedalDownFl);
|
2023-11-26 20:32:11 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2023-12-17 13:56:56 +00:00
|
|
|
|
|
|
|
if( midi::isPedal(m->status,d0) )
|
|
|
|
{
|
|
|
|
switch( d0 )
|
|
|
|
{
|
|
|
|
case midi::kSustainCtlMdId:
|
|
|
|
dampPedalDownFl = _processPedal("damper",curBarNumb,dampPedalDownFl,d1);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case midi::kSostenutoCtlMdId:
|
|
|
|
sostPedalDownFl = _processPedal("sostenuto",curBarNumb,sostPedalDownFl,d1);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case midi::kSoftPedalCtlMdId:
|
|
|
|
softPedalDownFl = _processPedal("soft",curBarNumb,softPedalDownFl,d1);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = file::printf(fH, ",%i,,,%i,%f,,,,,,,%i,%i,%i,,,,,%i,%i,%i\n",i,0,secs,m->status,d0,d1,dampPedalDownFl,softPedalDownFl,sostPedalDownFl);
|
|
|
|
|
2023-11-26 20:32:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if( rc != kOkRC )
|
|
|
|
{
|
|
|
|
rc = cwLogError(rc,"Write failed on line:%i", i+1 );
|
|
|
|
goto errLabel;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
errLabel:
|
|
|
|
file::close(fH);
|
2023-01-31 00:38:11 +00:00
|
|
|
|
2023-11-26 20:32:11 +00:00
|
|
|
cwLogInfo("Saved %i events to sync perf. file. '%s'.", msgN, out_fname );
|
2023-01-31 00:38:11 +00:00
|
|
|
|
2023-11-26 20:32:11 +00:00
|
|
|
return rc;
|
|
|
|
|
|
|
|
}
|