cmXScore.c : Added code to parse grace note group information during 'reorder' processing.

Added _cmXScoreProcessGraceNotes() to position grace notes.
This commit is contained in:
kevin 2016-07-27 19:05:42 -04:00
parent 49943bd43e
commit 83734d9949

View File

@ -37,28 +37,31 @@ cmXsH_t cmXsNullHandle = cmSTATIC_NULL_HANDLE;
enum
{
kSectionXsFl = 0x000001, // rvalue holds section number
kBarXsFl = 0x000002,
kRestXsFl = 0x000004,
kGraceXsFl = 0x000008,
kDotXsFl = 0x000010,
kChordXsFl = 0x000020,
kDynXsFl = 0x000040,
kEvenXsFl = 0x000080,
kTempoXsFl = 0x000100,
kHeelXsFl = 0x000200,
kTieBegXsFl = 0x000400,
kTieEndXsFl = 0x000800,
kTieProcXsFl = 0x001000,
kDampDnXsFl = 0x002000,
kDampUpXsFl = 0x004000,
kDampUpDnXsFl = 0x008000,
kSostDnXsFl = 0x010000,
kSostUpXsFl = 0x020000,
kMetronomeXsFl = 0x040000, // duration holds BPM
kOnsetXsFl = 0x080000, // this is a sounding note
kBegGroupXsFl = 0x100000,
kEndGroupXsFl = 0x200000
kSectionXsFl = 0x000001, // rvalue holds section number
kBarXsFl = 0x000002,
kRestXsFl = 0x000004,
kGraceXsFl = 0x000008,
kDotXsFl = 0x000010,
kChordXsFl = 0x000020,
kDynXsFl = 0x000040,
kEvenXsFl = 0x000080,
kTempoXsFl = 0x000100,
kHeelXsFl = 0x000200,
kTieBegXsFl = 0x000400,
kTieEndXsFl = 0x000800,
kTieProcXsFl = 0x001000,
kDampDnXsFl = 0x002000,
kDampUpXsFl = 0x004000,
kDampUpDnXsFl = 0x008000,
kSostDnXsFl = 0x010000,
kSostUpXsFl = 0x020000,
kMetronomeXsFl = 0x040000, // duration holds BPM
kOnsetXsFl = 0x080000, // this is a sounding note
kBegGroupXsFl = 0x100000,
kEndGroupXsFl = 0x200000,
kBegGraceXsFl = 0x400000, // beg grace note group
kEndGraceXsFl = 0x800000 // end grace note group
};
struct cmXsMeas_str;
@ -87,6 +90,7 @@ typedef struct cmXsNote_str
unsigned evenGroupId; // eveness group id
unsigned dynGroupId; // dynamics group id
unsigned tempoGroupId; // tempo group id
unsigned graceGroupId; // grace note group id
struct cmXsVoice_str* voice; // voice this note belongs to
struct cmXsMeas_str* meas; // measure this note belongs to
@ -94,7 +98,8 @@ typedef struct cmXsNote_str
const cmXmlNode_t* xmlNode; // note xml ptr
struct cmXsNote_str* tied; // subsequent note tied to this note
struct cmXsNote_str* grace; // grace note groups link backward in time from the anchor note
struct cmXsNote_str* mlink; // measure note list
struct cmXsNote_str* slink; // time sorted event list
@ -1459,6 +1464,89 @@ cmXsRC_t _cmXScoreProcessPedals( cmXScore_t* p )
return rc;
}
// Adjust the locations of grace notes. Note that this must be done
// after reordering so that we can be sure that the order in time of
// the notes in each group has been set prior to building the
// grace note groups - which must be in reverse time order.
cmXsRC_t _cmXScoreProcessGraceNotes( cmXScore_t* p )
{
cmXsRC_t rc = kOkXsRC;
unsigned graceGroupId = 1;
for(; 1; ++graceGroupId)
{
cmXsNote_t* gnp = NULL;
cmXsPart_t* pp = p->partL;
double ticksPerSec = 0;
for(; pp!=NULL; pp=pp->link)
{
cmXsMeas_t* mp = pp->measL;
for(; mp!=NULL; mp=mp->link)
{
cmXsNote_t* np = mp->noteL;
for(; np!=NULL; np=np->slink )
{
// notice change of tempo
if( cmIsFlag(np->flags,kMetronomeXsFl) )
{
// ticks/sec = ticks/qn * qn/sec
ticksPerSec = mp->divisions * np->duration / 60.0;
}
// if this note is part of the grace note group we are searching for
if( np->graceGroupId == graceGroupId )
{
// add the note to the grace note list
np->grace = gnp;
// set each grace note to have 1/20 of a second duration
if( cmIsFlag(np->flags,kGraceXsFl) )
np->duration = floor(ticksPerSec / 20.0);
gnp = np;
}
}
}
}
// no records were found for this grace id - we're done
if( gnp == NULL )
break;
cmXsNote_t* p0 = NULL;
cmXsNote_t* p1 = gnp;
for(; p1!=NULL; p1=p1->grace)
{
if(1)
{
const char* type = "g";
if( cmIsFlag(p1->flags,kBegGraceXsFl) )
type = "b";
if( cmIsFlag(p1->flags,kEndGraceXsFl) )
type = "i";
bool fl = p0 != NULL && p0->tick < p1->tick;
printf("%3i %s %i %5i %i\n",p1->graceGroupId,type,p1->tick,p1->duration,fl);
}
// TODO:
// position grace notes here
p0 = p1;
}
}
return rc;
}
cmXsRC_t cmXScoreInitialize( cmCtx_t* ctx, cmXsH_t* hp, const cmChar_t* xmlFn )
{
cmXsRC_t rc = kOkXsRC;
@ -1558,11 +1646,13 @@ typedef struct
float rval; //
unsigned midi; //
cmXsNote_t* note; // The cmXsNode_t* associated with this cmXsReorder_t record
cmXsNote_t* note; // The cmXsNote_t* associated with this cmXsReorder_t record
unsigned dynIdx; // cmInvalidIdx=ignore otherwise index into _cmXScoreDynMarkArray[]
unsigned newFlags; // 0=ignore | kSostUp/DnXsFl | kDampUp/DnXsFl | kTieEndXsFl
unsigned newFlags; // 0=ignore | kSostUp/DnXsFl | kDampUp/DnXsFl | kTieEndXsFl
unsigned newTick; // 0=ignore >0 new tick value
char graceType; // 0=ignore g=grace note i=anchor note
unsigned graceGroupId; // 0=ignore >0=grace note group id
unsigned pitch; // 0=ignore >0 new pitch
} cmXsReorder_t;
@ -1678,10 +1768,8 @@ cmXsRC_t _cmXScoreReorderMeas( cmXScore_t* p, unsigned measNumb, cmXsReorder_t*
// set the 'note' field on each cmXsReorder_t record
for(i=0; i<rN; ++i)
{
if((rV[i].note = _cmXsReorderFindNote(p,measNumb,rV+i,i)) == NULL )
return kSyntaxErrorXsRC;
}
cmXsMeas_t* mp = rV[0].note->meas;
cmXsNote_t* n0p = NULL;
@ -1719,6 +1807,25 @@ cmXsRC_t _cmXScoreReorderMeas( cmXScore_t* p, unsigned measNumb, cmXsReorder_t*
// if a new note value was specified
if( rV[i].pitch != 0 )
rV[i].note->pitch = rV[i].pitch;
switch(rV[i].graceType)
{
case 'b':
rV[i].note->flags = cmSetFlag(rV[i].note->flags,kBegGraceXsFl);
break;
case 'g':
break;
case 'a':
case 's':
case 'f':
rV[i].note->flags = cmSetFlag(rV[i].note->flags,kEndGraceXsFl);
break;
}
rV[i].note->graceGroupId = rV[i].graceGroupId;
n0p = rV[i].note;
n0p->slink = NULL;
@ -1897,6 +2004,36 @@ cmXsRC_t _cmXScoreReorderParseTick(cmXScore_t* p, const cmChar_t* b, unsigned l
return rc;
}
cmXsRC_t _cmXScoreReorderParseGrace(cmXScore_t* p, const cmChar_t* b, unsigned line, char* graceTypeRef )
{
cmXsRC_t rc = kOkXsRC;
const cmChar_t* s;
*graceTypeRef = 0;
if((s = strchr(b,'%')) == NULL )
return rc;
++s;
switch(*s)
{
case 'b':
case 'g':
case 'a':
case 's':
case 'f':
*graceTypeRef = *s;
break;
default:
{ assert(0); }
}
return rc;
}
cmXsRC_t _cmXScoreReorderParsePitch(cmXScore_t* p, const cmChar_t* b, unsigned line, unsigned* pitchRef )
{
cmXsRC_t rc = kOkXsRC;
@ -1937,26 +2074,24 @@ cmXsRC_t _cmXScoreReorderParsePitch(cmXScore_t* p, const cmChar_t* b, unsigned
else
rc = cmErrMsg(&p->err,kSyntaxErrorXsRC,"Pitch conversion from '%s' failed on line %i.",buf,line);
return rc;
return rc;
}
cmXsRC_t cmXScoreReorder( cmXsH_t h, const cmChar_t* fn )
{
typedef enum { kFindMeasStId, kFindEventStId, kReadEventStId } stateId_t;
cmXsRC_t rc = kOkXsRC;
cmXScore_t* p = _cmXScoreHandleToPtr(h);
cmFileH_t fH = cmFileNullHandle;
cmChar_t* b = NULL;
unsigned bN = 0;
unsigned ln = 0;
stateId_t stateId = kFindMeasStId;
unsigned rN = 1024;
unsigned ri = 0;
unsigned measNumb = 0;
cmXsRC_t rc = kOkXsRC;
cmXScore_t* p = _cmXScoreHandleToPtr(h);
cmFileH_t fH = cmFileNullHandle;
cmChar_t* b = NULL;
unsigned bN = 0;
unsigned ln = 0;
stateId_t stateId = kFindMeasStId;
unsigned rN = 1024;
unsigned ri = 0;
unsigned measNumb = 0;
unsigned graceGroupId = 1;
cmXsReorder_t rV[ rN ];
if( cmFileOpen(&fH,fn,kReadFileFl,p->err.rpt) != kOkFileRC )
@ -2029,10 +2164,27 @@ cmXsRC_t cmXScoreReorder( cmXsH_t h, const cmChar_t* fn )
if((rc = _cmXScoreReorderParseTick(p, b, ln+1, &r.newTick)) != kOkXsRC )
goto errLabel;
// parse the %grace note marker
if((rc = _cmXScoreReorderParseGrace(p, b, ln+1, &r.graceType)) != kOkXsRC )
goto errLabel;
// parse the $pitch marker
if((rc = _cmXScoreReorderParsePitch(p, b, ln+1, &r.pitch )) != kOkXsRC )
goto errLabel;
// process grace notes - these need to be processed separate from
// the _cmXScoreReorderMeas() because grace notes may cross measure boundaries.
if( r.graceType != 0 )
{
r.graceGroupId = graceGroupId;
// if this is an end of a grace note group
if( r.graceType != 'g' && r.graceType != 'b' )
{
graceGroupId += 1;
}
}
// store the record
assert( ri < rN );
@ -2075,6 +2227,8 @@ cmXsRC_t cmXScoreReorder( cmXsH_t h, const cmChar_t* fn )
// resort to force the links to be correct
_cmXScoreSort(p);
_cmXScoreProcessGraceNotes( p );
errLabel:
cmFileClose(&fH);
@ -2608,113 +2762,10 @@ void cmXScoreReport( cmXsH_t h, cmRpt_t* rpt, bool sortFl )
}
}
typedef struct cmXsMidiEvt_str
{
unsigned flags; // k???XsFl
unsigned tick; // start tick
unsigned durTicks; // dur-ticks
unsigned voice; // score voice number
unsigned d0; // MIDI d0 (barNumb)
unsigned d1; // MIDI d1
struct cmXsMidiEvt_str* link;
} cmXsMidiEvt_t;
typedef struct cmXsMidiFile_str
{
cmXsMidiEvt_t* elist;
cmXsMidiEvt_t* eol;
unsigned pitch_min;
unsigned pitch_max;
} cmXsMidiFile_t;
cmXsRC_t _cmXsWriteMidiSvg( cmCtx_t* ctx, cmXScore_t* p, cmXsMidiFile_t* mf, const cmChar_t* dir, const cmChar_t* fn )
{
cmXsRC_t rc = kOkXsRC;
cmSvgH_t svgH = cmSvgNullHandle;
cmXsMidiEvt_t* e = mf->elist;
unsigned noteHeight = 10;
const cmChar_t* svgFn = cmFsMakeFn(dir,fn,"html",NULL);
const cmChar_t* cssFn = cmFsMakeFn(NULL,fn,"css",NULL);
cmChar_t* t0 = NULL; // temporary dynamic string
// create the SVG writer
if( cmSvgWriterAlloc(ctx,&svgH) != kOkSvgRC )
{
rc = cmErrMsg(&p->err,kSvgFailXsRC,"Unable to create the MIDI SVG output file '%s'.",cmStringNullGuard(svgFn));
goto errLabel;
}
// for each MIDI file element
for(; e!=NULL && rc==kOkXsRC; e=e->link)
{
switch( e->flags & (kOnsetXsFl|kBarXsFl|kDampDnXsFl|kDampUpDnXsFl|kSostDnXsFl))
{
// if this is a note
case kOnsetXsFl:
{
const cmChar_t* classLabel = "note";
t0 = cmTsPrintfP(t0,"note_%i%s",e->voice, cmIsFlag(e->flags,kGraceXsFl) ? "_g":"");
if( cmIsFlag(e->flags,kGraceXsFl) )
classLabel = "grace";
if( cmSvgWriterRect(svgH, e->tick, e->d0 * noteHeight, e->durTicks, noteHeight-1, t0 ) != kOkSvgRC )
rc = kSvgFailXsRC;
else
if( cmSvgWriterText(svgH, e->tick + e->durTicks/2, e->d0 * noteHeight + noteHeight/2, cmMidiToSciPitch( e->d0, NULL, 0), "pitch") != kOkSvgRC )
rc = kSvgFailXsRC;
}
break;
// if this is a bar
case kBarXsFl:
{
if( cmSvgWriterLine(svgH, e->tick, 0, e->tick, 127*noteHeight, "bar") != kOkSvgRC )
rc = kSvgFailXsRC;
else
{
if( cmSvgWriterText(svgH, e->tick, 10, t0 = cmTsPrintfP(t0,"%i",e->d0), "text" ) != kOkSvgRC )
rc = kSvgFailXsRC;
}
}
break;
// if this is a pedal event
case kDampDnXsFl:
case kDampUpDnXsFl:
case kSostDnXsFl:
{
const cmChar_t* classLabel = cmIsFlag(e->flags,kSostDnXsFl) ? "sost" : "damp";
unsigned y = (128 + cmIsFlag(e->flags,kSostDnXsFl)?1:0) * noteHeight;
cmSvgWriterRect(svgH, e->tick, y, e->durTicks, noteHeight-1, classLabel);
}
break;
}
}
if( rc != kOkXsRC )
cmErrMsg(&p->err,kSvgFailXsRC,"SVG element insert failed.");
if( rc == kOkXsRC )
if( cmSvgWriterWrite(svgH,cssFn,svgFn) != kOkSvgRC )
rc = cmErrMsg(&p->err,kSvgFailXsRC,"SVG file write to '%s' failed.",cmStringNullGuard(svgFn));
errLabel:
cmSvgWriterFree(&svgH);
cmFsFreeFn(svgFn);
cmFsFreeFn(cssFn);
cmMemFree(t0);
return rc;
}
cmXsRC_t _cmXsWriteMidiFile( cmCtx_t* ctx, cmXScore_t* p, cmXsMidiFile_t* mf, const cmChar_t* dir, const cmChar_t* fn )
cmXsRC_t _cmXsWriteMidiFile( cmCtx_t* ctx, cmXsH_t h, const cmChar_t* dir, const cmChar_t* fn )
{
cmXsRC_t rc = kOkXsRC;
cmXScore_t* p = _cmXScoreHandleToPtr(h);
if( p->partL==NULL || p->partL->measL == NULL )
return rc;
@ -2808,9 +2859,115 @@ cmXsRC_t _cmXsWriteMidiFile( cmCtx_t* ctx, cmXScore_t* p, cmXsMidiFile_t* mf, co
}
void _cmXsPushMidiEvent( cmXScore_t* p, cmXsMidiFile_t* mf, unsigned flags, unsigned tick, unsigned durTick, unsigned voice, unsigned d0, unsigned d1 )
typedef struct cmXsSvgEvt_str
{
cmXsMidiEvt_t* e = cmLhAllocZ(p->lhH,cmXsMidiEvt_t,1);
unsigned flags; // k???XsFl
unsigned tick; // start tick
unsigned durTicks; // dur-ticks
unsigned voice; // score voice number
unsigned d0; // MIDI d0 (barNumb)
unsigned d1; // MIDI d1
struct cmXsSvgEvt_str* link;
} cmXsSvgEvt_t;
typedef struct cmXsMidiFile_str
{
cmXsSvgEvt_t* elist;
cmXsSvgEvt_t* eol;
unsigned pitch_min;
unsigned pitch_max;
} cmXsMidiFile_t;
cmXsRC_t _cmXsWriteMidiSvg( cmCtx_t* ctx, cmXScore_t* p, cmXsMidiFile_t* mf, const cmChar_t* dir, const cmChar_t* fn )
{
cmXsRC_t rc = kOkXsRC;
cmSvgH_t svgH = cmSvgNullHandle;
cmXsSvgEvt_t* e = mf->elist;
unsigned noteHeight = 10;
const cmChar_t* svgFn = cmFsMakeFn(dir,fn,"html",NULL);
const cmChar_t* cssFn = cmFsMakeFn(NULL,fn,"css",NULL);
cmChar_t* t0 = NULL; // temporary dynamic string
// create the SVG writer
if( cmSvgWriterAlloc(ctx,&svgH) != kOkSvgRC )
{
rc = cmErrMsg(&p->err,kSvgFailXsRC,"Unable to create the MIDI SVG output file '%s'.",cmStringNullGuard(svgFn));
goto errLabel;
}
// for each MIDI file element
for(; e!=NULL && rc==kOkXsRC; e=e->link)
{
switch( e->flags & (kOnsetXsFl|kBarXsFl|kDampDnXsFl|kDampUpDnXsFl|kSostDnXsFl))
{
// if this is a note
case kOnsetXsFl:
{
const cmChar_t* classLabel = "note";
t0 = cmTsPrintfP(t0,"note_%i%s",e->voice, cmIsFlag(e->flags,kGraceXsFl) ? "_g":"");
if( cmIsFlag(e->flags,kGraceXsFl) )
classLabel = "grace";
if( cmSvgWriterRect(svgH, e->tick, e->d0 * noteHeight, e->durTicks, noteHeight-1, t0 ) != kOkSvgRC )
rc = kSvgFailXsRC;
else
if( cmSvgWriterText(svgH, e->tick + e->durTicks/2, e->d0 * noteHeight + noteHeight/2, cmMidiToSciPitch( e->d0, NULL, 0), "pitch") != kOkSvgRC )
rc = kSvgFailXsRC;
}
break;
// if this is a bar
case kBarXsFl:
{
if( cmSvgWriterLine(svgH, e->tick, 0, e->tick, 127*noteHeight, "bar") != kOkSvgRC )
rc = kSvgFailXsRC;
else
{
if( cmSvgWriterText(svgH, e->tick, 10, t0 = cmTsPrintfP(t0,"%i",e->d0), "text" ) != kOkSvgRC )
rc = kSvgFailXsRC;
}
}
break;
// if this is a pedal event
case kDampDnXsFl:
case kDampUpDnXsFl:
case kSostDnXsFl:
{
const cmChar_t* classLabel = cmIsFlag(e->flags,kSostDnXsFl) ? "sost" : "damp";
unsigned y = (128 + cmIsFlag(e->flags,kSostDnXsFl)?1:0) * noteHeight;
cmSvgWriterRect(svgH, e->tick, y, e->durTicks, noteHeight-1, classLabel);
}
break;
}
}
if( rc != kOkXsRC )
cmErrMsg(&p->err,kSvgFailXsRC,"SVG element insert failed.");
if( rc == kOkXsRC )
if( cmSvgWriterWrite(svgH,cssFn,svgFn) != kOkSvgRC )
rc = cmErrMsg(&p->err,kSvgFailXsRC,"SVG file write to '%s' failed.",cmStringNullGuard(svgFn));
errLabel:
cmSvgWriterFree(&svgH);
cmFsFreeFn(svgFn);
cmFsFreeFn(cssFn);
cmMemFree(t0);
return rc;
}
void _cmXsPushSvgEvent( cmXScore_t* p, cmXsMidiFile_t* mf, unsigned flags, unsigned tick, unsigned durTick, unsigned voice, unsigned d0, unsigned d1 )
{
cmXsSvgEvt_t* e = cmLhAllocZ(p->lhH,cmXsSvgEvt_t,1);
e->flags = flags;
e->tick = tick;
e->durTicks = durTick;
@ -2832,17 +2989,14 @@ void _cmXsPushMidiEvent( cmXScore_t* p, cmXsMidiFile_t* mf, unsigned flags, unsi
mf->eol = e;
}
cmXsRC_t _cmXScoreGenMidi( cmCtx_t* ctx, cmXsH_t h, const cmChar_t* dir, const cmChar_t* fn )
cmXsRC_t _cmXScoreGenSvg( cmCtx_t* ctx, cmXsH_t h, const cmChar_t* dir, const cmChar_t* fn )
{
cmXScore_t* p = _cmXScoreHandleToPtr(h);
cmXsPart_t* pp = p->partL;
cmXsMidiFile_t mf;
memset(&mf,0,sizeof(mf));
// assign durations to pedal down events
_cmXScoreProcessPedals(p);
for(; pp!=NULL; pp=pp->link)
{
const cmXsMeas_t* meas = pp->measL;
@ -2856,7 +3010,7 @@ cmXsRC_t _cmXScoreGenMidi( cmCtx_t* ctx, cmXsH_t h, const cmChar_t* dir, const c
if( cmIsFlag(note->flags,kMetronomeXsFl) )
{
// set BPM as d0
_cmXsPushMidiEvent(p,&mf,note->flags,note->tick,0,0,note->duration,0);
_cmXsPushSvgEvent(p,&mf,note->flags,note->tick,0,0,note->duration,0);
continue;
}
@ -2872,14 +3026,14 @@ cmXsRC_t _cmXScoreGenMidi( cmCtx_t* ctx, cmXsH_t h, const cmChar_t* dir, const c
for(; tn!=NULL; tn=tn->tied)
durTick += tn->duration;
}
_cmXsPushMidiEvent(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);
continue;
}
// if this is a bar event
if( cmIsFlag(note->flags,kBarXsFl) )
{
_cmXsPushMidiEvent(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);
continue;
}
@ -2887,7 +3041,7 @@ cmXsRC_t _cmXScoreGenMidi( cmCtx_t* ctx, cmXsH_t h, const cmChar_t* dir, const c
if( cmIsFlag(note->flags,kDampDnXsFl|kDampUpDnXsFl|kSostDnXsFl) )
{
unsigned d0 = cmIsFlag(note->flags,kSostDnXsFl) ? kSostenutoCtlMdId : kSustainCtlMdId;
_cmXsPushMidiEvent(p,&mf,note->flags,note->tick,note->duration,0,d0,127);
_cmXsPushSvgEvent(p,&mf,note->flags,note->tick,note->duration,0,d0,127);
continue;
}
@ -2895,7 +3049,6 @@ cmXsRC_t _cmXScoreGenMidi( cmCtx_t* ctx, cmXsH_t h, const cmChar_t* dir, const c
}
}
_cmXsWriteMidiFile( ctx, p, &mf, dir, fn );
return _cmXsWriteMidiSvg( ctx, p, &mf, dir, fn );
@ -2916,6 +3069,9 @@ cmXsRC_t cmXScoreTest(
if( reorderFn != NULL )
cmXScoreReorder(h,reorderFn);
// assign durations to pedal down events
_cmXScoreProcessPedals(_cmXScoreHandleToPtr(h));
if( csvOutFn != NULL )
{
@ -2943,7 +3099,9 @@ cmXsRC_t cmXScoreTest(
{
cmFileSysPathPart_t* pp = cmFsPathParts(midiOutFn);
_cmXScoreGenMidi( ctx, h, pp->dirStr, pp->fnStr );
_cmXScoreGenSvg( ctx, h, pp->dirStr, pp->fnStr );
_cmXsWriteMidiFile(ctx, h, pp->dirStr, pp->fnStr );
cmFsFreePathParts(pp);