From 3fa4acceee2ec3cf40fb86a6e6f9d5ae564f8b8f Mon Sep 17 00:00:00 2001 From: kevin Date: Tue, 25 Jul 2023 20:24:03 -0400 Subject: [PATCH] cwSfScore.h/cpp, cwSfScoreParser.h/cpp, Makefile.am : Initial commmit. --- Makefile.am | 7 +- cwSfScore.cpp | 943 ++++++++++++++++++++++++++++++++++++ cwSfScore.h | 168 +++++++ cwSfScoreParser.cpp | 1105 +++++++++++++++++++++++++++++++++++++++++++ cwSfScoreParser.h | 106 +++++ 5 files changed, 2327 insertions(+), 2 deletions(-) create mode 100644 cwSfScore.cpp create mode 100644 cwSfScore.h create mode 100644 cwSfScoreParser.cpp create mode 100644 cwSfScoreParser.h diff --git a/Makefile.am b/Makefile.am index fb1f21d..1914939 100644 --- a/Makefile.am +++ b/Makefile.am @@ -67,8 +67,11 @@ 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 src/libcw/cwScoreFollowerPerf.h src/libcw/cwMidiState.h src/libcw/cwSvgMidi.h src/libcw/cwSvgScoreFollow.h -libcwSRC += src/libcw/cwCmInterface.cpp src/libcw/cwScoreFollower.cpp src/libcw/cwMidiState.cpp src/libcw/cwSvgMidi.cpp src/libcw/cwSvgScoreFollow.cpp +libcwHDR += src/libcw/cwCmInterface.h src/libcw/cwScoreFollower.h src/libcw/cwSfScoreParser.h src/libcw/cwSfScore.h +libcwSRC += src/libcw/cwCmInterface.cpp src/libcw/cwScoreFollower.cpp src/libcw/cwSfScoreParser.cpp src/libcw/cwSfScore.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/cwSfScore.cpp b/cwSfScore.cpp new file mode 100644 index 0000000..16f9d7e --- /dev/null +++ b/cwSfScore.cpp @@ -0,0 +1,943 @@ +#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 "cwSfScore.h" +#include "cwCsv.h" +#include "cwNumericConvert.h" +#include "cwTime.h" +#include "cwVectOps.h" +#include "cwSfScoreParser.h" + +namespace cw +{ + namespace sfscore + { + + /* + typedef struct varMap_str + { + unsigned typeId; + unsigned flag; + const char* label; + } varMap_t; + + typedef struct dynRef_str + { + char* label; + unsigned labelCharCnt; + uint8_t vel; + } dynRef_t; + + typedef idLabelPair_t opcodeMap_t; + + opcodeMap_t _opcodeMapA[] = + { + { kTimeSigEvtScId, "tsg" }, + { kKeySigEvtScId, "ksg" }, + { kTempoEvtScId, "tmp" }, + { kTrackEvtScId, "trk" }, + { kTextEvtScId, "txt" }, + { kNameEvtScId, "nam" }, + { kEOTrackEvtScId, "eot" }, + { kCopyEvtScId, "cpy" }, + { kBlankEvtScId, "blk" }, + { kBarEvtScId, "bar" }, + { kPgmEvtScId, "pgm" }, + { kCtlEvtScId, "ctl" }, + { kNonEvtScId, "non" }, + { kPedalEvtScId, "ped" }, + { kInvalidId, "" } + }; + + varMap_t _varMapA[] = + { + { kEvenVarScId, kEvenScFl, "e"}, + { kDynVarScId, kDynScFl, "d"}, + { kTempoVarScId,kTempoScFl,"t"}, + { kInvalidId, 0, ""} + }; + */ + + typedef struct sfscore_str + { + //dynRef_t* dynRefA; + //unsigned dynRefN; + + parser::handle_t parserH; + + event_t* eventA; + unsigned eventN; + unsigned eventAllocN; + + set_t* setL; + + section_t* sectionA; + unsigned sectionN; + unsigned sectionAllocN; + + } sfscore_t; + + sfscore_t* _handleToPtr( handle_t h ) + { return handleToPtr(h); } + + /* + unsigned _opcode_label_to_id( const char* label ) + { + unsigned id; + if((id = labelToId( _opcodeMapA, label, kInvalidId )) == kInvalidId ) + cwLogError(kInvalidArgRC,"'%s' is not a valid event opcode type label.",cwStringNullGuard(label)); + + return id; + } + + const char* _opcode_id_to_label( unsigned opcodeId ) + { + const char* label; + if((label = idToLabel( _opcodeMapA, opcodeId, kInvalidEvtScId)) == nullptr ) + cwLogError(kInvalidArgRC,"The event opcode type id '%i' is not valid.",opcodeId); + + return label; + } + + + unsigned _varLabelToTypeId( const char* label ) + { + if( label!=nullptr && textLength(label)>0 ) + { + char varLabel[] = { label[0],0 }; + for(unsigned i=0; _varMapA[i].typeId != kInvalidId; ++i) + if( textCompare(varLabel,_varMapA[i].label) == 0 ) + return _varMapA[i].typeId; + } + + cwLogError(kInvalidArgRC,"The variable label '%s' is not valid.",cwStringNullGuard(label)); + + return kInvalidId; + } + const char* _varTypeIdToLabel( unsigned varTypeId ) + { + for(unsigned i=0; _varMapA[i].typeId != kInvalidId; ++i) + if( _varMapA[i].typeId == varTypeId ) + return _varMapA[i].label; + + cwLogError(kInvalidArgRC,"The variable type id '%i' is not valid.",varTypeId); + + return nullptr; + } + + unsigned _varTypeIdToFlag( unsigned varTypeId ) + { + for(unsigned i=0; _varMapA[i].typeId != kInvalidId; ++i) + if( _varMapA[i].typeId == varTypeId ) + return _varMapA[i].flag; + + cwLogError(kInvalidArgRC,"The variable type id '%i' is not valid.",varTypeId); + return 0; + } + + unsigned _varFlagToTypeId( unsigned varFlag ) + { + for(unsigned i=0; _varMapA[i].typeId != kInvalidId; ++i) + if( _varMapA[i].flag == varFlag ) + return _varMapA[i].typeId; + + cwLogError(kInvalidArgRC,"The variable flag id '0x%x' is not valid.",varFlag); + return kInvalidId; + } + + unsigned _varLabelToFlag( const char* varLabel ) + { + unsigned varId; + if(varLabel==nullptr || textLength(varLabel)==0 || (varId = _varLabelToTypeId(varLabel)) == kInvalidId ) + return 0; + return _varTypeIdToFlag(varId); + } + + const char* _varTypeFlagToLabel( unsigned varFlag ) + { + unsigned varId; + if((varId = _varFlagToTypeId(varFlag)) != kInvalidId ) + return _varTypeIdToLabel(varId); + + return nullptr; + } + + unsigned _dynLabelToLevel( sfscore_t* p, const char* dynLabel ) + { + const char* end; + unsigned char_cnt; + + if( dynLabel == nullptr || textLength(dynLabel)==0 ) + return kInvalidDynVel; + + end = nextWhiteCharEOS(dynLabel); + assert(end != nullptr ); + char_cnt = end - dynLabel; + + for(unsigned i=0; idynRefN; ++i) + if( textCompare(p->dynRefA[i].label,dynLabel,char_cnt) == 0 ) + return p->dynRefA[i].vel; + + cwLogError(kSyntaxErrorRC,"The dynamic label '%s' is not valid.",cwStringNullGuard(dynLabel)); + return 0; + } + */ + + void _destroy_set( set_t* s ) + { + mem::release(s->eleArray); + mem::release(s->sectArray); + mem::release(s); + } + + void _destroy_section( section_t* s ) + { + char* ss = (char*)(s->label); + mem::release(ss); + } + + rc_t _destroy( sfscore_t* p ) + { + rc_t rc = kOkRC; + + set_t* s = p->setL; + while( s!= nullptr ) + { + set_t* s0 = s->llink; + _destroy_set(s); + s=s0; + } + + for(unsigned i=0; isectionN; ++i) + _destroy_section( p->sectionA + i ); + mem::release(p->sectionA); + + mem::release(p->eventA); + + /* + for(unsigned i=0; idynRefN; ++i) + mem::release(p->dynRefA[i].label); + mem::release(p->dynRefA); + */ + + destroy(p->parserH); + + mem::release(p); + return rc; + } + + /* + rc_t _parse_bar( sfscore_t* p, event_t* e, const char* barNumbString ) + { + rc_t rc = kOkRC; + return rc; + } + + // Iterate backward from the last event in a set to locate beginning of the set and return + // a pointer to the first event in the set and the count of events in the set. + unsigned _calc_set_event_count( sfscore_t* p, const event_t* prev_end_evt, event_t* end_set_evt, unsigned varTypeFlag, event_t*& begEvtRef ) + { + unsigned n = 0; + event_t* e0 = end_set_evt; + + begEvtRef = nullptr; + + for(; e0>=p->eventA; --e0) + if( cwIsFlag(e0->flags,varTypeFlag) ) + { + if( e0 == prev_end_evt ) + break; + + ++n; + begEvtRef = e0; + } + + return n; + } + + // Get the ending event from the previous set of the same var type. + const event_t* _get_prev_end_event( sfscore_t* p, unsigned varTypeFlag ) + { + const set_t* s; + unsigned varTypeId = _varFlagToTypeId(varTypeFlag); + for(s=p->setL; s!=nullptr; s=s->llink) + if( s->varId == varTypeId ) + return s->eleArray[ s->eleCnt-1 ]; + + return nullptr; + } + + section_t* _find_section(sfscore_t* p, const char* sectionLabel ) + { + for(unsigned i=0; isectionN; ++i) + if( textIsEqual(p->sectionA[i].label,sectionLabel) ) + return p->sectionA + i; + + return nullptr; + } + + rc_t _connect_section_to_set( sfscore_t* p, set_t* set, const char* section_label ) + { + rc_t rc = kOkRC; + + section_t* section; + if((section = _find_section(p,section_label)) == nullptr ) + { + rc = cwLogError(kSyntaxErrorRC,"The section label '%s' could not be found.",cwStringNullGuard(section_label)); + goto errLabel; + } + + set->sectCnt += 1; + set->sectArray = mem::resizeZ(set->sectArray,set->sectCnt); + set->sectArray[ set->sectCnt-1 ] = section; + + errLabel: + return rc; + } + + + // Create a 'set' record. + rc_t _create_set( sfscore_t* p, event_t* end_set_evt, unsigned varTypeFlag, const char* section_label ) + { + rc_t rc = kOkRC; + event_t* beg_set_evt = nullptr; + event_t* e = nullptr; + unsigned set_eventN = 0; + const event_t* prev_end_evt = _get_prev_end_event(p,varTypeFlag); + + // iterate backward to determine the beginning event in this set and the count of events in it + if((set_eventN = _calc_set_event_count( p, prev_end_evt, end_set_evt, varTypeFlag, beg_set_evt )) == 0 || beg_set_evt == nullptr ) + { + rc = cwLogError(kOpFailRC,"Unable to locate the '%s' var set.",_varTypeFlagToLabel(varTypeFlag)); + goto errLabel; + } + else + { + set_t* set = mem::allocZ(); + unsigned i; + + set->varId = _varFlagToTypeId(varTypeFlag); + set->eleCnt = set_eventN; + set->eleArray = mem::allocZ(set_eventN); + + // assign section to set + if((rc = _connect_section_to_set(p,set,section_label)) != kOkRC ) + { + cwLogError(rc,"Section '%s' assignment to set failed.",section_label); + goto errLabel; + } + + + // fill in s->eleArray[] + for(i=0,e=beg_set_evt; e<=end_set_evt; ++e) + if( cwIsFlag(e->flags,varTypeFlag) ) + { + assert( e > prev_end_evt ); + assert( i < set->eleCnt ); + set->eleArray[i++] = e; + } + + assert( i == set->eleCnt ); + + set->llink = p->setL; + p->setL = set; + } + + errLabel: + + return rc; + } + + // Once the var. spec. label is known to have a section id this function extracts the section label + unsigned _parse_set_end_section_label( const char* label, const char*& sectionLabelRef ) + { + rc_t rc = kOkRC; + const char* c = nullptr; + + sectionLabelRef = nullptr; + + // a label with a section will have internal whitespace + if((c = nextWhiteChar(label)) == nullptr ) + return rc; + + // advance past the white space to the first char of the section id + if( (c=nextNonWhiteChar(c)) == nullptr ) + goto errLabel; + + // parse the section id + sectionLabelRef = c; + + return rc; + + errLabel: + return cwLogError(kSyntaxErrorRC,"The section number could not be parsed from '%s'.",label); + } + + + // Parse the optional set section number following the var spec. label. + rc_t _parse_var_set_section_label(sfscore_t* p, event_t* e, const char* label, unsigned varTypeFlag ) + { + rc_t rc = kOkRC; + const char* section_label = nullptr; + + // attempt to get the section id following the var marker + if((rc = _parse_set_end_section_label(label,section_label)) != kOkRC ) + goto errLabel; + + // if a section id was found ... + if( section_label != nullptr ) + { + // then create the set + if((rc = _create_set(p,e,varTypeFlag,section_label)) != kOkRC ) + { + rc = cwLogError(rc,"Variable set create failed for var type:%s.",_varTypeFlagToLabel(varTypeFlag)); + goto errLabel; + } + } + + errLabel: + + return rc; + } + + // A variable specification indicate how a note is to be measured. It is contained + // in the 'tempo','even', and 'dyn' columns. The specification contains two parts: + // a char string id ('t','e', (e.g. p,pp,mf,fff,etc)) followed by + // an optional section identifier. The section identifer marks the end + // of a 'set' of var. spec's and also idicates the section which will be modified + // according to the measurements. + rc_t _parse_var_spec( sfscore_t* p, event_t* e, const char* label, unsigned varTypeFlag ) + { + rc_t rc = kOkRC; + + if( label==nullptr || textLength(label)==0) + return rc; + + // if this is a 'dynamics' marking + if( varTypeFlag == kDynScFl ) + { + if((e->dynVal = _dynLabelToLevel(p,label)) == kInvalidDynVel ) + { + cwLogError(kSyntaxErrorRC,"Note dynamic var spec parse failed on label:%s",cwStringNullGuard(label)); + goto errLabel; + } + } + else + { + // this is a 'tempo' or 'even' marking + if(_varLabelToFlag(label) != varTypeFlag ) + { + cwLogError(kSyntaxErrorRC,"Note %s var spec parse failed on label:%s.",_varTypeFlagToLabel(varTypeFlag),cwStringNullGuard(label)); + goto errLabel; + } + } + + // parse the optional section id. + if((rc = _parse_var_set_section_label(p,e,label,varTypeFlag)) != kOkRC ) + goto errLabel; + + e->flags |= varTypeFlag; + + errLabel: + return rc; + } + + rc_t _parse_csv_note(sfscore_t* p, + event_t* e, + uint8_t d0, + uint8_t d1, + const char* sciPitch, + const char* evenLabel, + const char* tempoLabel, + const char* dynLabel) + { + rc_t rc = kOkRC; + + if((rc = _parse_var_spec(p,e,evenLabel,kEvenScFl )) != kOkRC ) + goto errLabel; + + if((rc = _parse_var_spec(p,e,tempoLabel,kTempoScFl )) != kOkRC ) + goto errLabel; + + if((rc = _parse_var_spec(p,e,dynLabel,kDynScFl)) != kOkRC ) + goto errLabel; + + e->flags |= _varLabelToFlag(tempoLabel); + + e->pitch = d0; + e->vel = d1; + + errLabel: + return rc; + } + + + rc_t _parse_csv_row( sfscore_t* p, csv::handle_t& csvH, event_t* e, unsigned& curBarNumbRef, unsigned& curBarNoteIdxRef ) + { + rc_t rc = kOkRC; + + const char* opcodeLabel; + const char* arg0; + const char* evenLabel; + const char* tempoLabel; + const char* dynLabel; + uint8_t d0,d1; + const char* sectionLabel; + + if((rc = getv( csvH, + "opcode",opcodeLabel, + "evt", e->csvEventId, + "micros",e->secs, + "d0",d0, + "d1",d1, + "arg0",arg0, + "bar",e->barNumb, + "even",evenLabel, + "tempo",tempoLabel, + "t_frac",e->frac, + "dyn",dynLabel, + "section",sectionLabel )) != kOkRC ) + { + rc = cwLogError(rc,"Error parsing score CSV row." ); + goto errLabel; + } + + // validate the opcode + if((e->type = _opcode_label_to_id( opcodeLabel )) == kInvalidId ) + { + cwLogError(kSyntaxErrorRC,"The opcode type:'%s' is not valid.",cwStringNullGuard(opcodeLabel)); + goto errLabel; + } + + switch( e->type ) + { + case kBarEvtScId: + if( curBarNumbRef != kInvalidId && e->barNumb != curBarNumbRef+1 ) + { + rc = cwLogError(kInvalidStateRC,"Missig bar number %i. Jumped from bar:%i to bar:%i.", curBarNumbRef+1,curBarNumbRef,e->barNumb); + goto errLabel; + } + + curBarNumbRef = e->barNumb; + curBarNoteIdxRef = 0; + break; + + case kCtlEvtScId: + break; + + case kNonEvtScId: + e->barNumb = curBarNumbRef; + e->barNoteIdx = curBarNoteIdxRef++; + if((rc = _parse_csv_note(p,e,d0,d1,arg0,evenLabel,tempoLabel,dynLabel)) != kOkRC ) + { + cwLogError(rc,"Note parse failed."); + goto errLabel; + } + break; + + default: + cwLogError(kInvalidArgRC,"The opcode type '%s' is not valid in this context.",cwStringNullGuard(opcodeLabel)); + goto errLabel; + break; + } + + + // if this event has a section label + if( sectionLabel != nullptr and textLength(sectionLabel)>0 ) + { + section_t* section; + + // locate the section record + if((section = _find_section(p,sectionLabel)) == nullptr ) + { + rc = cwLogError(kSyntaxErrorRC,"The section label '%s' could not be found.",cwStringNullGuard(sectionLabel)); + goto errLabel; + } + + // verify that this section was not already assigned a starting event index + if( section->begEvtIndex != kInvalidIdx ) + { + rc = cwLogError(kInvalidIdRC,"The section label '%s' appears to be duplicated.",cwStringNullGuard(sectionLabel)); + goto errLabel; + } + + // assign a starting event index to this event + section->begEvtIndex = e->index; + } + + errLabel: + return rc; + } + + rc_t _parse_csv_events( sfscore_t* p, csv::handle_t csvH ) + { + rc_t rc; + unsigned cur_line_idx = 0; + unsigned curBarNumb = kInvalidId; + unsigned curBarNoteIdx = 0; + + // 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); + + do + { + cur_line_idx = cur_line_index(csvH); + + // advance the CSV line cursor + switch(rc = next_line(csvH)) + { + case kOkRC: + { + // verify that there is an available slot in the event array + if( p->eventN >= p->eventAllocN ) + { + rc = cwLogError(kBufTooSmallRC,"The event buffer is too small."); + goto errLabel; + } + + event_t* e = p->eventA + p->eventN; + e->index = p->eventN; + e->line = cur_line_idx; + e->csvRowNumb = cur_line_idx+1; + + if((rc = _parse_csv_row(p, csvH, e, curBarNumb, curBarNoteIdx )) != kOkRC ) + goto errLabel; + + + p->eventN += 1; + } + break; + + case kEofRC: + break; + + default: + rc = cwLogError(rc,"CSV line iteration error on CSV event parse."); + goto errLabel; + } + + }while( rc != kEofRC ); + + rc = kOkRC; + + errLabel: + if( rc != kOkRC ) + rc = cwLogError(rc,"CSV parse failed on row number:%i.",cur_line_idx+1); + + return rc; + } + + // scan the CSV and count the total number of sections + rc_t _get_section_count( csv::handle_t csvH, unsigned& sectionCntRef ) + { + rc_t rc = kOkRC ; + unsigned cur_line_idx = 0; + unsigned n = 0; + + sectionCntRef = 0; + do + { + cur_line_idx = cur_line_index(csvH); + + // advance the CSV line cursor + switch(rc = next_line(csvH)) + { + case kEofRC: + break; + + case kOkRC: + { + const char* sectionLabel = nullptr; + if((rc = getv(csvH,"section",sectionLabel)) != kOkRC ) + { + rc = cwLogError(rc,"Error parsing section field."); + goto errLabel; + } + + if( sectionLabel != nullptr ) + n += 1; + } + break; + + default: + rc = cwLogError(rc,"CSV line iteration failed."); + } + + } while( rc != kEofRC ); + + rc = kOkRC; + + sectionCntRef = n; + + errLabel: + if( rc != kOkRC ) + rc = cwLogError(rc,"Section count calculation failed on line index:%i.",cur_line_idx); + + return rc; + } + + // scan the CSV and create a record for every section + rc_t _parse_section_events(sfscore_t* p, csv::handle_t csvH, unsigned sectionCnt ) + { + rc_t rc = kOkRC; + unsigned cur_line_idx = 0; + + p->sectionAllocN = sectionCnt; + p->sectionN = 0; + p->sectionA = mem::allocZ(sectionCnt); + + do + { + cur_line_idx = cur_line_index(csvH); + + // advance the CSV line cursor + switch(rc = next_line(csvH)) + { + case kEofRC: + break; + + case kOkRC: + { + section_t* section = nullptr; + const char* sectionLabel = nullptr; + + // read the section column + if((rc = getv(csvH,"section",sectionLabel)) != kOkRC ) + { + rc = cwLogError(rc,"Error parsing section field."); + goto errLabel; + } + + // if the section column is not blank + if( sectionLabel != nullptr && textLength(sectionLabel)>0) + { + // verify that this section was not already created + if((section = _find_section(p,sectionLabel)) != nullptr ) + { + rc = cwLogError(kInvalidIdRC,"Multiple sections have the id '%s'.",sectionLabel); + goto errLabel; + } + else + { + // verify that an available slot exists to store the new section reocrd + if(p->sectionN >= p->sectionAllocN ) + { + rc = cwLogError(kBufTooSmallRC,"The section array is too small. The section count estimation is incomplete."); + goto errLabel; + } + + // create the section record + section = p->sectionA + p->sectionN; + section->label = mem::duplStr(sectionLabel); + section->index = p->sectionN; + section->begEvtIndex = kInvalidIdx; + vop::fill(section->vars,kScVarCnt,DBL_MAX); + p->sectionN += 1; + } + } + } + break; + default: + rc = cwLogError(rc,"CSV line iteration failed."); + } + + } while( rc != kEofRC ); + + rc = kOkRC; + + errLabel: + if( rc != kOkRC ) + rc = cwLogError(rc,"Section event parse failed on line index:%i.",cur_line_idx); + + return rc; + } + + rc_t _parse_sections(sfscore_t* p,csv::handle_t csvH) + { + rc_t rc; + unsigned sectionCnt = 0; + + if((rc = _get_section_count(csvH, sectionCnt )) != kOkRC ) + goto errLabel; + + if((rc = rewind(csvH)) != kOkRC ) + goto errLabel; + + if((rc = _parse_section_events(p,csvH,sectionCnt)) != kOkRC ) + goto errLabel; + + errLabel: + if(rc != kOkRC ) + rc = cwLogError(rc,"Section parse failed."); + return rc; + } + + + rc_t _parse_csv( sfscore_t* p, const char* fname ) + { + rc_t rc = kOkRC; + const char* titleA[] = { "id","trk","evt","opcode","dticks","micros","status","meta", + "ch","d0","d1","arg0","arg1","bar","skip","even","grace", + "tempo","t_frac","dyn","section","play_recd","remark" }; + 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; + } + + // create p->sectionA[] + if((rc = _parse_sections(p,csvH)) != kOkRC ) + goto errLabel; + + if((rc = rewind(csvH)) != kOkRC ) + goto errLabel; + + // create p->eventA[] + if((rc = _parse_csv_events(p,csvH)) != kOkRC ) + goto errLabel; + + errLabel: + if(rc != kOkRC ) + rc = cwLogError(rc,"CSV parse failed on '%s'.",fname); + destroy(csvH); + return rc; + } + */ + } +} + +cw::rc_t cw::sfscore::create( handle_t& hRef, + const char* fname, + double srate, + const dyn_ref_t* dynRefA, + unsigned dynRefN ) +{ + rc_t rc; + + if((rc = destroy(hRef)) != kOkRC ) + return rc; + + sfscore_t* p = mem::allocZ(); + + if((rc = parser::create(p->parserH,fname,dynRefA,dynRefN)) != kOkRC ) + { + rc = cwLogError(rc,"sfscore parse failed."); + goto errLabel; + } + + hRef.set(p); + + errLabel: + if( rc != kOkRC ) + { + rc = cwLogError(rc,"sfscore create failed."); + _destroy(p); + } + return rc; +} + +cw::rc_t cw::sfscore::destroy( handle_t& hRef ) +{ + rc_t rc = kOkRC; + if(!hRef.isValid()) + return rc; + + sfscore_t* p = _handleToPtr(hRef); + + if((rc = _destroy(p)) != kOkRC ) + { + rc = cwLogError(rc,"Destroy failed."); + goto errLabel; + } + + hRef.clear(); + + errLabel: + return rc; +} + + +unsigned cw::sfscore::event_count( handle_t h ) +{ + sfscore_t* p = _handleToPtr(h); + return p->eventN; +} + +void cw::sfscore::report( handle_t h ) +{ + sfscore_t* p = _handleToPtr(h); + report(p->parserH); +} + +cw::rc_t cw::sfscore::test( const object_t* cfg ) +{ + rc_t rc = kOkRC; + const char* cm_score_fname = nullptr; + const object_t* dynArrayNode = nullptr; + dyn_ref_t* dynRefA = nullptr; + unsigned dynRefN = 0; + double srate = 0; + handle_t h; + time::spec_t t0; + + + // parse the test cfg + if((rc = cfg->getv( "cm_score_fname", cm_score_fname, + "srate", srate, + "dyn_ref", dynArrayNode )) != kOkRC ) + { + rc = cwLogError(rc,"sfscore test parse params failed on."); + goto errLabel; + } + + // parse the dynamics ref. array + dynRefN = dynArrayNode->child_count(); + dynRefA = mem::allocZ(dynRefN); + + for(unsigned i=0; ichild_ele(i); + + if( !pair->is_pair() || pair->pair_label()==nullptr || pair->pair_value()==nullptr || pair->pair_value()->value( dynRefA[i].vel)!=kOkRC ) + { + rc = cwLogError(kSyntaxErrorRC,"Error parsing the dynamics reference array."); + goto errLabel; + } + + dynRefA[i].label = pair->pair_label(); + } + + + time::get(t0); + + if((rc = create(h,cm_score_fname,srate,dynRefA,dynRefN)) != kOkRC ) + { + rc = cwLogError(rc,"Score test create failed."); + goto errLabel; + } + + report(h); + + printf("%i events %i ms\n",event_count(h), time::elapsedMs(t0) ); + + errLabel: + destroy(h); + mem::release(dynRefA); + return rc; +} diff --git a/cwSfScore.h b/cwSfScore.h new file mode 100644 index 0000000..a232011 --- /dev/null +++ b/cwSfScore.h @@ -0,0 +1,168 @@ +#ifndef cwSfScore_h +#define cwSfScore_h + +namespace cw +{ + namespace sfscore + { + + enum + { + kInvalidEvtScId = 0, + kTimeSigEvtScId, + kKeySigEvtScId, + kTempoEvtScId, + kTrackEvtScId, + kTextEvtScId, + kNameEvtScId, + kEOTrackEvtScId, + kCopyEvtScId, + kBlankEvtScId, + kBarEvtScId, + kPgmEvtScId, + kCtlEvtScId, + kNonEvtScId, + kPedalEvtScId + }; + + // Flags used by event_t.flags + enum + { + kEvenScFl = 0x001, // This note is marked for evenness measurement + kDynScFl = 0x002, // This note is marked for dynamics measurement + kTempoScFl = 0x004, // This note is marked for tempo measurement + //kSkipScFl = 0x008, // This isn't a real event (e.g. tied note) skip over it + //kGraceScFl = 0x010, // This is a grace note + kInvalidScFl = 0x020, // This note has a calculated time + //kPedalDnScFl = 0x040, // This is a pedal down event (pitch holds the pedal id and durSecs holds the time the pedal will remain down.) + //kPedalUpScFl = 0x080, // This is a pedal up event (pitch holds the pedal id) + }; + + + // Id's used by set_t.varId and as indexes into + // section_t.vars[]. + enum + { + kInvalidVarScId = 0, // 0 + kMinVarScId = 1, + kEvenVarScId = kMinVarScId, // 1 + kDynVarScId = 2, // 2 + kTempoVarScId = 3, // 3 + kScVarCnt = 4 + }; + + enum : uint8_t { + kInvalidDynVel = 0 + }; + + struct loc_str; + struct set_str; + + // The score can be divided into arbitrary non-overlapping sections. + typedef struct section_str + { + const char* label; // section label + unsigned index; // index of this record in the internal section array + struct loc_str* locPtr; // location where this section starts + unsigned begEvtIndex; // score element index where this section starts + unsigned setCnt; // Count of elements in setArray[] + struct set_str** setArray; // Ptrs to sets which are applied to this section. + double vars[ kScVarCnt ]; // Set to DBL_MAX by default. + } section_t; + + typedef struct event_str + { + unsigned type; // Event type + double secs; // Time location in seconds + double durSecs; // Duration in seconds + unsigned index; // Index of this event in the event array. + unsigned locIdx; // Index of the location containing this event + midi::byte_t pitch; // MIDI pitch of this note or the MIDI pedal id of pedal down/up msg (64=sustain 65=sostenuto 66=soft) + midi::byte_t vel; // MIDI velocity of this note + unsigned flags; // Attribute flags for this event + unsigned dynVal; // Dynamcis value pppp to ffff (1 to 11) for this note. + double frac; // Note's time value for tempo and non-grace evenness notes. + unsigned barNumb; // Bar id of the measure containing this event. + unsigned barNoteIdx; // Index of this note in this bar + unsigned csvRowNumb; // File row number (not index) from which this record originated + unsigned perfSmpIdx; // Time this event was performed or cmInvalidIdx if the event was not performed. + unsigned perfVel; // Velocity of the performed note or 0 if the note was not performed. + unsigned perfDynLvl; // Index into dynamic level ref. array assoc'd with perfVel + unsigned line; // Line number of this event in the score file. + unsigned csvEventId; // EventId from CSV 'evt' column. + } event_t; + + // A 'set' is a collection of events that are grouped in time and all marked with a given attribute. + // (e.g. eveness, tempo, dynamcs ... ) + typedef struct set_str + { + unsigned varId; // See kXXXVarScId flags above + event_t** eleArray; // Events that make up this set in time order + unsigned eleCnt; // + section_t** sectArray; // Sections this set will be applied to + unsigned sectCnt; // + unsigned* symArray; // symArray[sectCnt] - symbol name of all variables represented by this set (e.g '1a-e', '1b-e', '2-t', etc) + unsigned* costSymArray; // costSymArray[sectCnt] - same as symbols in symArray[] with 'c' prepended to front + bool doneFl; + double value; + struct set_str* llink; // loc_t setList link + } set_t; + + typedef enum + { + kInvalidScMId, + kRecdBegScMId, + kRecdEndScMId, + kFadeScMId, + kPlayBegScMId, + kPlayEndScMId + } markerId_t; + + // score markers + typedef struct marker_str + { + markerId_t markTypeId; // marker type + unsigned labelSymId; // marker label + struct loc_str* scoreLocPtr; // score location of the marker + unsigned csvRowIdx; // score CSV file line assoc'd w/ this marker + struct marker_str* link; // loc_t.markList links + } marker_t; + + // All events which are simultaneous are collected into a single + // locc_t record. + typedef struct loc_str + { + unsigned index; // index of this location record + double secs; // Time of this location + unsigned evtCnt; // Count of events in evtArray[]. + event_t** evtArray; // Events which occur at this time. + unsigned barNumb; // Bar number this event is contained by. + set_t* setList; // Set's which end on this time location (linked through set_t.llink) + section_t* begSectPtr; // NULL if this location does not start a section + marker_t* markList; // List of markers assigned to this location + } loc_t; + + typedef struct + { + const char* label; + uint8_t vel; + } dyn_ref_t; + + typedef handle handle_t; + + rc_t create( handle_t& h, const char* fname, double srate, const dyn_ref_t* dynRefA=nullptr, unsigned dynRefN=0 ); + rc_t destroy( handle_t& h ); + + unsigned event_count( handle_t h ); + + void report( handle_t h ); + + + rc_t test( const object_t* cfg ); + + } +} + + + +#endif diff --git a/cwSfScoreParser.cpp b/cwSfScoreParser.cpp new file mode 100644 index 0000000..797434f --- /dev/null +++ b/cwSfScoreParser.cpp @@ -0,0 +1,1105 @@ +#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 "cwSfScore.h" +#include "cwCsv.h" +#include "cwNumericConvert.h" +#include "cwTime.h" +#include "cwSfScoreParser.h" +#include "cwSfScore.h" + +namespace cw +{ + namespace sfscore + { + typedef struct varMap_str + { + unsigned typeId; + unsigned flag; + unsigned endFl; + const char* label; + } varMap_t; + + typedef idLabelPair_t opcodeMap_t; + + opcodeMap_t _opcodeMapA[] = + { + { kTimeSigEvtScId, "tsg" }, + { kKeySigEvtScId, "ksg" }, + { kTempoEvtScId, "tmp" }, + { kTrackEvtScId, "trk" }, + { kTextEvtScId, "txt" }, + { kNameEvtScId, "nam" }, + { kEOTrackEvtScId, "eot" }, + { kCopyEvtScId, "cpy" }, + { kBlankEvtScId, "blk" }, + { kBarEvtScId, "bar" }, + { kPgmEvtScId, "pgm" }, + { kCtlEvtScId, "ctl" }, + { kNonEvtScId, "non" }, + { kPedalEvtScId, "ped" }, + { kInvalidId, "" } + }; + + varMap_t _varMapA[] = + { + { kEvenVarScId, kEvenFl, 0, "e"}, + { kEvenVarScId, kEvenFl | kEvenEndFl, kEvenEndFl, "E"}, + { kDynVarScId, kDynFl, 0, "d"}, + { kDynVarScId, kDynFl | kDynEndFl, kDynEndFl, "D"}, + { kTempoVarScId,kTempoFl, 0, "t"}, + { kTempoVarScId,kTempoFl | kTempoEndFl, kTempoEndFl, "T"}, + { kInvalidId, 0, 0, ""} + }; + + namespace parser + { + typedef struct dynRef_str + { + char* label; + unsigned labelCharCnt; + uint8_t vel; + } dynRef_t; + + typedef struct sfscore_parser_str + { + unsigned dynRefN; + dynRef_t* dynRefA; + + p_section_t* begSectionL; + p_section_t* endSectionL; + + unsigned nextSetId; + p_set_t* begSetL; + p_set_t* endSetL; + + unsigned eventAllocN; + unsigned eventN; + p_event_t* eventA; + + } sfscore_parser_t; + + sfscore_parser_t* _handleToPtr( handle_t h ) + { return handleToPtr(h); } + + unsigned _dyn_label_to_level( sfscore_parser_t* p, const char* dynLabel ) + { + const char* end; + unsigned char_cnt; + + if( dynLabel == nullptr || textLength(dynLabel)==0 ) + return kInvalidDynVel; + + end = nextWhiteCharEOS(dynLabel); + assert(end != nullptr ); + char_cnt = end - dynLabel; + + for(unsigned i=0; idynRefN; ++i) + if( textCompare(p->dynRefA[i].label,dynLabel,char_cnt) == 0 ) + return p->dynRefA[i].vel; + + cwLogError(kSyntaxErrorRC,"The dynamic label '%s' is not valid.",cwStringNullGuard(dynLabel)); + return kInvalidDynVel; + } + + rc_t _destroy( sfscore_parser_t* p ) + { + for(p_set_t* s = p->begSetL; s!=nullptr; ) + { + p_set_t* s0 = s->link; + mem::release(s->eventA); + mem::release(s); + s = s0; + } + + for(p_section_t* s = p->begSectionL; s!=nullptr; ) + { + p_section_t* s0 = s->link; + mem::release(s->label); + mem::release(s); + s = s0; + } + + for(p_event_t* e = p->eventA; eeventA+p->eventN; ++e) + { + mem::release(e->sciPitch); + for(unsigned i=kMinVarScId; isectionLabelA[i]); + } + + for(dynRef_t* d=p->dynRefA; ddynRefA + p->dynRefN; ++d) + mem::release(d->label); + + mem::release(p->dynRefA); + mem::release(p->eventA); + mem::release(p); + + return kOkRC; + } + + p_section_t* _find_section( sfscore_parser_t* p, const char* section_label) + { + for(p_section_t* s = p->begSectionL; s!=nullptr; s=s->link) + if( textIsEqual(s->label,section_label) ) + return s; + return nullptr; + } + + p_section_t* _create_section( sfscore_parser_t* p, const char* section_label, unsigned begEvtIdx ) + { + p_section_t* s; + + if((s = _find_section(p,section_label)) != nullptr ) + { + s = nullptr; + cwLogError(kInvalidIdRC,"Duplicate section label (%s) detected.",cwStringNullGuard(section_label)); + goto errLabel; + } + else + { + s = mem::allocZ(); + + s->label = mem::duplStr(section_label); + s->begEvtIdx = begEvtIdx; + + if( p->endSectionL == nullptr ) + { + p->endSectionL = s; + assert( p->begSectionL == nullptr ); + p->begSectionL = s; + } + else + { + p->endSectionL->link = s; + p->endSectionL = s; + } + } + + errLabel: + return s; + + } + + // Once the var. spec. label is known to have a section id this function extracts the section label + unsigned _parse_set_end_section_label( const char* label, const char*& sectionLabelRef ) + { + rc_t rc = kOkRC; + const char* c = nullptr; + + sectionLabelRef = nullptr; + + // a label with a section will have internal whitespace + if((c = nextWhiteChar(label)) == nullptr ) + return rc; + + // advance past the white space to the first char of the section id + if( (c=nextNonWhiteChar(c)) == nullptr ) + goto errLabel; + + // parse the section id + sectionLabelRef = c; + + return rc; + + errLabel: + return cwLogError(kSyntaxErrorRC,"The section number could not be parsed from '%s'.",label); + } + + + // Parse the optional set section number following the var spec. label. + rc_t _parse_var_set_section_label(sfscore_parser_t* p, p_event_t* e, const char* label, unsigned varTypeEndFlag, char*& sectionLabelRef ) + { + rc_t rc = kOkRC; + + const char* section_label = nullptr; + + sectionLabelRef = nullptr; + + // attempt to get the section id following the var marker + if((rc = _parse_set_end_section_label(label,section_label)) != kOkRC ) + goto errLabel; + + // if a section id was found then duplicate it + if( section_label != nullptr ) + { + sectionLabelRef = mem::duplStr(section_label); + e->flags |= varTypeEndFlag; + } + errLabel: + + return rc; + } + + // A variable specification indicate how a note is to be measured. It is contained + // in the 'tempo','even', and 'dyn' columns. The specification contains two parts: + // a char string id ('t','e', (e.g. p,pp,mf,fff,etc)) followed by + // an optional section identifier. The section identifer marks the end + // of a 'set' of var. spec's and also idicates the section which will be modified + // according to the measurements. + rc_t _parse_var_spec( sfscore_parser_t* p, p_event_t* e, const char* mark_label, unsigned varTypeFlag, unsigned varTypeEndFlag, char*& sectionLabelRef ) + { + rc_t rc = kOkRC; + + unsigned flags = 0; + sectionLabelRef = nullptr; + + if( mark_label==nullptr || textLength(mark_label)==0) + return rc; + + switch( varTypeFlag ) + { + case kDynFl: + if((e->dynVal = _dyn_label_to_level(p,mark_label)) == kInvalidDynVel ) + { + cwLogError(kSyntaxErrorRC,"Note dynamic var spec parse failed on label:%s",cwStringNullGuard(mark_label)); + goto errLabel; + } + flags |= kDynFl; + break; + + case kEvenFl: + case kTempoFl: + flags= var_label_to_type_flag(mark_label); + break; + + default: + rc = cwLogError(kSyntaxErrorRC,"The var spec flag '%s' is not valid.",cwStringNullGuard(mark_label)); + goto errLabel; + } + + + if( cwIsFlag(e->flags,varTypeFlag ) ) + { + rc = cwLogError(kInvalidIdRC,"The var spec flag was expected to be '%s' but instead was '%s'.",var_type_flag_to_label(varTypeFlag),var_type_flag_to_label(flags)); + goto errLabel; + } + + // parse the optional section id. + if((rc = _parse_var_set_section_label(p,e,mark_label,varTypeEndFlag,sectionLabelRef)) != kOkRC ) + goto errLabel; + + e->flags |= flags; + errLabel: + return rc; + } + + rc_t _parse_csv_note(sfscore_parser_t* p, + p_event_t* e, + uint8_t d0, + uint8_t d1, + const char* sciPitch, + const char* evenLabel, + const char* tempoLabel, + const char* dynLabel) + { + rc_t rc = kOkRC; + + if( textLength(sciPitch)<2 ) + { + rc = cwLogError(kSyntaxErrorRC,"Blank or invalid scientific pitch."); + goto errLabel; + } + + if((rc = _parse_var_spec(p,e,evenLabel,kEvenFl,kEvenEndFl,e->sectionLabelA[kEvenVarScId] )) != kOkRC ) + goto errLabel; + + if((rc = _parse_var_spec(p,e,tempoLabel,kTempoFl,kTempoEndFl,e->sectionLabelA[kTempoVarScId] )) != kOkRC ) + goto errLabel; + + if((rc = _parse_var_spec(p,e,dynLabel,kDynFl,kDynEndFl,e->sectionLabelA[kDynVarScId])) != kOkRC ) + goto errLabel; + + e->pitch = d0; + e->vel = d1; + e->sciPitch = mem::duplStr(sciPitch); + + errLabel: + return rc; + } + + + rc_t _parse_csv_row( sfscore_parser_t* p, + csv::handle_t& csvH, + unsigned cur_line_idx, + double& curSecRef, + unsigned& curLocIdxRef, + p_section_t*& curSectionRef, + unsigned& curSectionNoteIdxRef, + unsigned& curBarNumbRef, + unsigned& curBarNoteIdxRef ) + { + rc_t rc = kOkRC; + + const char* opcodeLabel; + const char* arg0; + const char* evenLabel; + const char* tempoLabel; + const char* dynLabel; + uint8_t d0,d1; + const char* sectionLabel; + p_event_t* e = p->eventA + p->eventN; + + // verify that there is an available slot in the event array + if( p->eventN >= p->eventAllocN ) + { + rc = cwLogError(kBufTooSmallRC,"The event record array is too small."); + goto errLabel; + } + + if((rc = getv( csvH, + "opcode",opcodeLabel, + "evt", e->csvEventId, + "micros",e->secs, + "d0",d0, + "d1",d1, + "arg0",arg0, + "bar",e->barNumb, + "even",evenLabel, + "tempo",tempoLabel, + "t_frac",e->t_frac, + "dyn",dynLabel, + "section",sectionLabel )) != kOkRC ) + { + rc = cwLogError(rc,"Error parsing score CSV row." ); + goto errLabel; + } + + // validate the opcode + if((e->typeId = opcode_label_to_id( opcodeLabel )) == kInvalidId ) + { + cwLogError(kSyntaxErrorRC,"The opcode type:'%s' is not valid.",cwStringNullGuard(opcodeLabel)); + goto errLabel; + } + + switch( e->typeId ) + { + case kBarEvtScId: + if( curBarNumbRef != kInvalidId && e->barNumb != curBarNumbRef+1 ) + { + rc = cwLogError(kInvalidStateRC,"Missig bar number %i. Jumped from bar:%i to bar:%i.", curBarNumbRef+1,curBarNumbRef,e->barNumb); + goto errLabel; + } + + curBarNumbRef = e->barNumb; + curBarNoteIdxRef = 0; + break; + + case kCtlEvtScId: + break; + + case kNonEvtScId: + { + if( e->secs != curSecRef ) + { + curSecRef = e->secs; + curLocIdxRef += 1; + } + + e->locIdx = curLocIdxRef; + e->index = p->eventN; + e->line = cur_line_idx; + e->csvRowNumb = cur_line_idx+1; + e->barNumb = curBarNumbRef; + e->barNoteIdx = curBarNoteIdxRef++; + + if((rc = _parse_csv_note(p,e,d0,d1,arg0,evenLabel,tempoLabel,dynLabel)) != kOkRC ) + { + cwLogError(rc,"Note parse failed."); + goto errLabel; + } + + // if this event has a section label + if( sectionLabel != nullptr and textLength(sectionLabel)>0 ) + { + // locate the section record + if((curSectionRef = _create_section(p,sectionLabel,e->index)) == nullptr ) + { + rc = cwLogError(kOpFailRC,"The section label '%s' create failed.",cwStringNullGuard(sectionLabel)); + goto errLabel; + } + curSectionNoteIdxRef = 0; + } + + + + e->section = curSectionRef; + e->sectionIdx = curSectionNoteIdxRef; + curSectionNoteIdxRef += 1; + + p->eventN += 1; + } + break; + + default: + cwLogError(kInvalidArgRC,"The opcode type '%s' is not valid in this context.",cwStringNullGuard(opcodeLabel)); + goto errLabel; + break; + } + + errLabel: + return rc; + } + + rc_t _parse_events( sfscore_parser_t* p, csv::handle_t csvH ) + { + rc_t rc; + unsigned cur_line_idx = 0; + double curSec = 0; + unsigned curLocIdx = 0; + p_section_t* curSection = nullptr; + unsigned curSectionNoteIdx = 0; + unsigned curBarNumb = kInvalidId; + unsigned curBarNoteIdx = 0; + + // 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); + + do + { + cur_line_idx = cur_line_index(csvH); + + // advance the CSV line cursor + switch(rc = next_line(csvH)) + { + case kOkRC: + { + + if((rc = _parse_csv_row(p, csvH, cur_line_idx, curSec, curLocIdx, curSection, curSectionNoteIdx, curBarNumb, curBarNoteIdx )) != kOkRC ) + goto errLabel; + + } + break; + + case kEofRC: + break; + + default: + rc = cwLogError(rc,"CSV line iteration error on CSV event parse."); + goto errLabel; + } + + }while( rc != kEofRC ); + + rc = kOkRC; + + errLabel: + if( rc != kOkRC ) + rc = cwLogError(rc,"CSV parse failed on row number:%i.",cur_line_idx+1); + + return rc; + + } + + rc_t _parse_csv( sfscore_parser_t* p, const char* fname ) + { + rc_t rc = kOkRC; + const char* titleA[] = { "id","trk","evt","opcode","dticks","micros","status","meta", + "ch","d0","d1","arg0","arg1","bar","skip","even","grace", + "tempo","t_frac","dyn","section","play_recd","remark" }; + 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; + } + + // 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; + } + + p_set_t* _alloc_set( sfscore_parser_t* p, unsigned varTypeId, p_event_t* beg_evt ) + { + p_set_t* s = mem::allocZ(); + s->varTypeId = varTypeId; + s->beg_event = beg_evt; + s->id = p->nextSetId++; + + if( p->endSetL == nullptr ) + { + p->begSetL = s; + p->endSetL = s; + } + else + { + p->endSetL->link = s; + p->endSetL = s; + } + + return s; + } + + rc_t _fill_set( sfscore_parser_t* p, p_set_t* set, p_event_t* end_event ) + { + rc_t rc = kOkRC; + unsigned refVarFlag = var_type_id_to_flag(set->varTypeId); + unsigned evtIdx = 0; + + set->eventA = mem::allocZ(set->eventN); + assert( set->beg_event <= end_event ); + + for(p_event_t* e = set->beg_event; e<=end_event && evtIdx < set->eventN; ++e) + if( cwIsFlag(e->flags,refVarFlag) ) + set->eventA[evtIdx++] = e; + + if( evtIdx != set->eventN ) + { + rc = cwLogError(kOpFailRC,"%i events were located for set '%i' but %i were filled.",set->eventN,set->id,evtIdx); + goto errLabel; + } + + errLabel: + return rc; + } + + + bool _is_end_of_set( sfscore_parser_t* p, unsigned varTypeId, p_event_t* e ) + { + + unsigned varTypeMask = var_type_id_to_mask(varTypeId); + + // if this e is marked as end-of-set + if( cwAllFlags(e->flags,varTypeMask) ) + { + + if( e->index < p->eventN-1 ) + { + p_event_t* e1 = e + 1; + // ... and the next event is at the same loc and also marked as end-of-set don't end the set + if( e1->locIdx == e->locIdx && cwAllFlags(e1->flags,varTypeMask) ) + { + e->flags = cwClrFlag(e->flags,var_type_id_to_end_flag(varTypeId)); + return false; + } + } + + return true; + } + + return false; + } + + rc_t _register_event_with_set( sfscore_parser_t* p, p_event_t* e, unsigned varTypeId, p_set_t*& setRef ) + { + rc_t rc = kOkRC; + + // if there is no set to register this event in then create a new set + if( setRef == nullptr ) + setRef = _alloc_set(p,varTypeId,e); + + setRef->eventN += 1; + + e->setA[ varTypeId ] = setRef; + + // if this event is marked as an end-of-set + if( _is_end_of_set(p,varTypeId,e) ) + { + if((rc = _fill_set(p, setRef, e )) != kOkRC ) + goto errLabel; + + setRef = nullptr; + } + + errLabel: + return kOkRC; + } + + rc_t _create_sets( sfscore_parser_t* p ) + { + rc_t rc = kOkRC; + p_set_t* setA[ kScVarCnt ]; + p_event_t* e = p->eventA; + + for(unsigned i=kMinVarScId; ieventA + p->eventN; ++e) + { + // for each possible var type + for(unsigned refVarTypeId=0; refVarTypeIdflags,refVarTypeFlag) ) + { + // insert the event into a set + if((rc = _register_event_with_set(p, e, refVarTypeId, setA[ refVarTypeId ])) != kOkRC ) + { + rc = cwLogError(rc,"Event '%s' set registration failed.",var_type_id_to_label(refVarTypeId)); + goto errLabel; + } + } + } + } + + errLabel: + return rc; + } + + + rc_t _assign_set_sections(sfscore_parser_t* p ) + { + rc_t rc = kOkRC; + + // for each possible var type + for(unsigned refVarTypeId=kMinVarScId; refVarTypeIdeventA + p->eventN - 1; + + // iterate backward + for(; e>=p->eventA; --e) + { + // if this event is the end of a section for this variable type + if( e->sectionLabelA[refVarTypeId]!=nullptr ) + { + // locate the section + if((section = _find_section( p, e->sectionLabelA[ refVarTypeId ] )) == nullptr ) + { + rc = cwLogError(kInvalidIdRC,"The section label '%s' at CSV line '%i' could not be found.", cwStringNullGuard(e->sectionLabelA[ refVarTypeId ]),e->csvRowNumb); + goto errLabel; + } + + } + + // if this event is assigned to a set of type refVarTypeId + if( e->setA[ refVarTypeId ] != nullptr ) + { + + // has this set already been assigned to a target + if( e->setA[ refVarTypeId ]->target_section != nullptr && e->setA[ refVarTypeId ]->target_section != section ) + { + cwLogWarning("The set '%i' was previously assigned to a different section:'%s' and is now assigned to '%s'.", + e->setA[ refVarTypeId ]->id, + e->setA[ refVarTypeId ]->target_section->label, + section->label); + } + + // assign a section to this set + e->setA[ refVarTypeId ]->target_section = section; + } + + } + } + + errLabel: + return rc; + } + + rc_t _validate_sets( sfscore_parser_t* p ) + { + rc_t rc = kOkRC; + + for(p_set_t* s = p->begSetL; s!=nullptr; s=s->link) + { + // the set must be assigned a target section + if( s->target_section == nullptr ) + { + rc = cwLogError(kInvalidStateRC,"Set (id=%i) of type '%s' beginning at (csv row:%i bar:%i bni:%i) has not been assigned a target section.", s->id, var_type_id_to_label(s->varTypeId), s->beg_event->csvRowNumb, s->beg_event->barNumb, s->beg_event->barNoteIdx ); + continue; + } + + // tempo and even sets must have at least 3 members + if( (s->varTypeId == kEvenVarScId || s->varTypeId == kTempoVarScId) && s->eventN < 3 ) + { + cwLogWarning("Set (id=%i) of type '%s' beginning at (csv row:%i bar:%i bni:%i) should have at least 3 members.", s->id, var_type_id_to_label(s->varTypeId), s->beg_event->csvRowNumb, s->beg_event->barNumb, s->beg_event->barNoteIdx ); + continue; + } + + p_event_t* e0 = nullptr; + + // for each set member + for(unsigned i=0; ieventN; ++i) + { + p_event_t* e1 = s->eventA[i]; + + // the set->eventA[] must be filled with valid pointers + if( e1 == nullptr ) + { + rc = cwLogError(kInvalidStateRC,"Set (id=%i) of type '%s' beginning at (csv row:%i bar:%i bni:%i) contains a NULL event.", s->id, var_type_id_to_label(s->varTypeId), s->beg_event->csvRowNumb, s->beg_event->barNumb, s->beg_event->barNoteIdx ); + goto errLabel; + } + + // the events contained in this set must also point to this set + if( e1->setA[ s->varTypeId ] != s ) + rc = cwLogError(kInvalidStateRC,"Set (id=%i) of type '%s' contains an event (csv row:%i bar:%i bni:%i) whose set pointer is not pointing to this set.", s->id, var_type_id_to_label(s->varTypeId), e1->csvRowNumb, e1->barNumb, e1->barNoteIdx ); + + // the events must be ordered in increasing time + if( e0 != nullptr && e0->secs > e1->secs ) + rc = cwLogError(kInvalidStateRC,"Set (id=%i) of type '%s' contains an event (csv row:%i bar:%i bni:%i) which is out of time order with the previous set event.", s->id,var_type_id_to_label(s->varTypeId), e1->csvRowNumb, e1->barNumb, e1->barNoteIdx ); + + // the locations must be ordered in increasing time + if( e0 != nullptr && e0->locIdx > e1->locIdx ) + rc = cwLogError(kInvalidStateRC,"Set (id=%i) of type '%s' contains an event (csv row:%i bar:%i bni:%i) which is out of loc. order with the previous set event.", s->id,var_type_id_to_label(s->varTypeId), e1->csvRowNumb, e1->barNumb, e1->barNoteIdx ); + + e0 = e1; + } + + } + errLabel: + return rc; + } + + + // Set section targets must occur on events prior to the first event in the target section + rc_t _validate_section_assignments( sfscore_parser_t* p ) + { + rc_t rc = kOkRC; + + // for each event + for(p_event_t* e=p->eventA; eeventA+p->eventN; ++e) + { + + // verify that this event has been assigned to a section + if( e->section == nullptr ) + { + rc = cwLogError(kInvalidStateRC,"The event at csv row:%i bar:%i bni:%i was not assigned to a section.", + e->csvRowNumb,e->barNumb,e->barNoteIdx); + continue; + } + + // for each possible var type that this event may belong to + for(unsigned refVarTypeId=kMinVarScId; refVarTypeIdsetA[ refVarTypeId ] != nullptr ) + { + if( e->setA[ refVarTypeId ]->target_section == nullptr ) + { + rc = cwLogError(kInvalidStateRC,"No target section was assigned to the set '%s' of the event at csv row:%i bar:%i bni:%i is after the event.", + var_type_id_to_label(refVarTypeId),e->csvRowNumb,e->barNumb,e->barNoteIdx); + continue; + } + + if( textCompare(e->setA[ refVarTypeId ]->target_section->label,e->section->label) <= 0 ) + { + rc = cwLogError(kInvalidStateRC,"The target section for '%s' of the event at csv row:%i bar:%i bni:%i is after the event.", + var_type_id_to_label(refVarTypeId),e->csvRowNumb,e->barNumb,e->barNoteIdx); + continue; + } + } + } + + return rc; + } + + } + } +} + +unsigned cw::sfscore::opcode_label_to_id( const char* label ) +{ + unsigned id; + if((id = labelToId( _opcodeMapA, label, kInvalidId )) == kInvalidId ) + cwLogError(kInvalidArgRC,"'%s' is not a valid event opcode type label.",cwStringNullGuard(label)); + + return id; +} + +const char* cw::sfscore::opcode_id_to_label( unsigned opcode_id ) +{ + const char* label; + if((label = idToLabel( _opcodeMapA, opcode_id, kInvalidEvtScId)) == nullptr ) + cwLogError(kInvalidArgRC,"The event opcode type id '%i' is not valid.",opcode_id); + + return label; +} + +unsigned cw::sfscore::var_label_to_type_id( const char* label ) +{ + if( label!=nullptr && textLength(label)>0 ) + { + char varLabel[] = { label[0],0 }; + for(unsigned i=0; _varMapA[i].typeId != kInvalidId; ++i) + if( textCompare(varLabel,_varMapA[i].label) == 0 ) + return _varMapA[i].typeId; + } + + cwLogError(kInvalidArgRC,"The variable label '%s' is not valid.",cwStringNullGuard(label)); + + return kInvalidId; +} + +unsigned cw::sfscore::var_label_to_type_flag( const char* varLabel ) +{ + if(varLabel==nullptr || textLength(varLabel) == 0) + return 0; + + for(unsigned i=0; _varMapA[i].typeId != kInvalidId; ++i) + if( textCompare(_varMapA[i].label,varLabel,textLength(_varMapA[i].label))==0 ) + return _varMapA[i].flag; + + + cwLogError(kInvalidArgRC,"The variable type label '%s' is not valid.",cwStringNullGuard(varLabel)); + + return 0; +} + +const char* cw::sfscore::var_type_id_to_label( unsigned varTypeId ) +{ + for(unsigned i=0; _varMapA[i].typeId != kInvalidId; ++i) + if( _varMapA[i].typeId == varTypeId ) + return _varMapA[i].label; + + cwLogError(kInvalidArgRC,"The variable type id '%i' is not valid.",varTypeId); + + return nullptr; +} + +const char* cw::sfscore::var_type_flag_to_label( unsigned varTypeFlag ) +{ + for(unsigned i=0; _varMapA[i].typeId != kInvalidId; ++i) + if( _varMapA[i].flag == (varTypeFlag & kFlagMask) ) + return _varMapA[i].label; + + return nullptr; +} + +char cw::sfscore::var_type_flag_to_char( unsigned varTypeFlag ) +{ + const char* s; + if((s = var_type_flag_to_label(varTypeFlag)) != nullptr ) + return *s; + return ' '; +} + + +unsigned cw::sfscore::var_type_id_to_flag( unsigned varTypeId ) +{ + for(unsigned i=0; _varMapA[i].typeId != kInvalidId; ++i) + if( _varMapA[i].typeId == varTypeId && _varMapA[i].endFl == false ) + return _varMapA[i].flag; + + return 0; +} + +unsigned cw::sfscore::var_type_id_to_mask( unsigned varTypeId ) +{ + for(unsigned i=0; _varMapA[i].typeId != kInvalidId; ++i) + if( _varMapA[i].typeId == varTypeId && _varMapA[i].endFl ) + return _varMapA[i].flag; + + return 0; +} + +unsigned cw::sfscore::var_type_id_to_end_flag( unsigned varTypeId ) +{ + for(unsigned i=0; _varMapA[i].typeId != kInvalidId; ++i) + if( _varMapA[i].typeId == varTypeId && _varMapA[i].endFl ) + return _varMapA[i].endFl; + + return 0; +} + + +unsigned cw::sfscore::var_type_flag_to_id( unsigned varTypeFlag ) +{ + for(unsigned i=0; _varMapA[i].typeId != kInvalidId; ++i) + if( _varMapA[i].flag == varTypeFlag ) + return _varMapA[i].typeId; + + cwLogError(kInvalidArgRC,"The variable flag id '0x%x' is not valid.",varTypeFlag); + return kInvalidId; +} + + + +cw::rc_t cw::sfscore::parser::create( handle_t& hRef, const char* fname, const dyn_ref_t* dynRefA, unsigned dynRefN ) +{ + rc_t rc; + if((rc = destroy(hRef)) != kOkRC ) + return rc; + + sfscore_parser_t* p = mem::allocZ(); + + p->dynRefN = dynRefN; + p->dynRefA = mem::allocZ(p->dynRefN); + for(unsigned i=0; idynRefN; ++i) + { + if( dynRefA[i].vel == kInvalidDynVel ) + { + cwLogError(kInvalidArgRC,"The value '%i' is reserved to mark invalid values and cannot be used in the dynamic reference array.",kInvalidDynVel); + goto errLabel; + } + else + { + p->dynRefA[i].label = mem::duplStr(dynRefA[i].label); + p->dynRefA[i].labelCharCnt = textLength(dynRefA[i].label); + p->dynRefA[i].vel = dynRefA[i].vel; + } + } + + if((rc = _parse_csv( p, fname )) != kOkRC ) + { + rc = cwLogError(rc,"sfscore CSV parse failed."); + goto errLabel; + } + + if((rc = _create_sets(p)) != kOkRC ) + { + rc = cwLogError(rc,"sfscore var. set creation failed."); + goto errLabel; + } + + if((rc = _assign_set_sections(p)) != kOkRC ) + { + rc = cwLogError(rc,"sfscore set target section assignmet failed."); + goto errLabel; + } + + + if((rc = _validate_sets( p )) != kOkRC ) + { + rc = cwLogError(rc,"sfscore set validation failed."); + goto errLabel; + } + + if((rc = _validate_section_assignments( p )) != kOkRC ) + goto errLabel; + + + hRef.set(p); + + errLabel: + if( rc != kOkRC ) + { + rc = cwLogError(rc,"sfscore create failed on '%s'.",cwStringNullGuard(fname)); + _destroy(p); + } + return rc; + +} + +cw::rc_t cw::sfscore::parser::destroy( handle_t& hRef ) +{ + rc_t rc = kOkRC; + + sfscore_parser_t* p = nullptr;; + + if( !hRef.isValid() ) + return rc; + + p = _handleToPtr(hRef); + + if((rc = _destroy(p)) != kOkRC ) + return rc; + + hRef.clear(); + + return rc; + +} + +unsigned cw::sfscore::parser::event_count( handle_t h ) +{ + sfscore_parser_t* p = _handleToPtr(h); + return p->eventN; +} + +const cw::sfscore::parser::p_event_t* cw::sfscore::parser::event_array( handle_t h ) +{ + sfscore_parser_t* p = _handleToPtr(h); + return p->eventA; +} + +unsigned cw::sfscore::parser::section_count( handle_t h ) +{ + sfscore_parser_t* p = _handleToPtr(h); + unsigned n=0; + for(p_section_t* s=p->begSectionL; s!=nullptr; s=s->link) + ++n; + return n; +} + +const cw::sfscore::parser::p_section_t* cw::sfscore::parser::section_list( handle_t h ) +{ + sfscore_parser_t* p = _handleToPtr(h); + return p->begSectionL; +} + +unsigned cw::sfscore::parser::set_count( handle_t h ) +{ + return 0; +} + +const cw::sfscore::parser::p_set_t* cw::sfscore::parser::set_array( handle_t h ) +{ + return nullptr; +} + +void cw::sfscore::parser::report( handle_t h ) +{ + sfscore_parser_t* p = _handleToPtr(h); + + for(p_event_t* e = p->eventA; eeventA+p->eventN; ++e) + { + + printf("%5i %5i %7.3f %s %s %3i %3i %3i %4s %3i %4.3f ", + e->index, + e->locIdx, + e->secs, + opcode_id_to_label(e->typeId), + e->section==nullptr ? " " : cwStringNullGuard(e->section->label), + e->sectionIdx, + e->barNumb, + e->barNoteIdx, + e->sciPitch, + e->vel, + e->t_frac ); + + for(unsigned refVarTypeId=kMinVarScId; refVarTypeIdsetA[refVarTypeId] == nullptr ) + printf(" "); + else + { + unsigned varRefFlags = var_type_id_to_mask(refVarTypeId); + const char* sect_label = e->setA[refVarTypeId]->target_section==nullptr ? "****" : e->setA[refVarTypeId]->target_section->label; + printf("%c-%03i-%s ",var_type_flag_to_char(e->flags & varRefFlags), e->setA[refVarTypeId]->id, sect_label); + } + } + + printf("\n"); + } + +} + +cw::rc_t cw::sfscore::parser::test( const char* fname, const dyn_ref_t* dynRefA, unsigned dynRefN ) +{ + handle_t h; + rc_t rc; + if((rc = create(h,fname,dynRefA,dynRefN)) != kOkRC ) + goto errLabel; + + errLabel: + + if( rc != kOkRC ) + rc = cwLogError(rc,"Parser test failed."); + + rc_t rc1 = destroy(h); + + return rcSelect(rc,rc1); +} diff --git a/cwSfScoreParser.h b/cwSfScoreParser.h new file mode 100644 index 0000000..f3f89bb --- /dev/null +++ b/cwSfScoreParser.h @@ -0,0 +1,106 @@ +#ifndef cwSfScoreParser_h +#define cwSfScoreParser_h + +namespace cw +{ + namespace sfscore + { + + enum { + kEvenFl = 0x0001, + kEvenEndFl = 0x0002, + kDynFl = 0x0004, + kDynEndFl = 0x0008, + kTempoFl = 0x0010, + kTempoEndFl = 0x0020, + + kFlagMask = 0x003f + }; + + + unsigned opcode_label_to_id( const char* label ); + const char* opcode_id_to_label( unsigned opcode_id ); + + unsigned var_label_to_type_id( const char* label ); + unsigned var_label_to_type_flag( const char* label ); + const char* var_type_id_to_label( unsigned varTypeId ); + const char* var_type_flag_to_label( unsigned varTypeFlag ); + char var_type_flag_to_char( unsigned varTypeFlag ); + unsigned var_type_id_to_flag( unsigned varTypeId ); // returns type flag + unsigned var_type_id_to_mask( unsigned varTypeId ); // returns type flag and end flag mask + unsigned var_type_id_to_end_flag( unsigned varTypeId ); // returns end flag + unsigned var_type_flag_to_id( unsigned varTypeFlag ); + + namespace parser + { + typedef handle handle_t; + + typedef struct p_section_str + { + char* label; + unsigned begEvtIdx; + unsigned endEvtIdx; + struct p_section_str* link; + } p_section_t; + + typedef struct p_set_str + { + unsigned id; // + unsigned varTypeId; // + p_section_t* target_section; // section to which this set will be applied + struct p_event_str* beg_event; // first event in this section + unsigned eventN; // count of events in this set + struct p_event_str** eventA; // points to each event this set + struct p_set_str* link; // + } p_set_t; + + + typedef struct p_event_str + { + unsigned typeId; // opcode type id + unsigned csvEventId; // CSV 'evt' id + unsigned csvRowNumb; // CSV line number + unsigned line; // + unsigned index; // index into eventArray[] + double secs; // event offset from beginning of score in seconds + double barSecs; // event offset from bar in sectonds + unsigned locIdx; // location index (chord notes share the same locIdx) + unsigned barNumb; // bar this event belongs to + unsigned barNoteIdx; // note index of this note in the bar to which it belongs. + p_section_t* section; // section to which this event belongs + unsigned sectionIdx; // note index of this note in the section to which it belongs + unsigned flags; // attribute flags + midi::byte_t pitch; + midi::byte_t vel; + unsigned dynVal; // index into dynRefA[] + double t_frac; // + char* sciPitch; // + + char* sectionLabelA[ kScVarCnt ]; // var end section labels (if this event is the last event in a section) + p_set_t* setA[ kScVarCnt ]; // set points (if this event is part of a set) + + } p_event_t; + + rc_t create( handle_t& hRef, const char* fname, const dyn_ref_t* dynRefA, unsigned dynRefN ); + rc_t destroy( handle_t& hRef ); + + unsigned event_count( handle_t h ); + const p_event_t* event_array( handle_t h ); + + unsigned section_count( handle_t h ); + const p_section_t* section_list( handle_t h ); + + unsigned set_count( handle_t h ); + const p_set_t* set_array( handle_t h ); + + + void report( handle_t h ); + + rc_t test( const char* fname, const dyn_ref_t* dynRefA, unsigned dynRefN ); + + } + + } +} + +#endif