1620 righe
42 KiB
C
1620 righe
42 KiB
C
#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 "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; i<rN; ++i)
|
|
printf("%s %i\n",cmScEvtTypeIdToLabel(r[i].sep->type),r[i].absSmp);
|
|
printf("\n");
|
|
|
|
}
|
|
|
|
cmTsbTempo_t* _cmTsbTempoFind( cmTsbTempo_t* r, unsigned rN, unsigned scEvtIdx )
|
|
{
|
|
unsigned i;
|
|
for(i=0; i<rN; ++i)
|
|
if( r[i].sep->index == 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(; ri<rN; ++ri)
|
|
{
|
|
// if this is a note event
|
|
if( r[ri].sep->type == 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; i<rN; ++i)
|
|
{
|
|
r[i].sep = cmScoreEvt(p->scH,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; i<rN; ++i)
|
|
if( r[i].sep->type == kBarEvtScId )
|
|
{
|
|
barCnt += 1; // count the number of bars found
|
|
|
|
// search ahead from the bar to the next note
|
|
unsigned j;
|
|
for(j=i; j<rN; ++j)
|
|
if( r[j].sep->type == 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; i<rN && j<barCnt; ++i)
|
|
{
|
|
tempoV[j++] = _cmTsbTempoCalc(r, rN, i, cmTimeLineSampleRate(p->tlH) );
|
|
|
|
for(; i<rN; ++i)
|
|
if( r[i].sep->type == kBarEvtScId )
|
|
break;
|
|
}
|
|
|
|
if( j > 0 )
|
|
{
|
|
*begBpSRef = tempoV[0];
|
|
*endBpSRef = tempoV[j-1];
|
|
}
|
|
|
|
|
|
printf("Bars:%i ",barCnt);
|
|
for(i=0; i<j; ++i)
|
|
printf("%i ",tempoV[i]);
|
|
printf("\n");
|
|
|
|
return kOkTsbRC;
|
|
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------------------
|
|
void _cmTsbPrintMidi( cmTakeTsb_t* t )
|
|
{
|
|
if( t==NULL )
|
|
return;
|
|
|
|
cmMidiTsb_t* m = t->midi;
|
|
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; i<p->scTrkTakeN; ++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; i<t->midiN; ++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; i<p->scTrkTakeN; ++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; j<t->midiN; ++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; i<n; ++i)
|
|
{
|
|
const cmMidiTrackMsg_t* mf1 = a[i];
|
|
|
|
// we are only interested in rendering notes and control msgs
|
|
switch( mf1->status )
|
|
{
|
|
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; i<n; ++i)
|
|
{
|
|
const cmJsonNode_t* e = cmJsonArrayElementC(arr, i );
|
|
cmMidiTsb_t* m = cmMemAllocZ(cmMidiTsb_t,1);
|
|
|
|
if((jsRC = cmJsonMemberValues( e, &jsErrLabel,
|
|
"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)) != 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; i<markerN; ++i)
|
|
{
|
|
if((tsbRC = cmTakeSeqBldrLoadTake(tsbH,markerIdV[i],false)) != kOkTsbRC )
|
|
cmErrMsg(&ctx->err,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;
|
|
}
|