#include <cstdio>
#include <cstring>
#include <algorithm>
#include <chrono>
#include <thread>
#include "qmpmidiplay.hpp"
#ifdef _WIN32
#define NOMINMAX
#include <windows.h>
uint64_t pf;
#endif
CMidiPlayer *CMidiPlayer::ref = nullptr;
bool CMidiPlayer::processEvent(const SEvent *e)
{
SEvent fe(*e);
fe.flags &= 0xfe;
if (tceptr >= eorder.size() - 1 || getEvent(tceptr + 1)->time > e->time)
fe.flags |= 0x01;
for (int i = 0; i < 16; ++i)
if (eventHandlerCB[i])
eventHandlerCB[i]->callBack((void *)&fe, eventHandlerCBuserdata[i]);
for (auto i = event_handlers.begin(); i != event_handlers.end(); ++i)
if (!std::get<2>(i->second))
std::get<0>(i->second)((void *)e, std::get<1>(i->second));
uint8_t ch = e->type & 0x0F;
if ((e->type & 0xF0) < 0xF0)
levtt[ch] = std::chrono::system_clock::now();
switch (e->type & 0xF0)
{
case 0x80://Note off
return true;
case 0x90://Note on
if ((mute >> ch) & 1 && e->p2)
return false; //muted
if (solo && !((solo >> ch) & 1) && e->p2)
return false; //excluded by solo flags
return true;
case 0xB0://CC
if (e->p1 == 100)
rpnid[ch] = e->p2;
if (e->p1 == 6)
rpnval[ch] = e->p2;
if (~rpnid[ch] && ~rpnval[ch])
{
if (rpnid[ch] == 0)
{
mididev[mappedoutput[ch]].dev->rpnMessage(ch, 0, rpnval[ch] << 7);
pbr[ch] = rpnval[ch];
}
rpnid[ch] = rpnval[ch] = -1;
}
chstatus[ch][e->p1] = e->p2;
return true;
case 0xC0://PC
chstatus[ch][128] = e->p1;
return true;
case 0xE0://PW
pbv[ch] = (e->p1 | (e->p2 << 7)) & 0x3FFF;
return true;
case 0xF0://Meta/SysEx
if ((e->type & 0x0F) == 0x0F)
{
switch (e->p1)
{
case 0x51:
{
ctempo = 0;
#if (__BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__)||defined(_WIN32)
std::string x(e->str);
std::reverse(x.begin(), x.end());
memcpy(&ctempo, x.data(), 3);
#else
memcpy(&ctempo, e->str.data(), 3);
ctempo >>= 8;
#endif
dpt = ctempo * 1000. / divs;
ttime = std::chrono::high_resolution_clock::now();
ttick = getTick();
}
break;
case 0x58:
ctsn = (uint32_t)e->str[0];
ctsd = 1 << ((uint32_t)e->str[1]);
break;
case 0x59:
if (e->str.length() == 2)
cks = (e->str[0] << 8u) | e->str[1];
break;
case 0x01:
case 0x02:
case 0x03:
case 0x04:
case 0x05:
case 0x06:
case 0x07:
//if(e->str)puts(e->str);
break;
}
}
if (((e->type & 0x0F) == 0x00 || (e->type & 0x0F) == 07) && sendSysEx)
for (auto &i : mididev)
if (i.refcnt)
i.dev->extendedMessage(uint32_t(e->str.length()), e->str.c_str());
return false;
}
return false;
}
void CMidiPlayer::processEventStub(const SEvent *e)
{
switch (e->type & 0xF0)
{
case 0xB0://CC
if (e->p1 == 100)
rpnid[e->type & 0x0F] = e->p2;
if (e->p1 == 6)
rpnval[e->type & 0x0F] = e->p2;
if (~rpnid[e->type & 0x0F] && ~rpnval[e->type & 0x0F])
{
if (rpnid[e->type & 0x0F] == 0)
ccc[e->type & 0x0F][134] = rpnval[e->type & 0x0F];
rpnval[e->type & 0x0F] = -1;
}
ccc[e->type & 0x0F][e->p1] = e->p2;
break;
case 0xC0://PC
ccc[e->type & 0x0F][128] = e->p1;
break;
case 0xD0://CP
ccc[e->type & 0x0F][129] = e->p1;
break;
case 0xE0://PW
ccc[e->type & 0x0F][130] = (e->p1 | (e->p2 << 7)) & 0x3FFF;
break;
case 0xF0://Meta/SysEx
if ((e->type & 0x0F) == 0x0F)
{
switch (e->p1)
{
case 0x51:
{
ctempo = 0;
#if (__BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__)||defined(_WIN32)
std::string x(e->str);
std::reverse(x.begin(), x.end());
memcpy(&ctempo, x.data(), 3);
#else
memcpy(&ctempo, e->str.data(), 3);
ctempo >>= 8;
#endif
dpt = ctempo * 1000. / divs;
ccc[0][131] = ctempo;
}
break;
case 0x58:
ccc[0][132] = (e->str[0] << 24u) | (e->str[1] << 16u);
break;
case 0x59:
if (e->str.length() >= 2)
ccc[0][133] = (e->str[0] << 8u) | e->str[1];
break;
}
}
break;
}
}
#ifdef _WIN32
void w32usleep(uint64_t t)
{
uint64_t st = 0, ct = 0;
timeBeginPeriod(1);
QueryPerformanceCounter((LARGE_INTEGER *)&st);
do
{
if (t > 10000 + (ct - st) * 1000000 / pf)
Sleep((t - (ct - st) * 1000000 / pf) / 2000);
else if (t > 5000 + (ct - st) * 1000000 / pf)
Sleep(1);
else
std::this_thread::yield();
QueryPerformanceCounter((LARGE_INTEGER *)&ct);
} while ((ct - st) * 1000000 < t * pf);
timeEndPeriod(1);
}
#endif
SEvent *CMidiPlayer::getEvent(uint32_t id)
{
size_t t = eorder[id].first, e = eorder[id].second;
return &midiFile->tracks[t].eventList[e];
}
void CMidiPlayer::prePlayInit()
{
playerPanic(true);
for (size_t i = 0; i < mididev.size(); ++i)
if (mididev[i].refcnt)
mididev[i].dev->reset(0xFF);
}
void CMidiPlayer::playEvents()
{
static uint32_t _lrtick;
_lrtick = 0;
ttick = getEvent(0)->time;
ttime = std::chrono::high_resolution_clock::now();
for (ct = getEvent(0)->time; tceptr < ecnt;)
{
using namespace std::chrono;
while (tcpaused)
std::this_thread::sleep_for(milliseconds(100));
if (resumed)
{
resumed = false;
ct = getEvent(tceptr)->time;
ttick = getTick();
ttime = high_resolution_clock::now();
continue;
}
high_resolution_clock::time_point b = high_resolution_clock::now();
if (getTick() - _lrtick > divs)
{
_lrtick = getTick();
//double _dt=(getTick()-ttick)*dpt-duration_cast<nanoseconds>((b-ttime)).count();
//<0: slower than expected, >0: faster than expected
//fprintf(stderr,"@ tick %u, dtime %.6fus",getTick(),_dt/1000.);
}
for (; !tcstop && midiReaders && tceptr < ecnt && ct == getEvent(tceptr)->time; ++tceptr)
{
if (processEvent(getEvent(tceptr)))
{
SEvent *e = getEvent(tceptr);
mididev[mappedoutput[e->type & 0x0F]].dev->basicMessage(e->type, e->p1, e->p2);
}
for (auto i = event_handlers.begin(); i != event_handlers.end(); ++i)
if (std::get<2>(i->second))
std::get<0>(i->second)((void *)getEvent(tceptr), std::get<1>(i->second));
}
if (tcstop || !midiReaders || tceptr >= ecnt)
break;
high_resolution_clock::time_point a = high_resolution_clock::now();
auto sendtime = a - b;
if (sendtime.count() < (getEvent(tceptr)->time - ct)*dpt)
{
double ns_sleep = (getEvent(tceptr)->time - ct) * dpt - sendtime.count();
double correction = (getTick() - ttick) * dpt - (b - ttime).count();
if (correction > 0)
correction = 0;
#ifdef _WIN32
w32usleep((uint64_t)(((getEvent(tceptr)->time - ct)*dpt - sendtime.count()) / 1000));
#else
std::this_thread::sleep_for(std::chrono::nanoseconds((uint64_t)(ns_sleep + correction)));
#endif
}
if (tcstop || !midiReaders)
break;
ct = getEvent(tceptr)->time;
}
if (tceptr >= ecnt)
finished = 1;
}
void CMidiPlayer::fileTimer1Pass()
{
ftime = .0;
ctempo = 0x7A120;
dpt = ctempo * 1000. / divs;
for (uint32_t eptr = 0, ct = getEvent(0)->time; eptr < ecnt;)
{
while (eptr < ecnt && ct == getEvent(eptr)->time)
processEventStub(getEvent(eptr++));
if (eptr >= ecnt)
break;
ftime += (getEvent(eptr)->time - ct) * dpt / 1e9;
ct = getEvent(eptr)->time;
}
}
void CMidiPlayer::fileTimer2Pass()
{
double ctime = .0;
uint32_t c = 1;
ctempo = 0x7A120;
dpt = ctempo * 1000. / divs;
memset(stamps, 0, sizeof(stamps));
memset(ccstamps, 0, sizeof(ccstamps));
memset(ccc, 0, sizeof(ccc));
memset(rpnid, 0xFF, sizeof(rpnid));
memset(rpnval, 0xFF, sizeof(rpnval));
for (int i = 0; i < 16; ++i)
{
memset(ccc[i], 0xFF, 128 * sizeof(uint32_t));
ccc[i][131] = ctempo;
ccc[i][132] = 0x04021808;
ccc[i][133] = 0;
ccc[i][134] = 2;
}
for (int i = 0; i < 16; ++i)
for (int j = 0; j < 135; ++j)
ccstamps[0][i][j] = ccc[i][j];
for (uint32_t eptr = 0, ct = getEvent(0)->time; eptr < ecnt;)
{
while (eptr < ecnt && ct == getEvent(eptr)->time)
processEventStub(getEvent(eptr++));
if (eptr >= ecnt)
break;
ctime += (getEvent(eptr)->time - ct) * dpt / 1e9;
while (ctime > ftime * c / 100.)
{
for (int i = 0; i < 16; ++i)
for (int j = 0; j < 135; ++j)
ccstamps[c][i][j] = ccc[i][j];
stamps[c++] = eptr;
if (c > 100)
break;
}
ct = getEvent(eptr)->time;
}
while (c < 101)
{
for (int i = 0; i < 16; ++i)
for (int j = 0; j < 135; ++j)
ccstamps[c][i][j] = ccc[i][j];
stamps[c++] = ecnt;
}
}
CMidiPlayer::CMidiPlayer()
{
midiReaders = new CMidiFileReaderCollection();
resumed = false;
midiFile = nullptr;
waitvoice = true;
event_handlers_id = event_read_handlers_id = file_read_finish_hooks_id = 0;
memset(eventHandlerCB, 0, sizeof(eventHandlerCB));
memset(eventHandlerCBuserdata, 0, sizeof(eventHandlerCBuserdata));
memset(eventReaderCB, 0, sizeof(eventReaderCB));
memset(eventReaderCBuserdata, 0, sizeof(eventReaderCBuserdata));
memset(fileReadFinishCB, 0, sizeof(fileReadFinishCB));
memset(fileReadFinishCBuserdata, 0, sizeof(fileReadFinishCBuserdata));
memset(mappedoutput, 0xff, sizeof(mappedoutput));
memset(chstatus, 0, sizeof(chstatus));
for (int i = 0; i < 16; ++i)
memset(chstatus[i], 0xff, 128 * sizeof(uint8_t));
#ifdef _WIN32
QueryPerformanceFrequency((LARGE_INTEGER *)&pf);
#endif
ref = this;
}
CMidiPlayer::~CMidiPlayer()
{
if (midiFile)
delete midiFile;
delete midiReaders;
}
void CMidiPlayer::playerPanic(bool reset)
{
for (auto &i : mididev)
if (i.refcnt)
{
for (uint8_t j = 0; j < 16; ++j)
reset ? i.dev->reset(j) : i.dev->panic(j);
}
}
bool CMidiPlayer::playerLoadFile(const char *fn)
{
notes = 0;
if (midiFile)
delete midiFile;
midiFile = midiReaders->readFile(fn);
if (!midiFile || !midiFile->valid)
return false;
divs = midiFile->divs;
maxtk = ecnt = 0;
for (CMidiTrack &i : midiFile->tracks)
{
ecnt += i.eventList.size();
if (i.eventList.size())
maxtk = std::max(maxtk, i.eventList.back().time);
}
for (int i = 0; i < 16; ++i)
if (fileReadFinishCB[i])
fileReadFinishCB[i]->callBack(nullptr, fileReadFinishCBuserdata[i]);
for (auto i = file_read_finish_hooks.begin(); i != file_read_finish_hooks.end(); ++i)
i->second.first(nullptr, i->second.second);
eorder.clear();
for (size_t i = 0; i < midiFile->tracks.size(); ++i)
for (size_t j = 0; j < midiFile->tracks[i].eventList.size(); ++j)
eorder.push_back(std::make_pair(i, j));
std::sort(eorder.begin(), eorder.end(),
[this](std::pair<size_t, size_t> &a, std::pair<size_t, size_t> &b)->bool
{
return midiFile->tracks[a.first].eventList[a.second] <
midiFile->tracks[b.first].eventList[b.second];
});
fileTimer1Pass();
fileTimer2Pass();
return true;
}
void CMidiPlayer::playerInit()
{
ctempo = 0x7A120;
ctsn = 4;
ctsd = 4;
cks = 0;
dpt = ctempo * 1000. / divs;
tceptr = 0;
tcstop = false;
tcpaused = 0;
finished = 0;
mute = solo = 0;
for (int i = 0; i < 16; ++i)
pbr[i] = 2, pbv[i] = 8192;
sendSysEx = true;
memset(rpnid, 0xFF, sizeof(rpnid));
memset(rpnval, 0xFF, sizeof(rpnval));
memset(chstatus, 0, sizeof(chstatus));
for (int i = 0; i < 16; ++i)
memset(chstatus[i], 0xff, 128 * sizeof(uint8_t));
}
void CMidiPlayer::playerDeinit()
{
tceptr = 0;
tcstop = true;
tcpaused = 0;
delete midiFile;
midiFile = nullptr;
}
void CMidiPlayer::playerThread()
{
prePlayInit();
playEvents();
}
void CMidiPlayer::sendSysX(bool send)
{
sendSysEx = send;
}
uint32_t CMidiPlayer::getStamp(int id)
{
return stamps[id];
}
uint32_t CMidiPlayer::getTCeptr()
{
return tceptr;
}
void CMidiPlayer::setTCeptr(uint32_t ep, uint32_t st)
{
resumed = true;
if (ep == ecnt)
tcstop = true;
else tceptr = ep;
for (int i = 0; i < 16; ++i)
{
qmpMidiOutDevice *dest = mididev[mappedoutput[i]].dev;
for (int j = 0; j < 120; ++j)
{
if (~ccstamps[st][i][j])
{
dest->basicMessage(0xB0 | i, j, ccstamps[st][i][j]);
chstatus[i][j] = ccstamps[st][i][j];
}
}
dest->basicMessage(0xC0 | i, ccstamps[st][i][128], 0);
chstatus[i][128] = ccstamps[st][i][128];
dest->rpnMessage(i, 0, ccstamps[st][i][134] << 7);
pbr[i] = ccstamps[st][i][134];
ctempo = ccstamps[st][0][131];
dpt = ctempo * 1000. / divs;
ctsn = ccstamps[st][0][132] >> 24;
ctsd = 1 << ((ccstamps[st][0][132] >> 16) & 0xFF);
cks = ccstamps[st][0][133];
}
}
double CMidiPlayer::getFtime()
{
return ftime;
}
void CMidiPlayer::getCurrentTimeSignature(int *n, int *d)
{
*n = ctsn;
*d = ctsd;
}
int CMidiPlayer::getCurrentKeySignature()
{
return cks;
}
uint32_t CMidiPlayer::getFileNoteCount()
{
return notes;
}
uint32_t CMidiPlayer::getFileStandard()
{
return midiFile ? midiFile->std : 0;
}
const char *CMidiPlayer::getTitle()
{
return midiFile ? midiFile->title : "";
}
const char *CMidiPlayer::getCopyright()
{
return midiFile ? midiFile->copyright : "";
}
double CMidiPlayer::getTempo()
{
return 60. / (ctempo / 1e6);
}
uint32_t CMidiPlayer::getTick()
{
return ct;
}
uint32_t CMidiPlayer::getRawTempo()
{
return ctempo;
}
uint32_t CMidiPlayer::getDivision()
{
return divs;
}
uint32_t CMidiPlayer::getMaxTick()
{
return maxtk;
}
double CMidiPlayer::getPitchBend(int ch)
{
return ((int)pbv[ch] - 8192) / 8192.*pbr[ch];
}
double CMidiPlayer::getPitchBendRaw(int ch, uint32_t *pb, uint32_t *pbr)
{
if (pb)
*pb = this->pbv[ch];
if (pbr)
*pbr = this->pbr[ch];
}
uint32_t CMidiPlayer::getTCpaused()
{
return tcpaused;
}
void CMidiPlayer::setTCpaused(uint32_t ps)
{
tcpaused = ps;
}
uint32_t CMidiPlayer::isFinished()
{
return finished;
}
bool CMidiPlayer::stopFlag()
{
return tcstop;
}
void CMidiPlayer::setResumed()
{
resumed = true;
}
void CMidiPlayer::setChannelPreset(int ch, int b, int p)
{
chstatus[ch][128] = p;
chstatus[ch][0] = b >> 7;
chstatus[ch][32] = b & 0x7F;
qmpMidiOutDevice *d = mididev[mappedoutput[ch]].dev;
d->basicMessage(0xB0 | ch, 0x00, b >> 7);
d->basicMessage(0xB0 | ch, 0x20, b & 0x7F);
d->basicMessage(0xC0 | ch, p, 0);
}
void CMidiPlayer::dumpFile()
{
if (!midiFile)
return;
for (CMidiTrack &i : midiFile->tracks)
for (SEvent &j : i.eventList)
if (j.str.length())
printf("type %x #%d @%d p1 %d p2 %d str %s\n", j.type,
j.iid, j.time, j.p1, j.p2, j.str.c_str());
else
printf("type %x #%d @%d p1 %d p2 %d\n", j.type, j.iid,
j.time, j.p1, j.p2);
}
//16MSB..LSB1
void CMidiPlayer::setBit(uint16_t &n, uint16_t bn, uint16_t b)
{
n ^= (((~b) + 1)^n) & (1 << bn);
}
void CMidiPlayer::setMute(int ch, bool m)
{
setBit(mute, ch, m ? 1 : 0);
}
void CMidiPlayer::setSolo(int ch, bool s)
{
setBit(solo, ch, s ? 1 : 0);
}
bool CMidiPlayer::getChannelMask(int ch)
{
return ((mute >> ch) & 1) || (solo && !((solo >> ch) & 1));
}
uint16_t CMidiPlayer::getCC(int ch, int id)
{
if (chstatus[ch][id] == 0xff)
return getChannelOutputDevice(ch)->getInitialCCValue(uint8_t(id), uint8_t(ch));
return chstatus[ch][id];
}
void CMidiPlayer::setCC(int ch, int id, int val)
{
chstatus[ch][id] = val;
mididev[mappedoutput[ch]].dev->basicMessage(0xB0 | ch, id, val);
}
void CMidiPlayer::registerMidiOutDevice(qmpMidiOutDevice *dev, std::string name)
{
SMidiDev d;
d.dev = dev;
d.name = name;
d.refcnt = 0;
mididev.push_back(d);
}
void CMidiPlayer::unregisterMidiOutDevice(std::string name)
{
for (auto i = mididev.begin(); i != mididev.end(); ++i)
if (i->name == name)
{
i->dev->deviceDeinit();
mididev.erase(i);
break;
}
}
std::vector<std::string> CMidiPlayer::getMidiOutDevices()
{
std::vector<std::string> ret;
for (auto &i : mididev)
ret.push_back(i.name);
return ret;
}
int CMidiPlayer::getChannelOutput(int ch)
{
return mappedoutput[ch];
}
qmpMidiOutDevice *CMidiPlayer::getChannelOutputDevice(int ch)
{
return mididev[mappedoutput[ch]].dev;
}
void CMidiPlayer::setChannelOutput(int ch, int outid)
{
int origoutput = mappedoutput[ch];
if (origoutput == outid)
return;
SMidiDev &dnew = mididev[outid];
dnew.dev->onMapped(ch, ++dnew.refcnt);
for (int i = 0; i < 124; ++i)
{
if (i != 6 && i != 38 && i != 100 && i != 101) //avoid sending RPN/NRPN
{
unsigned st = chstatus[ch][i];
if (!~st)
st = dnew.dev->getInitialCCValue(i, ch);
dnew.dev->basicMessage(0xB0 | ch, i, chstatus[ch][i]);
}
}
dnew.dev->basicMessage(0xC0 | ch, ~chstatus[ch][128] ? chstatus[ch][128] : dnew.dev->getInitialCCValue(128, ch), 0);
mappedoutput[ch] = outid;
if (~origoutput)
{
SMidiDev &dold = mididev[origoutput];
dold.dev->onUnmapped(ch, --dold.refcnt);
}
}
const std::chrono::system_clock::time_point *CMidiPlayer::getLastEventTS()
{
return levtt;
}
int CMidiPlayer::setEventHandlerCB(ICallBack *cb, void *userdata)
{
for (int i = 0; i < 16; ++i)
{
if (eventHandlerCB[i] == cb)
return i;
if (eventHandlerCB[i] == nullptr)
{
eventHandlerCB[i] = cb;
eventHandlerCBuserdata[i] = userdata;
return i;
}
}
return -1;
}
void CMidiPlayer::unsetEventHandlerCB(int id)
{
eventHandlerCB[id] = nullptr;
eventHandlerCBuserdata[id] = nullptr;
}
int CMidiPlayer::setEventReaderCB(ICallBack *cb, void *userdata)
{
for (int i = 0; i < 16; ++i)
{
if (eventReaderCB[i] == cb)
return i;
if (eventReaderCB[i] == nullptr)
{
eventReaderCB[i] = cb;
eventReaderCBuserdata[i] = userdata;
return i;
}
}
return -1;
}
void CMidiPlayer::unsetEventReaderCB(int id)
{
eventReaderCB[id] = nullptr;
eventReaderCBuserdata[id] = nullptr;
}
int CMidiPlayer::setFileReadFinishedCB(ICallBack *cb, void *userdata)
{
for (int i = 0; i < 16; ++i)
{
if (fileReadFinishCB[i] == cb)
return i;
if (fileReadFinishCB[i] == nullptr)
{
fileReadFinishCB[i] = cb;
fileReadFinishCBuserdata[i] = userdata;
return i;
}
}
return -1;
}
void CMidiPlayer::unsetFileReadFinishedCB(int id)
{
fileReadFinishCB[id] = nullptr;
fileReadFinishCBuserdata[id] = nullptr;
}
int CMidiPlayer::registerEventHandler(callback_t cb, void *userdata, bool post)
{
int ret;
event_handlers[ret = event_handlers_id++] = std::make_tuple(cb, userdata, post);
return ret;
}
void CMidiPlayer::unregisterEventHandler(int id)
{
event_handlers.find(id) != event_handlers.end() &&event_handlers.erase(id);
}
int CMidiPlayer::registerEventReadHandler(callback_t cb, void *userdata)
{
int ret;
event_read_handlers[ret = event_read_handlers_id++] = std::make_pair(cb, userdata);
return ret;
}
void CMidiPlayer::unregisterEventReadHandler(int id)
{
event_read_handlers.find(id) != event_read_handlers.end() &&event_read_handlers.erase(id);
}
int CMidiPlayer::registerFileReadFinishHook(callback_t cb, void *userdata)
{
int ret;
file_read_finish_hooks[ret = file_read_finish_hooks_id++] = std::make_pair(cb, userdata);
return ret;
}
void CMidiPlayer::unregisterFileReadFinishHook(int id)
{
file_read_finish_hooks.find(id) != file_read_finish_hooks.end() &&file_read_finish_hooks.erase(id);
}
void CMidiPlayer::registerReader(qmpFileReader *reader, std::string name)
{
midiReaders->registerReader(reader, name);
}
void CMidiPlayer::unregisterReader(std::string name)
{
midiReaders->unregisterReader(name);
}
void CMidiPlayer::callEventReaderCB(SEvent d)
{
if ((d.type & 0xF0) == 0x90)
++notes;
for (int i = 0; i < 16; ++i)
if (eventReaderCB[i])
eventReaderCB[i]->callBack(&d, eventReaderCBuserdata[i]);
for (auto i = event_read_handlers.begin(); i != event_read_handlers.end(); ++i)
i->second.first(&d, i->second.second);
}
void CMidiPlayer::discardCurrentEvent()
{
if (midiReaders->getCurrentReader())
midiReaders->getCurrentReader()->discardCurrentEvent();
}
void CMidiPlayer::commitEventChange(SEvent d)
{
if (midiReaders->getCurrentReader())
midiReaders->getCurrentReader()->commitEventChange(d);
}
CMidiPlayer *CMidiPlayer::getInstance()
{
return ref;
}