#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <set>
#include "qmpvisualization.hpp"
int viewdist = 100;
int notestretch = 100; //length of quarter note
int minnotelength = 100;
int noteappearance = 1, showpiano = 1, stairpiano = 1, savevp = 1, showlabel = 1;
int wwidth = 800, wheight = 600, wsupersample = 1, wmultisample = 0, showparticle = 1;
int horizontal = 1, flat = 0, osdpos = 0, fontsize = 16, showmeasure = 1;
int fov = 60, vsync = 1, tfps = 60, usespectrum = 0;
DWORD chkrtint = 0xFF999999;
const wchar_t *minors = L"abebbbf c g d a e b f#c#g#d#a#";
const wchar_t *majors = L"CbGbDbAbEbBbF C G D A E B F#C#";
double fpoffsets[] = {1, 18, 28, 50, 55, 82, 98, 109, 130, 137, 161, 164, 191};
double froffsets[] = {0, 18, 33, 50, 65, 82, 98, 113, 130, 145, 161, 176, 191};
DWORD iccolors[] = {0XFFFF0000, 0XFFFF8000, 0XFFFFBF00, 0XFFF0F000,
0XFFB2EE00, 0XFF80FF00, 0XFF00FF00, 0XFF00EEB2,
0XFF00EEEE, 0XFF333333, 0XFF00BFFF, 0XFF007FFF,
0XFF0000FF, 0XFF7F00FF, 0XFFBF00FF, 0XFFFF00BF
};
DWORD accolors[] = {0XFFFF9999, 0XFFFFCC99, 0XFFFFF4D4, 0XFFFFFFDD,
0XFFF0FFC2, 0XFFDDFFBB, 0XFFBBFFBB, 0XFFAAFFEA,
0XFFBBFFFF, 0XFF999999, 0XFF99EEFF, 0XFF99CCFF,
0XFF9999FF, 0XFFCC99FF, 0XFFEE99FF, 0XFFFF99EE
};
std::set<BYTE> sustaininst = {16, 17, 18, 19, 20, 21, 22, 23,
40, 41, 42, 43, 44, 45, 48, 49,
50, 51, 52, 53, 54, 56, 57, 58,
59, 60, 61, 62, 63, 64, 65, 66,
67, 68, 69, 70, 71, 72, 73, 74,
75, 76, 77, 78, 79, 80, 81, 82,
83, 84, 85, 86, 87, 89, 90, 91,
92, 93, 94, 95, 97, 101, 109, 110, 111
};
bool cmp(MidiVisualEvent *a, MidiVisualEvent *b)
{
if (a->tcs < b->tcs)
return true;
if (a->tcs > b->tcs)
return false;
if (a->tce < b->tce)
return true;
return false;
}
void qmpVisualization::showThread()
{
wwidth = api->getOptionInt("Visualization/wwidth");
wheight = api->getOptionInt("Visualization/wheight");
wsupersample = api->getOptionInt("Visualization/supersampling");
wmultisample = api->getOptionInt("Visualization/multisampling");
fov = api->getOptionInt("Visualization/fov");
noteappearance = api->getOptionBool("Visualization/3dnotes");
showpiano = api->getOptionBool("Visualization/showpiano");
stairpiano = api->getOptionBool("Visualization/stairpiano");
showlabel = api->getOptionBool("Visualization/showlabel");
showparticle = api->getOptionBool("Visualization/showparticle");
horizontal = api->getOptionBool("Visualization/horizontal");
flat = api->getOptionBool("Visualization/flat");
showmeasure = api->getOptionBool("Visualization/showmeasure");
savevp = api->getOptionBool("Visualization/savevp");
vsync = api->getOptionBool("Visualization/vsync");
tfps = api->getOptionInt("Visualization/tfps");
osdpos = api->getOptionEnumInt("Visualization/osdpos");
fontsize = api->getOptionInt("Visualization/fontsize");
viewdist = api->getOptionInt("Visualization/viewdist");
notestretch = api->getOptionInt("Visualization/notestretch");
minnotelength = api->getOptionInt("Visualization/minnotelen");
chkrtint = api->getOptionUint("Visualization/chkrtint");
usespectrum = api->getOptionBool("Visualization/usespectrum");
for (int i = 0; i < 16; ++i)
{
accolors[i] = api->getOptionUint("Visualization/chActiveColor" + std::to_string(i));
iccolors[i] = api->getOptionUint("Visualization/chInactiveColor" + std::to_string(i));
}
sm = smGetInterface(SMELT_APILEVEL);
sm->smVidMode(wwidth, wheight, true, !hidewindow);
sm->smUpdateFunc(h);
sm->smQuitFunc(closeh);
sm->smWinTitle("QMidiPlayer Visualization");
if (rendermode)
sm->smSetFPS(0);
else
sm->smSetFPS(vsync ? FPS_VSYNC : tfps);
sm->smNoSuspend(true);
sm->smInit();
shouldclose = false;
sm->smTextureOpt(TPOT_POT, TFLT_LINEAR);
chequer = sm->smTextureLoad("chequerboard.png");
if (!chequer)
chequer = sm->smTextureLoad("/usr/share/qmidiplayer/img/chequerboard.png");
pianotex = sm->smTextureLoad("kb_128.png");
if (!pianotex)
pianotex = sm->smTextureLoad("/usr/share/qmidiplayer/img/kb_128.png");
particletex = sm->smTextureLoad("particle.png");
if (!particletex)
particletex = sm->smTextureLoad("/usr/share/qmidiplayer/img/particle.png");
bgtex = sm->smTextureLoad(api->getOptionString("Visualization/background").c_str());
if (rendermode)
fbcont = new DWORD[wwidth * wheight];
if (showparticle && !horizontal)
{
smParticleSystemInfo psinfo;
psinfo.acc = smvec3d(0, 0, -0.05);
psinfo.accvar = smvec3d(0, 0, 0.005);
psinfo.vel = smvec3d(0, 0, 0.5);
psinfo.velvar = smvec3d(0.1, 0.1, 0.2);
psinfo.rotv = psinfo.rota = psinfo.rotavar = smvec3d(0, 0, 0);
psinfo.rotvvar = smvec3d(0.04, 0.04, 0.04);
psinfo.lifespan = 1;
psinfo.lifespanvar = 0.5;
psinfo.maxcount = 1000;
psinfo.emissioncount = 5;
psinfo.ecvar = 2;
psinfo.emissiondelay = 0.1;
psinfo.edvar = 0;
psinfo.initsize = 0.8;
psinfo.initsizevar = 0.1;
psinfo.finalsize = 0.1;
psinfo.finalsizevar = 0.05;
psinfo.initcolor = 0xFFFFFFFF;
psinfo.finalcolor = 0x00FFFFFF;
psinfo.initcolorvar = psinfo.finalcolorvar = 0;
psinfo.texture = particletex;
psinfo.blend = BLEND_ALPHAADD;
psepg = new smXLinePSGenerator(.6);
for (int i = 0; i < 16; ++i)
for (int j = 0; j < 128; ++j)
{
pss[i][j] = new smParticleSystem();
pss[i][j]->setPSEmissionPosGen(psepg);
psinfo.initcolor = accolors[i];
psinfo.finalcolor = SETA(accolors[i], 0);
pss[i][j]->setParticleSystemInfo(psinfo);
pss[i][j]->setPos(smvec3d(0.756 * ((double)j - 64) + .48, (stairpiano ? (56 - i * 7.) : (64 - i * 8.)), stairpiano * i * 2 + 0.1));
}
}
else memset(pss, 0, sizeof(pss));
if (showpiano && !horizontal)
for (int i = 0; i < 16; ++i)
p3d[i] = new qmpVirtualPiano3D();
memset(traveld, 0, sizeof(traveld));
nebuf = new smEntity3DBuffer();
tdscn = sm->smTargetCreate(wwidth * wsupersample, wheight * wsupersample, wmultisample);
tdparticles = sm->smTargetCreate(wwidth * wsupersample, wheight * wsupersample, wmultisample);
rdrtrg = rendermode ? sm->smTargetCreate(wwidth, wheight) : 0;
if (!api->getOptionString("Visualization/font2").length() || !font.loadTTF(api->getOptionString("Visualization/font2").c_str(), fontsize))
if (!font.loadTTF("/usr/share/fonts/truetype/freefont/FreeMono.ttf", fontsize))
if (!font.loadTTF("/usr/share/fonts/gnu-free/FreeMono.otf", fontsize))
if (!font.loadTTF((std::string(getenv("windir") ? getenv("windir") : "") + "/Fonts/cour.ttf").c_str(), fontsize))
fprintf(stderr, "W: Font load failed.\n");
if (!api->getOptionString("Visualization/font2").length() || !fonthdpi.loadTTF(api->getOptionString("Visualization/font2").c_str(), 180))
if (!fonthdpi.loadTTF("/usr/share/fonts/truetype/freefont/FreeMono.ttf", 180))
if (!fonthdpi.loadTTF("/usr/share/fonts/gnu-free/FreeMono.otf", 180))
if (!fonthdpi.loadTTF((std::string(getenv("windir") ? getenv("windir") : "") + "/Fonts/cour.ttf").c_str(), 180))
fprintf(stderr, "W: Font load failed.\n");
if (!api->getOptionString("Visualization/font1").length() || !font2.loadTTF(api->getOptionString("Visualization/font1").c_str(), fontsize))
if (!font2.loadTTF("/usr/share/fonts/truetype/wqy/wqy-microhei.ttc", fontsize))
if (!font2.loadTTF("/usr/share/fonts/wenquanyi/wqy-microhei/wqy-microhei.ttc", fontsize))
if (!font2.loadTTF((std::string(getenv("windir") ? getenv("windir") : "") + "/Fonts/msyh.ttc").c_str(), fontsize))
if (!font2.loadTTF((std::string(getenv("windir") ? getenv("windir") : "") + "/Fonts/segoeui.ttf").c_str(), fontsize))
fprintf(stderr, "W: Font load failed.\n");
if (pos[0] < -1e8)
{
if (horizontal)
{
pos[0] = -20;
pos[1] = 45;
pos[2] = 0;
rot[0] = 0;
rot[1] = 90;
rot[2] = 90;
}
else
{
pos[0] = 0;
pos[1] = 120;
pos[2] = 70;
rot[0] = 0;
rot[1] = 75;
rot[2] = 90;
}
}
debug = false;
ctk = api->getCurrentTimeStamp();
lst = std::chrono::steady_clock::now();
sm->smSetMouseGrab(false);
sm->smMainLoop();
sm->smFinale();
}
void qmpVisualization::show()
{
rendererTh = new std::thread(&qmpVisualization::showThread, this);
}
void qmpVisualization::close()
{
shouldclose = true;
if (rendererTh)
{
rendererTh->join();
delete rendererTh;
rendererTh = nullptr;
}
else return;
if (showpiano && !horizontal)
for (int i = 0; i < 16; ++i)
delete p3d[i];
if (showparticle && !horizontal)
for (int i = 0; i > 16; ++i)
for (int j = 0; j < 128; ++j)
{
delete pss[i][j];
pss[i][j] = 0;
}
delete nebuf;
if (savevp)
{
api->setOptionDouble("Visualization/px", pos[0]);
api->setOptionDouble("Visualization/py", pos[1]);
api->setOptionDouble("Visualization/pz", pos[2]);
api->setOptionDouble("Visualization/rx", rot[0]);
api->setOptionDouble("Visualization/ry", rot[1]);
api->setOptionDouble("Visualization/rz", rot[2]);
}
if (rendermode)
delete[] fbcont;
font.releaseTTF();
font2.releaseTTF();
fonthdpi.releaseTTF();
sm->smTextureFree(chequer);
sm->smTextureFree(pianotex);
sm->smTextureFree(particletex);
if (bgtex)
sm->smTextureFree(bgtex);
sm->smTargetFree(tdscn);
sm->smTargetFree(tdparticles);
sm->smRelease();
}
void qmpVisualization::reset()
{
for (unsigned i = 0; i < pool.size(); ++i)
delete pool[i];
pool.clear();
elb = ctk = lstk = cfr = 0;
roffset = 0;
tspool.clear();
cts = 0x0402;
cks = 0;
ctp = 500000;
for (int i = 0; i < 16; ++i)
{
cpbr[i] = 2;
cpw[i] = 8192;
}
for (int i = 0; i < 16; ++i)
for (int j = 0; j < 128; ++j)
{
if (showparticle && !horizontal && pss[i][j])
pss[i][j]->stopPS();
while (!pendingt[i][j].empty())
pendingt[i][j].pop();
while (!pendingv[i][j].empty())
pendingv[i][j].pop();
}
}
void qmpVisualization::switchToRenderMode(void(*frameCallback)(void *, size_t, uint32_t, uint32_t), bool _hidewindow)
{
rendermode = true;
framecb = frameCallback;
hidewindow = _hidewindow;
}
void qmpVisualization::start()
{
playing = true;
}
void qmpVisualization::stop()
{
playing = false;
}
void qmpVisualization::pause()
{
playing = !playing;
}
void qmpVisualization::updateVisualization3D()
{
smQuad q;
if (!rendermode)
{
if (sm->smGetKeyState(SMK_D))
pos[0] += cos(smMath::deg2rad(rot[2] - 90)), pos[1] += sin(smMath::deg2rad(rot[2] - 90));
if (sm->smGetKeyState(SMK_A))
pos[0] -= cos(smMath::deg2rad(rot[2] - 90)), pos[1] -= sin(smMath::deg2rad(rot[2] - 90));
if (sm->smGetKeyState(SMK_S))
pos[0] += cos(smMath::deg2rad(rot[2])), pos[1] += sin(smMath::deg2rad(rot[2]));
if (sm->smGetKeyState(SMK_W))
pos[0] -= cos(smMath::deg2rad(rot[2])), pos[1] -= sin(smMath::deg2rad(rot[2]));
if (sm->smGetKeyState(SMK_Q))
pos[2] += 1;
if (sm->smGetKeyState(SMK_E))
pos[2] -= 1;
if (sm->smGetKeyState(SMK_R))
{
if (horizontal)
{
pos[0] = -20;
pos[1] = 45;
pos[2] = 0;
rot[0] = 0;
rot[1] = 90;
rot[2] = 90;
}
else
{
pos[0] = 0;
pos[1] = 120;
pos[2] = 70;
rot[0] = 0;
rot[1] = 75;
rot[2] = 90;
}
}
if (sm->smGetKeyState(SMK_LBUTTON) == SMKST_HIT)
sm->smSetMouseGrab(true), sm->smGetMouse2f(&lastx, &lasty);
if (sm->smGetKeyState(SMK_LBUTTON) == SMKST_KEEP)
{
float x, y;
sm->smGetMouse2f(&x, &y);
rot[1] -= (y - lasty) * 0.1;
rot[2] += (x - lastx) * 0.1;
lastx = x;
lasty = y;
while (rot[1] > 360)
rot[1] -= 360;
while (rot[1] < 0)
rot[1] += 360;
while (rot[2] > 360)
rot[2] -= 360;
while (rot[2] < 0)
rot[2] += 360;
}
if (sm->smGetKeyState(SMK_LBUTTON) == SMKST_RELEASE)
{
sm->smSetMouseGrab(false);
sm->smSetMouse2f(wwidth / 2, wheight / 2);
}
if (sm->smGetKeyState(SMK_I))
rot[1] += 1;
if (sm->smGetKeyState(SMK_K))
rot[1] -= 1;
if (sm->smGetKeyState(SMK_L))
rot[0] += 1;
if (sm->smGetKeyState(SMK_J))
rot[0] -= 1;
if (sm->smGetKeyState(SMK_U))
rot[2] += 1;
if (sm->smGetKeyState(SMK_O))
rot[2] -= 1;
}
for (int i = 0; i < 4; ++i)
{
q.v[i].col = chkrtint;
q.v[i].z = (showpiano && !horizontal) ? -5 : 0;
}
q.tex = chequer;
q.blend = BLEND_ALPHABLEND;
q.v[0].x = q.v[3].x = -120;
q.v[1].x = q.v[2].x = 120;
q.v[0].y = q.v[1].y = -120;
q.v[2].y = q.v[3].y = 120;
if (horizontal)
{
for (int i = 0; i < 4; ++i)
q.v[i].x = -20;
q.v[0].y = q.v[3].y = -120;
q.v[1].y = q.v[2].y = 120;
q.v[0].z = q.v[1].z = -120;
q.v[2].z = q.v[3].z = 120;
}
q.v[0].tx = q.v[3].tx = 0;
q.v[1].tx = q.v[2].tx = 30;
q.v[0].ty = q.v[1].ty = 0;
q.v[2].ty = q.v[3].ty = 30;
sm->smRenderBegin3D(fov, true, tdscn);
sm->sm3DCamera6f2v(pos, rot);
sm->smClrscr(0, 1, 1);
sm->smRenderQuad(&q);
double lpt = (double)notestretch / api->getDivision() / 10.*(horizontal ? 0.25 : 1);
memcpy(lastnotestatus, notestatus, sizeof(notestatus));
memset(notestatus, 0, sizeof(notestatus));
for (uint32_t i = elb; i < pool.size(); ++i)
{
if (((double)pool[i]->tcs - ctk)*lpt > viewdist * 2)
break;
if (fabs((double)pool[i]->tcs - ctk)*lpt < viewdist * 2 || fabs((double)pool[i]->tce - ctk)*lpt < viewdist * 2)
{
if (pool[i]->ch == 999)
{
smvec3d a(0.63 * (-64) + .1 - 10, (stairpiano ? (56 - 0 * 7.) : (64 - 0 * 8.)) + 10, ((double)pool[i]->tcs - ctk)*lpt - minnotelength * .005);
smvec3d b(0.63 * 64 + .7 + 10, (stairpiano ? (56 - 15 * 7.) : (64 - 15 * 8.)) + .4 - 10, ((double)pool[i]->tcs - ctk)*lpt + minnotelength * .005);
if (horizontal)
{
a = smvec3d(((double)pool[i]->tcs - ctk) * lpt - 20 - minnotelength * .001, (16 - 0 * 2.) + 2.4, 0.63 * (-64) + .1);
b = smvec3d(((double)pool[i]->tcs - ctk) * lpt - 20 + minnotelength * .001, (16 - 15 * 2.) + 0.4, 0.63 * 64 + .7);
}
smMatrix I;
I.loadIdentity();
smEntity3D c = smEntity3D::cube(a, b, 0xFF000000, horizontal ? 51 : 60);
if (stairpiano && showpiano && !horizontal)
{
std::vector<size_t> il = {2, 3, 6, 7};
for (size_t ti : il)
{
smVertex t = c.vertex(ti);
t.z += 30;
c.setVertex(ti, t);
}
}
if (showmeasure)
nebuf->addTransformedEntity(&c, I, smvec3d(0, 0, 0));
continue;
}
if (pool[i]->ch >= 990)
continue;
if (api->getChannelMask(pool[i]->ch))
continue;
smvec3d a(0.63 * ((double)pool[i]->key - 64) + .1, (stairpiano ? (56 - pool[i]->ch * 7.) : (64 - pool[i]->ch * 8.)), ((double)pool[i]->tce - ctk)*lpt + (stairpiano && showpiano && !horizontal)*pool[i]->ch * 2.);
smvec3d b(0.63 * ((double)pool[i]->key - 64) + .7, (stairpiano ? (56 - pool[i]->ch * 7.) : (64 - pool[i]->ch * 8.)) + .4, ((double)pool[i]->tcs - ctk)*lpt + (stairpiano && showpiano && !horizontal)*pool[i]->ch * 2.);
bool isnoteon = pool[i]->tcs <= ctk && pool[i]->tce >= ctk;
double pb = ((int)cpw[pool[i]->ch] - 8192) / 8192.*cpbr[pool[i]->ch];
if (isnoteon)
{
a.x = 0.63 * ((double)pool[i]->key - 64 + pb) + .1;
b.x = 0.63 * ((double)pool[i]->key - 64 + pb) + .7;
}
notestatus[pool[i]->ch][pool[i]->key] |= isnoteon;
a.x *= 1.2;
b.x *= 1.2;
if (horizontal)
{
a = smvec3d(((double)pool[i]->tcs - ctk) * lpt - 20, (16 - pool[i]->ch * 2.), 0.63 * ((double)pool[i]->key - 64) + .1);
b = smvec3d(((double)pool[i]->tce - ctk) * lpt - 20, (16 - pool[i]->ch * 2.) + .4, 0.63 * ((double)pool[i]->key - 64) + .7);
if (isnoteon)
{
a.z = 0.63 * ((double)pool[i]->key - 64 + pb) + .1;
b.z = 0.63 * ((double)pool[i]->key - 64 + pb) + .7;
}
}
if (showparticle && !horizontal)
{
if (notestatus[pool[i]->ch][pool[i]->key] && !lastnotestatus[pool[i]->ch][pool[i]->key])
{
pss[pool[i]->ch][pool[i]->key]->startPS();
pss[pool[i]->ch][pool[i]->key]->setPos(smvec3d(0.756 * ((double)pool[i]->key - 64) + .48, (stairpiano ? (56 - pool[i]->ch * 7.) : (64 - pool[i]->ch * 8.)), stairpiano * pool[i]->ch * 2 + 0.1));
}
else pss[pool[i]->ch][pool[i]->key]->stopPS();
}
if (((double)pool[i]->tce - pool[i]->tcs)*lpt < minnotelength * (horizontal ? 0.0025 : 0.01))
{
if (horizontal)
a.x = ((double)pool[i]->tcs - ctk) * lpt - minnotelength / 400. - 20;
else
a.z = ((double)pool[i]->tcs - ctk) * lpt - minnotelength / 100. + stairpiano * pool[i]->ch * 2;
}
if (usespectrum)
{
if (notestatus[pool[i]->ch][pool[i]->key] && !lastnotestatus[pool[i]->ch][pool[i]->key])
spectra[pool[i]->ch][pool[i]->key] = pool[i]->vel * (api->getChannelCC(pool[i]->ch, 7) / 127.);
}
else
{
smColor col = smColor::fromHWColor(isnoteon ? accolors[pool[i]->ch] : iccolors[pool[i]->ch]);
drawCube(a, b, col.lighter(37 + pool[i]->vel / 2).toHWColor(), 0);
}
}
}
if (usespectrum && playing)
for (int i = 0; i < 16; ++i)
for (int j = 0; j < 128; ++j)
{
if (sustaininst.find(api->getChannelPreset(i)) != sustaininst.end())
{
if (!notestatus[i][j] && spectra[i][j])
spectra[i][j] = .95 * spectra[i][j];
}
else if (spectra[i][j])
spectra[i][j] = .95 * spectra[i][j];
if (spectrar[i][j] < spectra[i][j] * 0.9)
spectrar[i][j] += spectra[i][j] * 0.2;
else spectrar[i][j] = spectra[i][j];
if (spectrar[i][j])
{
double pb = ((int)cpw[i] - 8192) / 8192.*cpbr[i];
smvec3d a(0.756 * ((double)j - 64 + pb) + .12,
(stairpiano ? (56 - i * 7.) : (64 - i * 8.)),
spectrar[i][j] * 1.2 * (1 + 0.02 * sin(sm->smGetTime() * 32)) + (stairpiano && showpiano && !horizontal)*i * 2.);
smvec3d b(0.756 * ((double)j - 64 + pb) + .84,
(stairpiano ? (56 - i * 7.) : (64 - i * 8.)) + .4,
(stairpiano && showpiano && !horizontal)*i * 2.);
drawCube(a, b, SETA(iccolors[i], 204), 0);
}
}
nebuf->drawBatch();
if (showpiano && !horizontal)
for (int i = 0; i < 16; ++i)
{
for (int j = 0; j < 128; ++j)
{
if (notestatus[i][j])
if (traveld[i][j] < 10)
traveld[i][j] += 2;
else traveld[i][j] = 10;
else if (traveld[i][j] > 0)
traveld[i][j] -= 2;
else traveld[i][j] = 0;
p3d[i]->setKeyTravelDist(j, traveld[i][j] / 10.);
}
double pb = ((int)cpw[i] - 8192) / 8192.*cpbr[i];
p3d[i]->render(smvec3d(0.756 * pb, stairpiano ? 55 - i * 7 : 62 - i * 8, stairpiano * i * 2));
}
for (int i = 0; i < 16; ++i)
if (showlabel)
{
std::string s = api->getChannelPresetString(i);
wchar_t ws[1024];
mbstowcs(ws, s.c_str(), 1024);
fonthdpi.updateString(ws);
fonthdpi.render(-49, stairpiano ? 56 - i * 7 : 63 - i * 8, stairpiano * i * 2 + 0.1, 0xFFFFFFFF, ALIGN_RIGHT, .008, 0.01);
fonthdpi.render(-49.05, stairpiano ? 56.05 - i * 7 : 63.05 - i * 8, stairpiano * i * 2 + 0.2, 0xFF000000, ALIGN_RIGHT, .008, 0.01);
}
while (pool.size() && elb < pool.size() && ((double)ctk - pool[elb]->tce)*lpt > viewdist * 2)++elb;
sm->smRenderEnd();
if (showparticle && !horizontal)
{
sm->smRenderBegin3D(fov, false, tdparticles);
sm->sm3DCamera6f2v(pos, rot);
sm->smClrscr(0, 1, 1);
for (int i = 0; i < 16; ++i)
for (int j = 0; j < 128; ++j)
{
pss[i][j]->setPSLookAt(smvec3d(pos[0], pos[1], pos[2]));
pss[i][j]->updatePS();
pss[i][j]->renderPS();
}
sm->smRenderEnd();
}
}
void qmpVisualization::updateVisualization2D()
{
double lpt = -(double)notestretch / api->getDivision() / 2.;
memset(notestatus, 0, sizeof(notestatus));
double notew = wwidth / 128, nh = showpiano ? wwidth / 2048.*172. : 0;
if (horizontal)
{
notew = wheight / 128;
nh = showpiano ? wheight / 2048.*172. : 0;
lpt = -lpt;
}
smQuad nq;
nq.blend = BLEND_ALPHABLEND;
nq.tex = 0;
for (int i = 0; i < 4; ++i)
nq.v[i].z = 0, nq.v[i].tx = nq.v[i].ty = 0;
for (uint32_t i = elb; i < pool.size(); ++i)
{
bool upperbound = ((double)pool[i]->tcs - ctk) * lpt + wheight - nh < 0;
bool lowerbound = fabs((double)pool[i]->tce - ctk) * lpt + wheight - nh < wheight;
if (horizontal)
{
upperbound = ((double)pool[i]->tcs - ctk) * lpt + nh > wwidth;
lowerbound = fabs((double)pool[i]->tce - ctk) * lpt + nh > 0;
}
if (upperbound)
break;
if (!upperbound || lowerbound)
{
if (pool[i]->ch == 999)
{
smvec2d a(0, ((double)pool[i]->tcs - ctk)*lpt + wheight - nh - minnotelength * 0.02);
smvec2d b(wwidth, ((double)pool[i]->tcs - ctk)*lpt + wheight - nh);
if (horizontal)
{
a = smvec2d(((double)pool[i]->tcs - ctk) * lpt + nh - minnotelength * 0.02, 0);
b = smvec2d(((double)pool[i]->tcs - ctk) * lpt + nh, wheight);
}
nq.v[0].x = nq.v[3].x = a.x;
nq.v[0].y = nq.v[1].y = a.y;
nq.v[1].x = nq.v[2].x = b.x;
nq.v[2].y = nq.v[3].y = b.y;
for (int j = 0; j < 4; ++j)
nq.v[j].col = 0xC0000000;
if (showmeasure)
sm->smRenderQuad(&nq);
continue;
}
if (pool[i]->ch >= 990)
continue;
if (api->getChannelMask(pool[i]->ch))
continue;
smvec2d a((froffsets[12] * (pool[i]->key / 12) + froffsets[pool[i]->key % 12])*wwidth / 2048., ((double)pool[i]->tce - ctk)*lpt + wheight - nh);
smvec2d b(a.x + notew * 0.9, ((double)pool[i]->tcs - ctk)*lpt + wheight - nh);
if (horizontal)
{
a = smvec2d(((double)pool[i]->tce - ctk) * lpt + nh, (froffsets[12] * (pool[i]->key / 12) + froffsets[pool[i]->key % 12]) * wheight / 2048.);
b = smvec2d(((double)pool[i]->tcs - ctk) * lpt + nh, a.y + notew * 0.9);
}
bool isnoteon = pool[i]->tcs <= ctk && pool[i]->tce >= ctk;
if (isnoteon)
{
double pb = ((int)cpw[pool[i]->ch] - 8192) / 8192.*cpbr[pool[i]->ch];
uint32_t newkey = pool[i]->key + (int)floor(pb);
double fpb = pb - floor(pb);
if (horizontal)
{
a.y = (froffsets[12] * (newkey / 12) + froffsets[newkey % 12]) * wheight / 2048. + notew * fpb;
b.y = a.y + notew * 0.9;
}
else
{
a.x = (froffsets[12] * (newkey / 12) + froffsets[newkey % 12]) * wwidth / 2048. + notew * fpb;
b.x = a.x + notew * 0.9;
}
}
if (horizontal)
a.y = wheight - a.y, b.y = wheight - b.y;
notestatus[pool[i]->ch][pool[i]->key] |= isnoteon;
if (horizontal)
{
if (((double)pool[i]->tce - pool[i]->tcs)*lpt < minnotelength * 0.04)
a.x = ((double)pool[i]->tcs - ctk) * lpt + nh - minnotelength * 0.04;
}
else
{
if (((double)pool[i]->tcs - pool[i]->tce)*lpt < minnotelength * 0.04)
a.y = ((double)pool[i]->tcs - ctk) * lpt + wheight - nh - minnotelength * 0.04;
}
nq.v[0].x = nq.v[3].x = a.x;
nq.v[0].y = nq.v[1].y = a.y;
nq.v[1].x = nq.v[2].x = b.x;
nq.v[2].y = nq.v[3].y = b.y;
for (int j = 0; j < 4; ++j)
nq.v[j].col = SETA(isnoteon ? accolors[pool[i]->ch] : iccolors[pool[i]->ch], int(pool[i]->vel * 1.6 + (isnoteon ? 52 : 32)));
if (usespectrum)
{
if (notestatus[pool[i]->ch][pool[i]->key] && !lastnotestatus[pool[i]->ch][pool[i]->key])
spectra[pool[i]->ch][pool[i]->key] = pool[i]->vel * (api->getChannelCC(pool[i]->ch, 7) / 127.);
}
else sm->smRenderQuad(&nq);
}
}
if (horizontal)
while (pool.size() && elb < pool.size() && fabs((double)pool[elb]->tce - ctk)*lpt + nh < 0)++elb;
else
while (pool.size() && elb < pool.size() && fabs((double)pool[elb]->tce - ctk)*lpt + wheight - nh > wheight)++elb;
smQuad q;
q.tex = pianotex;
q.blend = BLEND_ALPHABLEND;
for (int i = 0; i < 4; ++i)
q.v[i].col = 0xFFFFFFFF, q.v[i].z = 0;
q.v[0].ty = q.v[3].ty = 0;
q.v[1].ty = q.v[2].ty = 172. / 256.;
q.v[0].tx = q.v[1].tx = 0;
q.v[2].tx = q.v[3].tx = 1.;
q.v[0].x = q.v[1].x = 0;
q.v[2].x = q.v[3].x = wwidth;
q.v[0].y = q.v[3].y = wheight - nh;
q.v[1].y = q.v[2].y = wheight;
if (horizontal)
{
q.v[0].tx = q.v[3].tx = 0;
q.v[1].tx = q.v[2].tx = 1;
q.v[0].ty = q.v[1].ty = 0;
q.v[2].ty = q.v[3].ty = 172. / 256.;
q.v[0].x = q.v[1].x = nh;
q.v[2].x = q.v[3].x = 0;
q.v[0].y = q.v[3].y = wheight;
q.v[1].y = q.v[2].y = 0;
}
sm->smRenderQuad(&q);
for (int i = 0, j; i < 128; ++i)
{
DWORD c = 0;
for (j = 0; j < 16; ++j)
if (notestatus[j][i])
{
c = SETA(iccolors[j], 0xFF);
break;
}
if (horizontal)
{
q.v[0].y = q.v[1].y = (fpoffsets[12] * (i / 12) + fpoffsets[i % 12]) * wheight / 2048.;
q.v[2].y = q.v[3].y = q.v[0].y;
q.v[0].x = q.v[3].x = nh;
q.v[1].x = q.v[2].x = 0;
}
else
{
q.v[0].x = q.v[1].x = (fpoffsets[12] * (i / 12) + fpoffsets[i % 12]) * wwidth / 2048.;
q.v[2].x = q.v[3].x = q.v[0].x;
q.v[0].y = q.v[3].y = wheight - nh;
q.v[1].y = q.v[2].y = wheight;
}
if (!c)
continue;
for (int j = 0; j < 4; ++j)
q.v[j].col = c;
switch (i % 12)
{
case 1:
case 3:
case 6:
case 8:
case 10:
if (horizontal)
{
q.v[1].x = q.v[2].x = nh - 115 * wheight / 2048.;
q.v[2].y += 15.*wheight / 2048;
q.v[3].y += 15.*wheight / 2048;
}
else
{
q.v[1].y = q.v[2].y = wheight - nh + 115 * wwidth / 2048.;
q.v[2].x += 15.*wwidth / 2048;
q.v[3].x += 15.*wwidth / 2048;
}
q.v[0].ty = q.v[1].ty = 1.;
q.v[2].ty = q.v[3].ty = 1. - 15 / 256.;
q.v[0].tx = q.v[3].tx = 1344 / 2048.;
q.v[1].tx = q.v[2].tx = 1459 / 2048.;
break;
case 0:
if (horizontal)
{
q.v[2].y += 27.*wheight / 2048;
q.v[3].y += 27.*wheight / 2048;
}
else
{
q.v[2].x += 27.*wwidth / 2048;
q.v[3].x += 27.*wwidth / 2048;
}
q.v[0].ty = q.v[1].ty = 1.;
q.v[2].ty = q.v[3].ty = 1. - 27 / 256.;
q.v[0].tx = q.v[3].tx = 0 / 2048.;
q.v[1].tx = q.v[2].tx = 172 / 2048.;
break;
case 2:
if (horizontal)
{
q.v[2].y += 29.*wheight / 2048;
q.v[3].y += 29.*wheight / 2048;
}
else
{
q.v[2].x += 29.*wwidth / 2048;
q.v[3].x += 29.*wwidth / 2048;
}
q.v[0].ty = q.v[1].ty = 1.;
q.v[2].ty = q.v[3].ty = 1. - 29 / 256.;
q.v[0].tx = q.v[3].tx = 192 / 2048.;
q.v[1].tx = q.v[2].tx = 364 / 2048.;
break;
case 4:
if (horizontal)
{
q.v[2].y += 28.*wheight / 2048;
q.v[3].y += 28.*wheight / 2048;
}
else
{
q.v[2].x += 28.*wwidth / 2048;
q.v[3].x += 28.*wwidth / 2048;
}
q.v[0].ty = q.v[1].ty = 1.;
q.v[2].ty = q.v[3].ty = 1. - 28 / 256.;
q.v[0].tx = q.v[3].tx = 384 / 2048.;
q.v[1].tx = q.v[2].tx = 556 / 2048.;
break;
case 5:
if (horizontal)
{
q.v[2].y += 28.*wheight / 2048;
q.v[3].y += 28.*wheight / 2048;
}
else
{
q.v[2].x += 28.*wwidth / 2048;
q.v[3].x += 28.*wwidth / 2048;
}
q.v[0].ty = q.v[1].ty = 1.;
q.v[2].ty = q.v[3].ty = 1. - 28 / 256.;
q.v[0].tx = q.v[3].tx = 576 / 2048.;
q.v[1].tx = q.v[2].tx = 748 / 2048.;
break;
case 7:
if (horizontal)
{
q.v[2].y += 29.*wheight / 2048;
q.v[3].y += 29.*wheight / 2048;
}
else
{
q.v[2].x += 29.*wwidth / 2048;
q.v[3].x += 29.*wwidth / 2048;
}
q.v[0].ty = q.v[1].ty = 1.;
q.v[2].ty = q.v[3].ty = 1. - 29 / 256.;
q.v[0].tx = q.v[3].tx = 768 / 2048.;
q.v[1].tx = q.v[2].tx = 940 / 2048.;
break;
case 9:
if (horizontal)
{
q.v[2].y += 28.*wheight / 2048;
q.v[3].y += 28.*wheight / 2048;
}
else
{
q.v[2].x += 28.*wwidth / 2048;
q.v[3].x += 28.*wwidth / 2048;
}
q.v[0].ty = q.v[1].ty = 1.;
q.v[2].ty = q.v[3].ty = 1. - 28 / 256.;
q.v[0].tx = q.v[3].tx = 960 / 2048.;
q.v[1].tx = q.v[2].tx = 1132 / 2048.;
break;
case 11:
if (horizontal)
{
q.v[2].y += 28.*wheight / 2048;
q.v[3].y += 28.*wheight / 2048;
}
else
{
q.v[2].x += 28.*wwidth / 2048;
q.v[3].x += 28.*wwidth / 2048;
}
q.v[0].ty = q.v[1].ty = 1.;
q.v[2].ty = q.v[3].ty = 1. - 28 / 256.;
q.v[0].tx = q.v[3].tx = 1152 / 2048.;
q.v[1].tx = q.v[2].tx = 1324 / 2048.;
break;
}
if (horizontal)
for (int j = 0; j < 4; ++j)
q.v[j].y = wheight - q.v[j].y;
sm->smRenderQuad(&q);
}
if (usespectrum && playing)
for (int i = 0; i < 16; ++i)
for (int j = 0; j < 128; ++j)
{
if (sustaininst.find(api->getChannelPreset(i)) != sustaininst.end())
{
if (!notestatus[i][j] && spectra[i][j])
spectra[i][j] = .95 * spectra[i][j];
}
else if (spectra[i][j])
spectra[i][j] = .95 * spectra[i][j];
if (spectrar[i][j] < spectra[i][j] * 0.9)
spectrar[i][j] += spectra[i][j] * 0.2;
else spectrar[i][j] = spectra[i][j];
if (spectrar[i][j])
{
smvec2d a((froffsets[12] * (j / 12) + froffsets[j % 12])*wwidth / 2048., spectrar[i][j] / -128.*(wheight - nh) * (1 + 0.02 * sin(sm->smGetTime() * 32)) + wheight - nh);
smvec2d b(a.x + notew * 0.9, lpt + wheight - nh);
double pb = ((int)cpw[i] - 8192) / 8192.*cpbr[i];
uint32_t newkey = j + (int)floor(pb);
double fpb = pb - floor(pb);
if (horizontal)
{
a = smvec2d(spectrar[i][j] / 128.*(wwidth - nh) * (1 + 0.02 * sin(sm->smGetTime() * 32)) + nh, (froffsets[12] * (j / 12) + froffsets[j % 12]) * wheight / 2048.);
b = smvec2d(nh, a.y + notew * 0.9);
a.y = (froffsets[12] * (newkey / 12) + froffsets[newkey % 12]) * wheight / 2048. + notew * fpb;
b.y = a.y + notew * 0.9;
a.y = wheight - a.y;
b.y = wheight - b.y;
}
else
{
a.x = (froffsets[12] * (newkey / 12) + froffsets[newkey % 12]) * wwidth / 2048. + notew * fpb;
b.x = a.x + notew * 0.9;
}
nq.v[0].x = nq.v[3].x = a.x;
nq.v[0].y = nq.v[1].y = a.y;
nq.v[1].x = nq.v[2].x = b.x;
nq.v[2].y = nq.v[3].y = b.y;
for (int j = 0; j < 4; ++j)
nq.v[j].col = SETA(iccolors[i], 204);
sm->smRenderQuad(&nq);
}
}
}
bool qmpVisualization::update()
{
smQuad q;
if (!rendermode)
{
if (sm->smGetKeyState(SMK_RIGHT) == SMKST_HIT)
{
auto p = api->getCurrentPlaybackPercentage();
p += (sm->smGetKeyState(SMK_SHIFT) ? 5 : 1);
api->playbackControl(PlaybackControlCommand::Seek, &p);
}
if (sm->smGetKeyState(SMK_LEFT) == SMKST_HIT)
{
auto p = api->getCurrentPlaybackPercentage();
p -= (sm->smGetKeyState(SMK_SHIFT) ? 5 : 1);
api->playbackControl(PlaybackControlCommand::Seek, &p);
}
if (sm->smGetKeyState(SMK_B) == SMKST_HIT)
debug ^= 1;
}
if (playing)
{
if (rendermode)
{
ctk = 1e6 * cfr / tfps / ctp * api->getDivision() + lstk;
++cfr;
}
else
ctk = lstk + std::chrono::duration_cast<std::chrono::duration<double, std::micro>>(std::chrono::steady_clock::now() - lst).count() / api->getRawTempo() * api->getDivision();
}
if (!flat)
updateVisualization3D();
sm->smRenderBegin2D(false, rdrtrg);
sm->smClrscr(0xFF666666);
q.blend = BLEND_ALPHABLEND;
for (int i = 0; i < 4; ++i)
{
q.v[i].col = 0xFFFFFFFF;
q.v[i].z = 0;
}
if (bgtex)
{
q.tex = bgtex;
q.v[0].x = q.v[3].x = 0;
q.v[1].x = q.v[2].x = wwidth;
q.v[0].y = q.v[1].y = 0;
q.v[2].y = q.v[3].y = wheight;
q.v[0].tx = q.v[3].tx = 0;
q.v[1].tx = q.v[2].tx = 1;
q.v[0].ty = q.v[1].ty = 0;
q.v[2].ty = q.v[3].ty = 1;
sm->smRenderQuad(&q);
}
if (flat)
updateVisualization2D();
else
{
q.tex = sm->smTargetTexture(tdscn);
q.v[0].tx = q.v[3].tx = 0;
q.v[1].tx = q.v[2].tx = 1;
q.v[0].ty = q.v[1].ty = 0;
q.v[2].ty = q.v[3].ty = 1;
q.v[0].x = q.v[1].x = 0;
q.v[2].x = q.v[3].x = wwidth;
q.v[0].y = q.v[3].y = 0;
q.v[1].y = q.v[2].y = wheight;
sm->smRenderQuad(&q);
if (showparticle && !horizontal)
{
q.tex = sm->smTargetTexture(tdparticles);
sm->smRenderQuad(&q);
}
}
uint32_t ltpc = ~0u;
for (uint32_t i = elb; i < pool.size(); ++i)
{
if (pool[i]->tcs > ctk)
break;
if (pool[i]->ch == 998)
cts = pool[i]->key;
if (pool[i]->ch == 997)
cks = pool[i]->key;
if (pool[i]->ch == 996)
ltpc = i;
if (pool[i]->ch == 995)
cpbr[pool[i]->vel] = pool[i]->key;
if (pool[i]->ch == 994)
cpw[pool[i]->vel] = pool[i]->key;
}
if (~ltpc && ctp != pool[ltpc]->key)
{
uint32_t oldtp = ctp;
ctp = pool[ltpc]->key;
if (rendermode)
{
if (ctk > pool[ltpc]->tcs)
{
double oldtpft = (double)(ctk - pool[ltpc]->tcs) * (oldtp / 1e6 / api->getDivision());
roffset += oldtpft;
}
lstk = pool[ltpc]->tcs;
cfr = 1;
while (roffset > 1. / tfps) //usually only run once
{
++ cfr;
roffset -= 1. / tfps;
}
}
}
int t, r;
t = cks;
r = (int8_t)((t >> 8) & 0xFF) + 7;
t &= 0xFF;
std::wstring ts(t ? minors : majors, 2 * r, 2);
int step = int(1.33 * fontsize);
int xp = (osdpos & 1) ? wwidth - step - 1 : 1;
int yp = osdpos < 2 ? wheight - step * 5 - 4 : step + 4;
int align = osdpos & 1 ? ALIGN_RIGHT : ALIGN_LEFT;
if (osdpos != 4)
{
font2.updateString(L"Title: %ls", api->getWTitle().c_str());
font2.render(xp, yp, 0.5, 0xFFFFFFFF, align);
font2.render(xp - 1, yp - 1, 0.5, 0xFF000000, align);
font.updateString(L"Time Sig: %d/%d", cts >> 8, 1 << (cts & 0xFF));
font.render(xp, yp += step, 0.5, 0xFFFFFFFF, align);
font.render(xp - 1, yp - 1, 0.5, 0xFF000000, align);
font.updateString(L"Key Sig: %ls", ts.c_str());
font.render(xp, yp += step, 0.5, 0xFFFFFFFF, align);
font.render(xp - 1, yp - 1, 0.5, 0xFF000000, align);
font.updateString(L"Tempo: %.2f", 60. / (ctp / 1e6));
font.render(xp, yp += step, 0.5, 0xFFFFFFFF, align);
font.render(xp - 1, yp - 1, 0.5, 0xFF000000, align);
font.updateString(L"Current tick: %d", ctk);
font.render(xp, yp += step, 0.5, 0xFFFFFFFF, align);
font.render(xp - 1, yp - 1, 0.5, 0xFF000000, align);
}
if (!rendermode)
{
font.updateString(L"FPS: %.2f", sm->smGetFPS());
font.render(xp, yp += step, 0.5, 0xFFFFFFFF, align);
font.render(xp - 1, yp - 1, 0.5, 0xFF000000, align);
}
if (debug)
{
int dosdpos = (osdpos + 1) % 4;
xp = (dosdpos & 1) ? wwidth - step - 1 : 1;
yp = dosdpos < 2 ? wheight - step * 5 - 4 : step + 4;
align = dosdpos & 1 ? ALIGN_RIGHT : ALIGN_LEFT;
std::string tstr;
tstr = std::string(sm->smGetOSInfo());
font.updateString(L"OS: %ls", std::wstring({std::begin(tstr), std::end(tstr)}).c_str());
font.render(xp, yp, 0.5, 0xFFFFFFFF, align);
font.render(xp - 1, yp - 1, 0.5, 0xFF000000, align);
tstr = std::string(sm->smGetCPUModel());
font.updateString(L"CPU: %ls", std::wstring({std::begin(tstr), std::end(tstr)}).c_str());
font.render(xp, yp += step, 0.5, 0xFFFFFFFF, align);
font.render(xp - 1, yp - 1, 0.5, 0xFF000000, align);
tstr = std::string(sm->smGetDispDriver());
font.updateString(L"Display %ls", std::wstring({std::begin(tstr), std::end(tstr)}).c_str());
font.render(xp, yp += 3 * step, 0.5, 0xFFFFFFFF, align);
font.render(xp - 1, yp - 1, 0.5, 0xFF000000, align);
}
if (rendermode)
{
if (ctk > api->getMaxTick())
framecb(nullptr, 0, ctk, api->getMaxTick());
else
{
sm->smPixelCopy(0, 0, wwidth, wheight, 4 * wwidth * wheight, fbcont);
framecb(fbcont, 4 * wwidth * wheight, ctk, api->getMaxTick());
}
}
sm->smRenderEnd();
return shouldclose;
}
void qmpVisualization::drawCube(smvec3d a, smvec3d b, DWORD col, SMTEX tex, int faces)
{
smQuad q;
q.blend = BLEND_ALPHABLEND;
q.tex = tex;
for (int i = 0; i < 4; ++i)
q.v[i].col = col;
if (noteappearance == 1)
{
smMatrix I;
I.loadIdentity();
smEntity3D c = smEntity3D::cube(a, b, col, faces);
nebuf->addTransformedEntity(&c, I, smvec3d(0, 0, 0));
}
else
{
q.v[0].x = a.x;
q.v[0].y = b.y;
q.v[0].z = a.z;
q.v[1].x = b.x;
q.v[1].y = b.y;
q.v[1].z = a.z;
q.v[2].x = b.x;
q.v[2].y = b.y;
q.v[2].z = b.z;
q.v[3].x = a.x;
q.v[3].y = b.y;
q.v[3].z = b.z;
sm->smRenderQuad(&q);
}
}
qmpVisualization *qmpVisualization::inst = nullptr;
qmpVisualization::qmpVisualization(qmpPluginAPI *_api)
{
api = _api;
inst = this;
rendermode = false;
hidewindow = false;
}
qmpVisualization::~qmpVisualization()
{
api = nullptr;
inst = nullptr;
}
void qmpVisualization::init()
{
h = new CMidiVisualHandler(this);
closeh = new CloseHandler(this);
rendererTh = nullptr;
playing = false;
cts = 0x0402;
cks = 0;
ctp = 500000;
memset(rpnid, 0xFF, sizeof(rpnid));
memset(rpnval, 0xFF, sizeof(rpnval));
memset(spectra, 0, sizeof(spectra));
memset(spectrar, 0, sizeof(spectrar));
api->registerFunctionality(this, "Visualization", "Visualization", api->isDarkTheme() ? ":/img/visualization_i.svg" : ":/img/visualization.svg", 0, true);
uihb = api->registerUIHook("main.start", [this](const void *, void *)
{
this->start();
}, nullptr);
uihs = api->registerUIHook("main.stop", [this](const void *, void *)
{
this->stop();
}, nullptr);
uihp = api->registerUIHook("main.pause", [this](const void *, void *)
{
this->pause();
}, nullptr);
uihr = api->registerUIHook("main.reset", [this](const void *, void *)
{
this->reset();
}, nullptr);
uihk = api->registerUIHook("main.seek", [this](const void *, void *)
{
cts = api->getTimeSig();
cks = api->getKeySig();
ctp = api->getRawTempo();
for (int i = 0; i < 16; ++i)
api->getPitchBendRaw(i, &cpw[i], &cpbr[i]);
}, nullptr);
herh = api->registerEventReadHandler(
[this](const void *ee, void *)
{
const SEvent *e = (const SEvent *)ee;
switch (e->type & 0xF0)
{
case 0x80:
this->pushNoteOff(e->time, e->type & 0x0F, e->p1);
break;
case 0x90:
if (e->p2)
this->pushNoteOn(e->time, e->type & 0x0F, e->p1, e->p2);
else
this->pushNoteOff(e->time, e->type & 0x0F, e->p1);
break;
case 0xB0:
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)
this->pool.push_back(new MidiVisualEvent{e->time, e->time, rpnval[e->type & 0x0F], e->type & 0x0Fu, 995});
rpnval[e->type & 0x0F] = ~0u;
}
break;
case 0xE0:
this->pool.push_back(new MidiVisualEvent{e->time, e->time, (e->p1 | (e->p2 << 7)) & 0x3FFFu, e->type & 0x0Fu, 994});
break;
case 0xF0:
if (e->type == 0xFF && e->p1 == 0x58)
{
this->tspool.push_back(std::make_pair(e->time, (e->str[0] << 24) | (e->str[1] << 16)));
this->pool.push_back(new MidiVisualEvent{e->time, e->time, (e->str[0] & 0xffu) << 8 | (e->str[1] & 0xffu), 0, 998});
}
else if (e->type == 0xFF && e->p1 == 0x59)
this->pool.push_back(new MidiVisualEvent{e->time, e->time, (e->str[0] & 0xffu) << 8 | (e->str[1] & 0xffu), 0, 997});
else if (e->type == 0xFF && e->p1 == 0x51)
this->pool.push_back(new MidiVisualEvent{e->time, e->time, (e->str[0] & 0xffu) << 16 | (e->str[1] & 0xffu) << 8 | (e->str[2] & 0xffu), 0, 996});
break;
}
}
, nullptr);
heh = api->registerEventHandler(
[this](const void *, void *)
{
if (this->ctk > this->api->getCurrentTimeStamp() + this->api->getDivision() / 3)
this->elb = 0;
/*if(abs((int)this->ctk-(int)this->api->getCurrentTimeStamp())>this->api->getDivision()/4)
fprintf(stderr,"Visualization: out of sync! %u vs %u ad: %u\n",this->ctk,this->api->getCurrentTimeStamp());*/
this->ctk = this->api->getCurrentTimeStamp();
this->lstk = this->ctk;
this->lst = std::chrono::steady_clock::now();
}
, nullptr);
hfrf = api->registerFileReadFinishHook(
[this](const void *, void *)
{
memset(rpnval, 0xFF, sizeof(rpnval));
memset(rpnid, 0xFF, sizeof(rpnid));
std::sort(this->tspool.begin(), this->tspool.end());
for (uint32_t tk = 0, n = 4, s = 0, d = 4; tk <= this->api->getMaxTick();)
{
while (tk < (s >= this->tspool.size() ? this->api->getMaxTick() : this->tspool[s].first))
{
this->pool.push_back(new MidiVisualEvent{tk, tk, 0, 0, 999});
tk += n * this->api->getDivision() * 4 / d;
}
tk = (s >= this->tspool.size() ? this->api->getMaxTick() : this->tspool[s].first);
if (tk == this->api->getMaxTick())
{
this->pool.push_back(new MidiVisualEvent{tk, tk, 0, 0, 999});
++tk;
break;
}
else
{
n = this->tspool[s].second >> 24;
d = 1 << ((this->tspool[s++].second >> 16) & 0xff);
}
}
std::sort(this->pool.begin(), this->pool.end(), cmp);
}
, nullptr);
api->registerOptionBool("Visualization-Appearance", "Show Piano", "Visualization/showpiano", true);
api->registerOptionBool("Visualization-Appearance", "3D Notes", "Visualization/3dnotes", true);
api->registerOptionBool("Visualization-Appearance", "Arrange channels on a stair", "Visualization/stairpiano", true);
api->registerOptionBool("Visualization-Appearance", "Show channel labels", "Visualization/showlabel", true);
api->registerOptionBool("Visualization-Appearance", "Show Particles", "Visualization/showparticle", false);
api->registerOptionBool("Visualization-Appearance", "Horizontal Visualization", "Visualization/horizontal", false);
api->registerOptionBool("Visualization-Appearance", "2D Visualization", "Visualization/flat", false);
api->registerOptionBool("Visualization-Appearance", "Show Measure Indicator", "Visualization/showmeasure", true);
api->registerOptionBool("Visualization-Appearance", "Use spectrum instead of piano roll", "Visualization/usespectrum", false);
api->registerOptionBool("Visualization-Video", "Enable VSync", "Visualization/vsync", true);
api->registerOptionBool("Visualization-Video", "Save Viewport", "Visualization/savevp", true);
api->registerOptionInt("Visualization-Video", "Window Width", "Visualization/wwidth", 320, 3200, 800);
api->registerOptionInt("Visualization-Video", "Window Height", "Visualization/wheight", 320, 3200, 600);
api->registerOptionInt("Visualization-Video", "Target FPS", "Visualization/tfps", 5, 1000, 60);
api->registerOptionInt("Visualization-Video", "Supersampling", "Visualization/supersampling", 1, 16, 0);
api->registerOptionInt("Visualization-Video", "Multisampling", "Visualization/multisampling", 0, 16, 0);
api->registerOptionInt("Visualization-Video", "FOV", "Visualization/fov", 30, 180, 60);
std::vector<std::string> tv;
tv.push_back("Bottom left");
tv.push_back("Bottom right");
tv.push_back("Top left");
tv.push_back("Top right");
tv.push_back("Hidden");
api->registerOptionEnumInt("Visualization-Video", "OSD Position", "Visualization/osdpos", tv, 0);
api->registerOptionInt("Visualization-Video", "Font Size", "Visualization/fontsize", 6, 180, 16);
api->registerOptionString("Visualization-Video", "Custom Sans Font", "Visualization/font1", "", true);
api->registerOptionString("Visualization-Video", "Custom Monospace Font", "Visualization/font2", "", true);
api->registerOptionInt("Visualization-Appearance", "View distance", "Visualization/viewdist", 20, 1000, 100);
api->registerOptionInt("Visualization-Appearance", "Note stretch", "Visualization/notestretch", 20, 500, 100);
api->registerOptionInt("Visualization-Appearance", "Minimum note length", "Visualization/minnotelen", 20, 500, 100);
api->registerOptionUint("Visualization-Appearance", "Chequer board tint (AARRGGBB)", "Visualization/chkrtint", 0, 0xFFFFFFFF, 0xFF999999);
api->registerOptionString("Visualization-Appearance", "Background Image", "Visualization/background", "", true);
api->registerOptionDouble("", "", "Visualization/px", -999999999, 999999999, -1e9);
api->registerOptionDouble("", "", "Visualization/py", -999999999, 999999999, -1e9);
api->registerOptionDouble("", "", "Visualization/pz", -999999999, 999999999, -1e9);
api->registerOptionDouble("", "", "Visualization/rx", -999999999, 999999999, -1e9);
api->registerOptionDouble("", "", "Visualization/ry", -999999999, 999999999, -1e9);
api->registerOptionDouble("", "", "Visualization/rz", -999999999, 999999999, -1e9);
for (int i = 0; i < 16; ++i)
{
api->registerOptionUint("", "", "Visualization/chActiveColor" + std::to_string(i), 0, 0xFFFFFFFF, accolors[i]);
api->registerOptionUint("", "", "Visualization/chInactiveColor" + std::to_string(i), 0, 0xFFFFFFFF, iccolors[i]);
}
wwidth = api->getOptionInt("Visualization/wwidth");
wheight = api->getOptionInt("Visualization/wheight");
wsupersample = api->getOptionInt("Visualization/supersampling");
wmultisample = api->getOptionInt("Visualization/multisampling");
fov = api->getOptionInt("Visualization/fov");
noteappearance = api->getOptionBool("Visualization/3dnotes");
showpiano = api->getOptionBool("Visualization/showpiano");
stairpiano = api->getOptionBool("Visualization/stairpiano");
showlabel = api->getOptionBool("Visualization/showlabel");
showparticle = api->getOptionBool("Visualization/showparticle");
horizontal = api->getOptionBool("Visualization/horizontal");
flat = api->getOptionBool("Visualization/flat");
savevp = api->getOptionBool("Visualization/savevp");
vsync = api->getOptionBool("Visualization/vsync");
tfps = api->getOptionInt("Visualization/tfps");
osdpos = api->getOptionEnumInt("Visualization/osdpos");
fontsize = api->getOptionInt("Visualization/fontsize");
viewdist = api->getOptionInt("Visualization/viewdist");
notestretch = api->getOptionInt("Visualization/notestretch");
minnotelength = api->getOptionInt("Visualization/minnotelen");
chkrtint = api->getOptionUint("Visualization/chkrtint");
for (int i = 0; i < 16; ++i)
{
accolors[i] = api->getOptionUint("Visualization/chActiveColor" + std::to_string(i));
iccolors[i] = api->getOptionUint("Visualization/chInactiveColor" + std::to_string(i));
}
if (savevp)
{
pos[0] = api->getOptionDouble("Visualization/px");
pos[1] = api->getOptionDouble("Visualization/py");
pos[2] = api->getOptionDouble("Visualization/pz");
rot[0] = api->getOptionDouble("Visualization/rx");
rot[1] = api->getOptionDouble("Visualization/ry");
rot[2] = api->getOptionDouble("Visualization/rz");
}
memset(pss, 0, sizeof(pss));
}
void qmpVisualization::deinit()
{
if (!api)
return;
close();
tspool.clear();
for (unsigned i = 0; i < pool.size(); ++i)
delete pool[i];
pool.clear();
api->unregisterUIHook("main.start", uihb);
api->unregisterUIHook("main.stop", uihs);
api->unregisterUIHook("main.pause", uihp);
api->unregisterUIHook("main.reset", uihr);
api->unregisterFunctionality("Visualization");
api->unregisterEventReadHandler(herh);
api->unregisterEventHandler(heh);
api->unregisterFileReadFinishHook(hfrf);
delete h;
delete closeh;
}
const char *qmpVisualization::pluginGetName()
{
return "QMidiPlayer Default Visualization Plugin";
}
const char *qmpVisualization::pluginGetVersion()
{
return PLUGIN_VERSION;
}
qmpVisualization *qmpVisualization::instance()
{
return inst;
}
void qmpVisualization::pushNoteOn(uint32_t tc, uint32_t ch, uint32_t key, uint32_t vel)
{
pendingt[ch][key].push(tc);
pendingv[ch][key].push(vel);
}
void qmpVisualization::pushNoteOff(uint32_t tc, uint32_t ch, uint32_t key)
{
if (pendingt[ch][key].size() < 1)
return;
MidiVisualEvent *ne = new MidiVisualEvent();
ne->tcs = pendingt[ch][key].top();
pendingt[ch][key].pop();
ne->tce = tc;
ne->ch = ch;
ne->key = key;
ne->vel = pendingv[ch][key].top();
pendingv[ch][key].pop();
pool.push_back(ne);
}