#include #include #include #include #include #include #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 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->tcstcs)return true;if(a->tcs>b->tcs)return false; if(a->tcetce)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); sm->smUpdateFunc(h);sm->smQuitFunc(closeh); sm->smWinTitle("QMidiPlayer Visualization"); 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(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); 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(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(); 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"); } 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]); } 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;istopPS(); while(!pendingt[i][j].empty())pendingt[i][j].pop(); while(!pendingv[i][j].empty())pendingv[i][j].pop(); } } void qmpVisualization::start(){playing=true;} void qmpVisualization::stop(){playing=false;} void qmpVisualization::pause(){playing=!playing;} void qmpVisualization::updateVisualization3D() { smQuad q; 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.01; rot[2]+=(x-lastx)*0.01; 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;itcs-ctk)*lpt>viewdist*2)break; if(fabs((double)pool[i]->tcs-ctk)*lpttce-ctk)*lptch==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 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(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;if(isnoteon) a.x=0.63*((double)pool[i]->key-64+api->getPitchBend(pool[i]->ch))+.1, b.x=0.63*((double)pool[i]->key-64+api->getPitchBend(pool[i]->ch))+.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+api->getPitchBend(pool[i]->ch))+.1, b.z=0.63*((double)pool[i]->key-64+api->getPitchBend(pool[i]->ch))+.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)*lpttcs-ctk)*lpt-minnotelength/100.-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]getPitchBend(i))+.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+api->getPitchBend(i))+.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.); } p3d[i]->render(smvec3d(0.756*api->getPitchBend(i),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()&&elbtce)*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;itcs-ctk)*lpt+wheight-nh<0; bool lowerbound=fabs((double)pool[i]->tce-ctk)*lpt+wheight-nhtcs-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(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) { uint32_t newkey=pool[i]->key+(int)floor(api->getPitchBend(pool[i]->ch)); double fpb=api->getPitchBend(pool[i]->ch)-floor(api->getPitchBend(pool[i]->ch)); 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)*lpttcs-ctk)*lpt+nh-minnotelength*0.04; } else { if(((double)pool[i]->tcs-pool[i]->tce)*lpttcs-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()&&elbtce-ctk)*lpt+nh<0)++elb; else while(pool.size()&&elbtce-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]smGetTime()*32))+wheight-nh); smvec2d b(a.x+notew*0.9,lpt+wheight-nh); uint32_t newkey=j+(int)floor(api->getPitchBend(i)); double fpb=api->getPitchBend(i)-floor(api->getPitchBend(i)); 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(sm->smGetKeyState(SMK_RIGHT)==SMKST_HIT) api->playerSeek(api->getCurrentPlaybackPercentage()+(sm->smGetKeyState(SMK_SHIFT)?5:1)); if(sm->smGetKeyState(SMK_LEFT)==SMKST_HIT) api->playerSeek(api->getCurrentPlaybackPercentage()-(sm->smGetKeyState(SMK_SHIFT)?5:1)); if(sm->smGetKeyState(SMK_B)==SMKST_HIT) debug^=1; if(playing)ctk+=1e6/api->getRawTempo()*api->getDivision()*sm->smGetDelta(); if(!flat) updateVisualization3D(); sm->smRenderBegin2D(); 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); } } if(osdpos==4){sm->smRenderEnd();return shouldclose;} int t,r;t=api->getKeySig();r=(int8_t)((t>>8)&0xFF)+7;t&=0xFF; std::wstring ts(t?minors:majors);ts=ts.substr(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; 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",api->getTimeSig()>>8,1<<(api->getTimeSig()&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",api->getRealTempo()); 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); 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); } 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(qmpPluginAPI* _api){api=_api;} qmpVisualization::~qmpVisualization(){api=nullptr;} void qmpVisualization::init() { h=new CMidiVisualHandler(this); closeh=new CloseHandler(this); rendererTh=nullptr;playing=false; 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); 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 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))); 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(); } ,nullptr); hfrf=api->registerFileReadFinishHook( [this](const void*,void*){ std::sort(this->tspool.begin(),this->tspool.end()); for(uint32_t tk=0,n=4,s=0;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(); } 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; } 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","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 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,0); api->registerOptionDouble("","","Visualization/py",-999999999,999999999,120); api->registerOptionDouble("","","Visualization/pz",-999999999,999999999,70); api->registerOptionDouble("","","Visualization/rx",-999999999,999999999,0); api->registerOptionDouble("","","Visualization/ry",-999999999,999999999,75); api->registerOptionDouble("","","Visualization/rz",-999999999,999999999,90); 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)); } memset(pss,0,sizeof(pss)); } void qmpVisualization::deinit() { if(!api)return;close();tspool.clear(); for(unsigned i=0;iunregisterUIHook("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;} 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); }