libcw/cwPianoScore.cpp

535 lines
13 KiB
C++

#include "cwCommon.h"
#include "cwLog.h"
#include "cwCommonImpl.h"
#include "cwMem.h"
#include "cwObject.h"
#include "cwPianoScore.h"
#include "cwMidi.h"
#include "cwTime.h"
#include "cwFile.h"
namespace cw
{
namespace score
{
typedef struct score_str
{
event_t* base;
event_t* end;
unsigned maxLocId;
event_t** uid_mapA;
unsigned uid_mapN;
unsigned min_uid;
} score_t;
score_t* _handleToPtr(handle_t h)
{
return handleToPtr<handle_t,score_t>(h);
}
rc_t _destroy( score_t* p )
{
rc_t rc = kOkRC;
event_t* e = p->base;
while( e != nullptr )
{
event_t* e0 = e->link;
mem::free(e);
e = e0;
}
return rc;
}
unsigned _scan_to_end_of_field( const char* lineBuf, unsigned buf_idx, unsigned bufCharCnt )
{
for(; buf_idx < bufCharCnt; ++buf_idx )
{
if( lineBuf[buf_idx] == '"' )
{
for(++buf_idx; buf_idx < bufCharCnt; ++buf_idx)
if( lineBuf[buf_idx] == '"' )
break;
}
if( lineBuf[buf_idx] == ',')
break;
}
return buf_idx;
}
rc_t _parse_csv_double( const char* lineBuf, unsigned bfi, unsigned efi, double &valueRef )
{
errno = 0;
valueRef = strtod(lineBuf+bfi,nullptr);
if( errno != 0 )
return cwLogError(kOpFailRC,"CSV String to number conversion failed.");
return kOkRC;
}
rc_t _parse_csv_unsigned( const char* lineBuf, unsigned bfi, unsigned efi, unsigned &valueRef )
{
rc_t rc;
double v;
if((rc = _parse_csv_double(lineBuf,bfi,efi,v)) == kOkRC )
valueRef = (unsigned)v;
return rc;
}
rc_t _parse_csv_line( score_t* p, event_t* e, char* line_buf, unsigned lineBufCharCnt )
{
enum
{
kMeas_FIdx,
kIndex_FIdx,
kVoice_FIdx,
kLoc_FIdx,
kTick_FIdx,
kSec_FIdx,
kDur_FIdx,
kRval_FIdx,
kSPitch_FIdx,
kDMark_FIdx,
kDLevel_FIdx,
kStatus_FIdx,
kD0_FIdx,
kD1_FIdx,
kBar_FIdx,
kSection_FIdx,
kBpm_FIdx,
kGrace_FIdx,
kPedal_FIdx,
kMax_FIdx
};
rc_t rc = kOkRC;
unsigned bfi = 0;
unsigned efi = 0;
unsigned field_idx = 0;
for(field_idx=0; field_idx != kMax_FIdx; ++field_idx)
{
if((efi = _scan_to_end_of_field(line_buf,efi,lineBufCharCnt)) == kInvalidIdx )
{
rc = cwLogError( rc, "End of field scan failed");
goto errLabel;
}
if( bfi != efi )
{
switch( field_idx )
{
case kLoc_FIdx:
rc = _parse_csv_unsigned( line_buf, bfi, efi, e->loc );
break;
case kSec_FIdx:
rc = _parse_csv_double( line_buf, bfi, efi, e->sec );
break;
case kStatus_FIdx:
rc = _parse_csv_unsigned( line_buf, bfi, efi, e->status );
break;
case kD0_FIdx:
rc = _parse_csv_unsigned( line_buf, bfi, efi, e->d0 );
break;
case kD1_FIdx:
rc = _parse_csv_unsigned( line_buf, bfi, efi, e->d1 );
break;
default:
break;
}
}
bfi = efi + 1;
efi = efi + 1;
}
errLabel:
return rc;
}
rc_t _parse_csv( score_t* p, const char* fn )
{
rc_t rc;
file::handle_t fH;
unsigned line_count = 0;
char* lineBufPtr = nullptr;
unsigned lineBufCharCnt = 0;
event_t* e = nullptr;
if((rc = file::open( fH, fn, file::kReadFl )) != kOkRC )
{
rc = cwLogError( rc, "Piano score file open failed on '%s'.",cwStringNullGuard(fn));
goto errLabel;
}
if((rc = file::lineCount(fH,&line_count)) != kOkRC )
{
rc = cwLogError( rc, "Line count query failed on '%s'.",cwStringNullGuard(fn));
goto errLabel;
}
p->min_uid = kInvalidId;
p->uid_mapN = 0;
for(unsigned line=0; line<line_count; ++line)
if( line > 0 ) // skip column title line
{
if((rc = getLineAuto( fH, &lineBufPtr, &lineBufCharCnt )) != kOkRC )
{
rc = cwLogError( rc, "Line read failed on '%s' line number '%i'.",cwStringNullGuard(fn),line+1);
goto errLabel;
}
e = mem::allocZ<event_t>();
if((rc = _parse_csv_line( p, e, lineBufPtr, lineBufCharCnt )) != kOkRC )
{
mem::release(e);
rc = cwLogError( rc, "Line parse failed on '%s' line number '%i'.",cwStringNullGuard(fn),line+1);
goto errLabel;
}
// assign the UID
e->uid = line;
// link the event into the event list
if( p->end != nullptr )
p->end->link = e;
else
p->base = e;
p->end = e;
// track the max 'loc' id
if( e->loc > p->maxLocId )
p->maxLocId = e->loc;
if( p->min_uid == kInvalidId || e->uid < p->min_uid )
p->min_uid = e->uid;
p->uid_mapN += 1;
}
errLabel:
mem::release(lineBufPtr);
file::close(fH);
return rc;
}
rc_t _parse_event_list( score_t* p, const object_t* cfg )
{
rc_t rc;
const object_t* eventL;
if((rc = cfg->getv( "evtL", eventL )) != kOkRC || eventL->is_list()==false )
rc = cwLogError( rc, "Unable to locate the 'evtL' configuration tag.");
else
{
//unsigned eventN = eventL->child_count();
const object_t* evt_cfg = eventL->next_child_ele(nullptr);
for(unsigned i=0; evt_cfg != nullptr; evt_cfg = eventL->next_child_ele(evt_cfg),++i)
{
const char* sci_pitch = nullptr;
const char* dmark = nullptr;
const char* grace_mark = nullptr;
//const object_t* evt_cfg = eventL->next_child_ele(i);
event_t* e = mem::allocZ<event_t>();
if((rc = evt_cfg->getv( "meas", e->meas,
"voice", e->voice,
"loc", e->loc,
"tick", e->tick,
"sec", e->sec )) != kOkRC )
{
rc = cwLogError(rc,"Score parse failed on required event fields at event index:%i.",i);
goto errLabel;
}
if((rc = evt_cfg->getv_opt( "rval", e->rval,
"sci_pitch", sci_pitch,
"dmark", dmark,
"dlevel", e->dlevel,
"status", e->status,
"d0", e->d0,
"d1", e->d1,
"grace", grace_mark,
"section", e->section,
"bpm", e->bpm,
"bar", e->bar)) != kOkRC )
{
rc = cwLogError(rc,"Score parse failed on optional event fields at event index:%i.",i);
goto errLabel;
}
if( sci_pitch != nullptr )
strncpy(e->sci_pitch,sci_pitch,sizeof(e->sci_pitch));
if( dmark != nullptr )
strncpy(e->dmark,dmark,sizeof(e->dmark));
if( grace_mark != nullptr )
strncpy(e->grace_mark,grace_mark,sizeof(e->grace_mark));
// assign the UID
e->uid = i;
// link the event into the event list
if( p->end != nullptr )
p->end->link = e;
else
p->base = e;
p->end = e;
// track the max 'loc' id
if( e->loc > p->maxLocId )
p->maxLocId = e->loc;
}
}
errLabel:
return rc;
}
const event_t* _uid_to_event( score_t* p, unsigned uid )
{
const event_t* e = p->base;
for(; e!=nullptr; e=e->link)
if( e->uid == uid )
return e;
return nullptr;
}
const event_t* _loc_to_event( score_t* p, unsigned loc )
{
const event_t* e = p->base;
for(; e!=nullptr; e=e->link)
if( e->loc == loc )
return e;
return nullptr;
}
}
}
cw::rc_t cw::score::create( handle_t& hRef, const char* fn )
{
rc_t rc;
object_t* cfg = nullptr;
score_t* p = nullptr;
if((rc = destroy(hRef)) != kOkRC )
return rc;
// parse the cfg file
/*
if((rc = objectFromFile( fn, cfg )) != kOkRC )
{
rc = cwLogError(rc,"Score parse failed on file: '%s'.", fn);
goto errLabel;
}
rc = create(hRef,cfg);
*/
p = mem::allocZ< score_t >();
rc = _parse_csv(p,fn);
hRef.set(p);
//errLabel:
if( cfg != nullptr )
cfg->free();
if( rc != kOkRC )
destroy(hRef);
return rc;
}
cw::rc_t cw::score::create( handle_t& hRef, const object_t* cfg )
{
rc_t rc;
if((rc = destroy(hRef)) != kOkRC )
return rc;
score_t* p = mem::allocZ< score_t >();
// parse the event list
if((rc = _parse_event_list(p, cfg)) != kOkRC )
{
rc = cwLogError(rc,"Score event list parse failed.");
goto errLabel;
}
hRef.set(p);
errLabel:
if( rc != kOkRC )
destroy(hRef);
return rc;
}
cw::rc_t cw::score::destroy( handle_t& hRef )
{
rc_t rc = kOkRC;
if( !hRef.isValid() )
return rc;
score_t* p = _handleToPtr(hRef);
if((rc = _destroy(p)) != kOkRC )
return rc;
mem::release(p);
hRef.clear();
return rc;
}
unsigned cw::score::event_count( handle_t h )
{
score_t* p = _handleToPtr(h);
unsigned n = 0;
for(event_t* e=p->base; e!=nullptr; e=e->link)
++n;
return n;
}
const cw::score::event_t* cw::score::base_event( handle_t h )
{
score_t* p = _handleToPtr(h);
return p->base;
}
cw::rc_t cw::score::event_to_string( handle_t h, unsigned uid, char* buf, unsigned buf_byte_cnt )
{
score_t* p = _handleToPtr(h);
const event_t* e = nullptr;
rc_t rc = kOkRC;
if((e = _uid_to_event( p, uid )) == nullptr )
rc = cwLogError(kInvalidIdRC,"A score event with uid=%i does not exist.",uid);
else
{
const char* sci_pitch = strlen(e->sci_pitch) ? e->sci_pitch : "";
const char* dyn_mark = strlen(e->dmark) ? e->dmark : "";
const char* grace_mark= strlen(e->grace_mark) ? e->grace_mark : "";
if( midi::isSustainPedal( e->status, e->d0 ) )
sci_pitch = midi::isPedalDown( e->status, e->d0, e->d1 ) ? "Dv" : "D^";
else
if( midi::isSostenutoPedal( e->status, e->d0 ) )
sci_pitch = midi::isPedalDown( e->status, e->d0, e->d1 ) ? "Sv" : "S^";
snprintf(buf,buf_byte_cnt,"uid:%5i meas:%4i loc:%4i tick:%8i sec:%8.3f %4s %5s %3s (st:0x%02x d1:0x%02x d0:0x%02x)", e->uid, e->meas, e->loc, e->tick, e->sec, sci_pitch, dyn_mark, grace_mark, e->status, e->d0, e->d1 );
}
return rc;
}
unsigned cw::score::loc_count( handle_t h )
{
score_t* p = _handleToPtr(h);
return p->maxLocId;
}
bool cw::score::is_loc_valid( handle_t h, unsigned locId )
{
score_t* p = _handleToPtr(h);
return locId < p->maxLocId;
}
unsigned cw::score::loc_to_measure( handle_t h, unsigned locId )
{
score_t* p = _handleToPtr(h);
const event_t* e;
if((e = _loc_to_event(p,locId)) == nullptr )
return 0;
return kInvalidId;
}
unsigned cw::score::loc_to_next_note_on_measure( handle_t h, unsigned locId )
{
score_t* p = _handleToPtr(h);
const event_t* e = _loc_to_event(p,locId);
while( e != nullptr )
if( midi::isNoteOn(e->status))
return e->meas;
return kInvalidId;
}
const cw::score::event_t* cw::score::uid_to_event( handle_t h, unsigned uid )
{
//hscore_t* p = _handleToPtr(h);
return nullptr;
}
cw::rc_t cw::score::test( const object_t* cfg )
{
handle_t h;
rc_t rc = kOkRC;
const char* fname = nullptr;
if((rc = cfg->getv( "filename", fname )) != kOkRC )
{
rc = cwLogError(rc,"Arg. parsing failed.");
goto errLabel;
}
if((rc = create( h, fname )) != kOkRC )
{
rc = cwLogError(rc,"Score create failed.");
goto errLabel;
}
cwLogInfo("Score created from '%s'.",cwStringNullGuard(fname));
errLabel:
destroy(h);
return rc;
}