cmProc4.h/c: Add cmMeas.vsi and callback args to cmScMatcher.
This commit is contained in:
parent
27bae4fca2
commit
56249c2f40
220
cmProc4.c
220
cmProc4.c
@ -1910,7 +1910,7 @@ void _cmScMatchPrintPath( cmScMatch* p, cmScMatchPath_t* cp, unsigned bsi, const
|
|||||||
|
|
||||||
|
|
||||||
//=======================================================================================================================
|
//=======================================================================================================================
|
||||||
cmScMatcher* cmScMatcherAlloc( cmCtx* c, cmScMatcher* p, double srate, cmScH_t scH, unsigned scWndN, unsigned midiWndN )
|
cmScMatcher* cmScMatcherAlloc( cmCtx* c, cmScMatcher* p, double srate, cmScH_t scH, unsigned scWndN, unsigned midiWndN, cmScMatcherCb_t cbFunc, void* cbArg )
|
||||||
{
|
{
|
||||||
cmScMatcher* op = cmObjAlloc(cmScMatcher,c,p);
|
cmScMatcher* op = cmObjAlloc(cmScMatcher,c,p);
|
||||||
|
|
||||||
@ -1919,7 +1919,7 @@ cmScMatcher* cmScMatcherAlloc( cmCtx* c, cmScMatcher* p, double srate, cmScH_t s
|
|||||||
|
|
||||||
if( srate != 0 )
|
if( srate != 0 )
|
||||||
{
|
{
|
||||||
if( cmScMatcherInit(op,srate,scH,scWndN,midiWndN) != cmOkRC )
|
if( cmScMatcherInit(op,srate,scH,scWndN,midiWndN,cbFunc,cbArg) != cmOkRC )
|
||||||
cmScMatcherFree(&op);
|
cmScMatcherFree(&op);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1944,7 +1944,7 @@ cmRC_t cmScMatcherFree( cmScMatcher** pp )
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cmRC_t cmScMatcherInit( cmScMatcher* p, double srate, cmScH_t scH, unsigned scWndN, unsigned midiWndN )
|
cmRC_t cmScMatcherInit( cmScMatcher* p, double srate, cmScH_t scH, unsigned scWndN, unsigned midiWndN, cmScMatcherCb_t cbFunc, void* cbArg )
|
||||||
{
|
{
|
||||||
cmRC_t rc;
|
cmRC_t rc;
|
||||||
if((rc = cmScMatcherFinal(p)) != cmOkRC )
|
if((rc = cmScMatcherFinal(p)) != cmOkRC )
|
||||||
@ -1956,14 +1956,17 @@ cmRC_t cmScMatcherInit( cmScMatcher* p, double srate, cmScH_t scH, unsigned scW
|
|||||||
if(( rc = cmScMatchInit(p->mp,scH,scWndN,midiWndN)) != cmOkRC )
|
if(( rc = cmScMatchInit(p->mp,scH,scWndN,midiWndN)) != cmOkRC )
|
||||||
return rc;
|
return rc;
|
||||||
|
|
||||||
|
p->cbFunc = cbFunc;
|
||||||
|
p->cbArg = cbArg;
|
||||||
p->mn = midiWndN;
|
p->mn = midiWndN;
|
||||||
p->midiBuf = cmMemResize(cmScMatchMidi_t,p->midiBuf,p->mn);
|
p->midiBuf = cmMemResize(cmScMatchMidi_t,p->midiBuf,p->mn);
|
||||||
p->stepCnt = 3;
|
p->stepCnt = 3;
|
||||||
p->maxMissCnt = p->stepCnt+1;
|
p->maxMissCnt = p->stepCnt+1;
|
||||||
p->rn = 2 * cmScoreEvtCount(scH);
|
p->rn = 2 * cmScoreEvtCount(scH);
|
||||||
p->res = cmMemResizeZ(cmScMatcherResult_t,p->res,p->rn);
|
p->res = cmMemResizeZ(cmScMatcherResult_t,p->res,p->rn);
|
||||||
|
p->printFl = false;
|
||||||
|
|
||||||
cmScMatcherReset(p);
|
cmScMatcherReset(p,0);
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
@ -1973,7 +1976,7 @@ cmRC_t cmScMatcherFinal( cmScMatcher* p )
|
|||||||
return cmScMatchFinal(p->mp);
|
return cmScMatchFinal(p->mp);
|
||||||
}
|
}
|
||||||
|
|
||||||
void cmScMatcherReset( cmScMatcher* p )
|
cmRC_t cmScMatcherReset( cmScMatcher* p, unsigned scLocIdx )
|
||||||
{
|
{
|
||||||
p->mbi = p->mp->mmn;
|
p->mbi = p->mp->mmn;
|
||||||
p->mni = 0;
|
p->mni = 0;
|
||||||
@ -1982,6 +1985,16 @@ void cmScMatcherReset( cmScMatcher* p )
|
|||||||
p->missCnt = 0;
|
p->missCnt = 0;
|
||||||
p->scanCnt = 0;
|
p->scanCnt = 0;
|
||||||
p->ri = 0;
|
p->ri = 0;
|
||||||
|
p->eli = cmInvalidIdx;
|
||||||
|
p->ili = 0;
|
||||||
|
|
||||||
|
// convert scLocIdx to an index into p->mp->loc[]
|
||||||
|
unsigned i;
|
||||||
|
for(i=0; i<p->mp->locN; ++i)
|
||||||
|
if( p->mp->loc[i].scLocIdx == scLocIdx )
|
||||||
|
p->ili = i;
|
||||||
|
|
||||||
|
return cmOkRC;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cmScMatcherInputMidi( cmScMatcher* p, unsigned smpIdx, unsigned status, cmMidiByte_t d0, cmMidiByte_t d1 )
|
bool cmScMatcherInputMidi( cmScMatcher* p, unsigned smpIdx, unsigned status, cmMidiByte_t d0, cmMidiByte_t d1 )
|
||||||
@ -2016,6 +2029,7 @@ void _cmScMatcherStoreResult( cmScMatcher* p, unsigned locIdx, unsigned scEvtIdx
|
|||||||
bool fpFl = locIdx==cmInvalidIdx || matchFl==false;
|
bool fpFl = locIdx==cmInvalidIdx || matchFl==false;
|
||||||
cmScMatcherResult_t * rp = NULL;
|
cmScMatcherResult_t * rp = NULL;
|
||||||
unsigned i;
|
unsigned i;
|
||||||
|
cmScMatcherResult_t r;
|
||||||
|
|
||||||
assert( tpFl==false || (tpFl==true && locIdx != cmInvalidIdx ) );
|
assert( tpFl==false || (tpFl==true && locIdx != cmInvalidIdx ) );
|
||||||
|
|
||||||
@ -2037,10 +2051,18 @@ void _cmScMatcherStoreResult( cmScMatcher* p, unsigned locIdx, unsigned scEvtIdx
|
|||||||
}
|
}
|
||||||
|
|
||||||
if( rp == NULL )
|
if( rp == NULL )
|
||||||
|
{
|
||||||
|
if( p->ri >= p->rn )
|
||||||
|
{
|
||||||
|
rp = &r;
|
||||||
|
memset(rp,0,sizeof(r));
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
rp = p->res + p->ri;
|
rp = p->res + p->ri;
|
||||||
++p->ri;
|
++p->ri;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
rp->locIdx = locIdx;
|
rp->locIdx = locIdx;
|
||||||
rp->scEvtIdx = scEvtIdx;
|
rp->scEvtIdx = scEvtIdx;
|
||||||
@ -2060,7 +2082,7 @@ void cmScMatcherPrintPath( cmScMatcher* p )
|
|||||||
_cmScMatchPrintPath(p->mp, p->mp->p_opt, p->begSyncLocIdx, p->midiBuf );
|
_cmScMatchPrintPath(p->mp, p->mp->p_opt, p->begSyncLocIdx, p->midiBuf );
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned cmScMatcherScan( cmScMatcher* p, unsigned bsi, unsigned scanCnt )
|
unsigned cmScMatcherScan( cmScMatcher* p, unsigned bli, unsigned scanCnt )
|
||||||
{
|
{
|
||||||
assert( p->mp != NULL && p->mp->mmn > 0 );
|
assert( p->mp != NULL && p->mp->mmn > 0 );
|
||||||
|
|
||||||
@ -2071,22 +2093,17 @@ unsigned cmScMatcherScan( cmScMatcher* p, unsigned bsi, unsigned scanCnt )
|
|||||||
|
|
||||||
// initialize the internal values set by this function
|
// initialize the internal values set by this function
|
||||||
p->missCnt = 0;
|
p->missCnt = 0;
|
||||||
p->esi = cmInvalidIdx;
|
p->eli = cmInvalidIdx;
|
||||||
p->s_opt = DBL_MAX;
|
p->s_opt = DBL_MAX;
|
||||||
|
|
||||||
// if the MIDI buf is not full
|
// if the MIDI buf is not full
|
||||||
if( p->mbi != 0 )
|
if( p->mbi != 0 )
|
||||||
return cmInvalidIdx;
|
return cmInvalidIdx;
|
||||||
|
|
||||||
// load a temporary MIDI pitch buffer for use by cmScMatch.
|
|
||||||
//unsigned pitchV[p->mp->mmn];
|
|
||||||
//for(i=0; i<p->mp->mmn; ++i)
|
|
||||||
// pitchV[i] = p->midiBuf[i].pitch;
|
|
||||||
|
|
||||||
// calc the edit distance from pitchV[] to a sliding score window
|
// calc the edit distance from pitchV[] to a sliding score window
|
||||||
for(i=0; rc==cmOkRC && (scanCnt==cmInvalidCnt || i<scanCnt); ++i)
|
for(i=0; rc==cmOkRC && (scanCnt==cmInvalidCnt || i<scanCnt); ++i)
|
||||||
{
|
{
|
||||||
rc = cmScMatchExec(p->mp, bsi + i, p->mp->msn, p->midiBuf, p->mp->mmn, s_opt );
|
rc = cmScMatchExec(p->mp, bli + i, p->mp->msn, p->midiBuf, p->mp->mmn, s_opt );
|
||||||
|
|
||||||
switch(rc)
|
switch(rc)
|
||||||
{
|
{
|
||||||
@ -2094,7 +2111,7 @@ unsigned cmScMatcherScan( cmScMatcher* p, unsigned bsi, unsigned scanCnt )
|
|||||||
if( p->mp->opt_cost < s_opt )
|
if( p->mp->opt_cost < s_opt )
|
||||||
{
|
{
|
||||||
s_opt = p->mp->opt_cost;
|
s_opt = p->mp->opt_cost;
|
||||||
i_opt = bsi + i;
|
i_opt = bli + i;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -2104,7 +2121,6 @@ unsigned cmScMatcherScan( cmScMatcher* p, unsigned bsi, unsigned scanCnt )
|
|||||||
default: // error state
|
default: // error state
|
||||||
return cmInvalidIdx;
|
return cmInvalidIdx;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// store the cost assoc'd with i_opt
|
// store the cost assoc'd with i_opt
|
||||||
@ -2115,10 +2131,10 @@ unsigned cmScMatcherScan( cmScMatcher* p, unsigned bsi, unsigned scanCnt )
|
|||||||
|
|
||||||
// set the locIdx field in midiBuf[], trailing miss count and
|
// set the locIdx field in midiBuf[], trailing miss count and
|
||||||
// return the latest positive-match locIdx
|
// return the latest positive-match locIdx
|
||||||
p->esi = cmScMatchDoSync(p->mp,i_opt,p->midiBuf,p->mp->mmn,&p->missCnt);
|
p->eli = cmScMatchDoSync(p->mp,i_opt,p->midiBuf,p->mp->mmn,&p->missCnt);
|
||||||
|
|
||||||
// if no positive matches were found
|
// if no positive matches were found
|
||||||
if( p->esi == cmInvalidIdx )
|
if( p->eli == cmInvalidIdx )
|
||||||
i_opt = cmInvalidIdx;
|
i_opt = cmInvalidIdx;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -2140,18 +2156,19 @@ cmRC_t cmScMatcherStep( cmScMatcher* p )
|
|||||||
unsigned pitch = p->midiBuf[ p->mn-1 ].pitch;
|
unsigned pitch = p->midiBuf[ p->mn-1 ].pitch;
|
||||||
unsigned locIdx = cmInvalidIdx;
|
unsigned locIdx = cmInvalidIdx;
|
||||||
unsigned pidx = cmInvalidIdx;
|
unsigned pidx = cmInvalidIdx;
|
||||||
|
|
||||||
// the tracker must be sync'd to step
|
// the tracker must be sync'd to step
|
||||||
if( p->esi == cmInvalidIdx )
|
if( p->eli == cmInvalidIdx )
|
||||||
return cmCtxRtCondition( &p->obj, cmInvalidArgRC, "The p->esi value must be valid to perform a step operation.");
|
return cmCtxRtCondition( &p->obj, cmInvalidArgRC, "The p->eli value must be valid to perform a step operation.");
|
||||||
|
|
||||||
// if the end of the score has been reached
|
// if the end of the score has been reached
|
||||||
if( p->esi + 1 >= p->mp->locN )
|
if( p->eli + 1 >= p->mp->locN )
|
||||||
return cmEofRC;
|
return cmEofRC;
|
||||||
|
|
||||||
// attempt to match to next location first
|
// attempt to match to next location first
|
||||||
if( (pidx = _cmScMatchIsMatchIndex(p->mp->loc + p->esi + 1, pitch)) != cmInvalidIdx )
|
if( (pidx = _cmScMatchIsMatchIndex(p->mp->loc + p->eli + 1, pitch)) != cmInvalidIdx )
|
||||||
{
|
{
|
||||||
locIdx = p->esi + 1;
|
locIdx = p->eli + 1;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -2159,16 +2176,16 @@ cmRC_t cmScMatcherStep( cmScMatcher* p )
|
|||||||
for(i=2; i<p->stepCnt; ++i)
|
for(i=2; i<p->stepCnt; ++i)
|
||||||
{
|
{
|
||||||
// go forward
|
// go forward
|
||||||
if( p->esi+i < p->mp->locN && (pidx=_cmScMatchIsMatchIndex(p->mp->loc + p->esi + i, pitch))!=cmInvalidIdx )
|
if( p->eli+i < p->mp->locN && (pidx=_cmScMatchIsMatchIndex(p->mp->loc + p->eli + i, pitch))!=cmInvalidIdx )
|
||||||
{
|
{
|
||||||
locIdx = p->esi + i;
|
locIdx = p->eli + i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// go backward
|
// go backward
|
||||||
if( p->esi >= (i-1) && (pidx=_cmScMatchIsMatchIndex(p->mp->loc + p->esi - (i-1), pitch))!=cmInvalidIdx )
|
if( p->eli >= (i-1) && (pidx=_cmScMatchIsMatchIndex(p->mp->loc + p->eli - (i-1), pitch))!=cmInvalidIdx )
|
||||||
{
|
{
|
||||||
locIdx = p->esi - (i-1);
|
locIdx = p->eli - (i-1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2184,7 +2201,7 @@ cmRC_t cmScMatcherStep( cmScMatcher* p )
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
p->missCnt = 0;
|
p->missCnt = 0;
|
||||||
p->esi = locIdx;
|
p->eli = locIdx;
|
||||||
}
|
}
|
||||||
|
|
||||||
// store the result
|
// store the result
|
||||||
@ -2192,32 +2209,36 @@ cmRC_t cmScMatcherStep( cmScMatcher* p )
|
|||||||
|
|
||||||
if( p->missCnt >= p->maxMissCnt )
|
if( p->missCnt >= p->maxMissCnt )
|
||||||
{
|
{
|
||||||
unsigned begScanLocIdx = p->esi > p->mn ? p->esi - p->mn : 0;
|
unsigned begScanLocIdx = p->eli > p->mn ? p->eli - p->mn : 0;
|
||||||
p->s_opt = DBL_MAX;
|
p->s_opt = DBL_MAX;
|
||||||
unsigned bsi = cmScMatcherScan(p,begScanLocIdx,p->mn*2);
|
unsigned bli = cmScMatcherScan(p,begScanLocIdx,p->mn*2);
|
||||||
++p->scanCnt;
|
++p->scanCnt;
|
||||||
|
|
||||||
// if the scan failed find a match
|
// if the scan failed find a match
|
||||||
if( bsi == cmInvalidIdx )
|
if( bli == cmInvalidIdx )
|
||||||
return cmCtxRtCondition( &p->obj, cmSubSysFailRC, "Scan resync. failed.");
|
return cmCtxRtCondition( &p->obj, cmSubSysFailRC, "Scan resync. failed.");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return cmOkRC;
|
return cmOkRC;
|
||||||
}
|
}
|
||||||
|
|
||||||
cmRC_t cmScMatcherExec( cmScMatcher* p, unsigned smpIdx, unsigned status, cmMidiByte_t d0, cmMidiByte_t d1 )
|
cmRC_t cmScMatcherExec( cmScMatcher* p, unsigned smpIdx, unsigned status, cmMidiByte_t d0, cmMidiByte_t d1, unsigned* scLocIdxPtr )
|
||||||
{
|
{
|
||||||
bool fl = p->mbi > 0;
|
bool fl = p->mbi > 0;
|
||||||
cmRC_t rc = cmOkRC;
|
cmRC_t rc = cmOkRC;
|
||||||
|
unsigned eli = p->eli;
|
||||||
|
|
||||||
|
if( scLocIdxPtr != NULL )
|
||||||
|
*scLocIdxPtr = cmInvalidIdx;
|
||||||
|
|
||||||
// update the MIDI buffer with the incoming note
|
// update the MIDI buffer with the incoming note
|
||||||
cmScMatcherInputMidi(p,smpIdx,status,d0,d1);
|
if( cmScMatcherInputMidi(p,smpIdx,status,d0,d1) == false )
|
||||||
|
return rc;
|
||||||
|
|
||||||
// if the MIDI buffer transitioned to full then perform an initial scan sync.
|
// if the MIDI buffer transitioned to full then perform an initial scan sync.
|
||||||
if( fl && p->mbi == 0 )
|
if( fl && p->mbi == 0 )
|
||||||
{
|
{
|
||||||
if( (p->begSyncLocIdx = cmScMatcherScan(p,0,cmInvalidCnt)) == cmInvalidIdx )
|
if( (p->begSyncLocIdx = cmScMatcherScan(p,p->ili,cmInvalidCnt)) == cmInvalidIdx )
|
||||||
rc = cmInvalidArgRC; // signal init. scan sync. fail
|
rc = cmInvalidArgRC; // signal init. scan sync. fail
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -2231,8 +2252,10 @@ cmRC_t cmScMatcherExec( cmScMatcher* p, unsigned smpIdx, unsigned status, c
|
|||||||
rc = cmScMatcherStep(p);
|
rc = cmScMatcherStep(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
return rc;
|
if( scLocIdxPtr!=NULL && p->eli != eli )
|
||||||
|
*scLocIdxPtr = p->mp->loc[p->eli].scLocIdx;
|
||||||
|
|
||||||
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
double cmScMatcherFMeas( cmScMatcher* p )
|
double cmScMatcherFMeas( cmScMatcher* p )
|
||||||
@ -2603,6 +2626,8 @@ cmRC_t cmScMeasInit( cmScMeas* p, cmScH_t scH, double srate, const unsigned*
|
|||||||
|
|
||||||
//_cmScMeasPrint(p);
|
//_cmScMeasPrint(p);
|
||||||
|
|
||||||
|
cmScMeasReset(p);
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2614,7 +2639,12 @@ cmRC_t cmScMeasReset( cmScMeas* p )
|
|||||||
cmRC_t rc = cmOkRC;
|
cmRC_t rc = cmOkRC;
|
||||||
p->mii = 0;
|
p->mii = 0;
|
||||||
p->nsi = cmInvalidIdx;
|
p->nsi = cmInvalidIdx;
|
||||||
|
p->vsi = cmInvalidIdx;
|
||||||
p->nsli = cmInvalidIdx;
|
p->nsli = cmInvalidIdx;
|
||||||
|
|
||||||
|
unsigned i;
|
||||||
|
for(i=0; i<p->sn; ++i)
|
||||||
|
p->set[i].value = DBL_MAX;
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2761,6 +2791,7 @@ unsigned _cmScMeasTimeAlign( cmScMeas* p, cmScMeasSet_t* sp, cmScMatchMidi_t* m,
|
|||||||
|
|
||||||
assert(bn<=cn);
|
assert(bn<=cn);
|
||||||
|
|
||||||
|
// TODO: this copy should be eliminated
|
||||||
// copy to output
|
// copy to output
|
||||||
for(i=0; i<bn && i<cn; ++i)
|
for(i=0; i<bn && i<cn; ++i)
|
||||||
c[i] = b[i];
|
c[i] = b[i];
|
||||||
@ -2900,9 +2931,47 @@ double _cmScMeasDyn( cmScMeas* p, cmScMeasSet_t* sp, cmScMatchMidi_t* m, unsigne
|
|||||||
return rp->value;
|
return rp->value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
unsigned MEAS_MATCH_CNT = 0;
|
unsigned MEAS_MATCH_CNT = 0;
|
||||||
|
|
||||||
void _cmScMeasMatch( cmScMeas* p, cmScMeasSet_t* sp, int n_mii )
|
void _cmScMeasPrintResult( cmScMeas* p, cmScMeasSet_t* sp, _cmScMeasResult_t* rp, unsigned bli, const cmScMatchMidi_t* mb )
|
||||||
|
{
|
||||||
|
const char* label = "<none>";
|
||||||
|
|
||||||
|
switch( sp->sp->varId )
|
||||||
|
{
|
||||||
|
case kEvenVarScId:
|
||||||
|
label = "even";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case kDynVarScId:
|
||||||
|
label = "dyn";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case kTempoVarScId:
|
||||||
|
label = "tempo";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cmChar_t* msg = "";
|
||||||
|
if( rp->value == DBL_MAX )
|
||||||
|
{
|
||||||
|
msg = "Measure FAILED.";
|
||||||
|
sp->value = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("%i set:%i %s bsli:%i esli:%i [set:%i match:%i] cost:%f val:%f %s",MEAS_MATCH_CNT, p->nsi, label, sp->bsli, sp->esli, rp->setN, rp->matchN, p->mp->opt_cost, sp->value, msg);
|
||||||
|
|
||||||
|
if( rp->tempo != 0 )
|
||||||
|
printf(" tempo:%f ",rp->tempo);
|
||||||
|
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
_cmScMatchPrintPath(p->mp, p->mp->p_opt, bli, mb );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void _cmScMeasCalcVal( cmScMeas* p, cmScMeasSet_t* sp, int n_mii )
|
||||||
{
|
{
|
||||||
unsigned mn = 0;
|
unsigned mn = 0;
|
||||||
int i;
|
int i;
|
||||||
@ -2930,7 +2999,6 @@ void _cmScMeasMatch( cmScMeas* p, cmScMeasSet_t* sp, int n_mii )
|
|||||||
|
|
||||||
assert(mn>0);
|
assert(mn>0);
|
||||||
|
|
||||||
|
|
||||||
// Create a copy of the the MIDI buffer to prevent the
|
// Create a copy of the the MIDI buffer to prevent the
|
||||||
// p->midiBuf[].locIdx from being overwritten by cmScMatchDoSync().
|
// p->midiBuf[].locIdx from being overwritten by cmScMatchDoSync().
|
||||||
cmScMatchMidi_t mb[ mn ];
|
cmScMatchMidi_t mb[ mn ];
|
||||||
@ -2949,6 +3017,8 @@ void _cmScMeasMatch( cmScMeas* p, cmScMeasSet_t* sp, int n_mii )
|
|||||||
unsigned bli = p->midiBuf[i].locIdx;
|
unsigned bli = p->midiBuf[i].locIdx;
|
||||||
unsigned ln = p->midiBuf[i+mn-1].locIdx - bli + 1;
|
unsigned ln = p->midiBuf[i+mn-1].locIdx - bli + 1;
|
||||||
double min_cost = DBL_MAX;
|
double min_cost = DBL_MAX;
|
||||||
|
_cmScMeasResult_t r;
|
||||||
|
memset(&r,0,sizeof(r));
|
||||||
|
|
||||||
// match MIDI to score
|
// match MIDI to score
|
||||||
if( cmScMatchExec(p->mp, bli, ln, mb, mn, min_cost ) != cmOkRC )
|
if( cmScMatchExec(p->mp, bli, ln, mb, mn, min_cost ) != cmOkRC )
|
||||||
@ -2958,65 +3028,32 @@ void _cmScMeasMatch( cmScMeas* p, cmScMeasSet_t* sp, int n_mii )
|
|||||||
if( cmScMatchDoSync(p->mp, bli, mb, mn, NULL ) == cmInvalidIdx )
|
if( cmScMatchDoSync(p->mp, bli, mb, mn, NULL ) == cmInvalidIdx )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// print the match result
|
|
||||||
const char* label = "<none>";
|
|
||||||
double val = DBL_MAX;
|
|
||||||
double tempoBpm = 0;
|
|
||||||
_cmScMeasResult_t r;
|
|
||||||
memset(&r,0,sizeof(r));
|
|
||||||
|
|
||||||
if( MEAS_MATCH_CNT == 12 )
|
|
||||||
{
|
|
||||||
//printf("blah\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
switch( sp->sp->varId )
|
switch( sp->sp->varId )
|
||||||
{
|
{
|
||||||
case kEvenVarScId:
|
case kEvenVarScId:
|
||||||
label = "even";
|
sp->value = _cmScMeasEven(p, sp, mb, mn, &r );
|
||||||
val = _cmScMeasEven(p, sp, mb, mn, &r );
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case kDynVarScId:
|
case kDynVarScId:
|
||||||
label = "dyn";
|
sp->value = _cmScMeasDyn(p, sp, mb, mn, &r );
|
||||||
val = _cmScMeasDyn(p, sp, mb, mn, &r );
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case kTempoVarScId:
|
case kTempoVarScId:
|
||||||
label = "tempo";
|
sp->value = _cmScMeasTempo(p, sp, mb, mn, &r );
|
||||||
tempoBpm = _cmScMeasTempo(p, sp, mb, mn, &r );
|
|
||||||
val = tempoBpm;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
{ assert(0); }
|
||||||
}
|
}
|
||||||
|
|
||||||
if(1)
|
sp->tempo = r.tempo;
|
||||||
{
|
|
||||||
const cmChar_t* msg = "";
|
|
||||||
if( val == DBL_MAX )
|
|
||||||
{
|
|
||||||
msg = "Measure FAILED.";
|
|
||||||
val = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("%i set:%i %s bsli:%i esli:%i [set:%i match:%i] cost:%f val:%f %s",MEAS_MATCH_CNT,p->nsi, label, sp->bsli, sp->esli, r.setN, r.matchN, p->mp->opt_cost, val, msg);
|
// print the result
|
||||||
if( r.tempo != 0 )
|
_cmScMeasPrintResult(p, sp, &r, bli, mb );
|
||||||
printf(" tempo:%f ",r.tempo);
|
|
||||||
printf("\n");
|
|
||||||
_cmScMatchPrintPath(p->mp, p->mp->p_opt, bli, mb );
|
|
||||||
}
|
|
||||||
|
|
||||||
MEAS_MATCH_CNT++;
|
MEAS_MATCH_CNT++;
|
||||||
|
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
The first note in Bar 1 F4 should be marked with a 'e' - it isn't.
|
|
||||||
|
|
||||||
Marker Set scli Type Note
|
|
||||||
4 8 48 even very bad match produces an evenesss = 0.979
|
|
||||||
10 12 69 even bad match
|
|
||||||
27 29 150 tempo Tempo measured as 0.
|
|
||||||
|
|
||||||
*/
|
|
||||||
cmRC_t cmScMeasExec( cmScMeas* p, unsigned mni, unsigned locIdx, unsigned scEvtIdx, unsigned flags, unsigned smpIdx, unsigned pitch, unsigned vel )
|
cmRC_t cmScMeasExec( cmScMeas* p, unsigned mni, unsigned locIdx, unsigned scEvtIdx, unsigned flags, unsigned smpIdx, unsigned pitch, unsigned vel )
|
||||||
{
|
{
|
||||||
cmRC_t rc = cmOkRC;
|
cmRC_t rc = cmOkRC;
|
||||||
@ -3026,6 +3063,7 @@ cmRC_t cmScMeasExec( cmScMeas* p, unsigned mni, unsigned locIdx, unsigned scEvtI
|
|||||||
return cmCtxRtCondition( &p->obj, cmEofRC, "The MIDI buffer is full.");
|
return cmCtxRtCondition( &p->obj, cmEofRC, "The MIDI buffer is full.");
|
||||||
|
|
||||||
int n_mii = cmInvalidIdx;
|
int n_mii = cmInvalidIdx;
|
||||||
|
// locate the MIDI event assoc'd with 'mni' ...
|
||||||
if( p->mii>0 && mni <= p->midiBuf[p->mii-1].mni )
|
if( p->mii>0 && mni <= p->midiBuf[p->mii-1].mni )
|
||||||
{
|
{
|
||||||
if( locIdx != cmInvalidIdx )
|
if( locIdx != cmInvalidIdx )
|
||||||
@ -3038,7 +3076,7 @@ cmRC_t cmScMeasExec( cmScMeas* p, unsigned mni, unsigned locIdx, unsigned scEvtI
|
|||||||
n_mii = cmInvalidIdx;
|
n_mii = cmInvalidIdx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else // ... or push a new record onto p->midiBuf[]
|
||||||
{
|
{
|
||||||
n_mii = p->mii;
|
n_mii = p->mii;
|
||||||
++p->mii;
|
++p->mii;
|
||||||
@ -3052,8 +3090,6 @@ cmRC_t cmScMeasExec( cmScMeas* p, unsigned mni, unsigned locIdx, unsigned scEvtI
|
|||||||
p->midiBuf[n_mii].pitch = pitch;
|
p->midiBuf[n_mii].pitch = pitch;
|
||||||
p->midiBuf[n_mii].vel = vel;
|
p->midiBuf[n_mii].vel = vel;
|
||||||
|
|
||||||
//++p->mi;
|
|
||||||
|
|
||||||
if( locIdx == cmInvalidIdx )
|
if( locIdx == cmInvalidIdx )
|
||||||
return cmOkRC;
|
return cmOkRC;
|
||||||
|
|
||||||
@ -3078,6 +3114,8 @@ cmRC_t cmScMeasExec( cmScMeas* p, unsigned mni, unsigned locIdx, unsigned scEvtI
|
|||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p->vsi = p->nsi;
|
||||||
|
|
||||||
// for each cmScore location between p->nsli and scLocIdx
|
// for each cmScore location between p->nsli and scLocIdx
|
||||||
for(; p->nsli<=scLocIdx && p->nsi < p->sn; ++p->nsli)
|
for(; p->nsli<=scLocIdx && p->nsi < p->sn; ++p->nsli)
|
||||||
{
|
{
|
||||||
@ -3085,7 +3123,9 @@ cmRC_t cmScMeasExec( cmScMeas* p, unsigned mni, unsigned locIdx, unsigned scEvtI
|
|||||||
// ahead of the next sets ending location.
|
// ahead of the next sets ending location.
|
||||||
while( cmMin(maxScLocIdx,p->set[p->nsi].esli+1) == p->nsli )
|
while( cmMin(maxScLocIdx,p->set[p->nsi].esli+1) == p->nsli )
|
||||||
{
|
{
|
||||||
_cmScMeasMatch(p, p->set + p->nsi, n_mii );
|
|
||||||
|
// calculate the value assoc'd with p->set[p->nsi]
|
||||||
|
_cmScMeasCalcVal(p, p->set + p->nsi, n_mii );
|
||||||
|
|
||||||
// advance the set index
|
// advance the set index
|
||||||
++p->nsi;
|
++p->nsi;
|
||||||
@ -3114,7 +3154,7 @@ cmRC_t cmScAlignScanToTimeLineEvent( cmScMatcher* p, cmTlH_t tlH, cmTlObj_t* top
|
|||||||
// if the time line MIDI msg a note-on
|
// if the time line MIDI msg a note-on
|
||||||
if( mep->msg->status == kNoteOnMdId )
|
if( mep->msg->status == kNoteOnMdId )
|
||||||
{
|
{
|
||||||
rc = cmScMatcherExec(p, mep->obj.seqSmpIdx, mep->msg->status, mep->msg->u.chMsgPtr->d0, mep->msg->u.chMsgPtr->d1 );
|
rc = cmScMatcherExec(p, mep->obj.seqSmpIdx, mep->msg->status, mep->msg->u.chMsgPtr->d0, mep->msg->u.chMsgPtr->d1, NULL );
|
||||||
|
|
||||||
switch( rc )
|
switch( rc )
|
||||||
{
|
{
|
||||||
@ -3124,11 +3164,14 @@ cmRC_t cmScAlignScanToTimeLineEvent( cmScMatcher* p, cmTlH_t tlH, cmTlObj_t* top
|
|||||||
case cmEofRC: // end of the score was encountered
|
case cmEofRC: // end of the score was encountered
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case cmInvalidArgRC: // p->esi was not set correctly
|
case cmInvalidArgRC: // p->eli was not set correctly
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case cmSubSysFailRC: // scan resync failed
|
case cmSubSysFailRC: // scan resync failed
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
{ assert(0); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3158,7 +3201,7 @@ void cmScAlignScanMarkers( cmRpt_t* rpt, cmTlH_t tlH, cmScH_t scH )
|
|||||||
unsigned dynRefCnt = sizeof(dynRefArray)/sizeof(dynRefArray[0]);
|
unsigned dynRefCnt = sizeof(dynRefArray)/sizeof(dynRefArray[0]);
|
||||||
cmCtx* ctx = cmCtxAlloc(NULL, rpt, cmLHeapNullHandle, cmSymTblNullHandle );
|
cmCtx* ctx = cmCtxAlloc(NULL, rpt, cmLHeapNullHandle, cmSymTblNullHandle );
|
||||||
cmScMeas* mp = cmScMeasAlloc(ctx,NULL,scH,srate,dynRefArray,dynRefCnt);
|
cmScMeas* mp = cmScMeasAlloc(ctx,NULL,scH,srate,dynRefArray,dynRefCnt);
|
||||||
cmScMatcher* p = cmScMatcherAlloc(ctx,NULL,srate,scH,scWndN,midiN);
|
cmScMatcher* p = cmScMatcherAlloc(ctx,NULL,srate,scH,scWndN,midiN,cmScMatcherCb,mp);
|
||||||
double scoreThresh = 0.5;
|
double scoreThresh = 0.5;
|
||||||
unsigned candCnt = 0;
|
unsigned candCnt = 0;
|
||||||
unsigned initFailCnt = 0;
|
unsigned initFailCnt = 0;
|
||||||
@ -3171,9 +3214,6 @@ void cmScAlignScanMarkers( cmRpt_t* rpt, cmTlH_t tlH, cmScH_t scH )
|
|||||||
|
|
||||||
cmTimeGet(&t0);
|
cmTimeGet(&t0);
|
||||||
|
|
||||||
p->cbArg = mp; // set the callback arg.
|
|
||||||
p->cbFunc = cmScMatcherCb;
|
|
||||||
|
|
||||||
// for each marker
|
// for each marker
|
||||||
for(i=0; i<markN; ++i)
|
for(i=0; i<markN; ++i)
|
||||||
{
|
{
|
||||||
@ -3199,7 +3239,7 @@ void cmScAlignScanMarkers( cmRpt_t* rpt, cmTlH_t tlH, cmScH_t scH )
|
|||||||
|
|
||||||
printf("=================== MARKER:%s ===================\n",markText);
|
printf("=================== MARKER:%s ===================\n",markText);
|
||||||
|
|
||||||
cmScMatcherReset(p); // reset the score follower to the beginnig of the score
|
cmScMatcherReset(p,0); // reset the score follower to the beginnig of the score
|
||||||
cmScMeasReset(mp);
|
cmScMeasReset(mp);
|
||||||
|
|
||||||
++candCnt;
|
++candCnt;
|
||||||
|
56
cmProc4.h
56
cmProc4.h
@ -341,41 +341,60 @@ typedef void (*cmScMatcherCb_t)( struct cmScMatcher_str* p, void* arg, cmScMatch
|
|||||||
|
|
||||||
double s_opt; //
|
double s_opt; //
|
||||||
unsigned missCnt; // count of consecutive trailing non-matches
|
unsigned missCnt; // count of consecutive trailing non-matches
|
||||||
unsigned esi; // index into loc[] of the last positive match.
|
unsigned ili; // index into loc[] to start scan following reset
|
||||||
|
unsigned eli; // index into loc[] of the last positive match.
|
||||||
unsigned mni; // track the count of MIDI events since the last call to cmScMatcherReset()
|
unsigned mni; // track the count of MIDI events since the last call to cmScMatcherReset()
|
||||||
unsigned mbi; // index of oldest MIDI event in midiBuf[]; 0 when the buffer is full.
|
unsigned mbi; // index of oldest MIDI event in midiBuf[]; 0 when the buffer is full.
|
||||||
unsigned begSyncLocIdx; // start of score window, in mp->loc[], of best match in previous scan
|
unsigned begSyncLocIdx; // start of score window, in mp->loc[], of best match in previous scan
|
||||||
unsigned stepCnt; // count of forward/backward score loc's to examine for a match during cmScMatcherStep().
|
unsigned stepCnt; // count of forward/backward score loc's to examine for a match during cmScMatcherStep().
|
||||||
unsigned maxMissCnt; // max. number of consecutive non-matches during step prior to executing a scan.
|
unsigned maxMissCnt; // max. number of consecutive non-matches during step prior to executing a scan.
|
||||||
unsigned scanCnt; // count of time scan was executed inside cmScMatcherStep()
|
unsigned scanCnt; // count of time scan was executed inside cmScMatcherStep()
|
||||||
|
|
||||||
|
bool printFl;
|
||||||
} cmScMatcher;
|
} cmScMatcher;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
cmScMatcher* cmScMatcherAlloc( cmCtx* c, cmScMatcher* p, double srate, cmScH_t scH, unsigned scWndN, unsigned midiWndN );
|
cmScMatcher* cmScMatcherAlloc( cmCtx* c, cmScMatcher* p, double srate, cmScH_t scH, unsigned scWndN, unsigned midiWndN, cmScMatcherCb_t cbFunc, void* cbArg );
|
||||||
cmRC_t cmScMatcherFree( cmScMatcher** pp );
|
cmRC_t cmScMatcherFree( cmScMatcher** pp );
|
||||||
cmRC_t cmScMatcherInit( cmScMatcher* p, double srate, cmScH_t scH, unsigned scWndN, unsigned midiWndN );
|
cmRC_t cmScMatcherInit( cmScMatcher* p, double srate, cmScH_t scH, unsigned scWndN, unsigned midiWndN, cmScMatcherCb_t cbFunc, void* cbArg );
|
||||||
cmRC_t cmScMatcherFinal( cmScMatcher* p );
|
cmRC_t cmScMatcherFinal( cmScMatcher* p );
|
||||||
void cmScMatcherReset( cmScMatcher* p );
|
|
||||||
bool cmScMatcherInputMidi( cmScMatcher* p, unsigned smpIdx, unsigned status, cmMidiByte_t d0, cmMidiByte_t d1 );
|
|
||||||
|
|
||||||
// Slide a score window scanCnt times, beginning at 'bsi',
|
// 'scLocIdx' is a score index as used by cmScoreLoc(scH) not into p->mp->loc[].
|
||||||
// looking for the best match to p->midiBuf[]. The score window
|
cmRC_t cmScMatcherReset( cmScMatcher* p, unsigned scLocIdx );
|
||||||
// contain scWndN (p->mp->mcn-1) score locations.
|
|
||||||
|
// Slide a score window scanCnt times, beginning at 'bli' (an
|
||||||
|
// index int p->mp->loc[]) looking for the best match to p->midiBuf[].
|
||||||
|
// The score window contain scWndN (p->mp->mcn-1) score locations.
|
||||||
// Returns the index into p->mp->loc[] of the start of the best
|
// Returns the index into p->mp->loc[] of the start of the best
|
||||||
// match score window. The score associated
|
// match score window. The score associated
|
||||||
// with this match is stored in s_opt.
|
// with this match is stored in s_opt.
|
||||||
unsigned cmScMatcherScan( cmScMatcher* p, unsigned bsi, unsigned scanCnt );
|
unsigned cmScMatcherScan( cmScMatcher* p, unsigned bli, unsigned scanCnt );
|
||||||
|
|
||||||
// Step forward/back by p->stepCnt from p->esi.
|
// Step forward/back by p->stepCnt from p->eli.
|
||||||
// p->esi must therefore be valid prior to calling this function.
|
// p->eli must therefore be valid prior to calling this function.
|
||||||
// If more than p->maxMissCnt consecutive MIDI events are
|
// If more than p->maxMissCnt consecutive MIDI events are
|
||||||
// missed then automatically run cmScAlignScan().
|
// missed then automatically run cmScAlignScan().
|
||||||
// Return cmEofRC if the end of the score is encountered.
|
// Return cmEofRC if the end of the score is encountered.
|
||||||
// Return cmSubSysFailRC if an internal scan resync. failed.
|
// Return cmSubSysFailRC if an internal scan resync. failed.
|
||||||
cmRC_t cmScMatcherStep( cmScMatcher* p );
|
cmRC_t cmScMatcherStep( cmScMatcher* p );
|
||||||
|
|
||||||
cmRC_t cmScMatcherExec( cmScMatcher* p, unsigned smpIdx, unsigned status, cmMidiByte_t d0, cmMidiByte_t d1 );
|
// This function calls cmScMatcherScan() and cmScMatcherStep() internally.
|
||||||
|
// If 'status' is not kNonMidiMdId then the function returns without changing the
|
||||||
|
// state of the object.
|
||||||
|
// If the MIDI note passed by the call results in a successful match then
|
||||||
|
// p->eli will be updated to the location in p->mp->loc[] of the latest
|
||||||
|
// match, the MIDI note in p->midiBuf[] associated with this match
|
||||||
|
// will be assigned valid locIdx and scLocIdx values, and *scLocIdxPtr
|
||||||
|
// will be set with the matched scLocIdx of the match.
|
||||||
|
// If this call does not result in a successful match *scLocIdxPtr is set
|
||||||
|
// to cmInvalidIdx.
|
||||||
|
// Return:
|
||||||
|
// cmOkRC - Continue processing MIDI events.
|
||||||
|
// cmEofRC - The end of the score was encountered.
|
||||||
|
// cmInvalidArgRC - scan failed or the object was in an invalid state to attempt a match.
|
||||||
|
// cmSubSysFailRC - a scan resync failed in cmScMatcherStep().
|
||||||
|
cmRC_t cmScMatcherExec( cmScMatcher* p, unsigned smpIdx, unsigned status, cmMidiByte_t d0, cmMidiByte_t d1, unsigned* scLocIdxPtr );
|
||||||
|
|
||||||
|
|
||||||
//=======================================================================================================================
|
//=======================================================================================================================
|
||||||
@ -393,11 +412,15 @@ typedef struct
|
|||||||
unsigned bli; //
|
unsigned bli; //
|
||||||
unsigned eli; //
|
unsigned eli; //
|
||||||
|
|
||||||
|
double value; // DBL_MAX if the value has not yet been set
|
||||||
|
double tempo; // DBL_MAX until set
|
||||||
|
|
||||||
} cmScMeasSet_t;
|
} cmScMeasSet_t;
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
cmObj obj;
|
cmObj obj;
|
||||||
|
double srate; //
|
||||||
cmScMatch* mp; //
|
cmScMatch* mp; //
|
||||||
unsigned mii; // next avail recd in midiBuf[]
|
unsigned mii; // next avail recd in midiBuf[]
|
||||||
unsigned mn; // length of of midiBuf[]
|
unsigned mn; // length of of midiBuf[]
|
||||||
@ -412,12 +435,16 @@ typedef struct
|
|||||||
unsigned nsi; // next set index
|
unsigned nsi; // next set index
|
||||||
unsigned nsli; // next score location index
|
unsigned nsli; // next score location index
|
||||||
|
|
||||||
double srate; // sample rate from schore
|
unsigned vsi; // set[vsi:nsi-1] indicates sets with new values following a call to cmScMeasExec()
|
||||||
|
|
||||||
} cmScMeas;
|
} cmScMeas;
|
||||||
|
|
||||||
|
//
|
||||||
// Notes:
|
// Notes:
|
||||||
|
//
|
||||||
// 1) midiBuf[] stores all MIDI notes for the duration of the performance
|
// 1) midiBuf[] stores all MIDI notes for the duration of the performance
|
||||||
// it is initialized to 2*score_event_count.
|
// it is initialized to 2*score_event_count.
|
||||||
|
//
|
||||||
// 2) dynRef][ is the gives the MIDI velocity range for each dynamics
|
// 2) dynRef][ is the gives the MIDI velocity range for each dynamics
|
||||||
// category: pppp-fff
|
// category: pppp-fff
|
||||||
//
|
//
|
||||||
@ -428,6 +455,9 @@ cmRC_t cmScMeasFree( cmScMeas** pp );
|
|||||||
cmRC_t cmScMeasInit( cmScMeas* p, cmScH_t scH, double srate, const unsigned* dynRefArray, unsigned dynRefCnt );
|
cmRC_t cmScMeasInit( cmScMeas* p, cmScH_t scH, double srate, const unsigned* dynRefArray, unsigned dynRefCnt );
|
||||||
cmRC_t cmScMeasFinal( cmScMeas* p );
|
cmRC_t cmScMeasFinal( cmScMeas* p );
|
||||||
|
|
||||||
|
// Empty MIDI buffer and set the next set nsi and nsli to zero.
|
||||||
|
cmRC_t cmScMeasReset( cmScMeas* p );
|
||||||
|
|
||||||
// This function is called for each input MIDI note which is assigned a
|
// This function is called for each input MIDI note which is assigned a
|
||||||
// score location by cmScMatcher.
|
// score location by cmScMatcher.
|
||||||
// 'mni' is the MIDI event index which uniquely identifies this MIDI event.
|
// 'mni' is the MIDI event index which uniquely identifies this MIDI event.
|
||||||
|
Loading…
Reference in New Issue
Block a user