1139 lines
35 KiB
C++
1139 lines
35 KiB
C++
//| Copyright: (C) 2020-2024 Kevin Larke <contact AT larke DOT org>
|
|
//| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file.
|
|
#include "cwCommon.h"
|
|
#include "cwLog.h"
|
|
#include "cwCommonImpl.h"
|
|
#include "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, "<invalid>" }
|
|
};
|
|
|
|
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, "<invalid>"}
|
|
};
|
|
|
|
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<handle_t,sfscore_parser_t>(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; i<p->dynRefN; ++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; e<p->eventA+p->eventN; ++e)
|
|
{
|
|
mem::release(e->sciPitch);
|
|
for(unsigned i=kMinVarScId; i<kScVarCnt; ++i)
|
|
mem::release(e->sectionLabelA[i]);
|
|
}
|
|
|
|
for(dynRef_t* d=p->dynRefA; d<p->dynRefA + 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<p_section_t>();
|
|
|
|
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',<dyn mark> (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_event_t>(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;
|
|
}
|
|
|
|
|
|
|
|
// Allocate a new set record.
|
|
p_set_t* _alloc_set( sfscore_parser_t* p, unsigned varTypeId, p_event_t* beg_evt )
|
|
{
|
|
p_set_t* s = mem::allocZ<p_set_t>();
|
|
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;
|
|
}
|
|
|
|
// Fill a set eventA[] by iterating from set->beg_event to 'end_event'
|
|
// and storing all events of type set->varTypeid.
|
|
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<p_event_t*>(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;
|
|
}
|
|
|
|
|
|
// Returns true if the event 'e' is the last event in a set of type 'varTypeId'.
|
|
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;
|
|
}
|
|
|
|
// Register an event to the set to which it belongs. If the set does not exist then create it.
|
|
// Setup pointers from the set to the event and the set to the event.
|
|
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;
|
|
}
|
|
|
|
// Check for the existence of each var type on each event and then register the event with the set.
|
|
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; i<kScVarCnt; ++i)
|
|
setA[i] = nullptr;
|
|
|
|
// for each event
|
|
for(; e<p->eventA + p->eventN; ++e)
|
|
{
|
|
// for each possible var type
|
|
for(unsigned refVarTypeId=0; refVarTypeId<kScVarCnt; ++refVarTypeId)
|
|
{
|
|
// get the flag for this var type
|
|
unsigned refVarTypeFlag = var_type_id_to_flag(refVarTypeId);
|
|
|
|
// is this event of this type of var
|
|
if( cwIsFlag(e->flags,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;
|
|
}
|
|
|
|
|
|
// Assign a target section to each set. Since only the last set of a consecutive set of sets
|
|
// has a target section id this function iterates backwards throught the events,
|
|
// notices the last event in the last set of a group of sets, and then propogates the
|
|
// associated target section back to previous sets.
|
|
rc_t _assign_set_sections(sfscore_parser_t* p )
|
|
{
|
|
rc_t rc = kOkRC;
|
|
|
|
// for each possible var type
|
|
for(unsigned refVarTypeId=kMinVarScId; refVarTypeId<kScVarCnt; ++refVarTypeId )
|
|
{
|
|
p_section_t* section = nullptr;
|
|
|
|
p_event_t* e = p->eventA + p->eventN - 1;
|
|
|
|
// iterate backward through the event list
|
|
for(; e>=p->eventA; --e)
|
|
{
|
|
// if this event is the end of a section for this variable type - then previous sets of this type will be assigned to this section
|
|
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;
|
|
}
|
|
|
|
// Validate as many set invariants as possible.
|
|
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; i<s->eventN; ++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; e<p->eventA+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; refVarTypeId<kScVarCnt; ++refVarTypeId)
|
|
if( e->setA[ refVarTypeId ] != nullptr )
|
|
{
|
|
// verify that the target section for this set begins after this event
|
|
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 = idToLabelNull( _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<sfscore_parser_t>();
|
|
|
|
p->dynRefN = dynRefN;
|
|
p->dynRefA = mem::allocZ<dynRef_t>(p->dynRefN);
|
|
for(unsigned i=0; i<p->dynRefN; ++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 )
|
|
{
|
|
sfscore_parser_t* p = _handleToPtr(h);
|
|
unsigned n=0;
|
|
for(p_set_t* s=p->begSetL; s!=nullptr; s=s->link)
|
|
++n;
|
|
return n;
|
|
}
|
|
|
|
const cw::sfscore::parser::p_set_t* cw::sfscore::parser::set_list( handle_t h )
|
|
{
|
|
sfscore_parser_t* p = _handleToPtr(h);
|
|
return p->begSetL;
|
|
}
|
|
|
|
void cw::sfscore::parser::report( handle_t h )
|
|
{
|
|
sfscore_parser_t* p = _handleToPtr(h);
|
|
unsigned bar0 = 0;
|
|
const char* sec0 = nullptr;
|
|
const char* blank_str = " ";
|
|
const char* sec_str = "S:";
|
|
const char* bar_str = "B:";
|
|
|
|
printf("e idx loc secs op sectn sdx bar bdx scip vel frac\n");
|
|
printf("----- ----- ------- --- ------ --- ----- --- ---- --- -----\n");
|
|
|
|
|
|
for(p_event_t* e = p->eventA; e<p->eventA+p->eventN; ++e)
|
|
{
|
|
|
|
const char* d_bar_str = bar0 != e->barNumb ? bar_str : blank_str;
|
|
const char* d_sec_str = e->section != nullptr && textIsEqual(sec0,e->section->label) ? blank_str : sec_str;
|
|
|
|
bar0 = e->barNumb;
|
|
if( e->section != nullptr )
|
|
sec0 = e->section->label;
|
|
|
|
printf("%5i %5i %7.3f %3s %2s%4s %3i %2s%3i %3i %4s %3i %5.3f ",
|
|
e->index,
|
|
e->locIdx,
|
|
e->secs,
|
|
opcode_id_to_label(e->typeId),
|
|
d_sec_str,
|
|
e->section==nullptr ? " " : cwStringNullGuard(e->section->label),
|
|
e->sectionIdx,
|
|
d_bar_str,
|
|
e->barNumb,
|
|
e->barNoteIdx,
|
|
e->sciPitch,
|
|
e->vel,
|
|
e->t_frac );
|
|
|
|
for(unsigned refVarTypeId=kMinVarScId; refVarTypeId<kScVarCnt; ++refVarTypeId)
|
|
{
|
|
if( e->setA[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);
|
|
}
|