cmXScore.c : Added cmXsComplexity_t related functions and added complexity measure output to MIDI file SVG plot.
This commit is contained in:
parent
a37e2e2c71
commit
cc334c8d7f
296
app/cmXScore.c
296
app/cmXScore.c
@ -71,6 +71,16 @@ enum
|
||||
struct cmXsMeas_str;
|
||||
struct cmXsVoice_str;
|
||||
|
||||
typedef struct cmXsComplexity_str
|
||||
{
|
||||
unsigned sum_d_vel;
|
||||
unsigned sum_d_rym;
|
||||
unsigned sum_d_lpch;
|
||||
unsigned sum_n_lpch;
|
||||
unsigned sum_d_rpch;
|
||||
unsigned sum_n_rpch;
|
||||
} cmXsComplexity_t;
|
||||
|
||||
typedef struct cmXsNote_str
|
||||
{
|
||||
unsigned uid; // unique id of this note record
|
||||
@ -107,6 +117,8 @@ typedef struct cmXsNote_str
|
||||
struct cmXsNote_str* mlink; // measure note list
|
||||
struct cmXsNote_str* slink; // time sorted event list
|
||||
|
||||
cmXsComplexity_t cplx;
|
||||
|
||||
} cmXsNote_t;
|
||||
|
||||
typedef struct cmXsVoice_str
|
||||
@ -157,6 +169,8 @@ typedef struct
|
||||
|
||||
cmXsSpan_t* spanL;
|
||||
unsigned nextUid;
|
||||
|
||||
cmXsComplexity_t cplx_max;
|
||||
} cmXScore_t;
|
||||
|
||||
cmXScore_t* _cmXScoreHandleToPtr( cmXsH_t h )
|
||||
@ -3050,6 +3064,200 @@ void cmXScoreReport( cmXsH_t h, cmRpt_t* rpt, bool sortFl )
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned ival;
|
||||
double fval;
|
||||
unsigned cnt;
|
||||
} cmXsHist_t;
|
||||
|
||||
void _cmXsHistUpdateI( cmXsHist_t* hist, unsigned histN, unsigned ival )
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
for(i=0; i<histN && hist[i].cnt!=0; ++i)
|
||||
if( hist[i].ival == ival )
|
||||
break;
|
||||
|
||||
if( i==histN )
|
||||
return;
|
||||
|
||||
hist[i].ival = ival;
|
||||
hist[i].cnt += 1;
|
||||
|
||||
}
|
||||
|
||||
void _cmXsHistUpdateF( cmXsHist_t* hist, unsigned histN, double fval )
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
for(i=0; i<histN && hist[i].cnt!=0; ++i)
|
||||
if( hist[i].fval == fval )
|
||||
break;
|
||||
|
||||
if( i==histN )
|
||||
return;
|
||||
|
||||
hist[i].fval = fval;
|
||||
hist[i].cnt += 1;
|
||||
}
|
||||
|
||||
unsigned _cmXsHistValue( cmXsHist_t* hist, unsigned histN )
|
||||
{
|
||||
unsigned i,n;
|
||||
for(i=0,n=0; i<histN && hist[i].cnt>0; ++i)
|
||||
n += 1;
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
const cmXsNote_t* _cmXsMeasComplexityInWindow( const cmXsNote_t* n0, cmXsNote_t* n1, double wndSecs )
|
||||
{
|
||||
const cmXsNote_t* n2 = NULL;
|
||||
unsigned l_pch_0 = 0;
|
||||
unsigned l_pch_value = 0;
|
||||
unsigned l_pch_cnt = 0;
|
||||
unsigned r_pch_0 = 0;
|
||||
unsigned r_pch_value = 0;
|
||||
unsigned r_pch_cnt = 0;
|
||||
unsigned i = 0;
|
||||
|
||||
|
||||
unsigned histN = 100;
|
||||
cmXsHist_t velHist[ histN ];
|
||||
cmXsHist_t rymHist[ histN ];
|
||||
|
||||
memset(velHist,0,sizeof(velHist));
|
||||
memset(rymHist,0,sizeof(rymHist));
|
||||
|
||||
const cmXsNote_t* n = n0;
|
||||
|
||||
while(n!=NULL && n != n1)
|
||||
{
|
||||
// if this event is less than wndSecs behind 'n1' and is not a sounding note ...
|
||||
if( n1->secs - n->secs <= wndSecs && cmIsFlag(n->flags,kOnsetXsFl) )
|
||||
{
|
||||
// if this is not the first note in the window
|
||||
if( i > 0 )
|
||||
{
|
||||
_cmXsHistUpdateI( velHist, histN, n->dynamics );
|
||||
_cmXsHistUpdateF( rymHist, histN, n->rvalue );
|
||||
|
||||
switch( n->staff )
|
||||
{
|
||||
case 1:
|
||||
r_pch_value += r_pch_0 > n->pitch ? r_pch_0-n->pitch : n->pitch-r_pch_0;
|
||||
r_pch_cnt += 1;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
l_pch_value += l_pch_0 > n->pitch ? l_pch_0-n->pitch : n->pitch-l_pch_0;
|
||||
r_pch_cnt += 1;
|
||||
break;
|
||||
|
||||
default:
|
||||
{ assert(0); }
|
||||
}
|
||||
}
|
||||
|
||||
// store the pitch values to compare on the next interation
|
||||
switch( n->staff )
|
||||
{
|
||||
case 1:
|
||||
r_pch_0 = n->pitch;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
l_pch_0 = n->pitch;
|
||||
break;
|
||||
|
||||
default:
|
||||
{ assert(0); }
|
||||
}
|
||||
|
||||
// track the first note that is inside the window
|
||||
if( i == 0 )
|
||||
n2 = n;
|
||||
|
||||
// count the number of notes in the window
|
||||
i += 1;
|
||||
|
||||
}
|
||||
|
||||
cmXsMeas_t* m = n->meas;
|
||||
|
||||
// advance th note pointer
|
||||
n = n->slink;
|
||||
|
||||
// if we have reached the end of a measure
|
||||
if( n == NULL )
|
||||
{
|
||||
if( m != NULL )
|
||||
{
|
||||
m = m->link;
|
||||
if( m != NULL )
|
||||
n = m->noteL;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
n1->cplx.sum_d_vel = _cmXsHistValue( velHist, histN );
|
||||
n1->cplx.sum_d_rym = _cmXsHistValue( rymHist, histN );
|
||||
n1->cplx.sum_d_lpch = l_pch_value;
|
||||
n1->cplx.sum_n_lpch = l_pch_cnt;
|
||||
n1->cplx.sum_d_rpch = r_pch_value;
|
||||
n1->cplx.sum_n_rpch = r_pch_cnt;
|
||||
|
||||
return n2;
|
||||
}
|
||||
|
||||
cmXsRC_t _cmXsMeasComplexity( cmXsH_t h, double wndSecs )
|
||||
{
|
||||
cmXsRC_t rc = kOkXsRC;
|
||||
cmXScore_t* p = _cmXScoreHandleToPtr(h);
|
||||
cmXsPart_t* pp = p->partL;
|
||||
|
||||
memset(&p->cplx_max,0,sizeof(p->cplx_max));
|
||||
|
||||
const cmXsNote_t* n0 = NULL;
|
||||
|
||||
// for each part
|
||||
for(; pp!=NULL; pp=pp->link)
|
||||
{
|
||||
cmXsMeas_t* mp = pp->measL;
|
||||
|
||||
// for each measure
|
||||
for(; mp!=NULL; mp=mp->link)
|
||||
{
|
||||
cmXsNote_t* n1 = mp->noteL;
|
||||
|
||||
|
||||
// for each note in this measure
|
||||
for(; n1!=NULL; n1=n1->slink)
|
||||
if( cmIsFlag(n1->flags,kOnsetXsFl) )
|
||||
{
|
||||
if( n0 == NULL )
|
||||
n0 = n1;
|
||||
else
|
||||
if((n0 = _cmXsMeasComplexityInWindow(n0,n1,wndSecs)) == NULL )
|
||||
n0 = n1;
|
||||
|
||||
// track the max value for all complexity values to allow
|
||||
// eventual normalization of the complexity values
|
||||
p->cplx_max.sum_d_vel = cmMax(p->cplx_max.sum_d_vel, n1->cplx.sum_d_vel);
|
||||
p->cplx_max.sum_d_rym = cmMax(p->cplx_max.sum_d_rym, n1->cplx.sum_d_rym);
|
||||
p->cplx_max.sum_d_lpch = cmMax(p->cplx_max.sum_d_lpch,n1->cplx.sum_d_lpch);
|
||||
p->cplx_max.sum_n_lpch = cmMax(p->cplx_max.sum_n_lpch,n1->cplx.sum_n_lpch);
|
||||
p->cplx_max.sum_d_rpch = cmMax(p->cplx_max.sum_d_rpch,n1->cplx.sum_d_rpch);
|
||||
p->cplx_max.sum_n_rpch = cmMax(p->cplx_max.sum_n_rpch,n1->cplx.sum_n_rpch);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
cmXsRC_t _cmXsWriteMidiFile( cmCtx_t* ctx, cmXsH_t h, const cmChar_t* dir, const cmChar_t* fn )
|
||||
{
|
||||
cmXsRC_t rc = kOkXsRC;
|
||||
@ -3163,6 +3371,7 @@ typedef struct cmXsSvgEvt_str
|
||||
unsigned voice; // score voice number
|
||||
unsigned d0; // MIDI d0 (barNumb)
|
||||
unsigned d1; // MIDI d1
|
||||
cmXsComplexity_t cplx;
|
||||
struct cmXsSvgEvt_str* link;
|
||||
} cmXsSvgEvt_t;
|
||||
|
||||
@ -3176,6 +3385,44 @@ typedef struct cmXsMidiFile_str
|
||||
|
||||
} cmXsMidiFile_t;
|
||||
|
||||
|
||||
void _cmXsWriteMidiSvgLegend( cmSvgH_t svgH, unsigned index, const cmChar_t* label, const cmChar_t* classStr )
|
||||
{
|
||||
double x = 100;
|
||||
double y = 120*10 - 20*index;
|
||||
|
||||
cmSvgWriterText( svgH, x, y, label, "legend" );
|
||||
|
||||
x += 75;
|
||||
cmSvgWriterLine( svgH, x, y, x+125, y, classStr );
|
||||
}
|
||||
|
||||
void _cmXsWriteMidiSvgLinePoint( cmSvgH_t svgH, double x0, double y0, double x1, double y1, double y_max, const cmChar_t* classStr, const cmChar_t* label )
|
||||
{
|
||||
int bn = 255;
|
||||
char b[bn+1];
|
||||
double y_scale = 10;
|
||||
double y_label = y1;
|
||||
|
||||
b[0] = 0;
|
||||
y0 = (y0/y_max) * 127.0 * y_scale;
|
||||
y1 = (y1/y_max) * 127.0 * y_scale;
|
||||
|
||||
cmSvgWriterLine(svgH, x0, y0, x1, y1, classStr );
|
||||
|
||||
if( y0 != y1 )
|
||||
snprintf(b,bn,"%5.0f %s",y_label,label==NULL?"":label);
|
||||
else
|
||||
{
|
||||
if( label != NULL )
|
||||
snprintf(b,bn,"%s",label);
|
||||
}
|
||||
|
||||
if( strlen(b) )
|
||||
cmSvgWriterText(svgH, x1, y1, b, "pt_text");
|
||||
|
||||
}
|
||||
|
||||
cmXsRC_t _cmXsWriteMidiSvg( cmCtx_t* ctx, cmXScore_t* p, cmXsMidiFile_t* mf, const cmChar_t* dir, const cmChar_t* fn )
|
||||
{
|
||||
cmXsRC_t rc = kOkXsRC;
|
||||
@ -3186,7 +3433,8 @@ cmXsRC_t _cmXsWriteMidiSvg( cmCtx_t* ctx, cmXScore_t* p, cmXsMidiFile_t* mf, con
|
||||
const cmChar_t* svgFn = cmFsMakeFn(dir,fn0 = cmTextAppendSS(fn0,"_midi_svg"),"html",NULL);
|
||||
const cmChar_t* cssFn = cmFsMakeFn(NULL,"score_midi_svg","css",NULL);
|
||||
cmChar_t* t0 = NULL; // temporary dynamic string
|
||||
|
||||
unsigned i = 0;
|
||||
const cmXsSvgEvt_t* e0 = NULL;
|
||||
cmMemFree(fn0);
|
||||
|
||||
// create the SVG writer
|
||||
@ -3196,6 +3444,13 @@ cmXsRC_t _cmXsWriteMidiSvg( cmCtx_t* ctx, cmXScore_t* p, cmXsMidiFile_t* mf, con
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
_cmXsWriteMidiSvgLegend( svgH, 0, "Velocity", "cplx_vel" );
|
||||
_cmXsWriteMidiSvgLegend( svgH, 1, "Duration", "cplx_rym" );
|
||||
_cmXsWriteMidiSvgLegend( svgH, 2, "Left Pitch", "cplx_lpch" );
|
||||
_cmXsWriteMidiSvgLegend( svgH, 3, "Right Pitch", "cplx_rpch" );
|
||||
_cmXsWriteMidiSvgLegend( svgH, 4, "Density", "cplx_density" );
|
||||
|
||||
|
||||
// for each MIDI file element
|
||||
for(; e!=NULL && rc==kOkXsRC; e=e->link)
|
||||
{
|
||||
@ -3220,7 +3475,25 @@ cmXsRC_t _cmXsWriteMidiSvg( cmCtx_t* ctx, cmXScore_t* p, cmXsMidiFile_t* mf, con
|
||||
|
||||
if( cmSvgWriterText(svgH, e->tick + e->durTicks/2, e->d0 * noteHeight + noteHeight/2, t0, "pitch") != kOkSvgRC )
|
||||
rc = kSvgFailXsRC;
|
||||
else
|
||||
{
|
||||
if( e0 != NULL )
|
||||
{
|
||||
bool fl = (i % 10) == 0;
|
||||
|
||||
_cmXsWriteMidiSvgLinePoint(svgH, e0->tick, e0->cplx.sum_d_vel, e->tick, e->cplx.sum_d_vel, p->cplx_max.sum_d_vel, "cplx_vel",fl?"V":NULL);
|
||||
_cmXsWriteMidiSvgLinePoint(svgH, e0->tick, e0->cplx.sum_d_rym, e->tick, e->cplx.sum_d_rym, p->cplx_max.sum_d_rym, "cplx_rym",fl?"D":NULL);
|
||||
_cmXsWriteMidiSvgLinePoint(svgH, e0->tick, e0->cplx.sum_d_lpch, e->tick, e->cplx.sum_d_lpch, p->cplx_max.sum_d_lpch, "cplx_lpch",fl?"L":NULL);
|
||||
_cmXsWriteMidiSvgLinePoint(svgH, e0->tick, e0->cplx.sum_d_rpch, e->tick, e->cplx.sum_d_rpch, p->cplx_max.sum_d_rpch, "cplx_rpch",fl?"R":NULL);
|
||||
_cmXsWriteMidiSvgLinePoint(svgH, e0->tick, e0->cplx.sum_n_lpch + e0->cplx.sum_n_rpch, e->tick, e->cplx.sum_n_lpch + e->cplx.sum_n_rpch, p->cplx_max.sum_n_lpch + p->cplx_max.sum_n_rpch, "cplx_density",fl?"N":NULL);
|
||||
}
|
||||
|
||||
e0 = e;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
i+=1;
|
||||
}
|
||||
break;
|
||||
|
||||
@ -3247,7 +3520,7 @@ cmXsRC_t _cmXsWriteMidiSvg( cmCtx_t* ctx, cmXScore_t* p, cmXsMidiFile_t* mf, con
|
||||
cmSvgWriterRect(svgH, e->tick, y, e->durTicks, noteHeight-1, classLabel);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( rc != kOkXsRC )
|
||||
@ -3267,7 +3540,7 @@ cmXsRC_t _cmXsWriteMidiSvg( cmCtx_t* ctx, cmXScore_t* p, cmXsMidiFile_t* mf, con
|
||||
}
|
||||
|
||||
|
||||
void _cmXsPushSvgEvent( cmXScore_t* p, cmXsMidiFile_t* mf, unsigned flags, unsigned tick, unsigned durTick, unsigned voice, unsigned d0, unsigned d1 )
|
||||
void _cmXsPushSvgEvent( cmXScore_t* p, cmXsMidiFile_t* mf, unsigned flags, unsigned tick, unsigned durTick, unsigned voice, unsigned d0, unsigned d1, const cmXsComplexity_t* cplx )
|
||||
{
|
||||
cmXsSvgEvt_t* e = cmLhAllocZ(p->lhH,cmXsSvgEvt_t,1);
|
||||
e->flags = flags;
|
||||
@ -3277,6 +3550,9 @@ void _cmXsPushSvgEvent( cmXScore_t* p, cmXsMidiFile_t* mf, unsigned flags, unsig
|
||||
e->d0 = d0; // note=pitch bar=number pedal=ctl# metronome=BPM
|
||||
e->d1 = d1;
|
||||
|
||||
if( cplx != NULL )
|
||||
e->cplx = *cplx;
|
||||
|
||||
if( mf->eol != NULL )
|
||||
mf->eol->link = e;
|
||||
else
|
||||
@ -3313,7 +3589,7 @@ cmXsRC_t _cmXScoreGenSvg( cmCtx_t* ctx, cmXsH_t h, const cmChar_t* dir, const cm
|
||||
if( cmIsFlag(note->flags,kMetronomeXsFl) )
|
||||
{
|
||||
// set BPM as d0
|
||||
_cmXsPushSvgEvent(p,&mf,note->flags,note->tick,0,0,note->duration,0);
|
||||
_cmXsPushSvgEvent(p,&mf,note->flags,note->tick,0,0,note->duration,0,NULL);
|
||||
continue;
|
||||
|
||||
}
|
||||
@ -3329,14 +3605,14 @@ cmXsRC_t _cmXScoreGenSvg( cmCtx_t* ctx, cmXsH_t h, const cmChar_t* dir, const cm
|
||||
for(; tn!=NULL; tn=tn->tied)
|
||||
durTick += tn->tied_dur;
|
||||
}
|
||||
_cmXsPushSvgEvent(p,&mf,note->flags,note->tick,durTick,note->voice->id,d0,note->vel);
|
||||
_cmXsPushSvgEvent(p,&mf,note->flags,note->tick,durTick,note->voice->id,d0,note->vel,¬e->cplx);
|
||||
continue;
|
||||
}
|
||||
|
||||
// if this is a bar event
|
||||
if( cmIsFlag(note->flags,kBarXsFl) )
|
||||
{
|
||||
_cmXsPushSvgEvent(p,&mf,note->flags,note->tick,0,0,note->meas->number,0);
|
||||
_cmXsPushSvgEvent(p,&mf,note->flags,note->tick,0,0,note->meas->number,0,NULL);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -3344,7 +3620,7 @@ cmXsRC_t _cmXScoreGenSvg( cmCtx_t* ctx, cmXsH_t h, const cmChar_t* dir, const cm
|
||||
if( cmIsFlag(note->flags,kDampDnXsFl|kDampUpDnXsFl|kSostDnXsFl) )
|
||||
{
|
||||
unsigned d0 = cmIsFlag(note->flags,kSostDnXsFl) ? kSostenutoCtlMdId : kSustainCtlMdId;
|
||||
_cmXsPushSvgEvent(p,&mf,note->flags,note->tick,note->duration,0,d0,127);
|
||||
_cmXsPushSvgEvent(p,&mf,note->flags,note->tick,note->duration,0,d0,127,NULL);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -3407,6 +3683,10 @@ cmXsRC_t cmXScoreTest(
|
||||
|
||||
if( midiOutFn != NULL )
|
||||
{
|
||||
double wndSecs = 1.0;
|
||||
_cmXsMeasComplexity(h,wndSecs);
|
||||
|
||||
|
||||
cmFileSysPathPart_t* pp = cmFsPathParts(midiOutFn);
|
||||
|
||||
_cmXsWriteMidiFile(ctx, h, pp->dirStr, pp->fnStr );
|
||||
@ -3417,7 +3697,7 @@ cmXsRC_t cmXScoreTest(
|
||||
|
||||
}
|
||||
|
||||
cmXScoreReport(h,&ctx->rpt,true);
|
||||
//cmXScoreReport(h,&ctx->rpt,true);
|
||||
|
||||
errLabel:
|
||||
return cmXScoreFinalize(&h);
|
||||
|
Loading…
Reference in New Issue
Block a user