//| Copyright: (C) 2020-2024 Kevin Larke //| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file. #include "cwCommon.h" #include "cwLog.h" #include "cwCommonImpl.h" #include "cwTest.h" #include "cwMem.h" #include "cwText.h" #include "cwObject.h" #include "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_stats( score_parse_t* p, csv::handle_t csvH, event_t* e ) { rc_t rc; if((rc = getv(csvH, "even_min", e->section->statsA[ kEvenStatIdx ].min, "even_max", e->section->statsA[ kEvenStatIdx ].max, "even_mean", e->section->statsA[ kEvenStatIdx ].mean, "even_std", e->section->statsA[ kEvenStatIdx ].std, "dyn_min", e->section->statsA[ kDynStatIdx ].min, "dyn_max", e->section->statsA[ kDynStatIdx ].max, "dyn_mean", e->section->statsA[ kDynStatIdx ].mean, "dyn_std", e->section->statsA[ kDynStatIdx ].std, "tempo_min", e->section->statsA[ kTempoStatIdx ].min, "tempo_max", e->section->statsA[ kTempoStatIdx ].max, "tempo_mean",e->section->statsA[ kTempoStatIdx ].mean, "tempo_std", e->section->statsA[ kTempoStatIdx ].std, "cost_min", e->section->statsA[ kCostStatIdx ].min, "cost_max", e->section->statsA[ kCostStatIdx ].max, "cost_mean", e->section->statsA[ kCostStatIdx ].mean, "cost_std", e->section->statsA[ kCostStatIdx ].std )) != kOkRC ) { rc = cwLogError(rc,"Error parsing CSV meas. stats field."); goto errLabel; } errLabel: return rc; } 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; } //if((rc = _parse_section_stats(p,csvH,e)) != kOkRC ) // 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, "rval",e->bpm_rval )) != 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=kOkRC,rc1=kOkRC,rc2=kOkRC; 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* oLocId = nullptr; if((rc = getv(csvH, "oloc", oLocId, "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(oLocId) > 0 ) if((rc = string_to_number(oLocId,e->oLocId)) != kOkRC ) { rc = cwLogError(rc,"Error converting oLocId (%s) to number.",oLocId); 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 _set_hash( score_parse_t* p, unsigned opId, unsigned barNumb, uint8_t midiPitch, unsigned barPitchIdx, unsigned& hashRef ) { rc_t rc = kOkRC; hashRef = 0; unsigned hash = form_hash(opId,barNumb,midiPitch,barPitchIdx); if( _hash_to_event(p,hash) != nullptr ) { rc = cwLogError(kInvalidStateRC,"The event hash '%x' is is duplicated.",hash); goto errLabel; } hashRef = hash; errLabel: 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 bpm = 0; double bpm_rval = 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->oLocId = kInvalidIdx; if((rc = getv(csvH, "opcode",opcodeLabel, "voice", e->voice, "eloc", e->eLocId, "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); e->index = p->eventN; e->dynLevel = kInvalidIdx; 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); if((rc = _set_hash( p, e->opId, cur_bar_numb, 0, 0, e->hash )) != kOkRC ) { rc = cwLogError(rc,"Error setting event hash for bar line:%i",cur_bar_numb); goto errLabel; } //printf("%i : 0x%x\n",cur_bar_numb,e->hash); } 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: if((rc = _parse_bpm_row(p,csvH, e )) == kOkRC ) { // if the cur BPM has not yet been set then go backward setting // all events prior to this to the initial BPM if( bpm == 0 && e->bpm != 0 ) std::for_each(p->eventA,e,[e](auto& x){ x.bpm = e->bpm; x.bpm_rval=e->bpm_rval; }); // if the parsed BPM is invalid ... if( e->bpm == 0 || e->bpm_rval==0 ) { e->bpm = bpm; // ... then ignore it e->bpm_rval = bpm_rval; } else { bpm = e->bpm; // ... otherwise make it the current BPM bpm_rval = e->bpm_rval; } // Be sure that all events on this location have the same BPM for(event_t* e0 = e - 1; e0>=p->eventA && e0->eLocId==e->eLocId; --e0) { e0->bpm = e->bpm; e0->bpm_rval = e->bpm_rval; } } break; case kNoteOnTId: if((rc = _parse_note_on_row(p,csvH,e)) == kOkRC ) { e->barPitchIdx = barPitchCntV[e->d0]; if((rc = _set_hash( p, e->opId, cur_bar_numb, e->d0, e->barPitchIdx, e->hash )) != kOkRC ) { rc = cwLogError(rc,"Error setting hash for note-on event: bar:%i pitch:%i bpi:%i", cur_bar_numb, e->d0, e->barPitchIdx ); goto errLabel; } 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++; e->bpm = bpm; e->bpm_rval = bpm_rval; 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","eloc","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 endLocId = 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( endLocId != kInvalidIdx && (e->eLocId > endLocId || ei==p->eventN-1) ) { cur_set->eventA = mem::allocZ(cur_set->eventN); setId += 1; setNoteIdx = 0; cur_set = nullptr; endLocId = kInvalidIdx; } // if this event 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) ) endLocId = e->eLocId; } } } 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; } } unsigned _set_count( score_parse_t* p ) { unsigned n = 0; for(set_t* s = p->begSetL; s!=nullptr; s=s->link) ++n; return n; } void _order_set_ids_by_time( score_parse_t* p ) { typedef struct set_order_str { unsigned beg_evt_idx; set_t* set; } set_order_t; unsigned setAllocN = _set_count(p); unsigned setN = 0; set_order_t* setA = mem::allocZ(setAllocN); for(set_t* s=p->begSetL; s!=nullptr; s=s->link) { if( s->eventN > 0 ) { setA[setN].beg_evt_idx = s->eventA[0]->index; setA[setN].set = s; setN += 1; } } std::sort( setA, setA+setN, [](auto a, auto b){return a.beg_evt_idxid = set_id++; }); mem::release(setA); } 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]->eLocId ) break; if( j == loc_i ) loc[ loc_i++ ] = set->eventA[i]->eLocId; } } // '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_section_sets( 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->begSetEvent == nullptr ) { set->targetSection->begSetEvent = set->eventA[0]; set->targetSection->endSetEvent = set->eventA[ set->eventN-1 ]; } else { if( set->eventA[0]->sec < set->targetSection->begSetEvent->sec ) set->targetSection->begSetEvent = set->eventA[0]; if( set->eventA[set->eventN-1]->sec > set->targetSection->endSetEvent->sec ) set->targetSection->endSetEvent = set->eventA[ set->eventN-1 ]; } } } rc_t _fill_section_beg_end_evt( score_parse_t* p ) { rc_t rc = kOkRC; for(unsigned i=0; ieventN; ++i) { event_t* e = p->eventA + i; assert( e->section != nullptr ); if( e->section->begEvent == nullptr || e->index < e->section->begEvent->index ) e->section->begEvent = e; if( e->section->endEvent == nullptr || e->index > e->section->endEvent->index ) e->section->endEvent = e; } return rc; } 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, bool show_warnings_fl ) { rc_t rc = kOkRC; section_t* s0 = nullptr; for(section_t* s=p->sectionL; s!=nullptr; s = s->link) { if( s->setN == 0 ) { if( show_warnings_fl ) cwLogWarning("The section '%s' does not have any sets assigned to it.",cwStringNullGuard(s->label)); } else if( s->begSetEvent == nullptr || s->endSetEvent == 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; } // verify that there are no event gaps between the sections if( p->sectionL != nullptr and p->sectionL->link != nullptr ) { s0 = p->sectionL; for(section_t* s=s0->link; s!=nullptr; s=s->link) { if( s0->endEvent == nullptr ) rc = cwLogError(kInvalidStateRC,"The section '%s' does not have an end event.",cwStringNullGuard(s0->label)); else { if( s->begEvent == nullptr ) rc = cwLogError(kInvalidStateRC,"The section '%s' does not have a begin event.",cwStringNullGuard(s->label)); else { if( s0->endEvent->index+1 != s->begEvent->index ) rc = cwLogError(kInvalidStateRC,"The sections '%s' and '%s' do not begin/end on consecutive events.",cwStringNullGuard(s0->label),cwStringNullGuard(s->label)); } } 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::dyn_ref_vel_to_level( handle_t h, uint8_t vel ) { score_parse_t* p = _handleToPtr(h); return velocity_to_level(p->dynRefH,vel); } 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, bool show_warnings_fl ) { 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); _order_set_ids_by_time( p ); if((rc = _validate_sets(p)) != kOkRC ) goto errLabel; _fill_target_sections(p); _fill_section_sets(p); _sort_sections(p); _fill_section_beg_end_evt(p); if((rc = _validate_sections(p,show_warnings_fl)) != 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); return _set_count(p); } 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 bpm b_rval bar bei voc tick sec rval dot eloc 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 %3i %6.4f %2s%3i %3i %3i %4i %7.3f %6.3f %3i %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 , e->bpm, e->bpm_rval, barLabel, e->barNumb, e->barEvtIdx, e->voice, e->tick, e->sec, e->rval, e->dotCnt, e->eLocId, e->oLocId, 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