#include "cmPrefix.h" #include "cmGlobal.h" #include "cmFloatTypes.h" #include "cmRpt.h" #include "cmErr.h" #include "cmCtx.h" #include "cmMem.h" #include "cmMallocDebug.h" #include "cmLinkedHeap.h" #include "cmJson.h" #include "cmTime.h" #include "cmMidi.h" #include "cmMidiFile.h" #include "cmAudioFile.h" #include "cmTimeLine.h" #include "cmSymTbl.h" #include "cmFile.h" #include "cmScore.h" #include "cmTakeSeqBldr.h" // Score track record: Map a score event to a MIDI event. typedef struct cmScTrkMidiTsb_str { unsigned mni; // MIDI note index as an offset from the take marker unsigned muid; // MIDI file msg unique id unsigned scEvtIdx; // score event index this note/pedal is assoc'd with or -1 if it did not match unsigned flags; // flags from cmScMatcherResult_t } cmScTrkMidiTsb_t; // Score Tracking info. from a single take (time-line marker) typedef struct cmScTrkTakeTsb_str { unsigned tlMarkerUid; // marker time line uid assoc'd with this take cmScTrkMidiTsb_t* midiV; // midiV[midiN] score to midi file map recd. array. unsigned midiN; // count of records in midiV[] unsigned minMuid; // min MIDI muid in midiV[] unsigned maxMuid; // max MIDI muid in midiV[] unsigned minScEvtIdx; // min scEvtIdx in midiV[] unsigned maxScEvtIdx; // max scEvtIdx in midiV[] bool failFl; } cmScTrkTakeTsb_t; // typedef struct cmMidiTsb_str { unsigned rid; // unique id among all object in this take unsigned srcId; // marker uid or -1 if this event was manually inserted unsigned scEvtIdx; // score event assocd with this midi event unsigned flags; // copy of cmScTrkMidiTsb_t.flags or 0 if not assoc'd with a score event struct cmMidiTsb_str* ref; // previous MIDI event in time unsigned offsetSmp; // time offset from *ref unsigned durSmp; // duration of this MIDI event unsigned status; // MIDI status value unsigned d0; // d0 MIDI channel msg data byte 0. unsigned d1; // d1 MIDI channel msg data byte 1. struct cmMidiTsb_str* link; // pointer to next MIDI event in list } cmMidiTsb_t; // This record contains all the score events and and score synchronized MIDI events // associated with a given take. Each call to cmTakeSeqBldrLoadTake() creates // one of these records. typedef struct cmTakeTsb_str { unsigned tlMarkerUid; // time-line marker uid associated with this take cmScTrkTakeTsb_t* stt; // score tracking info cmMidiTsb_t* midi; // midi events contained by this take struct cmTakeTsb_str* link; } cmTakeTsb_t; typedef struct { cmCtx_t ctx; // application context cmErr_t err; // internal error object cmJsonH_t jsH; // JSON tree used to hold score tracker info. const cmChar_t* tlFn; // time line filename const cmChar_t* scFn; // score file name const cmChar_t* tlPrefixPath; // path to time line audio and MIDI files cmTlH_t tlH; // time-line handle cmScH_t scH; // score handle cmScTrkTakeTsb_t* scTrkTakeV; // score tracker file info scTrkTakeV[ scTrkTakeN ] unsigned scTrkTakeN; // (one record per take) cmTakeTsb_t* takes; // list of loaded takes cmTakeTsb_t* manual; // list of manually inserted MIDI events cmTakeTsb_t* out; // render list cmMidiTsb_t* evt; // next event to play from the render list unsigned absPlaySmp; // absolute sample index of the clock unsigned absEvtSmp; // absolute sample index of the next event to play unsigned nextRid; cmMidiTsb_t* rend; } cmTsb_t; cmTakeSeqBldrH_t cmTakeSeqBldrNullHandle = cmSTATIC_NULL_HANDLE; cmTsb_t* _cmTsbHandleToPtr( cmTakeSeqBldrH_t h ) { cmTsb_t* p = (cmTsb_t*)h.h; return p; } //------------------------------------------------------------------------------------------------------------------------- typedef struct cmOrderTsb_str { cmTakeTsb_t* t; unsigned minScEvtIdx; // take begin unsigned maxScEvtIdx; // take end unsigned begScEvtIdx; // render begin (render beg/end is always contained unsigned endScEvtIdx; // render end withing min/maxScEvtIdx) struct cmOrderTsb_str* link; } cmOrderTsb_t; // Notes: // 1) begScEvtIdx - endScEvtIdx is always contained within minScEvtIdx-maxScEvtIdx. // 2) If begScEvtIdx == cmInvalidIdx then the take was entirely contained inside the // previous take and is therefore skipped over. // Free a linked list of cmOrderTsb_t records void _cmTakeSeqBldrFreeOrder( cmOrderTsb_t* o ) { while(o!=NULL) { cmOrderTsb_t* o0 = o->link; cmMemFree(o); o = o0; } } // Create a linked list of cmOrderTsb_t records from the p->takes list. cmOrderTsb_t* _cmTakeSeqBldrAllocOrder( cmTsb_t* p ) { cmOrderTsb_t* o = NULL; cmOrderTsb_t* o0 = NULL; cmTakeTsb_t* t = p->takes; // create a list of order records - one per take for(; t!=NULL; t=t->link) if( t->stt != NULL ) { cmOrderTsb_t* o1 = cmMemAllocZ(cmOrderTsb_t,1); o1->t = t; o1->minScEvtIdx = t->stt->minScEvtIdx; o1->maxScEvtIdx = t->stt->maxScEvtIdx; o1->begScEvtIdx = o1->minScEvtIdx; o1->endScEvtIdx = o1->maxScEvtIdx; if( o0 == NULL ) o = o1; else o0->link = o1; o0 = o1; } return o; } // Sort a list of cmOrderTsb_t records on minScEvtIdx. cmOrderTsb_t* _cmTakeSeqBldrSortOrder( cmOrderTsb_t* o ) { cmOrderTsb_t* beg = NULL; cmOrderTsb_t* end = NULL; cmOrderTsb_t* m = NULL; // while elements remain on the unordered list while( o != NULL ) { cmOrderTsb_t* c0 = NULL; cmOrderTsb_t* c1 = o->link; m = o; // set m to point to the recd with the min minScEvtIdx for(; c1!=NULL; c1=c1->link ) if( c1->minScEvtIdx < m->minScEvtIdx ) m = c1; // for each recd on the unordered list for(c1=o; c1!=NULL; c1=c1->link) { // if this is the min record if( c1==m ) { // remove the min recd from this list if( c0 == NULL ) o = c1->link; else c0->link = c1->link; c1->link = NULL; break; } c0 = c1; } // c1 now points to the min record assert(c1==m); if( end == NULL ) beg = end = m; else { end->link = m; end = m; } } return beg; } // Set the begScEvtIdx and endScEvtIdx fields to settle conflicts // between overlapping takes. void _cmTakeSeqBldrSetBegEndOrder( cmOrderTsb_t* o ) { if( o == NULL ) return; // Earlier takes have priority over later takes so the first // take has highest priority and therefore it's beg=min and end=max. o->begScEvtIdx = o->minScEvtIdx; o->endScEvtIdx = o->maxScEvtIdx; cmOrderTsb_t* o0 = o; cmOrderTsb_t* o1 = o->link; while( o1!=NULL) { bool skipFl = false; // if this take begins after the previous take if( o1->minScEvtIdx > o0->endScEvtIdx ) { o1->begScEvtIdx = o1->minScEvtIdx; o1->endScEvtIdx = o1->maxScEvtIdx; } else { // if this take is entirely contained in the previous take // then this take is skipped. if( o0->endScEvtIdx > o1->maxScEvtIdx ) { o1->begScEvtIdx = cmInvalidIdx; o1->endScEvtIdx = cmInvalidIdx; skipFl = true; } else // this take overlaps with the previous take { o1->begScEvtIdx = o0->endScEvtIdx + 1; o1->endScEvtIdx = o1->maxScEvtIdx; } } if( skipFl ) { // the current take is being skipped so do not change o0 o1 = o1->link; } else { // advance to the next take o0 = o1; o1 = o0->link; } } } void _cmTakeSeqBldrPrintOrder( cmOrderTsb_t* o ) { int i; for(i=0; o!=NULL; o=o->link,++i) printf("%i : %p min:%i max:%i beg:%i end:%i\n",i, o->t, o->minScEvtIdx, o->maxScEvtIdx, o->begScEvtIdx, o->endScEvtIdx); printf("\n"); } //------------------------------------------------------------------------------------------------------------------------- typedef struct { cmScoreEvt_t* sep; unsigned absSmp; } cmTsbTempo_t; void _cmTsbTempoPrint( cmTsbTempo_t* r, unsigned rN ) { unsigned i; for(i=0; itype),r[i].absSmp); printf("\n"); } cmTsbTempo_t* _cmTsbTempoFind( cmTsbTempo_t* r, unsigned rN, unsigned scEvtIdx ) { unsigned i; for(i=0; iindex == scEvtIdx ) return r + i; return NULL; } // Starting at r[ri] calc the tempo for the next two bars. unsigned _cmTsbTempoCalc( cmTsbTempo_t* r, unsigned rN, unsigned ri, double srate ) { unsigned durBarCnt = 2; unsigned barIdx = 0; double beats = 0; unsigned abs0Smp = -1; unsigned abs1Smp = -1; for(; ritype == kNonEvtScId ) { beats += r[ri].sep->frac; if( abs0Smp == -1 ) abs0Smp = r[ri].absSmp; if( r[ri].absSmp != -1 ) abs1Smp = r[ri].absSmp; } // if this is a bar event if( r[ri].sep->type == kBarEvtScId ) { barIdx += 1; if( barIdx == durBarCnt ) break; } } double durSmp = abs1Smp - abs0Smp; if( durSmp == 0 ) return 0; return (unsigned)round(beats / (durSmp / (srate * 60.0))); } cmTsbRC_t _cmTsbCalcTempo( cmTsb_t* p, cmTakeTsb_t* t, unsigned* begBpSRef, unsigned* endBpSRef ) { unsigned i,j; assert( begBpSRef != NULL && endBpSRef != NULL ); *begBpSRef = 0; *endBpSRef = 0; if( t->stt == NULL ) return cmErrMsg(&p->err,kMissingScTrkTsbRC,"The tempo of takes without score-tracking results cannot be calculated."); if( t->midi == NULL ) return cmErrMsg(&p->err,kInvalidArgTsbRC,"The tempo of a take without MIDI events cannot be estimated."); // allocate an array to with one record per score event in the take unsigned rN = t->stt->maxScEvtIdx - t->stt->minScEvtIdx + 1; cmTsbTempo_t r[ rN ]; // assign a score event pointer to each record for(i=0; iscH,t->stt->minScEvtIdx + i); assert( r[i].sep != NULL ); r[i].absSmp = -1; } // use MIDI events with score information to assign absolute // time information to as many score events as possible. unsigned absSmp = 0; cmMidiTsb_t* m = t->midi; while( m!=NULL ) { cmTsbTempo_t* r0 = NULL; if( m->scEvtIdx != cmInvalidIdx && (r0 = _cmTsbTempoFind(r,rN,m->scEvtIdx)) != NULL ) { assert( r0->sep->type == kNonEvtScId ); r0->absSmp = absSmp; } m = m->link; if( m != NULL ) absSmp += m->offsetSmp; } unsigned barCnt = 0; // assign an absolute time to each bar recd for(i=0; itype == kBarEvtScId ) { barCnt += 1; // count the number of bars found // search ahead from the bar to the next note unsigned j; for(j=i; jtype == kNonEvtScId ) { r[i].absSmp = r[j].absSmp; // the bar time is the same as the next break; } } //_cmTsbTempoPrint(r,rN); unsigned tempoV[ barCnt ]; for(i=0,j=0; itlH) ); for(; itype == kBarEvtScId ) break; } if( j > 0 ) { *begBpSRef = tempoV[0]; *endBpSRef = tempoV[j-1]; } printf("Bars:%i ",barCnt); for(i=0; imidi; for(; m!=NULL; m=m->link) printf("mark:%4i sei:%4i fl:0x%x offs:%8i dur:%8i\n", m->srcId, m->scEvtIdx,m->flags,m->offsetSmp,m->durSmp); printf("\n"); } void _cmTsbTakePrint( cmTakeTsb_t* t ) { for(; t!=NULL; t=t->link) { printf("Take:%i %p\n", t->tlMarkerUid, t->stt); _cmTsbPrintMidi(t); } } cmTsbRC_t _cmTsbScoreTrkFree( cmTsb_t* p ) { cmTsbRC_t rc = kOkTsbRC; unsigned i; if( cmJsonFinalize(&p->jsH) != kOkJsRC ) { rc = cmErrMsg(&p->err,kJsonFailTsbRC,"JSON object finalize failed."); goto errLabel; } if( p->scTrkTakeV != NULL ) { for(i=0; iscTrkTakeN; ++i) cmMemPtrFree(&p->scTrkTakeV[i].midiV); cmMemPtrFree(&p->scTrkTakeV); } if( cmTimeLineFinalize(&p->tlH) != kOkTlRC ) rc = cmErrMsg(&p->err,kTimeLineFailTsbRC,"Time line object finalize failed."); if( cmScoreFinalize(&p->scH) != kOkScRC ) rc = cmErrMsg(&p->err,kScoreFailTsbRC,"Score finalize failed."); errLabel: return rc; } // Free a take record. Note that the record must be unlinked // from p->takes (See _cmTakeTsbUnlink().) prior to calling this function. void _cmTsbTakeFree( cmTsb_t* p, cmTakeTsb_t** tp ) { if( tp==NULL || *tp==NULL ) return; cmMidiTsb_t* m = (*tp)->midi; while( m != NULL ) { cmMidiTsb_t* nm = m->link; cmMemFree(m); m = nm; } cmMemPtrFree(tp); } void _cmTsbRendTakeFree( cmTsb_t* p, cmTakeTsb_t** t ) { _cmTsbTakeFree(p,t); p->evt = NULL; p->absPlaySmp = 0; p->absEvtSmp = 0; p->nextRid = 0; p->rend = NULL; } // Unlink a 'take' record from p->takes. cmTakeTsb_t* _cmTsbTakeUnlink( cmTsb_t* p, cmTakeTsb_t* t ) { cmTakeTsb_t* t0 = NULL; cmTakeTsb_t* t1 = p->takes; while( t1 != NULL ) { if( t1 == t ) { if( t0 == NULL ) p->takes = t->link; else t0->link = t->link; return t; } } return NULL; } cmTsbRC_t _cmTsbFree( cmTsb_t* p ) { cmTsbRC_t rc = kOkTsbRC; if((rc = _cmTsbScoreTrkFree(p)) != kOkTsbRC ) goto errLabel; //_cmTsbTakePrint(p->takes); //_cmTsbTakePrint(p->out); cmTakeTsb_t* t = p->takes; while( t != NULL ) { cmTakeTsb_t* nt = t->link; _cmTsbTakeFree(p,&t); t = nt; } _cmTsbRendTakeFree(p,&p->out); cmMemFree(p); errLabel: return rc; } cmScTrkTakeTsb_t* _cmTsbMarkerIdToScTrkTake( cmTsb_t* p, unsigned markerUid ) { unsigned i; for(i=0; p->scTrkTakeN; ++i) if( p->scTrkTakeV[i].tlMarkerUid == markerUid ) return p->scTrkTakeV + i; return NULL; } cmScTrkMidiTsb_t* _cmTsbMuidToScTrkMidi( cmScTrkTakeTsb_t* t, unsigned muid ) { unsigned i; for(i=0; imidiN; ++i) if( t->midiV[i].muid == muid ) return t->midiV + i; return NULL; } cmTsbRC_t _cmTsbLoadScoreTrkFile( cmTsb_t* p, const cmChar_t* scoreTrkFn ) { cmTsbRC_t rc = kOkTsbRC; cmJsonNode_t* tkArrObj = NULL; cmJsRC_t jsRC = kOkJsRC; const cmChar_t* errMsg = NULL; unsigned i; // initialize the TSB json object if(( rc = cmJsonInitializeFromFile(&p->jsH,scoreTrkFn,&p->ctx)) != kOkJsRC ) { rc = cmErrMsg(&p->err,kJsonFailTsbRC,"The Take Sequence Builder JSON file object could not be initialized from '%s'.",cmStringNullGuard(scoreTrkFn)); goto errLabel; } // parse the header if((jsRC = cmJsonMemberValues( cmJsonRoot(p->jsH), &errMsg, "timeLineFn", kStringTId, &p->tlFn, "scoreFn", kStringTId, &p->scFn, "tlPrefixPath", kStringTId, &p->tlPrefixPath, "takeArray", kArrayTId | kOptArgJsFl, &tkArrObj, NULL )) != kOkJsRC ) { if( jsRC == kNodeNotFoundJsRC && errMsg != NULL ) rc = cmErrMsg(&p->err,kParseFailTsbRC,"JSON file header parse failed missing required field:'%s'",errMsg); else rc = cmErrMsg(&p->err,kParseFailTsbRC,"JSON file header parse failed."); goto errLabel; } // count of take records p->scTrkTakeN = cmJsonChildCount(tkArrObj); // array of take records p->scTrkTakeV = cmMemAllocZ(cmScTrkTakeTsb_t,p->scTrkTakeN); // for each take record for(i=0; iscTrkTakeN; ++i) { cmJsonNode_t* takeObj = NULL; cmJsonNode_t* noteArrObj = NULL; cmScTrkTakeTsb_t* t = p->scTrkTakeV + i; unsigned j; // get a pointer to the take record JSON object if((takeObj = cmJsonArrayElement(tkArrObj,i)) == NULL ) { rc = cmErrMsg(&p->err,kParseFailTsbRC,"Take record header at index %i access failed.",i); goto errLabel; } // parse the take record if((jsRC = cmJsonMemberValues( takeObj, &errMsg, "markerUid",kIntTId, &t->tlMarkerUid, "failFl", kIntTId, &t->failFl, "array", kArrayTId, ¬eArrObj, NULL)) != kOkJsRC ) { if( jsRC == kNodeNotFoundJsRC && errMsg != NULL ) rc = cmErrMsg(&p->err,kParseFailTsbRC,"JSON file take record parse failed missing required field:'%s'",errMsg); else rc = cmErrMsg(&p->err,kParseFailTsbRC,"JSON file take record parse failed."); goto errLabel; } // get the count of note records t->midiN = cmJsonChildCount(noteArrObj); // allocate a note record array for this take t->midiV = cmMemAllocZ(cmScTrkMidiTsb_t, t->midiN); t->minMuid = INT_MAX; t->maxMuid = 0; t->minScEvtIdx = INT_MAX; t->maxScEvtIdx = 0; // for each note record for(j=0; jmidiN; ++j) { cmJsonNode_t* noteObj = NULL; // get the note record JSON object if((noteObj = cmJsonArrayElement(noteArrObj,j)) == NULL ) { rc = cmErrMsg(&p->err,kParseFailTsbRC,"Access failed for note record at index %i at take index %i.",j,i); goto errLabel; } // parse the note record if((jsRC = cmJsonMemberValues( noteObj, &errMsg, "mni", kIntTId, &t->midiV[j].mni, "muid", kIntTId, &t->midiV[j].muid, "scEvtIdx", kIntTId, &t->midiV[j].scEvtIdx, "flags", kIntTId, &t->midiV[j].flags, NULL)) != kOkJsRC ) { if( jsRC == kNodeNotFoundJsRC && errMsg != NULL ) rc = cmErrMsg(&p->err,kParseFailTsbRC,"JSON file note record parse failed missing required field:'%s'",errMsg); else rc = cmErrMsg(&p->err,kParseFailTsbRC,"JSON file note record parse failed."); goto errLabel; } if( t->midiV[j].muid < t->minMuid ) t->minMuid = t->midiV[j].muid; if( t->midiV[j].muid > t->maxMuid ) t->maxMuid = t->midiV[j].muid; unsigned scEvtIdx = t->midiV[j].scEvtIdx; if( scEvtIdx!=0 && scEvtIdx!=cmInvalidIdx && scEvtIdx < t->minScEvtIdx ) t->minScEvtIdx = scEvtIdx; if( scEvtIdx!=0 && scEvtIdx!=cmInvalidIdx && scEvtIdx > t->maxScEvtIdx ) t->maxScEvtIdx = t->midiV[j].scEvtIdx; } } errLabel: if( rc != kOkTsbRC ) rc = _cmTsbScoreTrkFree(p); return rc; } cmTsbRC_t _cmTakeSeqBldrRender( cmTsb_t* p ) { cmTsbRC_t rc = kOkTsbRC; unsigned takeCnt = 0; unsigned midiCnt = 0; // delete the previous output rendering _cmTsbRendTakeFree(p,&p->out); // allocate a take order list cmOrderTsb_t* o = _cmTakeSeqBldrAllocOrder( p ); // sort the list by minScEvtIdx o = _cmTakeSeqBldrSortOrder(o); // assign beg/endScEvtIdx values to each take _cmTakeSeqBldrSetBegEndOrder(o); //_cmTakeSeqBldrPrintOrder(o); // if the render take has not yet been allocated p->out = cmMemAllocZ(cmTakeTsb_t,1); p->out->tlMarkerUid = cmInvalidId; p->nextRid = 0; cmMidiTsb_t* m0 = NULL; cmOrderTsb_t* t = o; for(; t!=NULL; t=t->link) { // skip takes whose begScEvtIdx is not valid if( t->begScEvtIdx == cmInvalidIdx ) continue; takeCnt += 1; //_cmTsbPrintMidi(t->t); // advance to the MIDI event assoc'd with t->begScEvtIdx cmMidiTsb_t* m = t->t->midi; for(; m!=NULL; m=m->link) if( m->scEvtIdx >= t->begScEvtIdx ) break; // copy the MIDI events from the take into the render list for(; m!=NULL; m=m->link) { midiCnt += 1; // allocate a MIDI record to hold this render event cmMidiTsb_t* m1 = cmMemAllocZ(cmMidiTsb_t,1); // copy in the MIDI record *m1 = *m; //memcpy(m1,m,sizeof(cmMidiTsb_t)); m1->link= NULL; // NULLify the copied link m1->ref = m0; // set prev. link m1->rid = p->nextRid++; // set the unique id // if this is not the first MIDI event ... if( m0 != NULL ) m0->link = m1; // then set next link on the previous record else // { // otherwise p->out->midi = m1; // 1) set the render take event list m1->offsetSmp = 0; // 2) the first event always starts at time zero. p->evt = m1; // 3) set the first event to play p->absPlaySmp = 0; // 4) set the current play clock to 0 p->absEvtSmp = 0; // 5) set the current next play event time to 0 } m0 = m1; // if this is the last event in this take if( m0->scEvtIdx == t->endScEvtIdx ) break; } } // free the take order list _cmTakeSeqBldrFreeOrder(o); printf("rendered takes:%i events:%i\n",takeCnt,midiCnt); //_cmTsbPrintMidi(p->out); return rc; } cmTsbRC_t cmTakeSeqBldrAlloc( cmCtx_t* ctx, cmTakeSeqBldrH_t* hp ) { cmTsbRC_t rc; if((rc = cmTakeSeqBldrFree(hp)) != kOkTsbRC ) return kOkTsbRC; cmTsb_t* p = cmMemAllocZ(cmTsb_t,1); cmErrSetup(&p->err,&ctx->rpt,"TakeSeqBldr"); p->ctx = *ctx; hp->h = p; return rc; } cmTsbRC_t cmTakeSeqBldrAllocFn( cmCtx_t* ctx, cmTakeSeqBldrH_t* hp, const cmChar_t* scoreTrkFn ) { cmTsbRC_t rc; if((rc = cmTakeSeqBldrAlloc(ctx,hp)) != kOkTsbRC ) return rc; if((rc = cmTakeSeqBldrInitialize(*hp,scoreTrkFn)) != kOkTsbRC ) return rc; return rc; } cmTsbRC_t cmTakeSeqBldrFree( cmTakeSeqBldrH_t* hp ) { cmRC_t rc = kOkTsbRC; if( hp == NULL || cmTakeSeqBldrIsValid(*hp)==false ) return kOkTsbRC; cmTsb_t* p = _cmTsbHandleToPtr(*hp); if((rc = _cmTsbFree(p)) != kOkTsbRC ) return rc; hp->h = NULL; return rc; } bool cmTakeSeqBldrIsValid( cmTakeSeqBldrH_t h ) { return h.h != NULL; } cmTsbRC_t cmTakeSeqBldrInitialize( cmTakeSeqBldrH_t h, const cmChar_t* scoreTrkFn ) { cmTsbRC_t rc = kOkTsbRC; cmTsb_t* p = _cmTsbHandleToPtr(h); if(( rc = _cmTsbLoadScoreTrkFile( p, scoreTrkFn )) != kOkTsbRC ) return rc; if( cmTimeLineInitializeFromFile(&p->ctx, &p->tlH, NULL, NULL, p->tlFn, p->tlPrefixPath ) != kOkTlRC ) { rc = cmErrMsg(&p->err,kTimeLineFailTsbRC,"The time-line file '%s' could not be loaded.",p->tlFn); goto errLabel; } if( cmScoreInitialize(&p->ctx, &p->scH, p->scFn, 0, NULL, 0, NULL, NULL, cmSymTblNullHandle ) != kOkScRC ) { rc = cmErrMsg(&p->err,kScoreFailTsbRC,"The score file '%s' could not be loaded.",p->scFn); goto errLabel; } errLabel: if( rc != kOkTsbRC ) _cmTsbScoreTrkFree(p); return rc; } cmTakeTsb_t* _cmTsbMarkerUidToTake( cmTsb_t* p, unsigned tlMarkerUid ) { cmTakeTsb_t* t = p->takes; for(; t != NULL; t=t->link) if( t->tlMarkerUid == tlMarkerUid ) return t; return NULL; } cmTsbRC_t cmTakeSeqBldrLoadTake( cmTakeSeqBldrH_t h, unsigned tlMarkUid, bool overwriteFL ) { cmTsbRC_t rc = kOkTsbRC; cmTsb_t* p = _cmTsbHandleToPtr(h); cmTlMarker_t* mark = NULL; cmTlMidiFile_t* mf = NULL; cmMidiFileH_t mfH = cmMidiFileNullHandle; cmScTrkTakeTsb_t* stt = NULL; // verify that the requested take has not already been loaded if( _cmTsbMarkerUidToTake( p, tlMarkUid ) != NULL ) { rc = cmErrMsg(&p->err,kInvalidArgTsbRC,"The take indicated by marker id %i has already been loaded.",tlMarkUid ); goto errLabel; } // find the score tracked take for the requested marker if((stt = _cmTsbMarkerIdToScTrkTake(p,tlMarkUid )) == NULL ) { rc = cmErrMsg(&p->err,kInvalidArgTsbRC,"The score tracked take indicated by marker id %i could not be found.", tlMarkUid ); goto errLabel; } // get a pointer to the time-line marker object if((mark = cmTlMarkerObjPtr( p->tlH, cmTimeLineIdToObj( p->tlH, cmInvalidId, tlMarkUid))) == NULL ) { rc = cmErrMsg(&p->err,kInvalidArgTsbRC,"The time-line marker uid '%i' is not valid.",tlMarkUid); goto errLabel; } // get the name of the MIDI file which contains the marker if((mf = cmTimeLineMidiFileAtTime( p->tlH, mark->obj.seqId, mark->obj.seqSmpIdx )) == NULL ) { rc = cmErrMsg(&p->err,kInvalidArgTsbRC,"The time-line marker '%i' does not intersect with a MIDI file.",tlMarkUid); goto errLabel; } // open the MIDI file if( cmMidiFileOpen( &p->ctx, &mfH, cmMidiFileName(mf->h) ) != kOkMfRC ) { rc = cmErrMsg(&p->err,kInvalidArgTsbRC,"The MIDI file '%s' could not be opened.", cmStringNullGuard(cmMidiFileName(mf->h))); goto errLabel; } // convert the dtick field to delta samples //cmMidiFileTickToSamples( mfH, cmTimeLineSampleRate(p->tlH), false ); // calculate MIDI note and pedal durations (see cmMidiChMsg_t.durTicks) cmMidiFileCalcNoteDurations( mfH ); unsigned i = 0; unsigned n = cmMidiFileMsgCount(mfH); const cmMidiTrackMsg_t** a = cmMidiFileMsgArray(mfH); double srate = cmTimeLineSampleRate(p->tlH); // allocate and link a new take render record cmTakeTsb_t* t = cmMemAllocZ(cmTakeTsb_t,1); t->tlMarkerUid = tlMarkUid; t->stt = stt; t->link = p->takes; p->takes = t; unsigned rid = 0; cmMidiTsb_t* m0 = NULL; const cmMidiTrackMsg_t* mf0 = NULL; // for each MIDI message in the file for(i=0; istatus ) { case kNoteOnMdId: case kNoteOffMdId: case kCtlMdId: break; default: continue; } // if this MIDI message is inside the tracked region of the take if( stt->minMuid > mf1->uid || mf1->uid > stt->maxMuid ) continue; // get a pointer to the tracking map for the given MIDI file event. // (Note that since control messages are not tracked so this function may return NULL.) cmScTrkMidiTsb_t* stm = _cmTsbMuidToScTrkMidi(stt, mf1->uid ); // create a MIDI render event cmMidiTsb_t* m1 = cmMemAllocZ(cmMidiTsb_t,1); m1->rid = rid++; m1->srcId = tlMarkUid; m1->scEvtIdx = stm != NULL ? stm->scEvtIdx : cmInvalidIdx; m1->flags = stm != NULL ? stm->flags : 0; m1->ref = m0; m1->offsetSmp = mf0 == NULL ? 0 : round(mf1->amicro * srate / 1000000.0); m1->durSmp = mf1->u.chMsgPtr->durMicros * srate / 1000000.0; m1->d0 = mf1->u.chMsgPtr->d0; m1->d1 = mf1->u.chMsgPtr->d1; m1->status = mf1->status; m1->link = NULL; //printf("0x%x %f %f\n",m1->status,m1->offsetSmp/96000.0,m1->durSmp/96000.0); if( m0 != NULL ) m0->link = m1; if( t->midi == NULL ) t->midi = m1; m0 = m1; mf0 = mf1; } //unsigned begBpS, endBpS; //_cmTsbCalcTempo(p, t, &begBpS, &endBpS ); // render the new output sequence if((rc = _cmTakeSeqBldrRender(p)) != kOkTsbRC) cmErrMsg(&p->err,rc,"Take sequence builder rendering failed."); errLabel: if( cmMidiFileClose(&mfH) != kOkMfRC ) rc = cmErrMsg(&p->err,kMidiFileFailTsbRC,"MIDI file close failed."); return rc; } cmTsbRC_t cmTakeSeqBldrUnloadTake( cmTakeSeqBldrH_t h, unsigned tlMarkUid ) { cmTsbRC_t rc = kOkTsbRC; cmTsb_t* p = _cmTsbHandleToPtr(h); cmTakeTsb_t* t; if((t = _cmTsbMarkerUidToTake(p, tlMarkUid )) == NULL ) return cmErrMsg(&p->err,kInvalidArgTsbRC,"The take indicated by marker id %i could not be found.",tlMarkUid); t = _cmTsbTakeUnlink(p,t); assert( t != NULL ); _cmTsbTakeFree(p,&t); // render the new output sequence if((rc = _cmTakeSeqBldrRender(p)) != kOkTsbRC) cmErrMsg(&p->err,rc,"Take sequence builder rendering failed."); return rc; } double cmTakeSeqBldrSampleRate( cmTakeSeqBldrH_t h ) { cmTsb_t* p = _cmTsbHandleToPtr(h); if( cmTimeLineIsValid( p->tlH ) ) return cmTimeLineSampleRate(p->tlH ); return 0; } cmScH_t cmTakeSeqBldrScoreHandle( cmTakeSeqBldrH_t h ) { cmTsb_t* p = _cmTsbHandleToPtr(h); return p->scH; } unsigned cmTakeSeqBldrScTrkTakeCount( cmTakeSeqBldrH_t h ) { cmTsb_t* p = _cmTsbHandleToPtr(h); return p->scTrkTakeN; } cmTsbRC_t cmTakeSeqBldrScTrkTake( cmTakeSeqBldrH_t h, unsigned idx, cmTksbScTrkTake_t* ref ) { cmTsb_t* p = _cmTsbHandleToPtr(h); assert( idx < p->scTrkTakeN ); ref->minScEvtIdx = p->scTrkTakeV[ idx ].minScEvtIdx; ref->maxScEvtIdx = p->scTrkTakeV[ idx ].maxScEvtIdx; ref->tlMarkerUid = p->scTrkTakeV[idx].tlMarkerUid; return kOkTsbRC; } const cmChar_t* cmTakeSeqBldrScTrkTakeText( cmTakeSeqBldrH_t h, unsigned tlMarkerUid ) { cmTsb_t* p = _cmTsbHandleToPtr(h); cmTlObj_t* tlObj = NULL; cmTlMarker_t* tlMark = NULL; if((tlObj = cmTimeLineIdToObj( p->tlH, cmInvalidId, tlMarkerUid )) == NULL ) { cmErrMsg(&p->err,kTimeLineFailTsbRC,"Unable to locate the time line record for score-track-take uid: %i.",tlMarkerUid); return NULL; } if((tlMark = cmTimeLineMarkerObjPtr( p->tlH, tlObj)) == NULL ) { cmErrMsg(&p->err,kTimeLineFailTsbRC,"Unable to cast a time line marker object for score-track-take %i.",tlMarkerUid); return NULL; } return tlMark->text; } cmTsbRC_t cmTakeSeqBldrPlaySeekLoc( cmTakeSeqBldrH_t h, unsigned scLocIdx ) { cmTsb_t* p = _cmTsbHandleToPtr(h); if( p->out == NULL ) return cmErrMsg(&p->err,kRenderSeqEmptyTsbRC,"Seek faied. The render sequence is empty."); if( scLocIdx == cmInvalidIdx ) p->evt = p->out->midi; else { cmScoreLoc_t* loc; if(( loc = cmScoreLoc(p->scH, scLocIdx )) == NULL ) return cmErrMsg(&p->err,kInvalidArgTsbRC,"Seek failed. The requested score location (%i) could not be found.",scLocIdx); if( loc->evtCnt == 0 || loc->evtArray[0]==NULL ) return cmErrMsg(&p->err,kInvalidArgTsbRC,"Seek failed. The requested score location (%i) has no associated event.",scLocIdx); unsigned scEvtIdx = loc->evtArray[0]->index; cmMidiTsb_t* m = p->out->midi; for(; m!=NULL; m=m->link) if( m->scEvtIdx >= scEvtIdx ) { p->evt = m; break; } if( m == NULL ) return cmErrMsg(&p->err,kInvalidArgTsbRC,"Seek failed. The requested score event index (%i) is out of range.",scEvtIdx ); } return kOkTsbRC; } cmTsbRC_t cmTakeSeqBldrPlayExec( cmTakeSeqBldrH_t h, unsigned deltaSmp, cmTakeSeqBldrPlayFunc_t cbFunc, void* cbArg ) { cmTsb_t* p = _cmTsbHandleToPtr(h); if( p->evt == NULL ) return kOkTsbRC; // advance the play clock p->absPlaySmp += deltaSmp; // if the next event time has elapsed while( p->evt != NULL && p->absEvtSmp <= p->absPlaySmp ) { // make the event callback cmTksbEvent_t e; e.smpIdx = p->absEvtSmp; e.status = p->evt->status; e.d0 = p->evt->d0; e.d1 = p->evt->d1; cbFunc(cbArg,&e); do { // advance the current play event p->evt = p->evt->link; // the last event was not encountered and the events offset time is legal if( p->evt != NULL && p->evt->offsetSmp != cmInvalidIdx ) { // advance the event absolute time p->absEvtSmp += p->evt->offsetSmp; break; } }while( p->evt != NULL ); } return kOkTsbRC; } void cmTakeSeqBldrRendReset( cmTakeSeqBldrH_t h ) { cmTsb_t* p = _cmTsbHandleToPtr(h); if( p->out == NULL ) return; p->rend = p->out->midi; } void _cmTakeSeqBldrMidiToRend( cmTksbRend_t* r, const cmMidiTsb_t* m ) { r->rid = m->rid; r->srcId = m->srcId; r->scEvtIdx = m->scEvtIdx; r->flags = m->flags; r->offsetSmp = m->offsetSmp; r->durSmp = m->durSmp; r->evt.smpIdx = 0; r->evt.status = m->status; r->evt.d0 = m->d0; r->evt.d1 = m->d1; } cmMidiTsb_t* _cmTakeSeqBldrRidToMidi( cmTsb_t* p, unsigned rid ) { cmMidiTsb_t* m; if( p->out == NULL ) return NULL; for(m=p->out->midi; m!=NULL; m=m->link) if( m->rid == rid ) return m; return NULL; } bool cmTakeSeqBldrRendNext(cmTakeSeqBldrH_t h, cmTksbRend_t* r) { cmTsb_t* p = _cmTsbHandleToPtr(h); cmMidiTsb_t* m = p->rend; if( m == NULL ) return false; p->rend = p->rend->link; _cmTakeSeqBldrMidiToRend(r,m); return true; } cmTsbRC_t cmTakeSeqBldrRendInfo( cmTakeSeqBldrH_t h, unsigned rid, cmTksbRend_t* r ) { cmTsb_t* p = _cmTsbHandleToPtr(h); cmMidiTsb_t* m; if((m = _cmTakeSeqBldrRidToMidi(p, rid )) == NULL ) return cmErrMsg(&p->err,kInvalidArgTsbRC,"Unable to locate the MIDI render record associated with rid:%i.",rid); _cmTakeSeqBldrMidiToRend(r,m); return kOkTsbRC; } cmTsbRC_t cmTakeSeqBldrRendDelete( cmTakeSeqBldrH_t h, unsigned rid ) { cmTsb_t* p = _cmTsbHandleToPtr(h); if( p->out == NULL || p->out->midi == NULL ) return kOkTsbRC; cmMidiTsb_t* m1 = p->out->midi; for(; m1!=NULL; m1=m1->link) if( m1->rid == rid ) { // if there is no recd before m1 if( m1->ref == NULL ) { p->out->midi = m1->link; // if there is a record after m1 if( p->out->midi != NULL ) { p->out->midi->ref = NULL; p->out->midi->offsetSmp = 0; // the first record never has a time offset } } else // there is a record before m1 { m1->ref->link = m1->link; // if there is a record after m1 if( m1->link != NULL ) { m1->link->ref = m1->ref; m1->link->offsetSmp += m1->offsetSmp; // absorb m1's offset time into the next revent } } cmMemFree(m1); break; } _cmTsbTakePrint(p->out); return kOkTsbRC; } cmTsbRC_t cmTakeSeqBldrRendInsert( cmTakeSeqBldrH_t h, const cmTksbEvent_t* e, unsigned durSmp, unsigned* ridRef) { cmTsb_t* p = _cmTsbHandleToPtr(h); if( ridRef != NULL ) *ridRef = cmInvalidId; cmMidiTsb_t* nm = cmMemAllocZ(cmMidiTsb_t,1); nm->rid = p->nextRid++; nm->srcId = cmInvalidId; nm->scEvtIdx = cmInvalidId; nm->flags = 0; nm->durSmp = durSmp; nm->status = e->status; nm->d0 = e->d0; nm->d1 = e->d1; if( p->out == NULL ) { p->out = cmMemAllocZ(cmTakeTsb_t,1); p->out->tlMarkerUid = cmInvalidId; } if( p->out->midi == NULL ) { p->out->midi = nm; goto doneLabel; } cmMidiTsb_t* m0 = NULL; cmMidiTsb_t* m1 = p->out->midi; unsigned absSmpIdx = m1==NULL ? 0 : m1->offsetSmp; for(; m1!=NULL; m1=m1->link) { // absSmpIdx is the absolute time of m1 // if m1 is the recd just after the new record if( absSmpIdx > e->smpIdx ) { // the record prior to the new record is m0 nm->ref = m0; // the reocrd after the new record is m1 nm->link = m1; // the new record is before m1 m1->ref = nm; // if the new record is first on the list if( m0 == NULL ) { p->out->midi = nm; // TODO: without giving more information there is no way // to give the old first event an offset relative to the new // first event - so the both events will be scheduled at // time zero. nm->offsetSmp = 0; } else // the new record is between m0 and m1 { m0->link = nm; // offset from new record to m1 unsigned dsi = absSmpIdx - e->smpIdx; // m1's time offset is being reduced assert( m1->offsetSmp >= dsi ); // the offset to the new record from m0 nm->offsetSmp = m1->offsetSmp - dsi; m1->offsetSmp = dsi; } break; } // if m1 is not the last element on the list if( m1->link != NULL ) absSmpIdx += m1->link->offsetSmp; else { // insert the new event at the end of the list nm->ref = m1; nm->link = NULL; m1->link = nm; assert( e->smpIdx > absSmpIdx ); nm->offsetSmp = e->smpIdx - absSmpIdx; break; } m0 = m1; } doneLabel: if( ridRef != NULL ) *ridRef = nm->rid; //_cmTsbTakePrint(p->out); return kOkTsbRC; } cmTsbRC_t cmTakeSeqBldrWrite( cmTakeSeqBldrH_t h, const cmChar_t* fn ) { cmTsbRC_t rc = kOkTsbRC; cmJsonH_t jsH = cmJsonNullHandle; cmJsonNode_t* arr; cmMidiTsb_t* m; cmTsb_t* p = _cmTsbHandleToPtr(h); if( p->out == NULL ) return rc; // allocate a JSON tree if( cmJsonInitialize(&jsH,&p->ctx) != kOkJsRC ) { rc = cmErrMsg(&p->err,kJsonFailTsbRC,"JSON write tree allocate failed."); goto errLabel; } // insert the root object if( cmJsonCreateObject(jsH, NULL ) != kOkJsRC ) { rc = cmErrMsg(&p->err,kJsonFailTsbRC,"JSON root object allocate failed."); goto errLabel; } // create the header record if( cmJsonInsertPairs(jsH, cmJsonRoot(jsH), "tlMarkerUid", kIntTId, p->out->tlMarkerUid, "tlFileName", kStringTId, cmTimeLineFileName(p->tlH), NULL) != kOkJsRC ) { rc = cmErrMsg(&p->err,kJsonFailTsbRC,"JSON header record create failed."); goto errLabel; } // create the MIDI event array if((arr = cmJsonInsertPairArray(jsH, cmJsonRoot(jsH), "midi")) == NULL ) { rc = cmErrMsg(&p->err,kJsonFailTsbRC,"Create the MIDI event array."); goto errLabel; } // fill the MIDI array for(m=p->out->midi; m!=NULL; m=m->link) { if( cmJsonCreateFilledObject(jsH, arr, "rid", kIntTId, m->rid, "srcId", kIntTId, m->srcId, "scEvtIdx", kIntTId, m->scEvtIdx, "flags", kIntTId, m->flags, "offsetSmp", kIntTId, m->offsetSmp, "durSmp", kIntTId, m->durSmp, "status", kIntTId, m->status, "d0", kIntTId, m->d0, "d1", kIntTId, m->d1, NULL) == NULL ) { rc = cmErrMsg(&p->err,kJsonFailTsbRC,"JSON MIDI record create failed."); goto errLabel; } } // write the tree if( cmJsonWrite( jsH, cmJsonRoot(jsH), fn ) != kOkJsRC ) { rc = cmErrMsg(&p->err,kJsonFailTsbRC,"JSON write to '%s' failed.",cmStringNullGuard(fn)); } errLabel: if( cmJsonFinalize(&jsH) != kOkJsRC ) rc = cmErrMsg(&p->err,kJsonFailTsbRC,"JSON tree finalize failed."); return rc; } cmTsbRC_t cmTakeSeqBldrRead( cmTakeSeqBldrH_t h, const cmChar_t* fn ) { cmTsbRC_t rc = kOkTsbRC; cmTsb_t* p = _cmTsbHandleToPtr(h); cmJsonH_t jsH = cmJsonNullHandle; const cmChar_t* jsErrLabel = NULL; cmTakeTsb_t* t = NULL; cmMidiTsb_t* m0 = NULL; cmJsonNode_t* arr = NULL; cmJsRC_t jsRC; if( cmJsonInitializeFromFile( &jsH, fn, &p->ctx ) != kOkJsRC ) { rc = cmErrMsg(&p->err,kJsonFailTsbRC,"Unable to parse the JSON file '%s'.",cmStringNullGuard(fn)); goto errLabel; } t = cmMemAllocZ(cmTakeTsb_t,1); if((jsRC = cmJsonMemberValues( cmJsonRoot(jsH), &jsErrLabel, "tlMarkerUid", kIntTId, &t->tlMarkerUid, "midi", kArrayTId, &arr, NULL)) != kOkJsRC ) { if( jsRC == kNodeNotFoundJsRC && jsErrLabel != NULL ) rc = cmErrMsg(&p->err,kParseFailTsbRC,"JSON file MIDI render header failed missing required field:'%s'",cmStringNullGuard(jsErrLabel)); else rc = cmErrMsg(&p->err,kParseFailTsbRC,"JSON file MIDI render header parse failed."); goto errLabel; } unsigned n = cmJsonChildCount(arr); unsigned i; for(i=0; irid, "srcId", kIntTId, &m->srcId, "scEvtIdx", kIntTId, &m->scEvtIdx, "flags", kIntTId, &m->flags, "offsetSmp", kIntTId, &m->offsetSmp, "durSmp", kIntTId, &m->durSmp, "status", kIntTId, &m->status, "d0", kIntTId, &m->d0, "d1", kIntTId, &m->d1, NULL)) != kOkJsRC ) { if( jsRC == kNodeNotFoundJsRC && jsErrLabel != NULL ) rc = cmErrMsg(&p->err,kParseFailTsbRC,"JSON file MIDI render element failed missing required field:'%s' on index:%i",cmStringNullGuard(jsErrLabel),i); else rc = cmErrMsg(&p->err,kParseFailTsbRC,"JSON file MIDI render element parse failed on index: %i.", i); goto errLabel; } m->ref = m0; if( m0 == NULL ) t->midi = m; else m0->link = m; } errLabel: if( cmJsonFinalize(&jsH) != kOkJsRC ) rc = cmErrMsg(&p->err,kJsonFailTsbRC,"JSON finalize failed."); if( rc != kOkTsbRC ) _cmTsbTakeFree(p,&t); else { _cmTsbRendTakeFree(p,&p->out); p->out = t; } return rc; } cmTsbRC_t cmTakeSeqBldrTest( cmCtx_t* ctx ) { const cmChar_t* scoreTrkFn = "/home/kevin/src/cmkc/src/kc/data/takeSeqBldr0.js"; cmTakeSeqBldrH_t tsbH = cmTakeSeqBldrNullHandle; cmTsbRC_t tsbRC = kOkTsbRC; unsigned markerIdV[] = { 2200, 2207 }; unsigned markerN = sizeof(markerIdV)/sizeof(markerIdV[0]); unsigned i; if((tsbRC = cmTakeSeqBldrAllocFn(ctx, &tsbH, scoreTrkFn )) != kOkTsbRC ) return cmErrMsg(&ctx->err,tsbRC,"TSB Allocate and parse '%s' failed.",scoreTrkFn); cmRptPrintf(&ctx->rpt, "TakeSeqBldr Allocation Completed."); for(i=0; ierr,tsbRC,"TSB load take failed."); cmRptPrintf(&ctx->rpt, "TakeSeqBldr Load Take %i Completed.",markerIdV[i]); } if((tsbRC = cmTakeSeqBldrFree(&tsbH)) != kOkTsbRC ) return cmErrMsg(&ctx->err,tsbRC,"TSB Free failed."); return tsbRC; }