393 lines
11 KiB
C++
393 lines
11 KiB
C++
//| Copyright: (C) 2020-2024 Kevin Larke <contact AT larke DOT org>
|
|
//| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file.
|
|
#include "cwCommon.h"
|
|
#include "cwLog.h"
|
|
#include "cwCommonImpl.h"
|
|
#include "cwTest.h"
|
|
#include "cwMem.h"
|
|
#include "cwTime.h"
|
|
#include "cwObject.h"
|
|
#include "cwText.h"
|
|
#include "cwTextBuf.h"
|
|
#include "cwThread.h"
|
|
|
|
#include "cwMidi.h"
|
|
#include "cwMidiDecls.h"
|
|
#include "cwMidiFile.h"
|
|
#include "cwMidiDevice.h"
|
|
#include "cwMidiDeviceTest.h"
|
|
|
|
namespace cw
|
|
{
|
|
namespace midi
|
|
{
|
|
namespace device
|
|
{
|
|
typedef struct test_msg_str
|
|
{
|
|
msg_t msg;
|
|
time::spec_t t;
|
|
} test_msg_t;
|
|
|
|
typedef struct test_str
|
|
{
|
|
test_msg_t* msgA;
|
|
unsigned msgN;
|
|
unsigned msg_idx;
|
|
unsigned file_dev_idx;
|
|
unsigned port_idx;
|
|
} test_t;
|
|
|
|
rc_t _test_create( test_t*& pRef )
|
|
{
|
|
rc_t rc = kOkRC;
|
|
test_t* p = nullptr;
|
|
|
|
p = mem::allocZ<test_t>();
|
|
|
|
p->msgN = 0;
|
|
p->msgA = nullptr;
|
|
p->file_dev_idx = kInvalidIdx;
|
|
p->port_idx = kInvalidIdx;
|
|
pRef = p;
|
|
|
|
return rc;
|
|
}
|
|
|
|
rc_t _test_open( test_t* p, unsigned fileDevIdx, unsigned portIdx, const char* fname )
|
|
{
|
|
rc_t rc = kOkRC;
|
|
|
|
if( p->file_dev_idx == kInvalidIdx )
|
|
{
|
|
file::handle_t mfH;
|
|
|
|
if((rc = open(mfH,fname)) != kOkRC )
|
|
goto errLabel;
|
|
|
|
p->msgN = msgCount(mfH);
|
|
p->msgA = mem::allocZ<test_msg_t>(p->msgN);
|
|
p->file_dev_idx = fileDevIdx;
|
|
p->port_idx = portIdx;
|
|
|
|
close(mfH);
|
|
|
|
}
|
|
|
|
errLabel:
|
|
return rc;
|
|
}
|
|
|
|
rc_t _test_destroy( test_t* p )
|
|
{
|
|
if( p != nullptr )
|
|
{
|
|
mem::release(p->msgA);
|
|
mem::release(p);
|
|
}
|
|
|
|
return kOkRC;
|
|
}
|
|
|
|
void _test_callback( void* cbArg, const packet_t* pktArray, unsigned pktCnt )
|
|
{
|
|
unsigned i,j;
|
|
time::spec_t cur_time = time::current_time();
|
|
|
|
for(i=0; i<pktCnt; ++i)
|
|
{
|
|
const packet_t* p = pktArray + i;
|
|
|
|
test_t* t = (test_t*)cbArg;
|
|
|
|
for(j=0; j<p->msgCnt; ++j)
|
|
if( p->msgArray != NULL )
|
|
{
|
|
if( t->msg_idx < t->msgN && p->devIdx == t->file_dev_idx && p->portIdx == t->port_idx )
|
|
{
|
|
t->msgA[t->msg_idx].msg = p->msgArray[j];
|
|
t->msgA[t->msg_idx].t = cur_time;
|
|
t->msg_idx += 1;
|
|
}
|
|
|
|
if( isNoteOn(p->msgArray[j].status,p->msgArray[j].d1) )
|
|
cwLogPrint("%ld %ld %i 0x%x %i %i\n", p->msgArray[j].timeStamp.tv_sec, p->msgArray[j].timeStamp.tv_nsec, p->msgArray[j].ch, p->msgArray[j].status,p->msgArray[j].d0, p->msgArray[j].d1);
|
|
}
|
|
else
|
|
{
|
|
cwLogPrint("0x%x ",p->sysExMsg[j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool _test_is_not_equal( const file::trackMsg_t* tmsg, const test_msg_t& m )
|
|
{ return tmsg->status != m.msg.status || tmsg->u.chMsgPtr->d0 != m.msg.d0 || tmsg->u.chMsgPtr->d1 != m.msg.d1; }
|
|
|
|
bool _test_is_equal( const file::trackMsg_t* tmsg, const test_msg_t& m )
|
|
{ return !_test_is_not_equal(tmsg,m); }
|
|
|
|
void _test_print( const file::trackMsg_t* tmsg, const test_msg_t& m )
|
|
{
|
|
const char* eql_mark = _test_is_equal(tmsg,m) ? "" : "*";
|
|
cwLogPrint("%2i 0x%2x %3i %3i : %2i 0x%2x %3i %3i : %s\n",tmsg->u.chMsgPtr->ch, tmsg->status, tmsg->u.chMsgPtr->d0, tmsg->u.chMsgPtr->d1, m.msg.ch, m.msg.status, m.msg.d0, m.msg.d1, eql_mark);
|
|
}
|
|
|
|
void _test_print( unsigned long long t0, const file::trackMsg_t* tmsg, unsigned long long t1, const test_msg_t& m, unsigned dt )
|
|
{
|
|
const char* eql_mark = _test_is_equal(tmsg,m) ? "" : "*";
|
|
cwLogPrint("%6llu %2i 0x%2x %3i %3i : %6llu %2i 0x%2x %3i %3i : %6i : %s\n",t0, tmsg->u.chMsgPtr->ch, tmsg->status, tmsg->u.chMsgPtr->d0, tmsg->u.chMsgPtr->d1, t1, m.msg.ch, m.msg.status, m.msg.d0, m.msg.d1, dt, eql_mark);
|
|
}
|
|
|
|
|
|
|
|
rc_t _test_analyze( test_t* p, const char* fname )
|
|
{
|
|
rc_t rc = kOkRC;
|
|
|
|
file::handle_t mfH;
|
|
const file::trackMsg_t** tmsgA;
|
|
unsigned tmsgN;
|
|
unsigned max_diff_micros = 0;
|
|
unsigned sum_micros = 0;
|
|
unsigned sum_cnt = 0;
|
|
unsigned i0 = kInvalidIdx;
|
|
unsigned j0 = kInvalidIdx;
|
|
|
|
// open the MIDI file under test
|
|
if((rc = open(mfH,fname)) != kOkRC )
|
|
goto errLabel;
|
|
|
|
tmsgA = msgArray(mfH);
|
|
tmsgN = msgCount(mfH);
|
|
|
|
cwLogPrint("file:%i test:%i\n",tmsgN,p->msg_idx);
|
|
|
|
// for file trk msg and recorded msg
|
|
for(unsigned i=0,j=0; i<tmsgN && j<p->msg_idx; ++i)
|
|
{
|
|
// skip non-channel messages
|
|
if( isChStatus(tmsgA[i]->status))
|
|
{
|
|
|
|
unsigned long long d0 = 0;
|
|
unsigned long long d1 = 0;
|
|
unsigned dt = 0;
|
|
|
|
// if there is a previous file msg
|
|
if( i0 != kInvalidIdx )
|
|
{
|
|
// get the elapsed time between the cur and prev file msg
|
|
d0 = tmsgA[i]->amicro - tmsgA[i0]->amicro;
|
|
|
|
// if there is a previous recorded msg
|
|
if( j0 != kInvalidIdx )
|
|
{
|
|
// get the time elapsed between the cur and prev recorded msg
|
|
d1 = time::elapsedMicros(p->msgA[j0].t,p->msgA[j].t);
|
|
|
|
dt = (unsigned)(d0>d1 ? d0-d1 : d1-d0);
|
|
|
|
sum_micros += dt;
|
|
sum_cnt += 1;
|
|
|
|
if( dt > max_diff_micros )
|
|
max_diff_micros = dt;
|
|
|
|
}
|
|
}
|
|
|
|
_test_print(d0, tmsgA[i], d1, p->msgA[j], dt );
|
|
|
|
i0 = i;
|
|
j0 = j;
|
|
j += 1;
|
|
}
|
|
}
|
|
|
|
cwLogPrint("max diff:%i avg diff:%i micros\n",max_diff_micros,sum_cnt==0 ? 0 : sum_micros/sum_cnt);
|
|
|
|
errLabel:
|
|
close(mfH);
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
cw::rc_t cw::midi::device::test( const object_t* cfg )
|
|
{
|
|
rc_t rc = kOkRC;
|
|
const char* testMidiFname = nullptr;
|
|
const char* fileDevName = nullptr;
|
|
const char* testFileLabel = nullptr;
|
|
bool testFileEnableFl = false;
|
|
const object_t* file_ports = nullptr;
|
|
test_t* t = nullptr;
|
|
bool quit_fl = false;
|
|
char ch;
|
|
handle_t h;
|
|
|
|
|
|
if((rc = _test_create( t )) != kOkRC )
|
|
{
|
|
rc = cwLogError(rc,"Test create failed.");
|
|
goto errLabel;
|
|
}
|
|
|
|
if((rc = create(h,_test_callback,t,cfg)) != kOkRC )
|
|
{
|
|
rc = cwLogError(rc,"MIDI dev create failed.");
|
|
goto errLabel;
|
|
}
|
|
|
|
report(h);
|
|
|
|
if((rc = cfg->getv("fileDevName", fileDevName,
|
|
"testFileLabel", testFileLabel,
|
|
"testFileEnableFl", testFileEnableFl,
|
|
"file_ports", file_ports)) != kOkRC )
|
|
{
|
|
rc = cwLogError(rc,"Parse 'file_ports' failed.");
|
|
goto errLabel;
|
|
}
|
|
|
|
|
|
// for each file dev port
|
|
for(unsigned i=0; i<file_ports->child_count(); ++i)
|
|
{
|
|
const char* fname = nullptr;
|
|
const char* label = nullptr;
|
|
bool enable_fl = false;
|
|
unsigned fileDevIdx = kInvalidIdx;
|
|
unsigned portIdx = kInvalidIdx;
|
|
|
|
// parse the file/label pair
|
|
if((rc = file_ports->child_ele(i)->getv("file",fname,
|
|
"enable_fl", enable_fl,
|
|
"label",label)) != kOkRC )
|
|
{
|
|
rc = cwLogError(rc,"Parse failed on 'file_port' index %i.",i);
|
|
goto errLabel;
|
|
}
|
|
|
|
// get the file device name
|
|
if((fileDevIdx = nameToIndex(h,fileDevName)) == kInvalidIdx )
|
|
{
|
|
rc = cwLogError(kInvalidArgRC,"Unable to locate the MIDI file device '%s'.",cwStringNullGuard(fileDevName));
|
|
goto errLabel;
|
|
}
|
|
|
|
// get the file/label port index
|
|
if((portIdx = portNameToIndex(h,fileDevIdx,kInMpFl,label)) == kInvalidIdx )
|
|
{
|
|
rc = cwLogError(kInvalidArgRC,"Unable to locate the port '%s' on device '%s'.",cwStringNullGuard(label),cwStringNullGuard(fileDevName));
|
|
goto errLabel;
|
|
}
|
|
|
|
// open the MIDI file on this port
|
|
if((rc = openMidiFile(h,fileDevIdx,portIdx,fname)) != kOkRC )
|
|
{
|
|
rc = cwLogError(rc,"MIDI file open failed on '%s'.",fname);
|
|
goto errLabel;
|
|
}
|
|
|
|
if((rc = portEnable(h,fileDevIdx,kInMpFl,portIdx,enable_fl)) != kOkRC )
|
|
{
|
|
rc = cwLogError(rc,"MIDI file enable failed on '%s'.",fname);
|
|
goto errLabel;
|
|
}
|
|
|
|
// if this is the test port
|
|
if( testFileEnableFl && testFileLabel != nullptr && textIsEqual(label,testFileLabel) )
|
|
{
|
|
testMidiFname = fname;
|
|
|
|
cwLogInfo("Test label:%s device:%i fname:%s",testFileLabel,fileDevIdx,fname);
|
|
|
|
if((rc = _test_open(t,fileDevIdx,portIdx,fname)) != kOkRC )
|
|
{
|
|
rc = cwLogError(rc,"Test create failed.");
|
|
goto errLabel;
|
|
}
|
|
}
|
|
}
|
|
|
|
cwLogInfo("menu: (q)uit (b)egin (s)top (p)ause (u)npause (n)ote-on\n");
|
|
|
|
while( !quit_fl)
|
|
{
|
|
ch = getchar();
|
|
|
|
switch(ch)
|
|
{
|
|
case 'q':
|
|
quit_fl = true;
|
|
break;
|
|
|
|
case 'b':
|
|
cwLogPrint("starting ...\n");
|
|
start(h);
|
|
break;
|
|
|
|
case 's':
|
|
cwLogPrint("stopping ...\n");
|
|
stop(h);
|
|
break;
|
|
|
|
case 'p':
|
|
cwLogPrint("pausing ...\n");
|
|
pause(h,true);
|
|
break;
|
|
|
|
case 'u':
|
|
cwLogPrint("unpausing ...\n");
|
|
pause(h,false);
|
|
break;
|
|
|
|
case 'n':
|
|
cwLogPrint("sending ...\n");
|
|
send(h,2,0,0x90,60,60);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
errLabel:
|
|
|
|
if( testMidiFname != nullptr )
|
|
_test_analyze(t,testMidiFname);
|
|
|
|
destroy(h);
|
|
_test_destroy(t);
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
cw::rc_t cw::midi::device::testReport()
|
|
{
|
|
rc_t rc = kOkRC;
|
|
textBuf::handle_t tbH;
|
|
handle_t h;
|
|
|
|
if((rc = create(h,nullptr,nullptr,nullptr,0,"test_report")) != kOkRC )
|
|
return rc;
|
|
|
|
// create a text buffer to hold the MIDI system report text
|
|
if((rc = textBuf::create(tbH)) != kOkRC )
|
|
goto errLabel;
|
|
|
|
// generate and print the MIDI system report
|
|
report(h,tbH);
|
|
cwLogInfo("%s",textBuf::text(tbH));
|
|
|
|
errLabel:
|
|
textBuf::destroy(tbH);
|
|
destroy(h);
|
|
return rc;
|
|
|
|
}
|