diff --git a/.gitignore b/.gitignore index 70845e0..d07bba3 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ Makefile.in +.DS_Store + diff --git a/app/cmMidiScoreFollow.c b/app/cmMidiScoreFollow.c index 5b38ad7..803d63a 100644 --- a/app/cmMidiScoreFollow.c +++ b/app/cmMidiScoreFollow.c @@ -112,6 +112,14 @@ void _cmMsf_ReportMidiErrors( const _cmMsf_ScoreFollow_t* f, cmScH_t scH, const } } +void _cmMsf_WriteMatchFileHeader( cmFileH_t fH ) +{ + cmFilePrintf(fH," Score Score Score MIDI MIDI MIDI\n"); + cmFilePrintf(fH," Bar UUID Pitch UUID Ptch Vel.\n"); + cmFilePrintf(fH,"- ----- ----- ----- ----- ---- ----\n"); +} + + // Write one scScoreMatcherResult_t record to the file fH. unsigned _cmMsf_WriteMatchFileLine( cmFileH_t fH, cmScH_t scH, const cmScMatcherResult_t* r ) { @@ -130,7 +138,7 @@ unsigned _cmMsf_WriteMatchFileLine( cmFileH_t fH, cmScH_t scH, const cmScMatcher cmMidiToSciPitch(e->pitch,buf,5); } - cmFilePrintf(fH,"m %3i %5i %4s %5i %4s %3i\n", + cmFilePrintf(fH,"m %5i %5i %5s %5i %4s %3i\n", loc==NULL ? 0 : loc->barNumb, // score evt bar scUid, // score event uuid buf, // score event pitch @@ -147,17 +155,17 @@ void _cmMsf_ScoreFollowCb( struct cmScMatcher_str* p, void* arg, cmScMatcherResu r->rV[r->rN++] = *rp; } -cmMsfRC_t cmMidiScoreFollowMain( cmCtx_t* ctx ) +cmMsfRC_t cmMidiScoreFollowMain( + cmCtx_t* ctx, + const cmChar_t* scoreCsvFn, // score CSV file as generated from cmXScoreTest(). + const cmChar_t* midiFn, // MIDI file to track + const cmChar_t* matchRptOutFn, // Score follow status report + const cmChar_t* matchSvgOutFn, // Score follow graphic report + const cmChar_t* midiOutFn, // (optional) midiFn with apply sostenuto and velocities from the score to the MIDI file + const cmChar_t* tlBarOutFn // (optional) bar positions sutiable for use in a cmTimeLine description file. +) { - cmMsfRC_t rc = kOkMsfRC; - //const cmChar_t* scoreFn = cmFsMakeUserDirFn("src/kc/src/kc/data","mod2e.csv"); - const cmChar_t* scoreFn = cmFsMakeUserDirFn("temp","a7.csv"); - const cmChar_t* midiFn = cmFsMakeUserDirFn("media/projects/imag_themes/scores/gen","round1-utf8_11.mid"); - const cmChar_t* outFn = cmFsMakeUserDirFn("temp","match.txt"); - const cmChar_t* svgFn = cmFsMakeUserDirFn("temp","score0.html"); - const cmChar_t* newMidiFn= cmFsMakeUserDirFn("temp","a7.mid"); - const cmChar_t* tlBarFn = cmFsMakeUserDirFn("temp","time_line_temp.txt"); - + cmMsfRC_t rc = kOkMsfRC; double srate = 96000.0; cmScMatcher* smp = NULL; cmScH_t scH = cmScNullHandle; @@ -179,16 +187,16 @@ cmMsfRC_t cmMidiScoreFollowMain( cmCtx_t* ctx ) cmCtx* prCtx = cmCtxAlloc(NULL, err.rpt, cmLHeapNullHandle, cmSymTblNullHandle ); // initialize the score - if( cmScoreInitialize( ctx, &scH, scoreFn, srate, NULL, 0, NULL, NULL, cmSymTblNullHandle) != kOkScRC ) + if( cmScoreInitialize( ctx, &scH, scoreCsvFn, srate, NULL, 0, NULL, NULL, cmSymTblNullHandle) != kOkScRC ) { - rc = cmErrMsg(&err,kFailMsfRC,"cmScoreInitialize() failed on %s",cmStringNullGuard(scoreFn)); + rc = cmErrMsg(&err,kFailMsfRC,"cmScoreInitialize() failed on %s",cmStringNullGuard(scoreCsvFn)); goto errLabel; } // setup the callback record if((sfr.rAllocN = cmScoreEvtCount( scH )*2) == 0) { - rc = cmErrMsg(&err,kFailMsfRC,"The score %s appears to be empty.",cmStringNullGuard(scoreFn)); + rc = cmErrMsg(&err,kFailMsfRC,"The score %s appears to be empty.",cmStringNullGuard(scoreCsvFn)); goto errLabel; } @@ -229,19 +237,21 @@ cmMsfRC_t cmMidiScoreFollowMain( cmCtx_t* ctx ) printf("MIDI notes:%i Score Events:%i\n",mN,cmScoreEvtCount(scH)); // create the output file - if( cmFileOpen(&fH,outFn,kWriteFileFl,&ctx->rpt) != kOkFileRC ) + if( cmFileOpen(&fH,matchRptOutFn,kWriteFileFl,&ctx->rpt) != kOkFileRC ) { - rc = cmErrMsg(&err,kFailMsfRC,"Unable to create the file '%s'.",cmStringNullGuard(outFn)); + rc = cmErrMsg(&err,kFailMsfRC,"Unable to create the file '%s'.",cmStringNullGuard(matchRptOutFn)); goto errLabel; } // allocate the graphics object - if( cmScoreMatchGraphicAlloc( ctx, &smgH, scoreFn, midiFn ) != kOkSmgRC ) + if( cmScoreMatchGraphicAlloc( ctx, &smgH, scoreCsvFn, midiFn ) != kOkSmgRC ) { rc = cmErrMsg(&err,kFailMsfRC,"Score Match Graphics allocation failed.."); goto errLabel; } + // write the match report output file header + _cmMsf_WriteMatchFileHeader(fH); // for each score follower callback record for(i=0; irpt ); // write the tracking match file as an SVG file. - cmScoreMatchGraphicWrite( smgH, svgFn ); + cmScoreMatchGraphicWrite( smgH, matchSvgOutFn ); // write a cmTimeLine file which contains markers at each bar position - cmScoreMatchGraphicGenTimeLineBars(smgH, tlBarFn, srate ); + if( tlBarOutFn != NULL ) + cmScoreMatchGraphicGenTimeLineBars(smgH, tlBarOutFn, srate ); - cmScoreMatchGraphicUpdateMidiFromScore( ctx, smgH, newMidiFn ); + if( midiOutFn != NULL ) + cmScoreMatchGraphicUpdateMidiFromScore( ctx, smgH, midiOutFn ); errLabel: @@ -286,12 +298,12 @@ cmMsfRC_t cmMidiScoreFollowMain( cmCtx_t* ctx ) cmCtxFree(&prCtx); - cmFsFreeFn(scoreFn); - cmFsFreeFn(midiFn); - cmFsFreeFn(outFn); - cmFsFreeFn(svgFn); - cmFsFreeFn(newMidiFn); - cmFsFreeFn(tlBarFn); + //cmFsFreeFn(scoreCsvFn); + //cmFsFreeFn(midiFn); + //cmFsFreeFn(matchRptOutFn); + //cmFsFreeFn(matchSvgOutFn); + //cmFsFreeFn(outMidiFn); + //cmFsFreeFn(tlBarFn); return rc; } diff --git a/app/cmMidiScoreFollow.h b/app/cmMidiScoreFollow.h index 5e0fc39..838c728 100644 --- a/app/cmMidiScoreFollow.h +++ b/app/cmMidiScoreFollow.h @@ -15,7 +15,15 @@ extern "C" { typedef cmRC_t cmMsfRC_t; - cmMsfRC_t cmMidiScoreFollowMain( cmCtx_t* ctx ); + cmMsfRC_t cmMidiScoreFollowMain( + cmCtx_t* ctx, + const cmChar_t* scoreCsvFn, // score CSV file as generated from cmXScoreTest(). + const cmChar_t* midiFn, // MIDI file to track + const cmChar_t* matchRptOutFn, // Score follow status report + const cmChar_t* matchSvgOutFn, // Score follow graphic report + const cmChar_t* midiOutFn, // (optional) midiFn with apply sostenuto and velocities from the score to the MIDI file + const cmChar_t* tlBarOutFn // (optional) bar positions sutiable for use in a cmTimeLine description file. + ); #ifdef __cplusplus } diff --git a/app/cmScore.c b/app/cmScore.c index ccb00bd..e99018d 100644 --- a/app/cmScore.c +++ b/app/cmScore.c @@ -154,15 +154,16 @@ cmScEvtRef_t _cmScEvtRefArray[] = cmScEvtRef_t _cmScDynRefArray[] = { - { 1, 0, "pppp"}, - { 2, 0, "ppp" }, - { 3, 0, "pp" }, - { 4, 0, "p" }, - { 5, 0, "mp" }, - { 6, 0, "mf" }, - { 7, 0, "f" }, - { 8, 0, "ff" }, - { 9, 0, "fff" }, + { 1, 0, "silent"}, + { 2, 0, "pppp"}, + { 3, 0, "ppp" }, + { 4, 0, "pp" }, + { 5, 0, "p" }, + { 6, 0, "mp" }, + { 7, 0, "mf" }, + { 8, 0, "f" }, + { 9, 0, "ff" }, + { 10, 0, "fff" }, { kInvalidDynScId,0, "***" }, }; @@ -361,6 +362,22 @@ void _cmScFreeMarkList( cmScMark_t* markList ) } } +void _cmScFreeSectList( cmSc_t* p ) +{ + + // release the section linked list + cmScSect_t* sp = p->sectList; + cmScSect_t* np = NULL; + while(sp!=NULL) + { + np = sp->link; + cmMemFree(sp); + sp = np; + } + + p->sectList = NULL; +} + void _cmScFreeSetList( cmScSet_t* setList ) { cmScSet_t* tp = setList; @@ -412,6 +429,8 @@ cmScRC_t _cmScFinalize( cmSc_t* p ) cmMemFree(p->sets); } + _cmScFreeSectList( p ); + _cmScFreeSetList(p->setList); _cmScFreeMarkList(p->markList); @@ -1124,9 +1143,10 @@ cmScRC_t _cmScProcSets( cmSc_t* p ) -cmScRC_t _cmScProcSections( cmSc_t* p, cmScSect_t* sectList ) +cmScRC_t _cmScProcSections( cmSc_t* p ) { cmScRC_t rc = kOkScRC; + cmScSect_t* sectList = p->sectList; unsigned i; // count the sections @@ -1171,16 +1191,9 @@ cmScRC_t _cmScProcSections( cmSc_t* p, cmScSect_t* sectList ) } } - // release the section linked list - sp = sectList; - cmScSect_t* np = NULL; - while(sp!=NULL) - { - np = sp->link; - cmMemFree(sp); - sp = np; - } + _cmScFreeSectList(p); + //_cmScPrintSets("Sets",p->setList ); _cmScProcSets(p); @@ -1592,7 +1605,7 @@ cmScRC_t cmScoreInitialize( cmCtx_t* ctx, cmScH_t* hp, const cmChar_t* fn, doubl if((rc = _cmScInitLocArray(p)) != kOkScRC ) goto errLabel; - if((rc = _cmScProcSections(p,p->sectList)) != kOkScRC ) + if((rc = _cmScProcSections(p)) != kOkScRC ) goto errLabel; if((rc = _cmScProcMarkers(p)) != kOkScRC ) @@ -2409,6 +2422,16 @@ cmScRC_t cmScoreDecode( const void* msg, unsigned msgByteCnt, cmScMsg_t* m) return kOkScRC; } +const cmChar_t* _cmScoreSectionLabel( cmSc_t* p, const cmScoreEvt_t* r ) +{ + unsigned i; + for(i=0; isectCnt; ++i) + if( p->sect[i].locPtr != NULL && p->sect[i].locPtr->index == r->locIdx && p->sect[i].begEvtIndex == r->index ) + return p->sect[i].label; + + return NULL; +} + void _cmScorePrintHdr( cmRpt_t* rpt ) { cmRptPrintf(rpt,"evnt CSV bar\n"); @@ -2416,12 +2439,13 @@ void _cmScorePrintHdr( cmRpt_t* rpt ) cmRptPrintf(rpt,"----- ----- ----- --- --- ----- ----- --- -------\n"); } -void _cmScorePrintEvent( const cmScoreEvt_t* r, unsigned i, cmRpt_t* rpt ) +void _cmScorePrintEvent( cmSc_t* p, const cmScoreEvt_t* r, unsigned i, cmRpt_t* rpt ) { + bool eolFl = true; switch(r->type) { case kBarEvtScId: - cmRptPrintf(rpt,"%5i %5i %3i bar\n", + cmRptPrintf(rpt,"%5i %5i %3i bar ", i, r->line, r->barNumb ); @@ -2429,7 +2453,7 @@ void _cmScorePrintEvent( const cmScoreEvt_t* r, unsigned i, cmRpt_t* rpt ) case kPedalEvtScId: case kNonEvtScId: - cmRptPrintf(rpt,"%5i %5i %5i %3i %3i %s %5s %c%c%c %s\n", + cmRptPrintf(rpt,"%5i %5i %5i %3i %3i %s %5s %c%c%c %-7s ", i, r->line, r->locIdx, @@ -2440,12 +2464,20 @@ void _cmScorePrintEvent( const cmScoreEvt_t* r, unsigned i, cmRpt_t* rpt ) cmIsFlag(r->flags,kEvenScFl) ? 'e' : ' ', cmIsFlag(r->flags,kTempoScFl) ? 't' : ' ', cmIsFlag(r->flags,kDynScFl) ? 'd' : ' ', + //cmIsFlag(r->flags,kDynScFl) ? 7-strlen(cmScDynIdToLabel(r->dynVal)) : 7, cmIsFlag(r->flags,kDynScFl) ? cmScDynIdToLabel(r->dynVal) : ""); break; default: + eolFl = false; break; } + + const cmChar_t* sectionLabel; + if((sectionLabel = _cmScoreSectionLabel(p,r)) != NULL ) + cmRptPrintf(rpt,"section:%s ",sectionLabel); + + cmRptPrintf(rpt,"\n"); } @@ -2458,7 +2490,7 @@ void cmScorePrint( cmScH_t h, cmRpt_t* rpt ) _cmScorePrintHdr(rpt); for(i=0; icnt; ++i) - _cmScorePrintEvent(p->array+i,i,rpt); + _cmScorePrintEvent(p,p->array+i,i,rpt); } @@ -2483,7 +2515,7 @@ void cmScorePrintSets( cmScH_t h, cmRpt_t* rpt ) _cmScorePrintHdr(rpt); for(j=0; jeleCnt; ++j) - _cmScorePrintEvent(*s->eleArray+j,j,rpt); + _cmScorePrintEvent(p,*s->eleArray+j,j,rpt); cmRptPrintf(rpt,"Targets Section: "); for(j=0; jsectCnt; ++j) @@ -2709,7 +2741,7 @@ cmScRC_t cmScoreFileFromMidi( cmCtx_t* ctx, const cmChar_t* midiFn, const c } -void cmScoreTest( cmCtx_t* ctx, const cmChar_t* fn ) +void cmScoreReport( cmCtx_t* ctx, const cmChar_t* fn ) { cmScH_t h = cmScNullHandle; if( cmScoreInitialize(ctx,&h,fn,0,NULL,0,NULL,NULL, cmSymTblNullHandle ) != kOkScRC ) @@ -2720,6 +2752,11 @@ void cmScoreTest( cmCtx_t* ctx, const cmChar_t* fn ) cmScoreFinalize(&h); } +void cmScoreTest( cmCtx_t* ctx, const cmChar_t* fn ) +{ +} + + // 1. Fix absolute message time which was incorrect on original score file. // 2. void cmScoreFix( cmCtx_t* ctx ) diff --git a/app/cmScore.h b/app/cmScore.h index 8b94537..f41965a 100644 --- a/app/cmScore.h +++ b/app/cmScore.h @@ -280,8 +280,12 @@ extern "C" { // Generate a new score file from a MIDI file. cmScRC_t cmScoreFileFromMidi( cmCtx_t* ctx, const cmChar_t* midiFn, const cmChar_t* scoreFn ); - void cmScoreTest( cmCtx_t* ctx, const cmChar_t* fn ); + // Print open the score file 'fn' and report the contents. This function + // simply wraps calls to cmScoreInitialize() and cmScorePrint(). + void cmScoreReport( cmCtx_t* ctx, const cmChar_t* fn ); + void cmScoreTest( cmCtx_t* ctx, const cmChar_t* fn ); + //) #ifdef __cplusplus diff --git a/app/cmScoreMatchGraphic.c b/app/cmScoreMatchGraphic.c index d1a3a4a..b013c97 100644 --- a/app/cmScoreMatchGraphic.c +++ b/app/cmScoreMatchGraphic.c @@ -538,7 +538,7 @@ cmSmgRC_t cmScoreMatchGraphicWrite( cmSmgH_t h, const cmChar_t* fn ) svgHeight += 10; // add a right and lower border svgWidth += 10; - cmFilePrintf(fH,"\n\n\n\n",svgWidth,svgHeight); + cmFilePrintf(fH,"\n\n\n\n",svgWidth,svgHeight); for(i=0; ilocN; ++i) { @@ -690,7 +690,7 @@ cmSmgRC_t _cmScoreMatchGraphicInsertMidiMsg( cmSmg_t* p, cmMidiFileH_t mfH, bool cmMidiByte_t midi_vel = pedalDnFl ? 64 : 0; cmMidiByte_t midi_ch = 0; - printf("pedal:%s\n",pedalDnFl?"down":"up"); + //printf("pedal:%s\n",pedalDnFl?"down":"up"); // insert a pedal msg relative to the reference event if( cmMidiFileInsertMsg(mfH, m->uid, dtick_offset, midi_ch, kCtlMdId, kSostenutoCtlMdId, midi_vel ) != kOkMfRC ) @@ -798,8 +798,6 @@ cmSmgRC_t cmScoreMatchGraphicUpdateMidiFromScore( cmCtx_t* ctx, cmSmgH_t h, cons goto errLabel; } - cmMidiFilePrintMsgs(mfH, p->err.rpt ); - errLabel: cmMidiFileClose(&mfH); diff --git a/app/cmTimeLine.c b/app/cmTimeLine.c index bc381d0..9ebc521 100644 --- a/app/cmTimeLine.c +++ b/app/cmTimeLine.c @@ -134,7 +134,7 @@ cmTlMidiFile_t* _cmTlMidiFileObjPtr( _cmTl_t* p, cmTlObj_t* op, bool errFl ) if( op==NULL || op->typeId != kMidiFileTlId ) { if( errFl && p != NULL) - cmErrMsg(&p->err,kTypeCvtFailTlRC,"A time line object type promotion failed."); + cmErrMsg(&p->err,kTypeCvtFailTlRC,"A time line object type promotion to MIDI file failed."); return NULL; } @@ -147,7 +147,7 @@ cmTlMidiEvt_t* _cmTlMidiEvtObjPtr( _cmTl_t* p, cmTlObj_t* op, bool errFl ) if( op==NULL || op->typeId != kMidiEvtTlId ) { if( errFl && p != NULL ) - cmErrMsg(&p->err,kTypeCvtFailTlRC,"A time line object type promotion failed."); + cmErrMsg(&p->err,kTypeCvtFailTlRC,"A time line object type promotion to MIDI event failed."); return NULL; } @@ -173,7 +173,7 @@ cmTlAudioEvt_t* _cmTlAudioEvtObjPtr( _cmTl_t* p, cmTlObj_t* op, bool errFl ) if( op==NULL || op->typeId != kAudioEvtTlId ) { if( errFl && p != NULL) - cmErrMsg(&p->err,kTypeCvtFailTlRC,"A time line object type promotion failed."); + cmErrMsg(&p->err,kTypeCvtFailTlRC,"A time line object type promotion to audio event failed."); return NULL; } return (cmTlAudioEvt_t*)op; @@ -186,7 +186,7 @@ cmTlMarker_t* _cmTlMarkerObjPtr( _cmTl_t* p, cmTlObj_t* op, bool errFl ) if( op==NULL || op->typeId != kMarkerTlId ) { if( errFl && p != NULL) - cmErrMsg(&p->err,kTypeCvtFailTlRC,"A time line object type promotion failed."); + cmErrMsg(&p->err,kTypeCvtFailTlRC,"A time line object type promotion to marker object failed."); return NULL; } return (cmTlMarker_t*)op; @@ -1227,31 +1227,37 @@ _cmTlObj_t* _cmTimeLineObjAtTime( _cmTl_t* p, unsigned seqId, unsigned seqSmpIdx _cmTlObj_t* op = p->seq[seqId].first; _cmTlObj_t* min_op = NULL; unsigned minDist = UINT_MAX; - + bool inFl = false; + + // for each object in the requested sequence for(; op!=NULL; op=op->next) if( typeId==cmInvalidId || op->obj->typeId == typeId ) { - // if seqSmpIdx is inside this object - then return it as the solution + bool in0Fl = false; + + // if seqSmpIdx is inside this object - then the returned object must contain seqSmpIdx + // (but the ideal point to return is the one which contains seqSmpIdx and also has + // a begin or end point very close to seqSmpIdx - so this defer selecting an object + // until all objects which may contain seqSmpIdx have been examined if((op->obj->seqSmpIdx <= seqSmpIdx && seqSmpIdx < (op->obj->seqSmpIdx + op->obj->durSmpCnt))) - return op; - + { + inFl = true; // the returned object must contain seqSmpIdx + in0Fl = true; // this object contains seqSmpIdx + } + // measure the distance from seqSmpIdx to the begin and end of this object unsigned d0 = op->obj->seqSmpIdx < seqSmpIdx ? seqSmpIdx - op->obj->seqSmpIdx : op->obj->seqSmpIdx - seqSmpIdx; unsigned d1 = op->obj->seqSmpIdx+op->obj->durSmpCnt < seqSmpIdx ? seqSmpIdx - op->obj->seqSmpIdx+op->obj->durSmpCnt : op->obj->seqSmpIdx+op->obj->durSmpCnt - seqSmpIdx; - // d0 and d1 should decrease as the cur object approaches seqSmpIdx - // If they do not then the search is over - return the closest point. - if( d0>minDist && d1>minDist) - break; // track the min dist and the assoc'd obj - if( d0 < minDist ) + if( d0 < minDist && (inFl==false || inFl==in0Fl)) { minDist = d0; min_op = op; } - if( d1 < minDist ) + if( d1 < minDist && (inFl==false || inFl==in0Fl)) { minDist = d1; min_op = op; diff --git a/app/cmXScore.c b/app/cmXScore.c index 40f81ce..c94ddab 100644 --- a/app/cmXScore.c +++ b/app/cmXScore.c @@ -37,45 +37,56 @@ cmXsH_t cmXsNullHandle = cmSTATIC_NULL_HANDLE; enum { - kSectionXsFl = 0x0000001, // rvalue holds section number - kBarXsFl = 0x0000002, - kRestXsFl = 0x0000004, - kGraceXsFl = 0x0000008, - kDotXsFl = 0x0000010, - kChordXsFl = 0x0000020, - kDynXsFl = 0x0000040, - kEvenXsFl = 0x0000080, - kTempoXsFl = 0x0000100, - kHeelXsFl = 0x0000200, - kTieBegXsFl = 0x0000400, - kTieEndXsFl = 0x0000800, - kTieProcXsFl = 0x0001000, - kDampDnXsFl = 0x0002000, - kDampUpXsFl = 0x0004000, - kDampUpDnXsFl = 0x0008000, - kSostDnXsFl = 0x0010000, - kSostUpXsFl = 0x0020000, - kMetronomeXsFl = 0x0040000, // duration holds BPM - kOnsetXsFl = 0x0080000, // this is a sounding note - kBegGroupXsFl = 0x0100000, - kEndGroupXsFl = 0x0200000, - kBegGraceXsFl = 0x0400000, // beg grace note group - kEndGraceXsFl = 0x0800000, // end grace note group - kAddGraceXsFl = 0x1000000, // end grace note group operator flag - add time - kSubGraceXsFl = 0x2000000, // " " " " " " - subtract time - kFirstGraceXsFl = 0x4000000, // " " " " " " - sync to first note + kSectionXsFl = 0x00000001, // rvalue holds section number + kBarXsFl = 0x00000002, + kRestXsFl = 0x00000004, + kGraceXsFl = 0x00000008, + kDotXsFl = 0x00000010, + kChordXsFl = 0x00000020, + kDynXsFl = 0x00000040, + kEvenXsFl = 0x00000080, + kTempoXsFl = 0x00000100, + kHeelXsFl = 0x00000200, + kTieBegXsFl = 0x00000400, + kTieEndXsFl = 0x00000800, + kTieProcXsFl = 0x00001000, + kDampDnXsFl = 0x00002000, + kDampUpXsFl = 0x00004000, + kDampUpDnXsFl = 0x00008000, + kSostDnXsFl = 0x00010000, + kSostUpXsFl = 0x00020000, + kMetronomeXsFl = 0x00040000, // duration holds BPM + kOnsetXsFl = 0x00080000, // this is a sounding note + kBegGroupXsFl = 0x00100000, + kEndGroupXsFl = 0x00200000, + kBegGraceXsFl = 0x00400000, // (b) beg grace note group + kEndGraceXsFl = 0x00800000, // end grace note group + kAddGraceXsFl = 0x01000000, // (a) end grace note group operator flag - add time + kSubGraceXsFl = 0x02000000, // (s) " " " " " " - subtract time + kAFirstGraceXsFl = 0x04000000, // (A) add time after first note + kNFirstGraceXsFl = 0x08000000 // (n) grace notes start as soon as possible after first note and add time }; struct cmXsMeas_str; struct cmXsVoice_str; +// Values measured for each sounding note in the preceding time window.... +typedef struct cmXsComplexity_str +{ + unsigned sum_d_vel; // sum of first order difference of cmXsNote_t.dynamics + unsigned sum_d_rym; // sum of first order difference of cmXsNote_t.rvalue + unsigned sum_d_lpch; // sum of first order difference of cmXsNote_t.pitch of note assigned to the bass cleff + unsigned sum_n_lpch; // count of notes assigned to the bass cleff + unsigned sum_d_rpch; // sum of first order difference of cmXsNote_t.pitch of note assigned to the treble cleff + unsigned sum_n_rpch; // count of notes assigned to the treble cleff +} cmXsComplexity_t; + typedef struct cmXsNote_str { unsigned uid; // unique id of this note record unsigned flags; // See k???XsFl unsigned pitch; // midi pitch - unsigned velocity; // midi velocity unsigned dynamics; // dynamic level 1=pppp 9=fff unsigned vel; // score specified MIDI velocity cmChar_t step; // A-G @@ -84,6 +95,7 @@ typedef struct cmXsNote_str unsigned staff; // 1=treble 2=bass unsigned tick; // unsigned duration; // duration in ticks + unsigned tied_dur; // duration in ticks (including all tied notes) double secs; // absolute time in seconds double dsecs; // delta time in seconds since previous event unsigned locIdx; // location index (chords share the same location index) @@ -106,6 +118,8 @@ typedef struct cmXsNote_str struct cmXsNote_str* mlink; // measure note list struct cmXsNote_str* slink; // time sorted event list + cmXsComplexity_t cplx; + } cmXsNote_t; typedef struct cmXsVoice_str @@ -156,6 +170,8 @@ typedef struct cmXsSpan_t* spanL; unsigned nextUid; + + cmXsComplexity_t cplx_max; } cmXScore_t; cmXScore_t* _cmXScoreHandleToPtr( cmXsH_t h ) @@ -239,8 +255,9 @@ cmXsRC_t _cmXScorePushNote( cmXScore_t* p, cmXsMeas_t* meas, unsigned voiceId, c n->mlink = note; } - note->voice = v; - note->uid = p->nextUid++; + note->voice = v; + note->uid = p->nextUid++; + note->tied_dur = note->duration; return kOkXsRC; } @@ -605,6 +622,7 @@ cmXsRC_t _cmXScorePushNonNote( cmXScore_t* p, cmXsMeas_t* meas, const cmXmlNode_ note->rvalue = rvalue; note->tvalue = tvalue; note->duration = duration; + note->tied_dur = duration; note->meas = meas; note->xmlNode = noteXmlNode; @@ -881,32 +899,6 @@ cmXsNote_t* _cmXScoreInsertSortedNote( cmXsNote_t* s0, cmXsNote_t* np ) return s0; } -void _cmXScoreSort( cmXScore_t* p ) -{ - // for each part - cmXsPart_t* pp = p->partL; - for(; pp!=NULL; pp=pp->link) - { - // for each measure in this part - cmXsMeas_t* mp = pp->measL; - for(; mp!=NULL; mp=mp->link) - { - // explicitely set noteL to NULL to in case we are re-sorting - mp->noteL = NULL; - - // for each voice in this measure - cmXsVoice_t* vp = mp->voiceL; - for(; vp!=NULL; vp=vp->link) - { - // for each note in this measure - cmXsNote_t* np = vp->noteL; - for(; np!=NULL; np=np->mlink) - mp->noteL = _cmXScoreInsertSortedNote(mp->noteL,np); - } - } - } -} - // Set the cmXsNode_t.secs and dsecs. void _cmXScoreSetAbsoluteTime( cmXScore_t* p ) { @@ -959,6 +951,36 @@ void _cmXScoreSetAbsoluteTime( cmXScore_t* p ) } } + +void _cmXScoreSort( cmXScore_t* p ) +{ + // for each part + cmXsPart_t* pp = p->partL; + for(; pp!=NULL; pp=pp->link) + { + // for each measure in this part + cmXsMeas_t* mp = pp->measL; + for(; mp!=NULL; mp=mp->link) + { + // explicitely set noteL to NULL to in case we are re-sorting + mp->noteL = NULL; + + // for each voice in this measure + cmXsVoice_t* vp = mp->voiceL; + for(; vp!=NULL; vp=vp->link) + { + // for each note in this measure + cmXsNote_t* np = vp->noteL; + for(; np!=NULL; np=np->mlink) + mp->noteL = _cmXScoreInsertSortedNote(mp->noteL,np); + } + } + } + + // The order of events may have changed update the absolute time. + _cmXScoreSetAbsoluteTime( p ); +} + // All notes in a[aN] are on the same tick unsigned _cmXsSpreadGraceNotes( cmXsNote_t** a, unsigned aN ) { @@ -1061,6 +1083,7 @@ void _cmXScoreSpreadGraceNotes( cmXScore_t* p ) bool _cmXScoreFindTiedNote( cmXScore_t* p, cmXsMeas_t* mp, cmXsNote_t* n0p, bool rptFl ) { + cmXsNote_t* nbp = n0p; cmXsNote_t* nnp = n0p->slink; // begin w/ note following np unsigned measNumb = mp->number; cmChar_t acc = n0p->alter==-1?'b' : (n0p->alter==1?'#':' '); @@ -1089,9 +1112,11 @@ bool _cmXScoreFindTiedNote( cmXScore_t* p, cmXsMeas_t* mp, cmXsNote_t* n0p, boo // if this note is tied to the originating note (np) if( nnp->voice->id == n0p->voice->id && nnp->step == n0p->step && nnp->octave == n0p->octave ) { - nnp->flags |= kTieProcXsFl; - nnp->flags = cmClrFlag(nnp->flags,kOnsetXsFl); - n0p->tied = nnp; + nnp->flags |= kTieProcXsFl; + nnp->flags = cmClrFlag(nnp->flags,kOnsetXsFl); + n0p->tied = nnp; + nbp->tied_dur += nnp->duration; + nnp->tied_dur = 0; if( rptFl ) printf("---> %i %i %s ",nnp->meas->number,nnp->tick,cmMidiToSciPitch(nnp->pitch,NULL,0)); @@ -1114,10 +1139,10 @@ bool _cmXScoreFindTiedNote( cmXScore_t* p, cmXsMeas_t* mp, cmXsNote_t* n0p, boo void _cmXScoreResolveTiesAndLoc( cmXScore_t* p ) { - unsigned n = 0; - unsigned m = 0; - bool rptFl = false; - cmXsPart_t* pp = p->partL; + unsigned n = 0; // count of notes which begin a tie + unsigned m = 0; // count of tied notes that are correctly terminated. + bool rptFl = false; + cmXsPart_t* pp = p->partL; // for each part for(; pp!=NULL; pp=pp->link) @@ -1203,6 +1228,83 @@ cmXsRC_t _cmXScoreResolveOctaveShift( cmXScore_t* p ) return kOkXsRC; } +cmXsNote_t* _cmXScoreFindOverlappingNote( cmXScore_t* p, const cmXsNote_t* knp ) +{ + cmXsPart_t* pp = p->partL; + + // for each part + for(; pp!=NULL; pp=pp->link) + { + cmXsMeas_t* mp = pp->measL; + + // for each measure + for(; mp!=NULL; mp=mp->link) + { + cmXsNote_t* np = mp->noteL; + + // for each note in this measure + for(; np!=NULL; np=np->slink) + if( np->uid != knp->uid + && cmIsFlag(np->flags,kOnsetXsFl) + && knp->pitch == np->pitch + && knp->tick >= np->tick + && knp->tick < (np->tick + np->tied_dur) ) + { + return np; + } + } + } + return NULL; +} + +void _cmXScoreProcessOverlappingNotes( cmXScore_t* p ) +{ + cmXsPart_t* pp = p->partL; + + // for each part + for(; pp!=NULL; pp=pp->link) + { + cmXsMeas_t* mp = pp->measL; + + // for each measure + for(; mp!=NULL; mp=mp->link) + { + cmXsNote_t* np = mp->noteL; + cmXsNote_t* fnp; + + // for each note in this measure + for(; np!=NULL; np=np->slink) + if( cmIsFlag(np->flags,kOnsetXsFl) && (fnp = _cmXScoreFindOverlappingNote(p,np)) != NULL) + { + // is np entirely contained inside fnp + bool embeddedFl = fnp->tick + fnp->tied_dur > np->tick + np->tied_dur; + + //printf("bar=%3i %4s voice:%2i %2i : %7i %7i : %7i %7i : %7i : %c \n",np->meas->number,cmMidiToSciPitch(np->pitch,NULL,0),np->voice->id,fnp->voice->id,fnp->tick,fnp->tick+fnp->duration,np->tick,np->tick+np->duration, (fnp->tick+fnp->duration) - np->tick, embeddedFl ? 'E' : 'O'); + + // turn off embedded notes + if( embeddedFl ) + { + if( np->voice->id == fnp->voice->id ) + cmErrWarnMsg(&p->err,kOverlapWarnXsRC,"A time embedded note (bar=%i %s) was removed even though it overlapped with a note in the same voice.",np->meas->number,cmMidiToSciPitch(np->pitch,NULL,0)); + + np->flags = cmClrFlag(np->flags,kOnsetXsFl); + } + else + { + int d = (fnp->tick+fnp->tied_dur) - np->tick; + + // shorten the first note + if( d > 0 && d < fnp->tied_dur ) + fnp->tied_dur -= d; + + // move the second note just past it + np->tick = fnp->tick + fnp->tied_dur + 1; + } + } + } + } +} + // The identical pitch may be notated to play simultaneously on different voices. // As performed on the piano this will equate to a single sounding note. @@ -1288,8 +1390,18 @@ void _cmXScoreSetMeasGroups( cmXScore_t* p, unsigned flag ) } if( cmIsFlag(np->flags,kSectionXsFl) ) - sectionId = np->tvalue==NULL ? 0 : strtol(np->tvalue,NULL,10); - + { + sectionId = 0; + + if( np->tvalue == NULL ) + cmErrWarnMsg(&p->err,kSyntaxErrorXsRC,"An apparent section label in measure '%i' is blank.",np->meas->number); + else + if( cmTextToUInt( np->tvalue, §ionId, NULL ) != kOkTxRC ) + cmErrWarnMsg(&p->err,kSyntaxErrorXsRC,"The section label '%s' is not an integer.",np->tvalue); + + //sectionId = np->tvalue==NULL ? 0 : strtol(np->tvalue,NULL,10); + } + n0 = NULL; } } @@ -1323,7 +1435,7 @@ cmXsRC_t _cmXScoreWriteScorePlotFile( cmXScore_t* p, const cmChar_t* fn ) { if( cmIsFlag(np->flags,kMetronomeXsFl) ) { - double bps = np->duration / 60.0; + double bps = np->tied_dur / 60.0; // t b t // - = - - @@ -1337,7 +1449,7 @@ cmXsRC_t _cmXScoreWriteScorePlotFile( cmXScore_t* p, const cmChar_t* fn ) { onset_secs += (np->tick - tick0) / ticks_per_sec; tick0 = np->tick; - cmFilePrintf(fH,"n %f %f %i %s %s\n",onset_secs,np->duration/ticks_per_sec,np->uid,cmMidiToSciPitch(np->pitch,NULL,0),cmIsFlag(np->flags,kGraceXsFl)?"G":"N"); + cmFilePrintf(fH,"n %f %f %i %s %s\n",onset_secs,np->tied_dur/ticks_per_sec,np->uid,cmMidiToSciPitch(np->pitch,NULL,0),cmIsFlag(np->flags,kGraceXsFl)?"G":"N"); } } } @@ -1464,6 +1576,9 @@ cmXsRC_t _cmXScoreProcessPedals( cmXScore_t* p ) cmErrWarnMsg(&p->err,kPedalStateErrorXsRc,"Sostenuto left down at the end of a part."); } + + _cmXScoreSort(p); + return rc; } @@ -1479,21 +1594,30 @@ void _cmXScoreInsertTime( cmXScore_t* p, cmXsMeas_t* mp, cmXsNote_t* np, unsigne } } -// Insert the grace notes in between the first and last note in the group -// by inserting time between the first and last note. -void _cmXScoreGraceInsertTime( cmXScore_t* p, unsigned graceGroupId, cmXsNote_t* aV[], unsigned aN ) +void _cmXScoreGraceInsertTimeBase( cmXScore_t* p, unsigned graceGroupId, cmXsNote_t* aV[], unsigned aN, unsigned initTick ) { - cmXsNote_t* np = NULL; - unsigned expand_ticks = 0; - unsigned ticks = aV[aN-1]->tick; - unsigned i; + cmXsNote_t* np = NULL; + unsigned expand_ticks = 0; + unsigned ticks = initTick; + + unsigned t0 = 0; + unsigned i; + for(i=0; iflags,kGraceXsFl) && aV[i]->graceGroupId == graceGroupId ) { - aV[i]->tick = ticks; - ticks += aV[i]->duration; - expand_ticks += aV[i]->duration; - np = aV[i]; + // if this grace note falls on the same tick as the previous grace note + if( np != NULL && aV[i]->tick == t0 ) + aV[i]->tick = np->tick; + else + { + t0 = aV[i]->tick; // store the unmodified tick value of this note + aV[i]->tick = ticks; // set the new tick value + ticks += aV[i]->duration; // calc the next grace not location + expand_ticks += aV[i]->duration; // track how much we are expanding time by + } + + np = aV[i]; } np = np->slink; @@ -1501,24 +1625,59 @@ void _cmXScoreGraceInsertTime( cmXScore_t* p, unsigned graceGroupId, cmXsNote_t* _cmXScoreInsertTime(p,np->meas,np,expand_ticks); } -// Insert the grace notes in between the first and last note in the group +// (a) Insert the grace notes in between the first and last note in the group +// by inserting time between the first and last note. +// Note that in effect his means that the last note is pushed back +// in time by the total duration of the grace notes. +void _cmXScoreGraceInsertTime( cmXScore_t* p, unsigned graceGroupId, cmXsNote_t* aV[], unsigned aN ) +{ + _cmXScoreGraceInsertTimeBase( p, graceGroupId,aV,aN, aV[aN-1]->tick ); +} + +// (s) Insert the grace notes in between the first and last note in the group // but do not insert any additional time betwee the first and last note. // In effect time is removed from the first note and taken by the grace notes. +// The time position of the last note is therefore unchanged. void _cmXScoreGraceOverlayTime( cmXScore_t* p, unsigned graceGroupId, cmXsNote_t* aV[], unsigned aN ) { assert(aN >= 3 ); - unsigned t = aV[aN-1]->tick; - int i = (int)aN-2; + int i = (int)aN-2; + cmXsNote_t* np = aV[aN-1]; + unsigned t0 = -1; for(; i>0; --i) if( cmIsFlag(aV[i]->flags,kGraceXsFl) && aV[i]->graceGroupId == graceGroupId ) { - aV[i]->tick = t - aV[i]->duration; - t = aV[i]->tick; + if( aV[i]->tick == t0) + aV[i]->tick = np->tick; + else + { + t0 = aV[i]->tick; + aV[i]->tick = np->tick - aV[i]->duration; + } + + np = aV[i]; } } +// (A) Play the first grace at the time of the first note in the group (which is a non-grace note) +// and then expand time while inserting the other grace notes. +void _cmXScoreGraceInsertAfterFirst( cmXScore_t* p, unsigned graceGroupId, cmXsNote_t* aV[], unsigned aN ) +{ + _cmXScoreGraceInsertTimeBase( p, graceGroupId,aV,aN, aV[0]->tick ); +} + + +// (n) Play the first grace not shortly (one grace note duration) after the first note +// in the group (which is a non-grace note) and then expand time while inserting the other +// grace notes. +void _cmXScoreGraceInsertSoonAfterFirst( cmXScore_t* p, unsigned graceGroupId, cmXsNote_t* aV[], unsigned aN ) +{ + _cmXScoreGraceInsertTimeBase( p, graceGroupId,aV,aN, aV[0]->tick + aV[1]->duration ); +} + + // Adjust the locations of grace notes. Note that this must be done // after reordering so that we can be sure that the order in time of // the notes in each group has been set prior to building the @@ -1528,14 +1687,18 @@ cmXsRC_t _cmXScoreProcessGraceNotes( cmXScore_t* p ) cmXsRC_t rc = kOkXsRC; unsigned graceGroupId = 1; double graceDurSec = 1.0/15.0; // duration of all grace notes in seconds - + for(; 1; ++graceGroupId) { - cmXsNote_t* gn0p = NULL; - cmXsNote_t* gn1p = NULL; + cmXsNote_t* gn0p = NULL; // first note in the grace group + cmXsNote_t* gn1p = NULL; // last note in the grace group unsigned gN = 0; cmXsPart_t* pp = p->partL; double ticksPerSec = 0; + + // Build a note chain, using cmXsNote_t.grace, between gn0p and + // gn1p containing all the grace notes with + // cmXsNote_t.graceGroupId == graceGroupId. for(; pp!=NULL; pp=pp->link) { @@ -1568,14 +1731,14 @@ cmXsRC_t _cmXScoreProcessGraceNotes( cmXScore_t* p ) // set each grace note to have 1/20 of a second duration if( cmIsFlag(np->flags,kGraceXsFl) ) - np->duration = floor(ticksPerSec * graceDurSec); + np->duration = np->tied_dur = floor(ticksPerSec * graceDurSec); gN += 1; - } + } - } // - } - } + } // for each note in this meassure + } // for each measure + } // for each part // no records were found for this grace id - we're done if( gn0p == NULL ) @@ -1587,7 +1750,6 @@ cmXsRC_t _cmXScoreProcessGraceNotes( cmXScore_t* p ) rc = cmErrMsg(&p->err,kSyntaxErrorXsRC,"The grace not group ending in meas %i has fewer than 3 (%i) members.", gn1p->meas->number, gN ); break; } - // gn0p is now set to the first note in th group // gn1p is now set to the last note in the group @@ -1606,7 +1768,7 @@ cmXsRC_t _cmXScoreProcessGraceNotes( cmXScore_t* p ) break; } - // count the total number of events between gn0p and gn1p + // Count the total number of events between gn0p and gn1p cmXsNote_t* n0p = NULL; cmXsNote_t* n1p = gn0p; cmXsMeas_t* mp = gn0p->meas; @@ -1621,11 +1783,11 @@ cmXsRC_t _cmXScoreProcessGraceNotes( cmXScore_t* p ) n1p = mp->noteL; } - if(1) + if(0) { bool fl = n0p != NULL && n0p->tick < n1p->tick; - unsigned type = n1p->flags & (kBegGraceXsFl|kEndGraceXsFl|kAddGraceXsFl|kSubGraceXsFl|kFirstGraceXsFl); - printf("%3i 0x%08x %i %5i %i\n",n1p->graceGroupId,type,n1p->tick,n1p->duration,fl); + unsigned type = n1p->flags & (kBegGraceXsFl|kEndGraceXsFl|kAddGraceXsFl|kSubGraceXsFl|kAFirstGraceXsFl|kNFirstGraceXsFl); + printf("%3i 0x%08x %i %3i %5i %i\n",n1p->graceGroupId,type,n1p->meas->number,n1p->tick,n1p->duration,fl); } ++aN; @@ -1653,7 +1815,7 @@ cmXsRC_t _cmXScoreProcessGraceNotes( cmXScore_t* p ) n0p = n1p; } - switch( gn1p->flags & (kAddGraceXsFl | kSubGraceXsFl | kFirstGraceXsFl) ) + switch( gn1p->flags & (kAddGraceXsFl | kSubGraceXsFl | kAFirstGraceXsFl | kNFirstGraceXsFl ) ) { case kAddGraceXsFl: _cmXScoreGraceInsertTime(p, graceGroupId, aV, aN ); @@ -1663,7 +1825,12 @@ cmXsRC_t _cmXScoreProcessGraceNotes( cmXScore_t* p ) _cmXScoreGraceOverlayTime(p, graceGroupId, aV, aN ); break; - case kFirstGraceXsFl: + case kAFirstGraceXsFl: + _cmXScoreGraceInsertAfterFirst(p,graceGroupId,aV,aN); + break; + + case kNFirstGraceXsFl: + _cmXScoreGraceInsertSoonAfterFirst(p,graceGroupId,aV,aN); break; default: @@ -1676,91 +1843,6 @@ cmXsRC_t _cmXScoreProcessGraceNotes( cmXScore_t* p ) return rc; } -cmXsRC_t cmXScoreInitialize( cmCtx_t* ctx, cmXsH_t* hp, const cmChar_t* xmlFn ) -{ - cmXsRC_t rc = kOkXsRC; - - if((rc = cmXScoreFinalize(hp)) != kOkXsRC ) - return rc; - - cmXScore_t* p = cmMemAllocZ(cmXScore_t,1); - - cmErrSetup(&p->err,&ctx->rpt,"XScore"); - - // create a local linked heap - if( cmLHeapIsValid( p->lhH = cmLHeapCreate(8196,ctx)) == false ) - return cmErrMsg(&p->err,kLHeapFailXsRC,"Lheap create failed."); - - // open the music xml file - if( cmXmlAlloc(ctx, &p->xmlH, xmlFn) != kOkXmlRC ) - { - rc = cmErrMsg(&p->err,kXmlFailXsRC,"Unable to open the MusicXML file '%s'.",cmStringNullGuard(xmlFn)); - goto errLabel; - } - - //cmXmlPrint(p->xmlH,&ctx->rpt); - - // parse the part-list - if((rc = _cmXScoreParsePartList( p )) != kOkXsRC ) - goto errLabel; - - // parse each score 'part' - cmXsPart_t* pp = p->partL; - for(; pp!=NULL; pp=pp->link) - if((rc = _cmXScoreParsePart(p,pp)) != kOkXsRC ) - goto errLabel; - - // fill in the note->slink chain to link the notes in each measure in time order - _cmXScoreSort(p); - - _cmXScoreSpreadGraceNotes(p); - - _cmXScoreSort(p); - - _cmXScoreSetAbsoluteTime(p); - - _cmXScoreResolveTiesAndLoc(p); - - _cmXScoreRemoveDuplicateNotes(p); - - _cmXScoreSetMeasGroups(p,kEvenXsFl); - _cmXScoreSetMeasGroups(p,kDynXsFl); - _cmXScoreSetMeasGroups(p,kTempoXsFl); - - //_cmXScoreResolveOctaveShift(p); - - // CSV output initialize failed. - if( cmCsvInitialize(&p->csvH,ctx) != kOkCsvRC ) - rc = cmErrMsg(&p->err,kCsvFailXsRC,"CSV output object create failed."); - - errLabel: - if( rc != kOkXsRC ) - _cmXScoreFinalize(p); - else - hp->h = p; - - return rc; -} - -cmXsRC_t cmXScoreFinalize( cmXsH_t* hp ) -{ - cmXsRC_t rc = kOkXsRC; - - if( hp == NULL || cmXScoreIsValid(*hp)==false ) - return kOkXsRC; - - cmXScore_t* p = _cmXScoreHandleToPtr(*hp); - - if((rc = _cmXScoreFinalize(p)) != kOkXsRC ) - return rc; - - hp->h = NULL; - - return rc; -} - -bool cmXScoreIsValid( cmXsH_t h ) -{ return h.h != NULL; } //------------------------------------------------------------------------------------------- @@ -1796,30 +1878,31 @@ typedef struct _cmXScoreDynMark_str _cmXScoreDynMark_t _cmXScoreDynMarkArray[] = { - {"pppp-", 1, 1, -1, 3}, - {"pppp", 2, 1, 0, 10}, - {"pppp+", 3, 1, 1, 22}, - {"ppp-", 3, 2, -1, 22}, - {"ppp", 4, 2, 0, 29}, - {"ppp+", 5, 2, 1, 36}, - {"pp-", 5, 3, -1, 36}, - {"pp", 6, 3, 0, 43}, - {"pp+", 7, 3, 1, 50}, - {"p-", 7, 4, -1, 50}, - {"p", 8, 4, 0, 57}, - {"p+", 9, 4, 1, 64}, - {"mp-", 9, 5, -1, 64}, - {"mp", 10, 5, 0, 71}, - {"mp+", 11, 5, 1, 78}, - {"mf-", 11, 6, -1, 78}, - {"mf", 12, 6, 0, 85}, - {"mf+", 13, 6, 1, 92}, - {"f-", 13, 7, -1, 92}, - {"f", 14, 7, 0, 99}, - {"f+", 15, 7, 1, 106}, - {"ff", 16, 8, 0, 113}, - {"ff+", 17, 8, 1, 120}, - {"fff", 18, 9, 0, 127}, + {"s", 1, 0, 0, 1}, // silent note + {"pppp-", 2, 1, -1, 3}, + {"pppp", 3, 1, 0, 10}, + {"pppp+", 4, 1, 1, 22}, + {"ppp-", 4, 2, -1, 22}, + {"ppp", 5, 2, 0, 29}, + {"ppp+", 6, 2, 1, 36}, + {"pp-", 6, 3, -1, 36}, + {"pp", 7, 3, 0, 43}, + {"pp+", 8, 3, 1, 50}, + {"p-", 8, 4, -1, 50}, + {"p", 9, 4, 0, 57}, + {"p+", 10, 4, 1, 64}, + {"mp-", 10, 5, -1, 64}, + {"mp", 11, 5, 0, 71}, + {"mp+", 12, 5, 1, 78}, + {"mf-", 12, 6, -1, 78}, + {"mf", 13, 6, 0, 85}, + {"mf+", 14, 6, 1, 92}, + {"f-", 14, 7, -1, 92}, + {"f", 15, 7, 0, 99}, + {"f+", 16, 7, 1, 106}, + {"ff", 17, 8, 0, 113}, + {"ff+", 18, 8, 1, 120}, + {"fff", 19, 9, 0, 127}, {NULL,0,0,0,0} }; @@ -1929,8 +2012,8 @@ cmXsRC_t _cmXScoreReorderMeas( cmXScore_t* p, unsigned measNumb, cmXsReorder_t* if( cmIsFlag(rV[i].newFlags,kTieEndXsFl) ) { rV[i].note->flags |= kTieEndXsFl; - rV[i].note->flags = cmClrFlag(rV[i].note->flags, kOnsetXsFl); - rV[i].newFlags = cmClrFlag(rV[i].newFlags,kTieEndXsFl ); + rV[i].note->flags = cmClrFlag( rV[i].note->flags, kOnsetXsFl ); + rV[i].newFlags = cmClrFlag( rV[i].newFlags, kTieEndXsFl); } // if a new note value was specified @@ -1950,7 +2033,6 @@ cmXsRC_t _cmXScoreReorderMeas( cmXScore_t* p, unsigned measNumb, cmXsReorder_t* { if( rV[i].newFlags != 0 ) { - if( cmIsFlag(rV[i].newFlags,kDampDnXsFl ) ) _cmXScoreInsertPedalEvent(p,rV + i,kDampDnXsFl); @@ -1961,8 +2043,7 @@ cmXsRC_t _cmXScoreReorderMeas( cmXScore_t* p, unsigned measNumb, cmXsReorder_t* _cmXScoreInsertPedalEvent(p,rV + i,kDampUpXsFl); if( cmIsFlag(rV[i].newFlags,kSostUpXsFl ) ) - _cmXScoreInsertPedalEvent(p,rV + i,kSostUpXsFl); - + _cmXScoreInsertPedalEvent(p,rV + i,kSostUpXsFl); } } @@ -2004,6 +2085,7 @@ cmXsRC_t _cmXScoreReorderParseDyn(cmXScore_t* p, const cmChar_t* b, unsigned lin { switch(s[i]) { + case 's': case 'm': case 'p': case 'f': @@ -2136,7 +2218,8 @@ cmXsRC_t _cmXScoreReorderParseGrace(cmXScore_t* p, const cmChar_t* b, unsigned case 'b': r->graceFlags |= kBegGraceXsFl; break; case 'a': r->graceFlags |= kAddGraceXsFl | kEndGraceXsFl; break; case 's': r->graceFlags |= kSubGraceXsFl | kEndGraceXsFl; break; - case 'f': r->graceFlags |= kFirstGraceXsFl | kEndGraceXsFl; break; + case 'A': r->graceFlags |= kAFirstGraceXsFl| kEndGraceXsFl; break; + case 'n': r->graceFlags |= kNFirstGraceXsFl| kEndGraceXsFl; break; case 'g': break; case '%': @@ -2198,12 +2281,11 @@ cmXsRC_t _cmXScoreReorderParsePitch(cmXScore_t* p, const cmChar_t* b, unsigned return rc; } -cmXsRC_t cmXScoreReorder( cmXsH_t h, const cmChar_t* fn ) +cmXsRC_t _cmXsApplyEditFile( cmXScore_t* p, const cmChar_t* fn ) { typedef enum { kFindMeasStId, kFindEventStId, kReadEventStId } stateId_t; cmXsRC_t rc = kOkXsRC; - cmXScore_t* p = _cmXScoreHandleToPtr(h); cmFileH_t fH = cmFileNullHandle; cmChar_t* b = NULL; unsigned bN = 0; @@ -2342,6 +2424,12 @@ cmXsRC_t cmXScoreReorder( cmXsH_t h, const cmChar_t* fn ) } + // If reorder records remain to be processed + if( ri > 0 ) + if((rc = _cmXScoreReorderMeas(p, measNumb, rV, ri )) != kOkXsRC ) + goto errLabel; + + // the ticks may have changed so the 'secs' and 'dsecs' must be updated _cmXScoreSetAbsoluteTime( p ); @@ -2365,9 +2453,110 @@ cmXsRC_t cmXScoreReorder( cmXsH_t h, const cmChar_t* fn ) +cmXsRC_t cmXScoreInitialize( cmCtx_t* ctx, cmXsH_t* hp, const cmChar_t* xmlFn, const cmChar_t* editFn ) +{ + cmXsRC_t rc = kOkXsRC; + + if((rc = cmXScoreFinalize(hp)) != kOkXsRC ) + return rc; + + cmXScore_t* p = cmMemAllocZ(cmXScore_t,1); + + cmErrSetup(&p->err,&ctx->rpt,"XScore"); + + // create a local linked heap + if( cmLHeapIsValid( p->lhH = cmLHeapCreate(8196,ctx)) == false ) + return cmErrMsg(&p->err,kLHeapFailXsRC,"Lheap create failed."); + + // open the music xml file + if( cmXmlAlloc(ctx, &p->xmlH, xmlFn) != kOkXmlRC ) + { + rc = cmErrMsg(&p->err,kXmlFailXsRC,"Unable to open the MusicXML file '%s'.",cmStringNullGuard(xmlFn)); + goto errLabel; + } + + //cmXmlPrint(p->xmlH,&ctx->rpt); + + // parse the part-list + if((rc = _cmXScoreParsePartList( p )) != kOkXsRC ) + goto errLabel; + + // parse each score 'part' + cmXsPart_t* pp = p->partL; + for(; pp!=NULL; pp=pp->link) + if((rc = _cmXScoreParsePart(p,pp)) != kOkXsRC ) + goto errLabel; + + // fill in the note->slink chain to link the notes in each measure in time order + _cmXScoreSort(p); + + _cmXScoreSpreadGraceNotes(p); + + _cmXScoreSort(p); + + _cmXScoreResolveTiesAndLoc(p); + + _cmXScoreRemoveDuplicateNotes(p); + + _cmXScoreSetMeasGroups(p,kEvenXsFl); + _cmXScoreSetMeasGroups(p,kDynXsFl); + _cmXScoreSetMeasGroups(p,kTempoXsFl); + + //_cmXScoreResolveOctaveShift(p); + + // CSV output initialize failed. + if( cmCsvInitialize(&p->csvH,ctx) != kOkCsvRC ) + rc = cmErrMsg(&p->err,kCsvFailXsRC,"CSV output object create failed."); + + if( editFn != NULL ) + { + if((rc = _cmXsApplyEditFile(p,editFn)) != kOkXsRC ) + { + cmErrMsg(&ctx->err,rc,"XScore reorder failed."); + goto errLabel; + } + } + + // assign durations to pedal down events + _cmXScoreProcessPedals(p); + + // remove some notes which share a pitch and are overlapped or embedded within another note. + _cmXScoreProcessOverlappingNotes(p); + + + errLabel: + if( rc != kOkXsRC ) + _cmXScoreFinalize(p); + else + hp->h = p; + + return rc; +} + +cmXsRC_t cmXScoreFinalize( cmXsH_t* hp ) +{ + cmXsRC_t rc = kOkXsRC; + + if( hp == NULL || cmXScoreIsValid(*hp)==false ) + return kOkXsRC; + + cmXScore_t* p = _cmXScoreHandleToPtr(*hp); + + if((rc = _cmXScoreFinalize(p)) != kOkXsRC ) + return rc; + + hp->h = NULL; + + return rc; +} + +bool cmXScoreIsValid( cmXsH_t h ) +{ return h.h != NULL; } -/* + + +/* CSV score columns kMidiFileIdColScIdx= 0, kTypeLabelColScIdx = 3, kDSecsColScIdx = 4, @@ -2413,6 +2602,7 @@ cmXsRC_t _cmXScoreWriteCsvHdr( cmXScore_t* p ) return kOkXsRC; } + cmXsRC_t _cmXScoreWriteCsvBlankCols( cmXScore_t* p, unsigned cnt, cmCsvCell_t** leftCellPtrPtr ) { unsigned i; @@ -2660,7 +2850,6 @@ cmXsRC_t _cmXScoreWriteCsvRow( errLabel: return rc; - } cmXsRC_t cmXScoreWriteCsv( cmXsH_t h, const cmChar_t* csvFn ) @@ -2796,7 +2985,7 @@ void _cmXScoreReportNote( cmRpt_t* rpt, const cmXsNote_t* note,unsigned index ) note->voice->id, note->locIdx, note->tick, - note->duration, + note->tied_dur, note->rvalue, N,B,R,G,D,C,e,d,t,P,s,S,H,T0,T1,O); @@ -2892,6 +3081,237 @@ void cmXScoreReport( cmXsH_t h, cmRpt_t* rpt, bool sortFl ) } } +void _cmXScoreGenEditFileWrite( void* arg, const cmChar_t* text ) +{ + if( text != NULL && arg != NULL ) + { + cmFileH_t* hp = (cmFileH_t*)arg; + cmFilePrint(*hp,text); + } +} + +cmXsRC_t cmXScoreGenEditFile( cmCtx_t* ctx, const cmChar_t* xmlFn, const cmChar_t* outFn ) +{ + cmXsH_t xsH = cmXsNullHandle; + cmFileH_t fH = cmFileNullHandle; + cmXsRC_t rc = kOkXsRC; + cmErr_t err; + cmRpt_t rpt; + + cmErrSetup(&err,&ctx->rpt,"cmXScoreGenEditFile"); + cmRptSetup(&rpt,_cmXScoreGenEditFileWrite,_cmXScoreGenEditFileWrite,&fH); + + if((rc = cmXScoreInitialize(ctx,&xsH,xmlFn,NULL)) != kOkXsRC ) + return rc; + + if( cmFileOpen(&fH,outFn,kWriteFileFl,&ctx->rpt) != kOkFileRC ) + { + cmErrMsg(&err,kFileFailXsRC,"Unable to open the output file '%s'.",cmStringNullGuard(outFn)); + goto errLabel; + } + + cmXScoreReport(xsH,&rpt,true); + + errLabel: + + if( cmFileClose(&fH) != kOkFileRC ) + rc = cmErrMsg(&err,kFileFailXsRC,"File close failed on '%s'.",cmStringNullGuard(outFn)); + + cmXScoreFinalize(&xsH); + + return rc; +} + +typedef struct +{ + unsigned ival; + double fval; + unsigned cnt; +} cmXsHist_t; + +void _cmXsHistUpdateI( cmXsHist_t* hist, unsigned histN, unsigned ival ) +{ + unsigned i; + + for(i=0; i0; ++i) + n += 1; + + return n; +} + +// Measure the score complexity for the the time window 'wndSecs' seconds +// prior to the note n1 and following n0. +const cmXsNote_t* _cmXsMeasComplexityInWindow( const cmXsNote_t* n0, cmXsNote_t* n1, double wndSecs ) +{ + const cmXsNote_t* n2 = NULL; + unsigned l_pch_0 = 0; + unsigned l_pch_value = 0; + unsigned l_pch_cnt = n1->staff==1 ? 0 : 1; + unsigned r_pch_0 = n1->staff==1 ? 1 : 0; + unsigned r_pch_value = 0; + unsigned r_pch_cnt = 0; + unsigned i = 0; + + + unsigned histN = 100; + cmXsHist_t velHist[ histN ]; + cmXsHist_t rymHist[ histN ]; + + memset(velHist,0,sizeof(velHist)); + memset(rymHist,0,sizeof(rymHist)); + + const cmXsNote_t* n = n0; + + while(n!=NULL && n != n1) + { + // if this event is less than wndSecs behind 'n1' and is not a sounding note ... + if( n1->secs - n->secs <= wndSecs && cmIsFlag(n->flags,kOnsetXsFl) ) + { + _cmXsHistUpdateI( velHist, histN, n->dynamics ); + _cmXsHistUpdateF( rymHist, histN, n->rvalue ); + + switch( n->staff ) + { + case 1: // treble cleff + if( i > 0 ) + { + r_pch_value += r_pch_0 > n->pitch ? r_pch_0-n->pitch : n->pitch-r_pch_0; + r_pch_cnt += 1; + } + + r_pch_0 = n->pitch; + break; + + case 2: // bass cleff + if( i > 0 ) + { + l_pch_value += l_pch_0 > n->pitch ? l_pch_0-n->pitch : n->pitch-l_pch_0; + l_pch_cnt += 1; + } + + l_pch_0 = n->pitch; + break; + + default: + { assert(0); } + } + + // track the first note that is inside the window + if( i == 0 ) + n2 = n; + + // count the number of notes in the window + i += 1; + + } + + cmXsMeas_t* m = n->meas; + + // advance th note pointer + n = n->slink; + + // if we have reached the end of a measure + if( n == NULL ) + { + if( m != NULL ) + { + m = m->link; + if( m != NULL ) + n = m->noteL; + } + } + + } + + // update the cplx record in n1 with the results of this window analysis + n1->cplx.sum_d_vel = _cmXsHistValue( velHist, histN ); + n1->cplx.sum_d_rym = _cmXsHistValue( rymHist, histN ); + n1->cplx.sum_d_lpch = l_pch_value; + n1->cplx.sum_n_lpch = l_pch_cnt; + n1->cplx.sum_d_rpch = r_pch_value; + n1->cplx.sum_n_rpch = r_pch_cnt; + + return n2; +} + +// Measure the score complexity and fill in the cmXsComplexity_t record associated +// with the cmXsNote_t record of each sounding note. +cmXsRC_t _cmXsMeasComplexity( cmXsH_t h, double wndSecs ) +{ + cmXsRC_t rc = kOkXsRC; + cmXScore_t* p = _cmXScoreHandleToPtr(h); + cmXsPart_t* pp = p->partL; + + memset(&p->cplx_max,0,sizeof(p->cplx_max)); + + const cmXsNote_t* n0 = NULL; + + // for each part + for(; pp!=NULL; pp=pp->link) + { + cmXsMeas_t* mp = pp->measL; + + // for each measure + for(; mp!=NULL; mp=mp->link) + { + cmXsNote_t* n1 = mp->noteL; + + + // for each note in this measure + for(; n1!=NULL; n1=n1->slink) + if( cmIsFlag(n1->flags,kOnsetXsFl) ) + { + if( n0 == NULL ) + n0 = n1; + else + if((n0 = _cmXsMeasComplexityInWindow(n0,n1,wndSecs)) == NULL ) + n0 = n1; + + // track the max value for all complexity values to allow + // eventual normalization of the complexity values + p->cplx_max.sum_d_vel = cmMax(p->cplx_max.sum_d_vel, n1->cplx.sum_d_vel); + p->cplx_max.sum_d_rym = cmMax(p->cplx_max.sum_d_rym, n1->cplx.sum_d_rym); + p->cplx_max.sum_d_lpch = cmMax(p->cplx_max.sum_d_lpch,n1->cplx.sum_d_lpch); + p->cplx_max.sum_n_lpch = cmMax(p->cplx_max.sum_n_lpch,n1->cplx.sum_n_lpch); + p->cplx_max.sum_d_rpch = cmMax(p->cplx_max.sum_d_rpch,n1->cplx.sum_d_rpch); + p->cplx_max.sum_n_rpch = cmMax(p->cplx_max.sum_n_rpch,n1->cplx.sum_n_rpch); + + } + } + } + return rc; +} + cmXsRC_t _cmXsWriteMidiFile( cmCtx_t* ctx, cmXsH_t h, const cmChar_t* dir, const cmChar_t* fn ) { cmXsRC_t rc = kOkXsRC; @@ -2929,18 +3349,25 @@ cmXsRC_t _cmXsWriteMidiFile( cmCtx_t* ctx, cmXsH_t h, const cmChar_t* dir, const switch( np->flags & (kOnsetXsFl|kMetronomeXsFl|kDampDnXsFl|kDampUpDnXsFl|kSostDnXsFl) ) { case kOnsetXsFl: - if( cmMidiFileInsertTrackChMsg(mfH, 1, np->tick, kNoteOnMdId, np->pitch, np->vel ) != kOkMfRC - ||cmMidiFileInsertTrackChMsg(mfH, 1, np->tick + np->duration, kNoteOffMdId, np->pitch, 0 ) != kOkMfRC ) { - rc = kMidiFailXsRC; + if( np->tied_dur <= 0 ) + cmErrWarnMsg(&p->err,kOkXsRC,"A zero length note was encountered bar:%i tick:%i %s",np->meas->number,np->tick,cmMidiToSciPitch(np->pitch,NULL,0)); + + if( cmMidiFileInsertTrackChMsg(mfH, 1, np->tick, kNoteOnMdId, np->pitch, np->vel ) != kOkMfRC + ||cmMidiFileInsertTrackChMsg(mfH, 1, np->tick + np->tied_dur, kNoteOffMdId, np->pitch, 0 ) != kOkMfRC ) + { + rc = kMidiFailXsRC; + } } - break; case kDampDnXsFl: case kDampUpDnXsFl: case kSostDnXsFl: { + if( np->duration <= 0 ) + cmErrWarnMsg(&p->err,kOkXsRC,"A zero length pedal event was encountered bar:%i tick:%i",np->meas->number,np->tick); + cmMidiByte_t d0 = cmIsFlag(np->flags,kSostDnXsFl) ? kSostenutoCtlMdId : kSustainCtlMdId; if( (cmMidiFileInsertTrackChMsg(mfH, 1, np->tick, kCtlMdId, d0, 127 ) != kOkMfRC ) ||(cmMidiFileInsertTrackChMsg(mfH, 1, np->tick + np->duration, kCtlMdId, d0, 0 ) != kOkMfRC ) ) @@ -2988,16 +3415,15 @@ cmXsRC_t _cmXsWriteMidiFile( cmCtx_t* ctx, cmXsH_t h, const cmChar_t* dir, const return rc; } - - typedef struct cmXsSvgEvt_str { - unsigned flags; // k???XsFl - unsigned tick; // start tick - unsigned durTicks; // dur-ticks - unsigned voice; // score voice number - unsigned d0; // MIDI d0 (barNumb) - unsigned d1; // MIDI d1 + unsigned flags; // k???XsFl + unsigned tick; // start tick + unsigned durTicks; // dur-ticks + unsigned voice; // score voice number + unsigned d0; // MIDI d0 (barNumb) + unsigned d1; // MIDI d1 + cmXsComplexity_t cplx; struct cmXsSvgEvt_str* link; } cmXsSvgEvt_t; @@ -3011,15 +3437,57 @@ typedef struct cmXsMidiFile_str } cmXsMidiFile_t; + +void _cmXsWriteMidiSvgLegend( cmSvgH_t svgH, unsigned index, const cmChar_t* label, const cmChar_t* classStr ) +{ + double x = 100; + double y = 120*10 - 20*index; + + cmSvgWriterText( svgH, x, y, label, "legend" ); + + x += 75; + cmSvgWriterLine( svgH, x, y, x+125, y, classStr ); +} + +void _cmXsWriteMidiSvgLinePoint( cmSvgH_t svgH, double x0, double y0, double x1, double y1, double y_max, const cmChar_t* classStr, const cmChar_t* label ) +{ + int bn = 255; + char b[bn+1]; + double y_scale = 10; + double y_label = y1; + + b[0] = 0; + y0 = (y0/y_max) * 127.0 * y_scale; + y1 = (y1/y_max) * 127.0 * y_scale; + + cmSvgWriterLine(svgH, x0, y0, x1, y1, classStr ); + + if( y0 != y1 ) + snprintf(b,bn,"%5.0f %s",y_label,label==NULL?"":label); + else + { + if( label != NULL ) + snprintf(b,bn,"%s",label); + } + + if( strlen(b) ) + cmSvgWriterText(svgH, x1, y1, b, "pt_text"); + +} + cmXsRC_t _cmXsWriteMidiSvg( cmCtx_t* ctx, cmXScore_t* p, cmXsMidiFile_t* mf, const cmChar_t* dir, const cmChar_t* fn ) { cmXsRC_t rc = kOkXsRC; cmSvgH_t svgH = cmSvgNullHandle; - cmXsSvgEvt_t* e = mf->elist; + cmXsSvgEvt_t* e = mf->elist; unsigned noteHeight = 10; - const cmChar_t* svgFn = cmFsMakeFn(dir,fn,"html",NULL); - const cmChar_t* cssFn = cmFsMakeFn(NULL,fn,"css",NULL); + cmChar_t* fn0 = cmMemAllocStr( fn ); + const cmChar_t* svgFn = cmFsMakeFn(dir,fn0 = cmTextAppendSS(fn0,"_midi_svg"),"html",NULL); + const cmChar_t* cssFn = cmFsMakeFn(NULL,"score_midi_svg","css",NULL); cmChar_t* t0 = NULL; // temporary dynamic string + unsigned i = 0; + const cmXsSvgEvt_t* e0 = NULL; + cmMemFree(fn0); // create the SVG writer if( cmSvgWriterAlloc(ctx,&svgH) != kOkSvgRC ) @@ -3028,6 +3496,13 @@ cmXsRC_t _cmXsWriteMidiSvg( cmCtx_t* ctx, cmXScore_t* p, cmXsMidiFile_t* mf, con goto errLabel; } + _cmXsWriteMidiSvgLegend( svgH, 0, "Velocity", "cplx_vel" ); + _cmXsWriteMidiSvgLegend( svgH, 1, "Duration", "cplx_rym" ); + _cmXsWriteMidiSvgLegend( svgH, 2, "Left Pitch", "cplx_lpch" ); + _cmXsWriteMidiSvgLegend( svgH, 3, "Right Pitch", "cplx_rpch" ); + _cmXsWriteMidiSvgLegend( svgH, 4, "Density", "cplx_density" ); + + // for each MIDI file element for(; e!=NULL && rc==kOkXsRC; e=e->link) { @@ -3047,8 +3522,30 @@ cmXsRC_t _cmXsWriteMidiSvg( cmCtx_t* ctx, cmXScore_t* p, cmXsMidiFile_t* mf, con if( cmSvgWriterRect(svgH, e->tick, e->d0 * noteHeight, e->durTicks, noteHeight-1, t0 ) != kOkSvgRC ) rc = kSvgFailXsRC; else - if( cmSvgWriterText(svgH, e->tick + e->durTicks/2, e->d0 * noteHeight + noteHeight/2, cmMidiToSciPitch( e->d0, NULL, 0), "pitch") != kOkSvgRC ) + { + t0 = cmTsPrintfP(t0,"%s",cmMidiToSciPitch( e->d0, NULL, 0)); + + if( cmSvgWriterText(svgH, e->tick + e->durTicks/2, e->d0 * noteHeight + noteHeight/2, t0, "pitch") != kOkSvgRC ) rc = kSvgFailXsRC; + else + { + if( e0 != NULL ) + { + bool fl = (i % 10) == 0; + + _cmXsWriteMidiSvgLinePoint(svgH, e0->tick, e0->cplx.sum_d_vel, e->tick, e->cplx.sum_d_vel, p->cplx_max.sum_d_vel, "cplx_vel",fl?"V":NULL); + _cmXsWriteMidiSvgLinePoint(svgH, e0->tick, e0->cplx.sum_d_rym, e->tick, e->cplx.sum_d_rym, p->cplx_max.sum_d_rym, "cplx_rym",fl?"D":NULL); + _cmXsWriteMidiSvgLinePoint(svgH, e0->tick, e0->cplx.sum_d_lpch, e->tick, e->cplx.sum_d_lpch, p->cplx_max.sum_d_lpch, "cplx_lpch",fl?"L":NULL); + _cmXsWriteMidiSvgLinePoint(svgH, e0->tick, e0->cplx.sum_d_rpch, e->tick, e->cplx.sum_d_rpch, p->cplx_max.sum_d_rpch, "cplx_rpch",fl?"R":NULL); + _cmXsWriteMidiSvgLinePoint(svgH, e0->tick, e0->cplx.sum_n_lpch + e0->cplx.sum_n_rpch, e->tick, e->cplx.sum_n_lpch + e->cplx.sum_n_rpch, p->cplx_max.sum_n_lpch + p->cplx_max.sum_n_rpch, "cplx_density",fl?"N":NULL); + } + + e0 = e; + } + + } + + i+=1; } break; @@ -3075,7 +3572,7 @@ cmXsRC_t _cmXsWriteMidiSvg( cmCtx_t* ctx, cmXScore_t* p, cmXsMidiFile_t* mf, con cmSvgWriterRect(svgH, e->tick, y, e->durTicks, noteHeight-1, classLabel); } break; - } + } } if( rc != kOkXsRC ) @@ -3095,7 +3592,7 @@ cmXsRC_t _cmXsWriteMidiSvg( cmCtx_t* ctx, cmXScore_t* p, cmXsMidiFile_t* mf, con } -void _cmXsPushSvgEvent( cmXScore_t* p, cmXsMidiFile_t* mf, unsigned flags, unsigned tick, unsigned durTick, unsigned voice, unsigned d0, unsigned d1 ) +void _cmXsPushSvgEvent( cmXScore_t* p, cmXsMidiFile_t* mf, unsigned flags, unsigned tick, unsigned durTick, unsigned voice, unsigned d0, unsigned d1, const cmXsComplexity_t* cplx ) { cmXsSvgEvt_t* e = cmLhAllocZ(p->lhH,cmXsSvgEvt_t,1); e->flags = flags; @@ -3104,6 +3601,10 @@ void _cmXsPushSvgEvent( cmXScore_t* p, cmXsMidiFile_t* mf, unsigned flags, unsig e->voice = voice; e->d0 = d0; // note=pitch bar=number pedal=ctl# metronome=BPM e->d1 = d1; + + if( cplx != NULL ) + e->cplx = *cplx; + if( mf->eol != NULL ) mf->eol->link = e; else @@ -3140,7 +3641,7 @@ cmXsRC_t _cmXScoreGenSvg( cmCtx_t* ctx, cmXsH_t h, const cmChar_t* dir, const cm if( cmIsFlag(note->flags,kMetronomeXsFl) ) { // set BPM as d0 - _cmXsPushSvgEvent(p,&mf,note->flags,note->tick,0,0,note->duration,0); + _cmXsPushSvgEvent(p,&mf,note->flags,note->tick,0,0,note->duration,0,NULL); continue; } @@ -3149,21 +3650,21 @@ cmXsRC_t _cmXScoreGenSvg( cmCtx_t* ctx, cmXsH_t h, const cmChar_t* dir, const cm if( cmIsFlag(note->flags,kOnsetXsFl) ) { unsigned d0 = cmSciPitchToMidiPitch( note->step, note->alter, note->octave ); - unsigned durTick = note->duration; + unsigned durTick = note->tied_dur; if( note->tied != NULL ) { cmXsNote_t* tn = note->tied; for(; tn!=NULL; tn=tn->tied) - durTick += tn->duration; + durTick += tn->tied_dur; } - _cmXsPushSvgEvent(p,&mf,note->flags,note->tick,durTick,note->voice->id,d0,note->vel); + _cmXsPushSvgEvent(p,&mf,note->flags,note->tick,durTick,note->voice->id,d0,note->vel,¬e->cplx); continue; } // if this is a bar event if( cmIsFlag(note->flags,kBarXsFl) ) { - _cmXsPushSvgEvent(p,&mf,note->flags,note->tick,0,0,note->meas->number,0); + _cmXsPushSvgEvent(p,&mf,note->flags,note->tick,0,0,note->meas->number,0,NULL); continue; } @@ -3171,38 +3672,32 @@ cmXsRC_t _cmXScoreGenSvg( cmCtx_t* ctx, cmXsH_t h, const cmChar_t* dir, const cm if( cmIsFlag(note->flags,kDampDnXsFl|kDampUpDnXsFl|kSostDnXsFl) ) { unsigned d0 = cmIsFlag(note->flags,kSostDnXsFl) ? kSostenutoCtlMdId : kSustainCtlMdId; - _cmXsPushSvgEvent(p,&mf,note->flags,note->tick,note->duration,0,d0,127); + _cmXsPushSvgEvent(p,&mf,note->flags,note->tick,note->duration,0,d0,127,NULL); continue; } } } } - - + return _cmXsWriteMidiSvg( ctx, p, &mf, dir, fn ); - } + cmXsRC_t cmXScoreTest( cmCtx_t* ctx, const cmChar_t* xmlFn, - const cmChar_t* reorderFn, + const cmChar_t* editFn, const cmChar_t* csvOutFn, const cmChar_t* midiOutFn) { cmXsRC_t rc; cmXsH_t h = cmXsNullHandle; - if((rc = cmXScoreInitialize( ctx, &h, xmlFn)) != kOkXsRC ) + // Parse the XML file and apply the changes in editFn. + if((rc = cmXScoreInitialize( ctx, &h, xmlFn,editFn)) != kOkXsRC ) return cmErrMsg(&ctx->err,rc,"XScore alloc failed."); - if( reorderFn != NULL ) - cmXScoreReorder(h,reorderFn); - - // assign durations to pedal down events - _cmXScoreProcessPedals(_cmXScoreHandleToPtr(h)); - if( csvOutFn != NULL ) { cmScH_t scH = cmScNullHandle; @@ -3224,20 +3719,26 @@ cmXsRC_t cmXScoreTest( cmSymTblDestroy(&stH); } - + if( midiOutFn != NULL ) { - cmFileSysPathPart_t* pp = cmFsPathParts(midiOutFn); + + // measure the score complexity + double wndSecs = 1.0; + _cmXsMeasComplexity(h,wndSecs); + - _cmXScoreGenSvg( ctx, h, pp->dirStr, pp->fnStr ); + cmFileSysPathPart_t* pp = cmFsPathParts(midiOutFn); _cmXsWriteMidiFile(ctx, h, pp->dirStr, pp->fnStr ); + + _cmXScoreGenSvg( ctx, h, pp->dirStr, pp->fnStr ); cmFsFreePathParts(pp); } - cmXScoreReport(h,&ctx->rpt,true); + //cmXScoreReport(h,&ctx->rpt,true); return cmXScoreFinalize(&h); diff --git a/app/cmXScore.h b/app/cmXScore.h index af9a556..a47cfa1 100644 --- a/app/cmXScore.h +++ b/app/cmXScore.h @@ -18,7 +18,9 @@ extern "C" { kPedalStateErrorXsRc, kMidiFailXsRC, kFileFailXsRC, - kSvgFailXsRC + kSvgFailXsRC, + kOverlapWarnXsRC, + kZeroLengthEventXsRC }; typedef cmRC_t cmXsRC_t; @@ -41,32 +43,23 @@ extern "C" { // // M-x load-file ~/src/emacs/proc_music_xml.el // - // 3) How to assigned dynamic markings (they are not attached to notes). (from MIDI file?) - // 4) Tempo syntax is inconsistent (only a problem in full part2 score) - // 5) Heel is being parsed but not used. - // 6) Sostenuto pedal events are not being parsed because they are not pedal events. - // 7) What is a 'pedal-change' event vs. a 'pedal-stop' event. - // 8) Verify the colors. (done) - // 9) Remove blank bars at end (done in xml - do in score) - //10) Need to assign section targets (always default to next section) - //11) Mark tied notes for skip. (done) - //12) Determine note off locations based on ties and slurs - defer 'pedal' to player - //13) Check that the measures are given in sorted order. - //14) Current implementation assumes meter changes only occur at measure boundaries. - //15) Score Fixes: Add meter to bar 1, fix time errors (shown in voice report) - //16) The order of notes is now correct (4/6/16) after applying - // the grace note ordering changed specified in 'score_print_mk_edit.txt', - // via cmXScoreReorder() however the ticks are now incorrect - fix them. - - cmXsRC_t cmXScoreInitialize( cmCtx_t* ctx, cmXsH_t* hp, const cmChar_t* xmlFn ); + + // Initialize an cmXScore object from a Sibelius generated MusicXML file. + // Optionally include an 'edit' file to attach additional score information. + // Note that the 'edit' file is created by marking up a file created via + // cmXScoreReport(). + cmXsRC_t cmXScoreInitialize( cmCtx_t* ctx, cmXsH_t* hp, const cmChar_t* xmlFn, const cmChar_t* editFn ); cmXsRC_t cmXScoreFinalize( cmXsH_t* hp ); + bool cmXScoreIsValid( cmXsH_t h ); cmXsRC_t cmXScoreWriteCsv( cmXsH_t h, const cmChar_t* csvFn ); void cmXScoreReport( cmXsH_t h, cmRpt_t* rpt, bool sortFl ); + cmXsRC_t cmXScoreGenEditFile( cmCtx_t* ctx, const cmChar_t* xmlFn, const cmChar_t* outFn ); + // Generate the CSV file suitable for use by cmScore. cmXsRC_t cmXScoreTest( cmCtx_t* ctx, const cmChar_t* xmlFn, const cmChar_t* reorderFn, const cmChar_t* csvOutFn, const cmChar_t* midiOutFn ); diff --git a/cmMidiFile.c b/cmMidiFile.c index 0071468..ce6e6e8 100644 --- a/cmMidiFile.c +++ b/cmMidiFile.c @@ -8,8 +8,11 @@ #include "cmMallocDebug.h" #include "cmLinkedHeap.h" #include "cmTime.h" +#include "cmText.h" #include "cmMidi.h" #include "cmMidiFile.h" +#include "cmSvgWriter.h" + #ifdef cmBIG_ENDIAN #define mfSwap16(v) (v) @@ -256,7 +259,7 @@ cmMfRC_t _cmMidiFileReadChannelMsg( _cmMidiFile_t* mfp, cmMidiByte_t* rsPtr, cmM unsigned byteN = cmMidiStatusToByteCount(tmp->status); if( byteN==kInvalidMidiByte || byteN > 2 ) - return cmErrMsg(&mfp->err,kInvalidStatusMfRC,"Invalid status:0x%x %i.",tmp->status,tmp->status); + return cmErrMsg(&mfp->err,kInvalidStatusMfRC,"Invalid status:0x%x %i byte cnt:%i.",tmp->status,tmp->status,byteN); unsigned i; for(i=useRsFl; ierr,kFileFailMfRC,"MIDI file close failed."); if( rc != kOkMfRC ) + { _cmMidiFileClose(p); - + hp->h = NULL; + } + return rc; } @@ -971,12 +977,75 @@ cmMfRC_t _cmMidiFileWriteMetaMsg( _cmMidiFile_t* mfp, const cmMidiTrackMsg_t* tm return rc; } +cmMfRC_t _cmMidiFileInsertEotMsg( _cmMidiFile_t* p, unsigned trkIdx ) +{ + _cmMidiTrack_t* trk = p->trkV + trkIdx; + cmMidiTrackMsg_t* m0 = NULL; + cmMidiTrackMsg_t* m = trk->base; + + // locate the current EOT msg on this track + for(; m!=NULL; m=m->link) + { + if( m->status == kMetaStId && m->metaId == kEndOfTrkMdId ) + { + // If this EOT msg is the last msg in the track ... + if( m->link == NULL ) + { + assert( m == trk->last ); + return kOkMfRC; // ... then there is nothing else to do + } + + // If this EOT msg is not the last in the track ... + if( m0 != NULL ) + m0->link = m->link; // ... then unlink it + + break; + } + + m0 = m; + } + + // if we get here then the last msg in the track was not an EOT msg + + // if there was no previously allocated EOT msg + if( m == NULL ) + { + m = _cmMidiFileAllocMsg(p, trkIdx, 1, kMetaStId ); + m->metaId = kEndOfTrkMdId; + trk->cnt += 1; + + } + + // link an EOT msg as the last msg on the track + + // if the track is currently empty + if( m0 == NULL ) + { + trk->base = m; + trk->last = m; + } + else // link the msg as the last on on the track + { + assert( m0 == trk->last); + m0->link = m; + m->link = NULL; + trk->last = m; + } + + return kOkMfRC; + +} + cmMfRC_t _cmMidiFileWriteTrack( _cmMidiFile_t* mfp, unsigned trkIdx ) { cmMfRC_t rc = kOkMfRC; cmMidiTrackMsg_t* tmp = mfp->trkV[trkIdx].base; cmMidiByte_t runStatus = 0; + // be sure there is a EOT msg at the end of this track + if((rc = _cmMidiFileInsertEotMsg(mfp, trkIdx )) != kOkMfRC ) + return rc; + for(; tmp != NULL; tmp=tmp->link) { // write the msg tick count @@ -1379,8 +1448,11 @@ cmMfRC_t cmMidiFileInsertTrackMsg( cmMidiFileH_t h, unsigned trkIdx, const cmMi assert( m1->atick >= m->atick ); m1->dtick = m1->atick - m->atick; } - + + p->trkV[trkIdx].cnt += 1; p->msgVDirtyFl = true; + + return kOkMfRC; @@ -1402,6 +1474,8 @@ cmMfRC_t cmMidiFileInsertTrackChMsg( cmMidiFileH_t h, unsigned trkIdx, unsigned m.status = status & 0xf0; m.byteCnt = sizeof(cm); m.u.chMsgPtr = &cm; + + assert( m.status >= kNoteOffMdId && m.status <= kPbendMdId ); return cmMidiFileInsertTrackMsg(h,trkIdx,&m); } @@ -1836,6 +1910,38 @@ void cmMidiFilePrintTracks( cmMidiFileH_t h, unsigned trkIdx, cmRpt_t* rpt ) void cmMidiFileTestPrint( void* printDataPtr, const char* fmt, va_list vl ) { vprintf(fmt,vl); } +cmMidiFileDensity_t* cmMidiFileNoteDensity( cmMidiFileH_t h, unsigned* cntRef ) +{ + int msgN = cmMidiFileMsgCount(h); + const cmMidiTrackMsg_t** msgs = cmMidiFileMsgArray(h); + cmMidiFileDensity_t* dV = cmMemAllocZ(cmMidiFileDensity_t,msgN); + + int i,j,k; + for(i=0,k=0; istatus == kNoteOnMdId && msgs[i]->u.chMsgPtr->d1 > 0 ) + { + dV[k].uid = msgs[i]->uid; + dV[k].amicro = msgs[i]->amicro; + + for(j=i; j>=0; --j) + { + if( msgs[i]->amicro - msgs[j]->amicro > 1000000 ) + break; + + dV[k].density += 1; + } + + k += 1; + + } + + if( cntRef != NULL ) + *cntRef = k; + + return dV; +} + + cmMfRC_t cmMidiFileGenPlotFile( cmCtx_t* ctx, const cmChar_t* midiFn, const cmChar_t* outFn ) { cmMfRC_t rc = kOkMfRC; @@ -1872,6 +1978,99 @@ cmMfRC_t cmMidiFileGenPlotFile( cmCtx_t* ctx, const cmChar_t* midiFn, const cmCh return rc; } +cmMfRC_t cmMidiFileGenSvgFile( cmCtx_t* ctx, const cmChar_t* midiFn, const cmChar_t* outSvgFn, const cmChar_t* cssFn ) +{ + cmMfRC_t rc = kOkMfRC; + cmSvgH_t svgH = cmSvgNullHandle; + cmMidiFileH_t mfH = cmMidiFileNullHandle; + unsigned msgN = 0; + const cmMidiTrackMsg_t** msgs = NULL; + unsigned noteHeight = 10; + double micros_per_sec = 1000.0; + unsigned i; + + if((rc = cmMidiFileOpen(ctx,&mfH,midiFn)) != kOkMfRC ) + { + rc = cmErrMsg(&ctx->err,rc,"Unable to open the MIDI file '%s'.",cmStringNullGuard(midiFn)); + goto errLabel; + } + + cmMidiFileCalcNoteDurations( mfH ); + + msgN = cmMidiFileMsgCount(mfH); + msgs = cmMidiFileMsgArray(mfH); + + + if( cmSvgWriterAlloc(ctx,&svgH) != kOkSvgRC ) + { + rc = cmErrMsg(&ctx->err,kSvgFailMfRC,"Unable to create the MIDI SVG output file '%s'.",cmStringNullGuard(outSvgFn)); + goto errLabel; + } + + + for(i=0; istatus == kNoteOnMdId && msgs[i]->u.chMsgPtr->d1 > 0 ) + { + const cmMidiTrackMsg_t* m = msgs[i]; + + + if( cmSvgWriterRect(svgH, m->amicro/micros_per_sec, m->u.chMsgPtr->d0 * noteHeight, m->u.chMsgPtr->durMicros/micros_per_sec, noteHeight-1, "note" ) != kOkSvgRC ) + rc = kSvgFailMfRC; + + const cmChar_t* t0 = cmMidiToSciPitch(m->u.chMsgPtr->d0,NULL,0); + + if( cmSvgWriterText(svgH, (m->amicro + (m->u.chMsgPtr->durMicros/2)) / micros_per_sec, m->u.chMsgPtr->d0 * noteHeight, t0, "text" ) != kOkSvgRC ) + rc = kSvgFailMfRC; + + } + + if( rc != kOkMfRC ) + { + cmErrMsg(&ctx->err,rc,"SVG Shape insertion failed."); + goto errLabel; + } + + unsigned dN = 0; + cmMidiFileDensity_t* dV = cmMidiFileNoteDensity( mfH, &dN ); + double t0 = 0; + double y0 = 64.0; + cmChar_t* tx = NULL; + + for(i=0; ierr,kUidNotFoundMfRC,"The MIDI msg form UID:%i was not found.",dV[i].uid); + else + { + double t1 = m->amicro / micros_per_sec; + double y1 = dV[i].density * noteHeight; + + cmSvgWriterLine(svgH, t0, y0, t1, y1, "density" ); + cmSvgWriterText(svgH, t1, y1, tx = cmTsPrintfP(tx,"%i",dV[i].density),"dtext"); + + t0 = t1; + y0 = y1; + + } + } + + cmMemFree(dV); + cmMemFree(tx); + + if( rc == kOkMfRC ) + if( cmSvgWriterWrite(svgH,cssFn,outSvgFn) != kOkSvgRC ) + rc = cmErrMsg(&ctx->err,kSvgFailMfRC,"SVG file write to '%s' failed.",cmStringNullGuard(outSvgFn)); + + + errLabel: + cmMidiFileClose(&mfH); + cmSvgWriterFree(&svgH); + + return rc; +} + void cmMidiFilePrintControlNumbers( cmCtx_t* ctx, const char* fn ) { cmMidiFileH_t h = cmMidiFileNullHandle; diff --git a/cmMidiFile.h b/cmMidiFile.h index 55b8ef7..f77e51a 100644 --- a/cmMidiFile.h +++ b/cmMidiFile.h @@ -117,7 +117,8 @@ extern "C" { kLargeDeltaTickMfRC, // 12 (a large delta tick value was filtered) kUidNotFoundMfRC, // 13 kUidNotANoteMsgMfRC, // 14 - kInvalidTrkIndexMfRC // 15 + kInvalidTrkIndexMfRC,// 15 + kSvgFailMfRC // 16 }; extern cmMidiFileH_t cmMidiFileNullHandle; @@ -212,11 +213,27 @@ extern "C" { void cmMidiFilePrintMsgs( cmMidiFileH_t h, cmRpt_t* rpt ); void cmMidiFilePrintTrack( cmMidiFileH_t h, unsigned trkIdx, cmRpt_t* rpt ); - void cmMidiFileTest( const char* fn, cmCtx_t* ctx ); + + typedef struct + { + unsigned uid; + unsigned long long amicro; + unsigned density; + + } cmMidiFileDensity_t; + + // Generate the note onset density measure for each note in the MIDI file. + // Delete the returned memory with a call to cmMemFree(). + cmMidiFileDensity_t* cmMidiFileNoteDensity( cmMidiFileH_t h, unsigned* cntRef ); // Generate a piano-roll plot description file which can be displayed with cmXScore.m cmMfRC_t cmMidiFileGenPlotFile( cmCtx_t* ctx, const cmChar_t* midiFn, const cmChar_t* outFn ); + cmMfRC_t cmMidiFileGenSvgFile( cmCtx_t* ctx, const cmChar_t* midiFn, const cmChar_t* outSvgFn, const cmChar_t* cssFn ); + + void cmMidiFileTest( const char* fn, cmCtx_t* ctx ); + + //) diff --git a/cmSvgWriter.c b/cmSvgWriter.c index d854932..7845955 100644 --- a/cmSvgWriter.c +++ b/cmSvgWriter.c @@ -167,6 +167,32 @@ void _cmSvgSize( cmSvg_t* p, double* widthRef, double* heightRef ) *heightRef = max_y - min_y; } +void _cmSvgWriterFlipY( cmSvg_t* p, unsigned height ) +{ + cmSvgEle_t* e = p->elist; + for(; e!=NULL; e=e->link) + { + e->y0 = (-e->y0) + height; + e->y1 = (-e->y1) + height; + + if( e->id == kRectSvgId ) + { + double t = e->y1; + e->y1 = e->y0; + e->y0 = t; + } + + } +} + +/* + "\n" + "\n" + + */ cmSvgRC_t cmSvgWriterWrite( cmSvgH_t h, const cmChar_t* cssFn, const cmChar_t* outFn ) { @@ -176,15 +202,29 @@ cmSvgRC_t cmSvgWriterWrite( cmSvgH_t h, const cmChar_t* cssFn, const cmChar_t* double svgHeight = 0; cmSvgEle_t* e = p->elist; cmFileH_t fH = cmFileNullHandle; + cmChar_t* s0 = NULL; + cmChar_t* s1 = NULL; + + cmChar_t hdr[] = + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n"; + + _cmSvgSize(p, &svgWidth, &svgHeight ); - - if( cmFileOpen(&fH,outFn,kWriteFileFl,p->err.rpt) != kOkFileRC ) - return cmErrMsg(&p->err,kFileFailSvgRC,"SVG file create failed for '%s'.",cmStringNullGuard(outFn)); - if( cmFilePrintf(fH,"\n\n\n\n",svgWidth,svgHeight,cssFn) != kOkFileRC ) + _cmSvgWriterFlipY( p, svgHeight ); + + // print the file header + if( (s0 = cmTsPrintfP(s0,hdr,cssFn,svgWidth,svgHeight)) == NULL ) { - rc = cmErrMsg(&p->err,kFileFailSvgRC,"File prefix write failed."); + rc = cmErrMsg(&p->err,kPrintFailSvgRC,"File prefix write failed."); goto errLabel; } @@ -193,18 +233,18 @@ cmSvgRC_t cmSvgWriterWrite( cmSvgH_t h, const cmChar_t* cssFn, const cmChar_t* switch( e->id ) { case kRectSvgId: - if( cmFilePrintf(fH,"\n",e->x0,e->y0,e->x1-e->x0,e->y1-e->y0,e->cssClass) != kOkFileRC ) - rc = kFileFailSvgRC; + if( (s1 = cmTsPrintfP(s1,"\n",e->x0,e->y0,e->x1-e->x0,e->y1-e->y0,e->cssClass)) == NULL ) + rc = kPrintFailSvgRC; break; case kLineSvgId: - if( cmFilePrintf(fH,"\n",e->x0,e->y0,e->x1,e->y1,e->cssClass) != kOkFileRC ) - rc = kFileFailSvgRC; + if( (s1 = cmTsPrintfP(s1,"\n",e->x0,e->y0,e->x1,e->y1,e->cssClass)) == NULL ) + rc = kPrintFailSvgRC; break; case kTextSvgId: - if( cmFilePrintf(fH,"%s\n",e->x0,e->y0,e->cssClass,e->text) != kOkFileRC ) - rc = kFileFailSvgRC; + if( (s1 = cmTsPrintfP(s1,"%s\n",e->x0,e->y0,e->cssClass,e->text)) == NULL ) + rc = kPrintFailSvgRC; break; default: @@ -214,21 +254,38 @@ cmSvgRC_t cmSvgWriterWrite( cmSvgH_t h, const cmChar_t* cssFn, const cmChar_t* if( rc != kOkSvgRC ) { - rc = cmErrMsg(&p->err,kFileFailSvgRC,"Element write failed."); + rc = cmErrMsg(&p->err,kPrintFailSvgRC,"Element write failed."); break; } + + s0 = cmTextAppendSS(s0,s1); + } - if( cmFilePrint(fH,"\n\n\n") != kOkFileRC ) + if( (s1 = cmTsPrintfP(s1,"\n\n\n")) == NULL ) { - rc = cmErrMsg(&p->err,kFileFailSvgRC,"File suffix write failed."); + rc = cmErrMsg(&p->err,kPrintFailSvgRC,"File suffix write failed."); goto errLabel; } + if( cmFileOpen(&fH,outFn,kWriteFileFl,p->err.rpt) != kOkFileRC ) + { + rc = cmErrMsg(&p->err,kFileFailSvgRC,"SVG file create failed for '%s'.",cmStringNullGuard(outFn)); + goto errLabel; + } + + if( cmFilePrint(fH,s0 = cmTextAppendSS(s0,s1)) != kOkFileRC ) + { + rc = cmErrMsg(&p->err,kFileFailSvgRC,"File write failed."); + goto errLabel; + } errLabel: cmFileClose(&fH); + cmMemFree(s0); + cmMemFree(s1); + return rc; } diff --git a/cmSvgWriter.h b/cmSvgWriter.h index aca0a43..01214cf 100644 --- a/cmSvgWriter.h +++ b/cmSvgWriter.h @@ -9,6 +9,7 @@ enum { kOkSvgRC = cmOkRC, kFileFailSvgRC, + kPrintFailSvgRC, kLHeapFailSvgRC }; @@ -21,10 +22,14 @@ enum cmSvgRC_t cmSvgWriterFree( cmSvgH_t* hp ); bool cmSvgWriterIsValid( cmSvgH_t h ); - cmSvgRC_t cmSvgWriterRect( cmSvgH_t h, double x, double y, double ww, double hh, const cmChar_t* cssClassLabel ); + cmSvgRC_t cmSvgWriterRect( cmSvgH_t h, double x, double y, double ww, double hh, const cmChar_t* cssClassLabel ); cmSvgRC_t cmSvgWriterLine( cmSvgH_t h, double x0, double y0, double x1, double y1, const cmChar_t* cssClassLabel ); cmSvgRC_t cmSvgWriterText( cmSvgH_t h, double x, double y, const cmChar_t* text, const cmChar_t* cssClassLabel ); + // Write the SVG file. Note that header on this file references the CSS file 'cssFn' + // and the Javascript file svg-pan-zoom.min.js from https://github.com/ariutta/svg-pan-zoom. + // Both the CSS file and svg-pan-zoom.min.js should therefore be in the same directory + // as the output HTML file. cmSvgRC_t cmSvgWriterWrite( cmSvgH_t h, const cmChar_t* cssFn, const cmChar_t* outFn ); #ifdef __cplusplus diff --git a/dsp/cmDspKr.c b/dsp/cmDspKr.c index 53e08f2..4b8a6cb 100644 --- a/dsp/cmDspKr.c +++ b/dsp/cmDspKr.c @@ -657,7 +657,7 @@ cmDspRC_t _cmDspTimeLineRecv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_ cmTlObj_t* op; if((op = cmTimeLineIdToObj(p->tlH, cmInvalidId, markerId )) != NULL ) { - assert(op->typeId == kMarkerTlId); + assert(op->typeId == kMarkerTlId || op->typeId == kMidiEvtTlId ); p->afIdx = op->begSmpIdx; diff --git a/dsp/cmDspPgmKrChain.c b/dsp/cmDspPgmKrChain.c index ec558ce..474ad49 100644 --- a/dsp/cmDspPgmKrChain.c +++ b/dsp/cmDspPgmKrChain.c @@ -201,7 +201,7 @@ void _cmDspSys_TlXformChain( cmDspSysH_t h, cmDspTlXform_t* c, unsigned preGrpS cmDspInst_t* max_lwr_ctl = cmDspSysAllocScalarP( h,preGrpSymId, NULL, lbl("Max Lwr"), 0.0, -1.0, 5.0, 3.0); cmDspInst_t* min_off_ctl = cmDspSysAllocScalarP( h,preGrpSymId, NULL, lbl("Min Off"), 0.0, 50.0, 0.1, 30.0); cmDspInst_t* max_off_ctl = cmDspSysAllocScalarP( h,preGrpSymId, NULL, lbl("Max Off"), 0.0, 50.0, 0.1, 30.0); - cmDspInst_t* min_wet_ctl = cmDspSysAllocScalarP( h,preGrpSymId, NULL, lbl("Min Wet"), 0.0, 1.0, 0.01, 1.0); + cmDspInst_t* min_wet_ctl = cmDspSysAllocScalarP( h,preGrpSymId, NULL, lbl("Min Wet"), 0.0, 1.0, 0.01, 0.0); cmDspInst_t* max_wet_ctl = cmDspSysAllocScalarP( h,preGrpSymId, NULL, lbl("Max Wet"), 0.0, 1.0, 0.01, 1.0);