From 1572e23910ac9a87ed4efdb3c86b93e90434d297 Mon Sep 17 00:00:00 2001 From: kevin Date: Sun, 20 Aug 2023 17:10:45 -0400 Subject: [PATCH] cwScoreParse,cwScoreTest,Makefile : Iniital commit. --- Makefile.am | 13 +- cwScoreParse.cpp | 1114 ++++++++++++++++++++++++++++++++++++++++++++++ cwScoreParse.h | 147 ++++++ cwScoreTest.cpp | 113 +++++ cwScoreTest.h | 8 + 5 files changed, 1391 insertions(+), 4 deletions(-) create mode 100644 cwScoreParse.cpp create mode 100644 cwScoreParse.h create mode 100644 cwScoreTest.cpp create mode 100644 cwScoreTest.h diff --git a/Makefile.am b/Makefile.am index 5e27ade..6fbcf56 100644 --- a/Makefile.am +++ b/Makefile.am @@ -67,15 +67,20 @@ libcwSRC += src/libcw/cwIoMidiRecordPlay.cpp src/libcw/cwIoAudioRecordPlay.cpp libcwHDR += src/libcw/cwIoPresetSelApp.h src/libcw/cwPianoScore.h src/libcw/cwPresetSel.h src/libcw/cwVelTableTuner.h libcwSRC += src/libcw/cwIoPresetSelApp.cpp src/libcw/cwPianoScore.cpp src/libcw/cwPresetSel.cpp src/libcw/cwVelTableTuner.cpp -libcwHDR += src/libcw/cwCmInterface.h src/libcw/cwScoreFollower.h -libcwSRC += src/libcw/cwCmInterface.cpp src/libcw/cwScoreFollower.cpp +libcwHDR += src/libcw/cwScoreFollower.h +libcwSRC += src/libcw/cwScoreFollower.cpp -libcwHDR += src/libcw/cwSfScoreParser.h src/libcw/cwSfScore.h src/libcw/cwSfMatch.h src/libcw/cwSfTrack.h -libcwSRC += src/libcw/cwSfScoreParser.cpp src/libcw/cwSfScore.cpp src/libcw/cwSfMatch.cpp src/libcw/cwSfTrack.cpp +libcwHDR += src/libcw/cwDynRefTbl.h src/libcw/cwScoreParse.h src/libcw/cwSfScore.h src/libcw/cwSfMatch.h src/libcw/cwScoreTest.h +libcwSRC += src/libcw/cwDynRefTbl.cpp src/libcw/cwScoreParse.cpp src/libcw/cwSfScore.cpp src/libcw/cwSfMatch.cpp src/libcw/cwScoreTest.cpp + +libcwHDR += src/libcw/cwSfTrack.h +libcwSRC += src/libcw/cwSfTrack.cpp libcwHDR += src/libcw/cwScoreFollowerPerf.h src/libcw/cwMidiState.h src/libcw/cwSvgMidi.h src/libcw/cwSvgScoreFollow.h libcwSRC += src/libcw/cwMidiState.cpp src/libcw/cwSvgMidi.cpp src/libcw/cwSvgScoreFollow.cpp + + endif diff --git a/cwScoreParse.cpp b/cwScoreParse.cpp new file mode 100644 index 0000000..d0d36ad --- /dev/null +++ b/cwScoreParse.cpp @@ -0,0 +1,1114 @@ +#include "cwCommon.h" +#include "cwLog.h" +#include "cwCommonImpl.h" +#include "cwMem.h" +#include "cwText.h" +#include "cwObject.h" +#include "cwMidi.h" +#include "cwFileSys.h" +#include "cwCsv.h" +#include "cwNumericConvert.h" +#include "cwTime.h" +#include "cwDynRefTbl.h" +#include "cwScoreParse.h" +#include "cwSfScore.h" +#include "cwVectOps.h" + +namespace cw +{ + namespace score_parse + { + typedef struct label_id_str + { + unsigned id; + const char* label; + } label_id_t; + + typedef struct var_ref_str + { + unsigned flags; + unsigned id; + const char* label; + } var_ref_t; + + typedef struct dynRef_str + { + char* label; + unsigned level; + uint8_t vel; + } dynRef_t; + + typedef struct score_parse_str + { + csv::handle_t csvH; + + double srate; + + dyn_ref_tbl::handle_t dynRefH; + + set_t* begSetL; + set_t* endSetL; + + section_t* sectionL; + + unsigned eventAllocN; + unsigned eventN; + event_t* eventA; + + } score_parse_t; + + label_id_t _opcode_ref[] = { + { kBarTId, "bar" }, + { kSectionTId, "sec" }, + { kBpmTId, "bpm" }, + { kNoteOnTId, "non" }, + { kNoteOffTId, "nof" }, + { kPedalTId, "ped" }, + { kRestTId, "rst" }, + { kCtlTId, "ctl" }, + { kInvalidTId, "" } + }; + + var_ref_t _var_ref[] = { + { kDynVarFl, kDynVarIdx, "d" }, + { kDynVarFl | kSetEndVarFl, kDynVarIdx, "D" }, + { kEvenVarFl, kEvenVarIdx, "e" }, + { kEvenVarFl | kSetEndVarFl, kEvenVarIdx, "E" }, + { kTempoVarFl, kTempoVarIdx, "t" }, + { kTempoVarFl| kSetEndVarFl, kTempoVarIdx, "T" }, + { 0, kInvalidIdx, "" } + }; + + + + score_parse_t* _handleToPtr( handle_t h ) + { return handleToPtr(h); } + + rc_t _destroy( score_parse_t* p ) + { + rc_t rc = kOkRC; + + + section_t* s = p->sectionL; + while( s != nullptr ) + { + section_t* s0 = s->link; + mem::release(s->label); + mem::release(s->setA); + mem::release(s); + s = s0; + } + + set_t* set = p->begSetL; + while( set!=nullptr ) + { + set_t* s0 = set->link; + mem::release(set->eventA); + mem::release(set); + set = s0; + } + + + for(unsigned i=0; ieventN; ++i) + mem::release(p->eventA[i].sciPitch); + + mem::release(p->eventA); + mem::release(p); + return rc; + } + + rc_t _parse_bar_row( score_parse_t* p, csv::handle_t csvH, event_t* e ) + { + rc_t rc; + + if((rc = getv(csvH,"bar",e->barNumb)) != kOkRC ) + rc = cwLogError(rc,"Bar row parse failed."); + + return rc; + } + + event_t* _hash_to_event( score_parse_t* p, unsigned hash ) + { + for(unsigned i=0; ieventN; ++i) + if( p->eventA[i].hash == hash ) + return p->eventA + i; + return nullptr; + } + + section_t* _find_section( score_parse_t* p, const char* sectionLabel ) + { + section_t* s = p->sectionL; + for(; s!=nullptr; s=s->link) + if( textIsEqual(s->label,sectionLabel) ) + return s; + + return nullptr; + } + + section_t* _find_or_create_section( score_parse_t* p, const char* sectionLabel ) + { + section_t* s; + if((s = _find_section(p,sectionLabel)) == nullptr ) + { + s = mem::allocZ(); + + s->label = mem::duplStr(sectionLabel); + s->link = p->sectionL; + p->sectionL = s; + } + + //if( s != nullptr ) + // printf("Section:%s\n",s->label); + + return s; + } + + rc_t _parse_section_row( score_parse_t* p, csv::handle_t csvH, event_t* e ) + { + rc_t rc; + const char* sectionLabel = nullptr; + + if((rc = getv(csvH,"section",sectionLabel)) != kOkRC ) + { + rc = cwLogError(rc,"Section row parse failed."); + goto errLabel; + } + + if((e->section = _find_or_create_section(p,sectionLabel)) == nullptr ) + { + rc = cwLogError(kOpFailRC,"Section find/create failed for section: '%s'.",cwStringNullGuard(sectionLabel)); + goto errLabel; + } + + e->section->csvRowNumb = e->csvRowNumb; + + errLabel: + return rc; + } + + rc_t _parse_bpm_row( score_parse_t* p, csv::handle_t csvH, event_t* e ) + { + rc_t rc; + + if((rc = getv(csvH,"bpm",e->bpm)) != kOkRC ) + rc = cwLogError(rc,"BPM row parse failed."); + + return rc; + } + + rc_t _parse_advance_to_var_label_section( const char*& text ) + { + rc_t rc = kOkRC; + + // the label must have at least 3 characters to include both the flag character, a space, and a section identifier + if( textLength(text) < 3 ) + { + text += textLength(text); + return rc; + } + + if((text = nextWhiteChar(text)) != nullptr ) + if((text = nextNonWhiteChar(text)) != nullptr) + if( textLength(text) > 0) + goto errLabel; + + rc = cwLogError(kSyntaxErrorRC,"Parse of var target section failed on '%s'.",cwStringNullGuard(text)); + + errLabel: + return rc; + } + + rc_t _parse_var_label( score_parse_t* p, const char* text, unsigned varIdx, unsigned varFlag, event_t* e ) + { + rc_t rc = kOkRC; + section_t* section = nullptr; + + if( textLength(text) == 0 ) + return rc; + + unsigned flags = var_char_to_flags(text); + + e->varA[ varIdx ].flags = flags; + + if( !cwIsFlag(flags,varFlag) ) + { + rc = cwLogError(kSyntaxErrorRC,"Unknown attribute flag '%s'. Expected: '%s'.",text,var_flags_to_char(varFlag)); + goto errLabel; + } + + // The last set in a group will have a target section id appended to the character identifier + if((rc = _parse_advance_to_var_label_section(text)) != kOkRC ) + goto errLabel; + + // if this set has a target section id + if( textLength(text) > 0 ) + { + if((section = _find_or_create_section(p, text )) == nullptr ) + { + rc = cwLogError(kOpFailRC,"The var target section find/create failed."); + goto errLabel; + } + + e->varA[ varIdx ].target_section = section; + e->varA[ varIdx ].flags |= kSetEndVarFl; // if a target section was included then this is also the 'end' id in the set. + } + errLabel: + return rc; + + } + + rc_t _parse_note_on_row( score_parse_t* p, csv::handle_t csvH, event_t* e ) + { + rc_t rc,rc0,rc1,rc2; + const char* sciPitch = nullptr; + const char* dmark = nullptr; + const char* graceLabel = nullptr; + const char* tieLabel = nullptr; + const char* onsetLabel = nullptr; + const char* dynLabel = nullptr; + const char* evenLabel = nullptr; + const char* tempoLabel = nullptr; + const char* oloc = nullptr; + + if((rc = getv(csvH, + "oloc", oloc, + "rval", e->rval, + "dots", e->dotCnt, + "sci_pitch",sciPitch, + "dmark",dmark, + "status",e->status, + "d0", e->d0, + "d1", e->d1, + "grace", graceLabel, + "tie", tieLabel, + "onset", onsetLabel, + "dyn", dynLabel, + "even", evenLabel, + "tempo", tempoLabel)) != kOkRC ) + { + rc = cwLogError(rc,"Error parsing CSV note-on row."); + goto errLabel; + } + + if( textLength(oloc) > 0 ) + if((rc = string_to_number(oloc,e->oloc)) != kOkRC ) + { + rc = cwLogError(rc,"Error converting oloc (%s) to number.",oloc); + goto errLabel; + } + + rc0 = _parse_var_label(p, dynLabel, kDynVarIdx, kDynVarFl, e ); + rc1 = _parse_var_label(p, evenLabel, kEvenVarIdx, kEvenVarFl, e ); + rc2 = _parse_var_label(p, tempoLabel,kTempoVarIdx, kTempoVarFl, e ); + + if( textIsEqual(graceLabel,"g") ) + e->flags |= kGraceFl; + + if( textIsEqual(tieLabel,"t") ) + e->flags |= kTieBegFl; + + if( textIsEqual(tieLabel,"_") ) + e->flags |= kTieContinueFl; + + if( textIsEqual(tieLabel,"T") ) + e->flags |= kTieEndFl; + + if( textIsEqual(onsetLabel, "o" )) + e->flags |= kOnsetFl; + + if( sciPitch != nullptr ) + e->sciPitch = mem::duplStr(sciPitch); + + if( e->d1 > 0 ) + if((e->dynLevel = marker_to_level(p->dynRefH,dmark)) == kInvalidIdx ) + { + rc = cwLogError(kSyntaxErrorRC,"An invalid dynamic mark (%s) was encountered.",cwStringNullGuard(dmark)); + goto errLabel; + } + + if( cwIsFlag(e->flags,kOnsetFl) ) + { + if( cwIsFlag(e->flags,kTieContinueFl | kTieEndFl) ) + { + rc = cwLogError(kSyntaxErrorRC,"The '%s' event has both an onset flag and tie continue/end flag.",cwStringNullGuard(sciPitch)); + goto errLabel; + } + + } + else + { + if( e->varA[kDynVarIdx].flags ) + { + rc = cwLogError(kSyntaxErrorRC,"The '%s' event has no onset flag but is included in a dynamics set.",cwStringNullGuard(sciPitch)); + goto errLabel; + } + + } + + errLabel: + return rcSelect(rc,rc0,rc1,rc2); + } + + rc_t _parse_note_off_row( score_parse_t* p, csv::handle_t csvH, event_t* e ) + { + rc_t rc; + + if((rc = getv(csvH, + "status",e->status, + "d0",e->d0, + "d1",e->d1)) != kOkRC ) + { + rc = cwLogError(rc,"Error parsing CSV note-off row."); + } + return rc; + } + + rc_t _parse_pedal_row( score_parse_t* p, csv::handle_t csvH, event_t* e ) + { + rc_t rc; + + if((rc = getv(csvH, + "status",e->status, + "d0",e->d0, + "d1",e->d1)) != kOkRC ) + { + rc = cwLogError(rc,"Error parsing CSV pedal row."); + } + return rc; + } + + rc_t _parse_ctl_row( score_parse_t* p, csv::handle_t csvH, event_t* e ) + { + rc_t rc; + + if((rc = getv(csvH, + "status",e->status, + "d0",e->d0, + "d1",e->d1)) != kOkRC ) + { + rc = cwLogError(rc,"Error parsing CSV ctl row."); + } + return rc; + } + + rc_t _parse_events( score_parse_t* p, csv::handle_t csvH ) + { + rc_t rc = kOkRC; + section_t* cur_section = nullptr; + unsigned cur_bar_numb = 1; + unsigned bar_evt_idx = 0; + unsigned barPitchCntV[ midi::kMidiNoteCnt ]; + vop::zero(barPitchCntV,midi::kMidiNoteCnt); + + while((rc = next_line(csvH)) == kOkRC ) + { + const char* opcodeLabel = nullptr; + + if( p->eventN >= p->eventAllocN ) + { + rc = cwLogError(kBufTooSmallRC,"Event array full."); + break; + } + + event_t* e = p->eventA + p->eventN; + + e->oloc = kInvalidIdx; + + if((rc = getv(csvH, + "opcode",opcodeLabel, + "index", e->csvId, + "voice", e->voice, + "loc", e->loc, + "loctn", e->loctn, + "tick", e->tick, + "sec", e->sec )) != kOkRC ) + { + rc = cwLogError(rc,"Error parsing score CSV row." ); + goto errLabel; + } + + e->csvRowNumb = cur_line_index(csvH) + 1; + e->opId = opcode_label_to_id(opcodeLabel); + + switch( e->opId ) + { + case kBarTId: + if((rc = _parse_bar_row(p, csvH, e )) == kOkRC ) + { + cur_bar_numb = e->barNumb; + bar_evt_idx = 0; + vop::zero(barPitchCntV,midi::kMidiNoteCnt); + } + break; + + case kSectionTId: + if((rc = _parse_section_row(p, csvH, e )) == kOkRC ) + { + if( cur_section == nullptr ) + for(event_t* e0 = e-1; e0 >= p->eventA; --e0) + e0->section = e->section; + + cur_section = e->section; + } + break; + + case kBpmTId: + rc = _parse_bpm_row(p,csvH, e ); + break; + + case kNoteOnTId: + if((rc = _parse_note_on_row(p,csvH,e)) == kOkRC ) + { + e->barPitchIdx = barPitchCntV[e->d0]; + + unsigned hash = form_hash(e->opId,cur_bar_numb,e->d0,e->barPitchIdx); + if( _hash_to_event(p,hash) != nullptr ) + { + rc = cwLogError(kInvalidStateRC,"The event hash '%x' is is duplicated.",hash); + break; + } + + e->hash = hash; + barPitchCntV[e->d0] += 1; + } + break; + + case kNoteOffTId: + rc = _parse_note_off_row(p,csvH,e); + break; + + case kPedalTId: + rc = _parse_ctl_row(p,csvH,e); + break; + + case kRestTId: + break; + + case kCtlTId: + rc = _parse_ctl_row(p,csvH,e); + break; + + case kInvalidTId: + rc = cwLogError(kInvalidIdRC,"Invalid opocde '%s'.", cwStringNullGuard(opcodeLabel)); + break; + + default: + rc = cwLogError(kInvalidIdRC,"Unknown opocde '%s'.", cwStringNullGuard(opcodeLabel)); + } + + if( rc != kOkRC ) + break; + + e->section = cur_section; + e->barNumb = cur_bar_numb; + e->barEvtIdx = bar_evt_idx++; + p->eventN += 1; + + } + + errLabel: + switch( rc ) + { + case kOkRC: + assert(0); + break; + + case kEofRC: + rc = kOkRC; + break; + + default: + cwLogError(rc,"CSV parse event failed on row:%i.", cur_line_index(csvH)+1 ); + } + + return rc; + } + + rc_t _parse_csv( score_parse_t* p, const char* fname ) + { + rc_t rc = kOkRC; + const char* titleA[] = { "opcode","meas","index","voice","loc","loctn","oloc","tick","sec", + "dur","rval","dots","sci_pitch","dmark","dlevel","status","d0","d1", + "bar","section","bpm","grace","tie","onset","pedal","dyn","even","tempo" }; + + unsigned titleN = sizeof(titleA)/sizeof(titleA[0]); + csv::handle_t csvH; + + // open the CSV file and validate the title row + if((rc = create( csvH, fname, titleA, titleN )) != kOkRC ) + { + rc = cwLogError(rc,"Score CSV parse failed on '%s'.",fname); + goto errLabel; + } + + // get the line count from the CSV file + if((rc = line_count(csvH,p->eventAllocN)) != kOkRC ) + { + rc = cwLogError(rc,"Score CSV line count failed."); + goto errLabel; + } + + // allocate the event array + p->eventA = mem::allocZ(p->eventAllocN); + + // parse the events + if((rc = _parse_events(p, csvH )) != kOkRC ) + goto errLabel; + + errLabel: + if(rc != kOkRC ) + rc = cwLogError(rc,"CSV parse failed on '%s'.",fname); + destroy(csvH); + return rc; + } + + set_t* _find_set( score_parse_t* p, unsigned setId ) + { + for(set_t* set=p->begSetL; set!=nullptr; set=set->link) + if( set->id == setId ) + return set; + + return nullptr; + } + + set_t* _find_or_create_set( score_parse_t* p, unsigned setId, unsigned varTypeId ) + { + set_t* set; + if((set = _find_set(p,setId)) == nullptr ) + { + set = mem::allocZ(); + set->id = setId; + set->varTypeId = varTypeId; + + if( p->endSetL == nullptr ) + { + p->endSetL = set; + p->begSetL = set; + } + else + { + p->endSetL->link = set; + p->endSetL = set; + } + + } + + return set; + } + + void _create_sets( score_parse_t* p ) + { + set_t* cur_set = nullptr; + unsigned setId = 0; + unsigned setNoteIdx = 0; + unsigned endLoc = kInvalidIdx; + + for(unsigned vi=0; vieventN; ++ei) + { + event_t* e = p->eventA + ei; + + // if an end evt has been located for this set + // and this loc is past the loc of the end event + // then the set is complete + // (this handles the case where there are multiple events + // on the same end set location) + if( endLoc != kInvalidIdx && (e->loctn > endLoc || ei==p->eventN-1) ) + { + cur_set->eventA = mem::allocZ(cur_set->eventN); + setId += 1; + setNoteIdx = 0; + cur_set = nullptr; + endLoc = kInvalidIdx; + } + + if( e->varA[vi].flags != 0 ) + { + + if( cur_set == nullptr ) + cur_set = _find_or_create_set(p,setId,vi); + + e->varA[vi].set = cur_set; + e->varA[vi].setNoteIdx = setNoteIdx++; + cur_set->eventN += 1; + + if( cwIsFlag(e->varA[vi].flags,kSetEndVarFl) ) + endLoc = e->loctn; + } + } + } + + void _fill_sets( score_parse_t* p ) + { + for(unsigned ei=0; eieventN; ++ei) + for(unsigned vi=0; vieventA[ei].varA[vi].set != nullptr ) + { + event_t* e = p->eventA + ei; + + assert( e->varA[vi].setNoteIdx < e->varA[vi].set->eventN ); + + e->varA[vi].set->eventA[ e->varA[vi].setNoteIdx ] = e; + } + + } + + rc_t _validate_sets( score_parse_t* p ) + { + rc_t rc = kOkRC; + for(set_t* set = p->begSetL; set!=nullptr; set=set->link) + { + unsigned loc[] = { kInvalidIdx, kInvalidIdx, kInvalidIdx }; + unsigned locN = sizeof(loc)/sizeof(loc[0]); + unsigned loc_i = 0; + unsigned csvRowNumb = -1; + unsigned barNumb = -1; + + // for each event in this set. + for(unsigned i=0; ieventN; ++i) + { + if( set->eventA[i] == nullptr ) + { + rc = cwLogError(kInvalidStateRC,"The set %i of type %i section:%s.\n", set->id, set->varTypeId, set->targetSection->label ); + continue; + } + + // track the last valid csv row numb + csvRowNumb = set->eventA[i]->csvRowNumb; + barNumb = set->eventA[i]->barNumb; + + // track the number of locations the events in this set occur at + if( loc_i < locN ) + { + unsigned j; + for(j=0; jeventA[i]->loctn ) + break; + + if( j == loc_i ) + loc[ loc_i++ ] = set->eventA[i]->loctn; + } + } + + // 'even' and 'tempo' sets must have at least three events + if( set->varTypeId == kEvenVarIdx && loc_i < locN ) + { + rc = cwLogError(kSyntaxErrorRC,"The 'even' set %i (CSV row:%i bar:%i) of type %i does must have at least 3 events at different locations.",set->id,csvRowNumb,barNumb,set->varTypeId); + } + + if( set->varTypeId == kTempoVarIdx && loc_i < 2 ) + { + rc = cwLogError(kSyntaxErrorRC,"The 'tempo' set %i (CSV row:%i bar:%i) of type %i does must have at least 2 events at different locations.",set->id,csvRowNumb,barNumb,set->varTypeId); + } + } + + if( rc != kOkRC ) + rc = cwLogError(rc,"Var set validation failed."); + + return rc; + } + + void _fill_target_sections( score_parse_t* p ) + { + + for(unsigned vi=0; vieventN; ei>=0; --ei) + if( p->eventA[ei].varA[vi].set != nullptr ) + if( cwIsFlag(p->eventA[ei].varA[vi].flags,kSetEndVarFl) ) + { + if( p->eventA[ei].varA[vi].target_section == nullptr ) + p->eventA[ei].varA[vi].target_section = cur_sec; + else + cur_sec = p->eventA[ei].varA[vi].target_section; + + p->eventA[ei].varA[vi].set->sectionSetIdx = cur_sec->setN; + p->eventA[ei].varA[vi].set->targetSection = cur_sec; + + cur_sec->setN += 1; + + } + } + } + + void _fill_sections( score_parse_t* p ) + { + // allocate memory to hold the set ptr arrays in each section + for(section_t* s = p->sectionL; s!=nullptr; s=s->link) + s->setA = mem::allocZ(s->setN); + + // fill the section->setA[] ptrs + for(set_t* set = p->begSetL; set!=nullptr; set=set->link) + { + assert( set->sectionSetIdx < set->targetSection->setN ); + set->targetSection->setA[ set->sectionSetIdx ] = set; + + // set the section beg/end events + if( set->targetSection->begEvent == nullptr ) + { + set->targetSection->begEvent = set->eventA[0]; + set->targetSection->endEvent = set->eventA[ set->eventN-1 ]; + } + else + { + if( set->eventA[0]->sec < set->targetSection->begEvent->sec ) + set->targetSection->begEvent = set->eventA[0]; + + if( set->eventA[set->eventN-1]->sec > set->targetSection->endEvent->sec ) + set->targetSection->endEvent = set->eventA[ set->eventN-1 ]; + } + } + } + + bool _compare_sections(const section_t* sec0, const section_t* sec1) + { + return sec0->csvRowNumb < sec1->csvRowNumb; + } + + void _sort_sections( score_parse_t* p ) + { + // get count of sections + unsigned secN = 0; + for(section_t* s=p->sectionL; s!=nullptr; s=s->link) + ++secN; + + // load secA[] with sections + section_t** secA = mem::allocZ(secN); + unsigned i = 0; + for(section_t* s=p->sectionL; s!=nullptr; s=s->link) + secA[i++] = s; + + // sort the sections + std::sort( secA, secA+secN, _compare_sections); + + // rebuild the section list in order + section_t* begSec = nullptr; + section_t* endSec = nullptr; + + for(i=0; ilink = nullptr; + + if( begSec == nullptr ) + { + begSec = secA[i]; + endSec = secA[i]; + } + else + { + endSec->link = secA[i]; + endSec = secA[i]; + } + } + + p->sectionL = begSec; + + mem::release(secA); + } + + rc_t _validate_sections( score_parse_t* p ) + { + rc_t rc = kOkRC; + + section_t* s0 = nullptr; + + for(section_t* s=p->sectionL; s!=nullptr; s = s->link) + { + if( s->setN == 0 ) + { + cwLogWarning("The section '%s' does not have any sets assigned to it.",cwStringNullGuard(s->label)); + } + else + if( s->begEvent == nullptr || s->endEvent == nullptr ) + { + rc = cwLogError(kInvalidStateRC,"The section '%s' does not beg/end events.",cwStringNullGuard(s->label)); + continue; + } + + if( s0 != nullptr && (textCompare( s0->label, s->label ) >= 0 || s0->csvRowNumb > s->csvRowNumb )) + { + rc = cwLogError(kInvalidStateRC,"The section label '%s' is out of order with '%s'.",cwStringNullGuard(s->label),cwStringNullGuard(s0->label)); + continue; + } + + s0 = s; + } + + if( rc != kOkRC ) + rc = cwLogError(rc,"Section validation failed."); + return rc; + } + + void _var_print( const event_t* e, unsigned varId, char* text, unsigned textCharN ) + { + if( e->varA[varId].set == nullptr ) + snprintf(text,textCharN,"%s"," "); + else + { + snprintf(text,textCharN,"%3s-%03i-%02i %4s",var_flags_to_char(e->varA[varId].flags), + e->varA[varId].set->id, + e->varA[varId].setNoteIdx, + e->varA[varId].flags & kSetEndVarFl ? e->varA[varId].set->targetSection->label : " "); + } + } + } +} + +const char* cw::score_parse::opcode_id_to_label( unsigned opId ) +{ + for(unsigned i=0; _opcode_ref[i].id != kInvalidTId; ++i) + if( _opcode_ref[i].id == opId ) + return _opcode_ref[i].label; + return nullptr; +} + +unsigned cw::score_parse::opcode_label_to_id( const char* label ) +{ + for(unsigned i=0; _opcode_ref[i].id != kInvalidTId; ++i) + if( textIsEqual(_opcode_ref[i].label,label) ) + return _opcode_ref[i].id; + return kInvalidTId; +} + +unsigned cw::score_parse::var_char_to_flags( const char* label ) +{ + for(unsigned i=0; _var_ref[i].flags != 0; ++i) + if( textIsEqual(_var_ref[i].label,label,1) ) + return _var_ref[i].flags; + return 0; +} + +const char* cw::score_parse::var_flags_to_char( unsigned flags ) +{ + for(unsigned i=0; _var_ref[i].flags != 0; ++i) + if( _var_ref[i].flags == flags ) + return _var_ref[i].label; + return nullptr; +} + +const char* cw::score_parse::var_index_to_char( unsigned var_idx ) +{ + for(unsigned i=0; _var_ref[i].flags != 0; ++i) + if( _var_ref[i].id == var_idx ) + return _var_ref[i].label; + return nullptr; +} + +const char* cw::score_parse::dyn_ref_level_to_label( handle_t h, unsigned level ) +{ + score_parse_t* p = _handleToPtr(h); + return level_to_marker( p->dynRefH, level ); +} + +unsigned cw::score_parse::dyn_ref_label_to_level( handle_t h, const char* label ) +{ + score_parse_t* p = _handleToPtr(h); + return marker_to_level( p->dynRefH, label ); +} + + +unsigned cw::score_parse::form_hash( unsigned op_id, unsigned bar, uint8_t midi_pitch, unsigned barPitchIdx ) +{ + unsigned hash = 0; + + assert(barPitchIdx < 256 && bar < 0x7ff && op_id < 0xf ); + + hash += (barPitchIdx & 0x000000ff); + hash += (midi_pitch & 0x000000ff) << 8; + hash += (bar & 0x00000fff) << 16; + hash += (op_id & 0x0000000f) << 28; + + return hash; +} + +void cw::score_parse::parse_hash( unsigned hash, unsigned& op_idRef, unsigned& barRef, uint8_t& midi_pitchRef, unsigned& barPitchIdxRef ) +{ + barPitchIdxRef = hash & 0x000000ff; + midi_pitchRef = (hash & 0x0000ff00) >> 8; + barRef = (hash & 0x0fff0000) >> 16; + op_idRef = (hash & 0xf0000000) >> 28; +} + + +cw::rc_t cw::score_parse::create( handle_t& hRef, const char* fname, double srate, dyn_ref_tbl::handle_t dynRefH ) +{ + rc_t rc = kOkRC; + score_parse_t* p = nullptr; + + if((rc = destroy(hRef)) != kOkRC ) + return rc; + + p = mem::allocZ(); + + p->srate = srate; + p->dynRefH = dynRefH; + + if((rc = _parse_csv(p,fname)) != kOkRC ) + { + rc = cwLogError(rc,"CSV parse failed."); + goto errLabel; + } + + _create_sets(p); + + _fill_sets(p); + + if((rc = _validate_sets(p)) != kOkRC ) + goto errLabel; + + _fill_target_sections(p); + + _fill_sections(p); + + _sort_sections(p); + + if((rc = _validate_sections(p)) != kOkRC ) + goto errLabel; + + + + + hRef.set(p); + + errLabel: + if( rc != kOkRC ) + { + rc = cwLogError(rc,"Score parse failed on '%s'.",cwStringNullGuard(fname)); + _destroy(p); + } + + return rc; +} + +cw::rc_t cw::score_parse::destroy( handle_t& hRef ) +{ + rc_t rc = kOkRC; + if( !hRef.isValid() ) + return rc; + + score_parse_t* p = _handleToPtr(hRef); + + if((rc = _destroy(p)) != kOkRC ) + return rc; + + hRef.clear(); + + return rc; +} + +double cw::score_parse::sample_rate( handle_t h ) +{ + score_parse_t* p = _handleToPtr(h); + return p->srate; +} + +unsigned cw::score_parse::event_count( handle_t h ) +{ + score_parse_t* p = _handleToPtr(h); + return p->eventN; +} + +const cw::score_parse::event_t* cw::score_parse::event_array( handle_t h ) +{ + score_parse_t* p = _handleToPtr(h); + return p->eventA; +} + +unsigned cw::score_parse::section_count( handle_t h ) +{ + score_parse_t* p = _handleToPtr(h); + unsigned n = 0; + for(section_t* s = p->sectionL; s!=nullptr; s=s->link) + ++n; + return n; +} + +const cw::score_parse::section_t* cw::score_parse::section_list( handle_t h ) +{ + score_parse_t* p = _handleToPtr(h); + return p->sectionL; +} + +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; +} + +const cw::score_parse::set_t* cw::score_parse::set_list( handle_t h ) +{ + score_parse_t* p = _handleToPtr(h); + return p->begSetL; +} + +void cw::score_parse::report( handle_t h ) +{ + score_parse_t* p = _handleToPtr(h); + unsigned textBufN = 255; + char textBuf[ textBufN+1 ]; + const char* S = "S:"; + const char* B = "B:"; + const char* blank = " "; + const unsigned flN = 6; + + + + printf("row op section bar bei voc tick sec rval dot lo c lctn oloc flags bpm stat d0 d1 spich hash \n"); + printf("---- --- ------- ----- --- --- ---- ------- ------ --- ----- ----- ----- ----- --- ---- ---- --- ----- --------\n"); + + for(unsigned i=0; ieventN; ++i) + { + const event_t* e = p->eventA + i; + const char* secLabel = i==0 || !textIsEqual(e->section->label,p->eventA[i-1].section->label) ? S : blank; + const char* barLabel = i==0 || e->barNumb != p->eventA[i-1].barNumb ? B : blank; + + unsigned fli = 0; + char flag_str[ flN ] = {0}; + + if( cwIsFlag(e->flags,kGraceFl)) { flag_str[fli++] = 'g'; } + if( cwIsFlag(e->flags,kTieBegFl)) { flag_str[fli++] = 't'; } + if( cwIsFlag(e->flags,kTieContinueFl)) { flag_str[fli++] = '_'; } + if( cwIsFlag(e->flags,kTieEndFl)) { flag_str[fli++] = 'T'; } + if( cwIsFlag(e->flags,kOnsetFl)) { flag_str[fli++] = 'o'; } + + printf("%4i %3s %2s%4s %2s%3i %3i %3i %4i %7.3f %6.3f %3i %5i %5i %5i %5s %3i 0x%02x 0x%02x %3i %5s", + e->csvRowNumb, + opcode_id_to_label(e->opId), + secLabel, + e->section == nullptr || e->section->label==nullptr ? "" : e->section->label , + barLabel, + e->barNumb, + e->barEvtIdx, + e->voice, + e->tick, + e->sec, + e->rval, + e->dotCnt, + e->loc, + e->loctn, + e->oloc, + flag_str, + e->bpm, + e->status, + e->d0, + e->d1, + e->sciPitch == nullptr ? "" : e->sciPitch); + + if( e->hash != 0 ) + printf(" %08x",e->hash); + + for(unsigned vi = 0; vi handle_t; + + const char* opcode_id_to_label( unsigned opId ); + unsigned opcode_label_to_id( const char* label ); + + unsigned var_char_to_flags( const char* label ); + const char* var_flags_to_char( unsigned flags ); + const char* var_index_to_char( unsigned var_idx ); + + const char* dyn_ref_level_to_label( handle_t h, unsigned vel ); + unsigned dyn_ref_label_to_level( handle_t h, const char* label ); + + unsigned form_hash( unsigned op_id, unsigned bar, uint8_t midi_pitch, unsigned barPitchIdx ); + void parse_hash( unsigned hash, unsigned& op_idRef, unsigned& barRef, uint8_t& midi_pitchRef, unsigned& barPitchIdxRef ); + + rc_t create( handle_t& hRef, const char* fname, double srate, dyn_ref_tbl::handle_t dynRefH ); + rc_t destroy( handle_t& hRef ); + + double sample_rate( handle_t h ); + + unsigned event_count( handle_t h ); + const event_t* event_array( handle_t h ); + + unsigned section_count( handle_t h ); + // Returns a linked list of section records in time order. + const section_t* section_list( handle_t h ); + + unsigned set_count( handle_t h ); + // Returns a linked list of sets. + const set_t* set_list( handle_t h ); + + + void report( handle_t h ); + + // see score_test::test() for testing this object + + } +} diff --git a/cwScoreTest.cpp b/cwScoreTest.cpp new file mode 100644 index 0000000..301cc90 --- /dev/null +++ b/cwScoreTest.cpp @@ -0,0 +1,113 @@ +#include "cwCommon.h" +#include "cwLog.h" +#include "cwCommonImpl.h" +#include "cwMem.h" +#include "cwText.h" +#include "cwObject.h" +#include "cwMidi.h" +#include "cwFileSys.h" +#include "cwDynRefTbl.h" +#include "cwScoreParse.h" +#include "cwSfScore.h" +#include "cwSfMatch.h" +#include "cwSfTrack.h" +#include "cwScoreTest.h" + +cw::rc_t cw::score_test::test( const object_t* cfg ) +{ + + rc_t rc = kOkRC; + const char* cm_score_fname = nullptr; + const object_t* dynArrayNode = nullptr; + const object_t* sfmatchNode = nullptr; + const object_t* sftrackNode = nullptr; + bool parse_fl = false; + bool parse_report_fl = false; + bool score_fl = false; + bool score_report_fl = false; + bool match_fl = false; + bool track_fl = false; + double srate = 0; + dyn_ref_tbl::handle_t dynRefH; + sfscore::handle_t scoreH; + score_parse::handle_t spH; + + + // parse the test cfg + if((rc = cfg->getv( "score_fname", cm_score_fname, + "srate", srate, + "dyn_ref", dynArrayNode, + "sfmatch", sfmatchNode, + "sftrack", sftrackNode, + "parse_fl", parse_fl, + "parse_report_fl", parse_report_fl, + "score_fl", score_fl, + "score_report_fl", score_report_fl, + "match_fl", match_fl, + "track_fl", track_fl)) != kOkRC ) + { + rc = cwLogError(rc,"sfscore test parse params failed on."); + goto errLabel; + } + + // parse the dynamics reference array + if((rc = dyn_ref_tbl::create(dynRefH,dynArrayNode)) != kOkRC ) + { + rc = cwLogError(rc,"The reference dynamics array parse failed."); + goto errLabel; + } + + // if parsing was requested + if( parse_fl ) + { + // create the score_parse object + if((rc = score_parse::create(spH,cm_score_fname,srate,dynRefH)) != kOkRC ) + { + rc = cwLogError(rc,"Score parse failed on '%s'.",cwStringNullGuard(cm_score_fname)); + goto errLabel; + } + + if( parse_report_fl ) + report(spH); + + // if score processing was requested + if( score_fl ) + { + if((rc = create(scoreH,spH)) != kOkRC ) + { + rc = cwLogError(rc,"Score test create failed."); + goto errLabel; + } + + if( score_report_fl ) + report(scoreH,nullptr); + + if( match_fl ) + { + if((rc = sfmatch::test(sfmatchNode,scoreH)) != kOkRC ) + { + rc = cwLogError(rc,"Score match test failed."); + goto errLabel; + } + } + + if( track_fl ) + { + if((rc = sftrack::test(sftrackNode,scoreH)) != kOkRC ) + { + rc = cwLogError(rc,"Score track test failed."); + goto errLabel; + } + } + } + + } + + errLabel: + destroy(scoreH); + destroy(spH); + destroy(dynRefH); + return rc; + + +} diff --git a/cwScoreTest.h b/cwScoreTest.h new file mode 100644 index 0000000..0010a77 --- /dev/null +++ b/cwScoreTest.h @@ -0,0 +1,8 @@ +namespace cw +{ + namespace score_test + { + + rc_t test( const object_t* cfg ); + } +}