aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Chris Xiong <chirs241097@gmail.com> 2020-04-30 01:12:38 +0800
committerGravatar Chris Xiong <chirs241097@gmail.com> 2020-04-30 01:12:38 +0800
commitbd165c0254b9095bb9e5ea54def56b6404033ebe (patch)
treee6e965ff343c0cd4feea0180dd63522e05085567
parent8766f3b12e13d40b65eca23a850f687b0043d022 (diff)
downloadQMidiPlayer-bd165c0254b9095bb9e5ea54def56b6404033ebe.tar.xz
Add visualization renderer.
Add API for getting raw pitch bend values. Fix non-compliant RPN handling. The visualization renderer is still at the "proof-of-concept" stage. It's not very usable (yet).
-rw-r--r--core/qmpmidiplay.cpp7
-rw-r--r--core/qmpmidiplay.hpp1
-rw-r--r--include/qmpcorepublic.hpp1
-rw-r--r--qmidiplayer-desktop/qmpplugin.cpp5
-rw-r--r--qmidiplayer-desktop/qmpplugin.hpp1
-rw-r--r--visualization/CMakeLists.txt2
-rw-r--r--visualization/qmpvisualization.cpp227
-rw-r--r--visualization/qmpvisualization.hpp19
-rw-r--r--visualization/renderer/CMakeLists.txt26
-rw-r--r--visualization/renderer/main.cpp22
-rw-r--r--visualization/renderer/qmppluginapistub.cpp198
-rw-r--r--visualization/renderer/qmppluginapistub.hpp85
-rw-r--r--visualization/renderer/qmpsettingsro.cpp167
-rw-r--r--visualization/renderer/qmpsettingsro.hpp73
-rw-r--r--visualization/renderer/qmpvisrendercore.cpp88
-rw-r--r--visualization/renderer/qmpvisrendercore.hpp44
16 files changed, 892 insertions, 74 deletions
diff --git a/core/qmpmidiplay.cpp b/core/qmpmidiplay.cpp
index 01045c1..a354d6e 100644
--- a/core/qmpmidiplay.cpp
+++ b/core/qmpmidiplay.cpp
@@ -106,7 +106,7 @@ void CMidiPlayer::processEventStub(const SEvent *e)
if(~rpnid[e->type&0x0F]&&~rpnval[e->type&0x0F])
{
if(rpnid[e->type&0x0F]==0)ccc[e->type&0x0F][134]=rpnval[e->type&0x0F];
- rpnid[e->type&0x0F]=rpnval[e->type&0x0F]=-1;
+ rpnval[e->type&0x0F]=-1;
}
ccc[e->type&0x0F][e->p1]=e->p2;
break;
@@ -404,6 +404,11 @@ 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;}
diff --git a/core/qmpmidiplay.hpp b/core/qmpmidiplay.hpp
index 83a7128..471c1db 100644
--- a/core/qmpmidiplay.hpp
+++ b/core/qmpmidiplay.hpp
@@ -135,6 +135,7 @@ class CMidiPlayer
uint32_t getDivision();
uint32_t getMaxTick();
double getPitchBend(int ch);
+ double getPitchBendRaw(int ch,uint32_t *pb,uint32_t *pbr);
const char* getTitle();
const char* getCopyright();
diff --git a/include/qmpcorepublic.hpp b/include/qmpcorepublic.hpp
index bebde30..c6caed5 100644
--- a/include/qmpcorepublic.hpp
+++ b/include/qmpcorepublic.hpp
@@ -136,6 +136,7 @@ class qmpPluginAPI
virtual int getChannelPreset(int ch)=0;
virtual void playerSeek(uint32_t percentage)=0;
virtual double getPitchBend(int ch)=0;
+ virtual void getPitchBendRaw(int ch,uint32_t *pb,uint32_t *pbr)=0;
virtual bool getChannelMask(int ch)=0;
virtual std::string getTitle()=0;
virtual std::wstring getWTitle()=0;
diff --git a/qmidiplayer-desktop/qmpplugin.cpp b/qmidiplayer-desktop/qmpplugin.cpp
index 4785b16..9cd1f35 100644
--- a/qmidiplayer-desktop/qmpplugin.cpp
+++ b/qmidiplayer-desktop/qmpplugin.cpp
@@ -167,6 +167,11 @@ void qmpPluginAPIImpl::playerSeek(uint32_t percentage)
{if(qmw)qmw->playerSeek(percentage);}
double qmpPluginAPIImpl::getPitchBend(int ch)
{return qmw&&qmw->getPlayer()?qmw->getPlayer()->getPitchBend(ch):0;}
+void qmpPluginAPIImpl::getPitchBendRaw(int ch,uint32_t *pb,uint32_t *pbr)
+{
+ if(qmw&&qmw->getPlayer())
+ qmw->getPlayer()->getPitchBendRaw(ch,pb,pbr);
+}
bool qmpPluginAPIImpl::getChannelMask(int ch)
{return qmw&&qmw->getPlayer()?qmw->getPlayer()->getChannelMask(ch):false;}
std::string qmpPluginAPIImpl::getTitle()
diff --git a/qmidiplayer-desktop/qmpplugin.hpp b/qmidiplayer-desktop/qmpplugin.hpp
index 08343cd..99dedcd 100644
--- a/qmidiplayer-desktop/qmpplugin.hpp
+++ b/qmidiplayer-desktop/qmpplugin.hpp
@@ -35,6 +35,7 @@ public:
int getChannelPreset(int ch);
void playerSeek(uint32_t percentage);
double getPitchBend(int ch);
+ void getPitchBendRaw(int ch,uint32_t *pb,uint32_t *pbr);
bool getChannelMask(int ch);
std::string getTitle();
std::wstring getWTitle();
diff --git a/visualization/CMakeLists.txt b/visualization/CMakeLists.txt
index 678f4a7..6d474e0 100644
--- a/visualization/CMakeLists.txt
+++ b/visualization/CMakeLists.txt
@@ -17,6 +17,8 @@ set(BUILD_DUMB ON)
set(BUILD_EXAMPLE OFF)
add_subdirectory(SMELT)
+add_subdirectory(renderer)
+
find_package(glfw3 REQUIRED)
find_package(GLEW REQUIRED)
find_package(DevIL REQUIRED)
diff --git a/visualization/qmpvisualization.cpp b/visualization/qmpvisualization.cpp
index 013e615..e4e4428 100644
--- a/visualization/qmpvisualization.cpp
+++ b/visualization/qmpvisualization.cpp
@@ -72,7 +72,7 @@ void qmpVisualization::showThread()
iccolors[i]=api->getOptionUint("Visualization/chInactiveColor"+std::to_string(i));
}
sm=smGetInterface(SMELT_APILEVEL);
- sm->smVidMode(wwidth,wheight,true);
+ sm->smVidMode(wwidth,wheight,true,!hidewindow);
sm->smUpdateFunc(h);sm->smQuitFunc(closeh);
sm->smWinTitle("QMidiPlayer Visualization");
sm->smSetFPS(vsync?FPS_VSYNC:tfps);
@@ -86,6 +86,8 @@ void qmpVisualization::showThread()
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;
@@ -178,6 +180,8 @@ void qmpVisualization::close()
api->setOptionDouble("Visualization/ry",rot[1]);
api->setOptionDouble("Visualization/rz",rot[2]);
}
+ if(rendermode)
+ delete[] fbcont;
font.releaseTTF();
font2.releaseTTF();
fonthdpi.releaseTTF();
@@ -192,8 +196,10 @@ void qmpVisualization::close()
void qmpVisualization::reset()
{
for(unsigned i=0;i<pool.size();++i)delete pool[i];
- pool.clear();elb=ctk=lstk=0;tspool.clear();
+ pool.clear();elb=ctk=lstk=cfr=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();
@@ -201,55 +207,65 @@ void qmpVisualization::reset()
while(!pendingv[i][j].empty())pendingv[i][j].pop();
}
}
+
+void qmpVisualization::switchToRenderMode(void(*frameCallback)(void*,size_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(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(!rendermode)
{
- if(horizontal)
+ 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))
{
- pos[0]=-20;pos[1]=45;pos[2]=0;
- rot[0]=0;rot[1]=90;rot[2]=90;
+ 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;
+ }
}
- else
+ if(sm->smGetKeyState(SMK_LBUTTON)==SMKST_HIT)
+ sm->smSetMouseGrab(true),sm->smGetMouse2f(&lastx,&lasty);
+ if(sm->smGetKeyState(SMK_LBUTTON)==SMKST_KEEP)
{
- pos[0]=0;pos[1]=120;pos[2]=70;
- rot[0]=0;rot[1]=75;rot[2]=90;
+ 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;
}
- 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;
@@ -298,21 +314,27 @@ void qmpVisualization::updateVisualization3D()
nebuf->addTransformedEntity(&c,I,smvec3d(0,0,0));
continue;
}
- if(pool[i]->ch>=996)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;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;
+ 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+api->getPitchBend(pool[i]->ch))+.1,
- b.z=0.63*((double)pool[i]->key-64+api->getPitchBend(pool[i]->ch))+.7;
+ {
+ 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)
{
@@ -354,10 +376,11 @@ void qmpVisualization::updateVisualization3D()
else spectrar[i][j]=spectra[i][j];
if(spectrar[i][j])
{
- smvec3d a(0.756*((double)j-64+api->getPitchBend(i))+.12,
+ 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+api->getPitchBend(i))+.84,
+ 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);
@@ -375,7 +398,8 @@ void qmpVisualization::updateVisualization3D()
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));
+ 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)
@@ -435,7 +459,7 @@ void qmpVisualization::updateVisualization2D()
if(showmeasure)sm->smRenderQuad(&nq);
continue;
}
- if(pool[i]->ch>=996)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);
@@ -444,10 +468,12 @@ void qmpVisualization::updateVisualization2D()
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)
+ 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));
+ 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;
@@ -607,8 +633,9 @@ void qmpVisualization::updateVisualization2D()
{
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);
- uint32_t newkey=j+(int)floor(api->getPitchBend(i));
- double fpb=api->getPitchBend(i)-floor(api->getPitchBend(i));
+ 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.);
@@ -632,18 +659,21 @@ void qmpVisualization::updateVisualization2D()
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(!rendermode)
+ {
+ 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)
{
- if(internal_clock_source)
+ if(rendermode)
{
- ctk=1.*lstk/tfps/api->getRawTempo()*api->getDivision();
- ++lstk;
+ 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();
@@ -684,7 +714,17 @@ bool qmpVisualization::update()
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)ctp=pool[i]->key;
+ if(pool[i]->ch==996)
+ {
+ ctp=pool[i]->key;
+ if(rendermode)
+ {
+ lstk=ctk;
+ cfr=1;
+ }
+ }
+ if(pool[i]->ch==995)cpbr[pool[i]->vel]=pool[i]->key;
+ if(pool[i]->ch==994)cpw[pool[i]->vel]=pool[i]->key;
}
int t,r;t=cks;r=(int8_t)((t>>8)&0xFF)+7;t&=0xFF;
std::wstring ts(t?minors:majors,2*r,2);
@@ -707,9 +747,12 @@ bool qmpVisualization::update()
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(!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;
@@ -726,11 +769,21 @@ bool qmpVisualization::update()
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.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();
+ if(rendermode)
+ {
+ if(ctk>api->getMaxTick())
+ framecb(nullptr,0);
+ else
+ {
+ sm->smPixelCopy(0,0,wwidth,wheight,4*wwidth*wheight,fbcont);
+ framecb(fbcont,4*wwidth*wheight);
+ }
+ }
return shouldclose;
}
@@ -753,13 +806,27 @@ void qmpVisualization::drawCube(smvec3d a,smvec3d b,DWORD col,SMTEX tex,int face
}
}
-qmpVisualization::qmpVisualization(qmpPluginAPI* _api){api=_api;}
-qmpVisualization::~qmpVisualization(){api=nullptr;}
+qmpVisualization* qmpVisualization::inst=nullptr;
+
+qmpVisualization::qmpVisualization(qmpPluginAPI* _api)
+{
+ api=_api;
+ inst=this;
+ rendermode=false;
+}
+qmpVisualization::~qmpVisualization()
+{
+ api=nullptr;
+ inst=nullptr;
+}
void qmpVisualization::init()
{
h=new CMidiVisualHandler(this);
closeh=new CloseHandler(this);
rendererTh=nullptr;playing=false;
+ hidewindow=false;
+ 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);
@@ -771,6 +838,8 @@ void qmpVisualization::init()
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*){
@@ -786,6 +855,19 @@ void qmpVisualization::init()
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)
{
@@ -813,6 +895,8 @@ void qmpVisualization::init()
,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;tk<=this->api->getMaxTick();){
while(tk<(s>=this->tspool.size()?this->api->getMaxTick():this->tspool[s].first)){
@@ -914,6 +998,9 @@ const char* qmpVisualization::pluginGetName()
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);
diff --git a/visualization/qmpvisualization.hpp b/visualization/qmpvisualization.hpp
index a5dad02..824093a 100644
--- a/visualization/qmpvisualization.hpp
+++ b/visualization/qmpvisualization.hpp
@@ -37,14 +37,17 @@ class qmpVisualization:public qmpPluginIntf,public qmpFuncBaseIntf
smParticleSystem* pss[16][128];
smPSEmissionPositionGenerator* psepg;
float pos[3],rot[3],lastx,lasty;
- uint32_t ctc,ctk,elb,lstk;
- uint32_t cts,cks,ctp;
+ uint32_t ctc,ctk,elb,lstk,cfr;
+ uint32_t cts,cks,ctp,cpbr[16],cpw[16];
+ uint32_t rpnid[16],rpnval[16];
std::chrono::steady_clock::time_point lst;
double etps;
bool shouldclose,playing,debug;
- bool internal_clock_source;
+ bool rendermode,hidewindow;
int herh,heh,hfrf;
int uihb,uihs,uihp,uihr,uihk;
+ void(*framecb)(void*,size_t);
+ DWORD* fbcont;
std::vector<std::pair<uint32_t,uint32_t>>tspool;
int traveld[16][128];bool notestatus[16][128],lastnotestatus[16][128];
int spectra[16][128],spectrar[16][128];
@@ -54,6 +57,8 @@ class qmpVisualization:public qmpPluginIntf,public qmpFuncBaseIntf
void pushNoteOff(uint32_t tc,uint32_t ch,uint32_t key);
void updateVisualization3D();
void updateVisualization2D();
+
+ static qmpVisualization* inst;
public:
qmpVisualization(qmpPluginAPI* _api);
~qmpVisualization();
@@ -64,11 +69,14 @@ class qmpVisualization:public qmpPluginIntf,public qmpFuncBaseIntf
void stop();
void pause();
void reset();
+ void switchToRenderMode(void(*frameCallback)(void*,size_t),bool _hidewindow);
void init();
void deinit();
const char* pluginGetName();
const char* pluginGetVersion();
+
+ static qmpVisualization* instance();
};
class CMidiVisualHandler:public smHandler
@@ -101,6 +109,11 @@ extern "C"{
{return new qmpVisualization(api);}
EXPORTSYM const char* qmpPluginGetAPIRev()
{return QMP_PLUGIN_API_REV;}
+ EXPORTSYM void switchToRenderMode(void(*frameCallback)(void*,size_t),bool hidewindow)
+ {
+ if(qmpVisualization::instance())
+ qmpVisualization::instance()->switchToRenderMode(frameCallback,hidewindow);
+ }
}
#endif // QMPVISUALIZATION_H
diff --git a/visualization/renderer/CMakeLists.txt b/visualization/renderer/CMakeLists.txt
new file mode 100644
index 0000000..90a4704
--- /dev/null
+++ b/visualization/renderer/CMakeLists.txt
@@ -0,0 +1,26 @@
+set(qmpvisrender_SOURCES
+ qmpsettingsro.hpp
+ qmppluginapistub.hpp
+ qmpvisrendercore.hpp
+ main.cpp
+ qmpsettingsro.cpp
+ qmppluginapistub.cpp
+ qmpvisrendercore.cpp
+)
+
+set(CMAKE_AUTOMOC ON)
+
+include_directories(${PROJECT_SOURCE_DIR}/core/)
+include_directories(${PROJECT_SOURCE_DIR}/include/)
+
+add_executable(qmpvisrender
+ ${qmpvisrender_SOURCES}
+)
+
+target_link_libraries(qmpvisrender
+ Qt5::Core
+ qmpcore
+ ${CMAKE_DL_LIBS}
+)
+
+install(TARGETS qmpvisrender)
diff --git a/visualization/renderer/main.cpp b/visualization/renderer/main.cpp
new file mode 100644
index 0000000..ec4dd1d
--- /dev/null
+++ b/visualization/renderer/main.cpp
@@ -0,0 +1,22 @@
+#include <QProcess>
+#include <QCommandLineParser>
+
+#include "qmpvisrendercore.hpp"
+
+int main(int argc,char **argv)
+{
+ QCoreApplication::setApplicationName("qmpvisrender");
+ QCoreApplication a(argc,argv);
+ QCommandLineParser clp;
+ clp.setApplicationDescription("Renderer a visualization of a midi file.");
+ clp.addHelpOption();
+ clp.parse(a.arguments());
+ qmpVisRenderCore core;
+ core.loadVisualizationLibrary();
+ if(clp.positionalArguments().size())
+ core.setMIDIFile(clp.positionalArguments().front().toStdString().c_str());
+ core.startRender();
+ int retval=a.exec();
+ core.unloadVisualizationLibrary();
+ return retval;
+}
diff --git a/visualization/renderer/qmppluginapistub.cpp b/visualization/renderer/qmppluginapistub.cpp
new file mode 100644
index 0000000..d37e191
--- /dev/null
+++ b/visualization/renderer/qmppluginapistub.cpp
@@ -0,0 +1,198 @@
+#include "qmpmidiplay.hpp"
+#include "qmpvisrendercore.hpp"
+#include "qmpsettingsro.hpp"
+#include "qmppluginapistub.hpp"
+
+#include <QTextCodec>
+
+qmpPluginAPIStub::qmpPluginAPIStub(qmpVisRenderCore *_core):
+ core(_core)
+{
+}
+
+qmpPluginAPIStub::~qmpPluginAPIStub()
+{
+ core=nullptr;
+}
+
+uint32_t qmpPluginAPIStub::getDivision()
+{
+ return core->player->getDivision();
+}
+uint32_t qmpPluginAPIStub::getRawTempo(){return 0;}
+double qmpPluginAPIStub::getRealTempo(){return 0;}
+uint32_t qmpPluginAPIStub::getTimeSig(){return 0;}
+int qmpPluginAPIStub::getKeySig(){return 0;}
+uint32_t qmpPluginAPIStub::getNoteCount(){return 0;}
+uint32_t qmpPluginAPIStub::getMaxTick()
+{
+ return core->player->getMaxTick();
+}
+uint32_t qmpPluginAPIStub::getCurrentPolyphone(){return 0;}
+uint32_t qmpPluginAPIStub::getMaxPolyphone(){return 0;}
+uint32_t qmpPluginAPIStub::getCurrentTimeStamp(){return 0;}
+uint32_t qmpPluginAPIStub::getCurrentPlaybackPercentage(){return 0;}
+int qmpPluginAPIStub::getChannelCC(int ch, int cc){return 0;}
+int qmpPluginAPIStub::getChannelPreset(int ch){return 0;}
+void qmpPluginAPIStub::playerSeek(uint32_t percentage){}
+double qmpPluginAPIStub::getPitchBend(int ch){return 0;}
+void qmpPluginAPIStub::getPitchBendRaw(int ch,uint32_t *pb,uint32_t *pbr){}
+bool qmpPluginAPIStub::getChannelMask(int ch){return 0;}
+std::string qmpPluginAPIStub::getTitle()
+{
+ if(core->settings()->getOptionEnumIntOptName("Midi/TextEncoding")=="Unicode")
+ return std::string(core->player->getTitle());
+ return QTextCodec::codecForName(
+ core->settings()->getOptionEnumIntOptName("Midi/TextEncoding").c_str())->
+ toUnicode(core->player->getTitle()).toStdString();
+}
+std::wstring qmpPluginAPIStub::getWTitle()
+{
+ if(core->settings()->getOptionEnumIntOptName("Midi/TextEncoding")=="Unicode")
+ return QString(core->player->getTitle()).toStdWString();
+ return QTextCodec::codecForName(
+ core->settings()->getOptionEnumIntOptName("Midi/TextEncoding").c_str())->
+ toUnicode(core->player->getTitle()).toStdWString();
+}
+std::string qmpPluginAPIStub::getChannelPresetString(int ch){return std::string();}
+bool qmpPluginAPIStub::isDarkTheme(){return false;}
+void *qmpPluginAPIStub::getMainWindow(){return nullptr;}
+void qmpPluginAPIStub::discardCurrentEvent(){}
+void qmpPluginAPIStub::commitEventChange(SEvent d){}
+void qmpPluginAPIStub::callEventReaderCB(SEvent d){}
+void qmpPluginAPIStub::setFuncState(std::string name,bool state){}
+void qmpPluginAPIStub::setFuncEnabled(std::string name, bool enable)
+{}
+void qmpPluginAPIStub::registerFunctionality(qmpFuncBaseIntf *i, std::string name, std::string desc, const char *icon, int iconlen, bool checkable)
+{
+ if(name=="Visualization")
+ core->vf=i;
+}
+void qmpPluginAPIStub::unregisterFunctionality(std::string name)
+{
+ if(name=="Visualization")
+ core->vf=nullptr;
+}
+
+int qmpPluginAPIStub::registerUIHook(std::string e, ICallBack *cb, void *userdat){}
+int qmpPluginAPIStub::registerUIHook(std::string e, callback_t cb, void *userdat)
+{
+ if(e=="main.start")
+ core->startcb=cb;
+ return 0;
+}
+void qmpPluginAPIStub::unregisterUIHook(std::string e, int hook)
+{
+ if(e=="main.start")
+ core->startcb=nullptr;
+}
+
+void qmpPluginAPIStub::registerMidiOutDevice(qmpMidiOutDevice *dev, std::string name){}
+void qmpPluginAPIStub::unregisterMidiOutDevice(std::string name){}
+
+int qmpPluginAPIStub::registerEventReaderIntf(ICallBack *cb, void *userdata){}
+void qmpPluginAPIStub::unregisterEventReaderIntf(int intfhandle){}
+int qmpPluginAPIStub::registerEventHandlerIntf(ICallBack *cb, void *userdata){}
+void qmpPluginAPIStub::unregisterEventHandlerIntf(int intfhandle){}
+int qmpPluginAPIStub::registerFileReadFinishedHandlerIntf(ICallBack *cb, void *userdata){}
+void qmpPluginAPIStub::unregisterFileReadFinishedHandlerIntf(int intfhandle){}
+
+int qmpPluginAPIStub::registerEventHandler(callback_t cb, void *userdata, bool post){}
+void qmpPluginAPIStub::unregisterEventHandler(int id){}
+int qmpPluginAPIStub::registerEventReadHandler(callback_t cb, void *userdata)
+{
+ return core->player->registerEventReadHandler(cb,userdata);
+}
+void qmpPluginAPIStub::unregisterEventReadHandler(int id)
+{
+ core->player->unregisterEventReadHandler(id);
+}
+int qmpPluginAPIStub::registerFileReadFinishHook(callback_t cb, void *userdata)
+{
+ return core->player->registerFileReadFinishHook(cb,userdata);
+}
+void qmpPluginAPIStub::unregisterFileReadFinishHook(int id)
+{
+ core->player->unregisterFileReadFinishHook(id);
+}
+
+void qmpPluginAPIStub::registerFileReader(qmpFileReader *reader, std::string name){}
+void qmpPluginAPIStub::unregisterFileReader(std::string name){}
+
+void qmpPluginAPIStub::registerOptionInt(std::string tab, std::string desc, std::string key, int min, int max, int defaultval)
+{
+ core->settings()->registerOptionInt(tab,desc,key,min,max,defaultval);
+}
+int qmpPluginAPIStub::getOptionInt(std::string key)
+{
+ return core->settings()->getOptionInt(key);
+}
+void qmpPluginAPIStub::setOptionInt(std::string key, int val)
+{
+ core->settings()->setOptionInt(key,val);
+}
+
+void qmpPluginAPIStub::registerOptionUint(std::string tab, std::string desc, std::string key, unsigned min, unsigned max, unsigned defaultval)
+{
+ core->settings()->registerOptionUint(tab,desc,key,min,max,defaultval);
+}
+unsigned qmpPluginAPIStub::getOptionUint(std::string key)
+{
+ return core->settings()->getOptionUint(key);
+}
+void qmpPluginAPIStub::setOptionUint(std::string key, unsigned val)
+{
+ return core->settings()->setOptionUint(key,val);
+}
+
+void qmpPluginAPIStub::registerOptionBool(std::string tab, std::string desc, std::string key, bool defaultval)
+{
+ core->settings()->registerOptionBool(tab,desc,key,defaultval);
+}
+bool qmpPluginAPIStub::getOptionBool(std::string key)
+{
+ return core->settings()->getOptionBool(key);
+}
+void qmpPluginAPIStub::setOptionBool(std::string key, bool val)
+{
+ core->settings()->setOptionBool(key,val);
+}
+
+void qmpPluginAPIStub::registerOptionDouble(std::string tab, std::string desc, std::string key, double min, double max, double defaultval)
+{
+ core->settings()->registerOptionDouble(tab,desc,key,min,max,defaultval);
+}
+double qmpPluginAPIStub::getOptionDouble(std::string key)
+{
+ return core->settings()->getOptionDouble(key);
+}
+void qmpPluginAPIStub::setOptionDouble(std::string key, double val)
+{
+ core->settings()->setOptionDouble(key,val);
+}
+
+void qmpPluginAPIStub::registerOptionString(std::string tab, std::string desc, std::string key, std::string defaultval, bool ispath)
+{
+ core->settings()->registerOptionString(tab,desc,key,defaultval,ispath);
+}
+std::string qmpPluginAPIStub::getOptionString(std::string key)
+{
+ return core->settings()->getOptionString(key);
+}
+void qmpPluginAPIStub::setOptionString(std::string key, std::string val)
+{
+ core->settings()->setOptionString(key,val);
+}
+
+void qmpPluginAPIStub::registerOptionEnumInt(std::string tab, std::string desc, std::string key, std::vector<std::string> options, int defaultval)
+{
+ core->settings()->registerOptionEnumInt(tab,desc,key,options,defaultval);
+}
+int qmpPluginAPIStub::getOptionEnumInt(std::string key)
+{
+ return core->settings()->getOptionEnumInt(key);
+}
+void qmpPluginAPIStub::setOptionEnumInt(std::string key, int val)
+{
+ core->settings()->setOptionEnumInt(key,val);
+}
diff --git a/visualization/renderer/qmppluginapistub.hpp b/visualization/renderer/qmppluginapistub.hpp
new file mode 100644
index 0000000..d96cfca
--- /dev/null
+++ b/visualization/renderer/qmppluginapistub.hpp
@@ -0,0 +1,85 @@
+#ifndef QMPPLUGINAPISTUB_HPP
+#define QMPPLUGINAPISTUB_HPP
+
+#include "qmpcorepublic.hpp"
+
+class qmpVisRenderCore;
+class qmpPluginAPIStub:public qmpPluginAPI
+{
+public:
+ qmpPluginAPIStub(qmpVisRenderCore *_core);
+ ~qmpPluginAPIStub();
+ uint32_t getDivision();
+ uint32_t getRawTempo();
+ double getRealTempo();
+ uint32_t getTimeSig();
+ int getKeySig();
+ uint32_t getNoteCount();
+ uint32_t getMaxTick();
+ uint32_t getCurrentPolyphone();
+ uint32_t getMaxPolyphone();
+ uint32_t getCurrentTimeStamp();
+ uint32_t getCurrentPlaybackPercentage();
+ int getChannelCC(int ch,int cc);
+ int getChannelPreset(int ch);
+ void playerSeek(uint32_t percentage);
+ double getPitchBend(int ch);
+ void getPitchBendRaw(int ch,uint32_t *pb,uint32_t *pbr);
+ bool getChannelMask(int ch);
+ std::string getTitle();
+ std::wstring getWTitle();
+ std::string getChannelPresetString(int ch);
+ bool isDarkTheme();
+ void* getMainWindow();
+
+ void discardCurrentEvent();
+ void commitEventChange(SEvent d);
+ void callEventReaderCB(SEvent d);
+ void setFuncState(std::string name,bool state);
+ void setFuncEnabled(std::string name,bool enable);
+
+ void registerFunctionality(qmpFuncBaseIntf* i,std::string name,std::string desc,const char* icon,int iconlen,bool checkable);
+ void unregisterFunctionality(std::string name);
+ int registerUIHook(std::string e,ICallBack* cb,void* userdat);
+ int registerUIHook(std::string e,callback_t cb,void* userdat);
+ void unregisterUIHook(std::string e,int hook);
+ void registerMidiOutDevice(qmpMidiOutDevice* dev,std::string name);
+ void unregisterMidiOutDevice(std::string name);
+ int registerEventReaderIntf(ICallBack* cb,void* userdata);
+ void unregisterEventReaderIntf(int intfhandle);
+ int registerEventHandlerIntf(ICallBack* cb,void* userdata);
+ void unregisterEventHandlerIntf(int intfhandle);
+ int registerFileReadFinishedHandlerIntf(ICallBack* cb,void* userdata);
+ void unregisterFileReadFinishedHandlerIntf(int intfhandle);
+ int registerEventHandler(callback_t cb,void *userdata,bool post=false);
+ void unregisterEventHandler(int id);
+ int registerEventReadHandler(callback_t cb,void *userdata);
+ void unregisterEventReadHandler(int id);
+ int registerFileReadFinishHook(callback_t cb,void *userdata);
+ void unregisterFileReadFinishHook(int id);
+ void registerFileReader(qmpFileReader* reader,std::string name);
+ void unregisterFileReader(std::string name);
+
+ void registerOptionInt(std::string tab,std::string desc,std::string key,int min,int max,int defaultval);
+ int getOptionInt(std::string key);
+ void setOptionInt(std::string key,int val);
+ void registerOptionUint(std::string tab,std::string desc,std::string key,unsigned min,unsigned max,unsigned defaultval);
+ unsigned getOptionUint(std::string key);
+ void setOptionUint(std::string key,unsigned val);
+ void registerOptionBool(std::string tab,std::string desc,std::string key,bool defaultval);
+ bool getOptionBool(std::string key);
+ void setOptionBool(std::string key,bool val);
+ void registerOptionDouble(std::string tab,std::string desc,std::string key,double min,double max,double defaultval);
+ double getOptionDouble(std::string key);
+ void setOptionDouble(std::string key,double val);
+ void registerOptionString(std::string tab,std::string desc,std::string key,std::string defaultval,bool ispath=false);
+ std::string getOptionString(std::string key);
+ void setOptionString(std::string key,std::string val);
+ void registerOptionEnumInt(std::string tab,std::string desc,std::string key,std::vector<std::string> options,int defaultval);
+ int getOptionEnumInt(std::string key);
+ void setOptionEnumInt(std::string key,int val);
+private:
+ qmpVisRenderCore* core;
+};
+
+#endif // QMPPLUGINAPISTUB_HPP
diff --git a/visualization/renderer/qmpsettingsro.cpp b/visualization/renderer/qmpsettingsro.cpp
new file mode 100644
index 0000000..cc6e0bf
--- /dev/null
+++ b/visualization/renderer/qmpsettingsro.cpp
@@ -0,0 +1,167 @@
+#include <QScopedPointer>
+#include <QSettings>
+
+#include "qmpsettingsro.hpp"
+
+qmpSettingsRO::qmpSettingsRO()
+{
+}
+
+void qmpSettingsRO::registerOptionInt(std::string tab,std::string desc,std::string key,int min,int max,int defaultval)
+{
+ Q_UNUSED(tab)
+ optionlist.push_back(key);
+ options[key]=qmpOptionR(desc,qmpOptionR::ParameterType::parameter_int,defaultval,min,max);
+}
+int qmpSettingsRO::getOptionInt(std::string key)
+{
+ if(options.find(key)!=options.end()&&options[key].type==qmpOptionR::ParameterType::parameter_int)
+ return settings.value(QString(key.c_str()),options[key].defaultval).toInt();
+ return options[key].defaultval.toInt();
+}
+void qmpSettingsRO::setOptionInt(std::string key,int val)
+{
+ if(options.find(key)!=options.end()&&options[key].type==qmpOptionR::ParameterType::parameter_int)
+ settings.insert(QString(key.c_str()),val);
+}
+
+void qmpSettingsRO::registerOptionUint(std::string tab,std::string desc,std::string key,unsigned min,unsigned max,unsigned defaultval)
+{
+ Q_UNUSED(tab)
+ optionlist.push_back(key);
+ options[key]=qmpOptionR(desc,qmpOptionR::ParameterType::parameter_uint,defaultval,min,max);
+}
+unsigned qmpSettingsRO::getOptionUint(std::string key)
+{
+ if(options.find(key)!=options.end()&&options[key].type==qmpOptionR::ParameterType::parameter_uint)
+ return settings.value(QString(key.c_str()),options[key].defaultval).toUInt();
+ return options[key].defaultval.toUInt();
+}
+void qmpSettingsRO::setOptionUint(std::string key,unsigned val)
+{
+ if(options.find(key)!=options.end()&&options[key].type==qmpOptionR::ParameterType::parameter_uint)
+ settings.insert(QString(key.c_str()),val);
+}
+
+void qmpSettingsRO::registerOptionBool(std::string tab,std::string desc,std::string key,bool defaultval)
+{
+ Q_UNUSED(tab)
+ optionlist.push_back(key);
+ options[key]=qmpOptionR(desc,qmpOptionR::ParameterType::parameter_bool,defaultval);
+}
+bool qmpSettingsRO::getOptionBool(std::string key)
+{
+ if(options.find(key)!=options.end()&&options[key].type==qmpOptionR::ParameterType::parameter_bool)
+ return settings.value(QString(key.c_str()),options[key].defaultval).toBool();
+ return options[key].defaultval.toBool();
+}
+void qmpSettingsRO::setOptionBool(std::string key,bool val)
+{
+ if(options.find(key)!=options.end()&&options[key].type==qmpOptionR::ParameterType::parameter_bool)
+ settings.insert(QString(key.c_str()),val);
+}
+
+void qmpSettingsRO::registerOptionDouble(std::string tab,std::string desc,std::string key,double min,double max,double defaultval)
+{
+ Q_UNUSED(tab)
+ optionlist.push_back(key);
+ options[key]=qmpOptionR(desc,qmpOptionR::ParameterType::parameter_double,defaultval,min,max);
+}
+double qmpSettingsRO::getOptionDouble(std::string key)
+{
+ if(options.find(key)!=options.end()&&options[key].type==qmpOptionR::ParameterType::parameter_double)
+ return settings.value(QString(key.c_str()),options[key].defaultval).toDouble();
+ return options[key].defaultval.toDouble();
+}
+void qmpSettingsRO::setOptionDouble(std::string key,double val)
+{
+ if(options.find(key)!=options.end()&&options[key].type==qmpOptionR::ParameterType::parameter_double)
+ settings.insert(QString(key.c_str()),val);
+}
+
+void qmpSettingsRO::registerOptionString(std::string tab,std::string desc,std::string key,std::string defaultval,bool is_url)
+{
+ Q_UNUSED(tab)
+ optionlist.push_back(key);
+ options[key]=qmpOptionR(desc,
+ is_url?qmpOptionR::ParameterType::parameter_url:qmpOptionR::ParameterType::parameter_str,
+ QString(defaultval.c_str()));
+}
+std::string qmpSettingsRO::getOptionString(std::string key)
+{
+ if(options.find(key)!=options.end()&&
+ (options[key].type==qmpOptionR::ParameterType::parameter_str||options[key].type==qmpOptionR::ParameterType::parameter_url))
+ return settings.value(QString(key.c_str()),options[key].defaultval).toString().toStdString();
+ return options[key].defaultval.toString().toStdString();
+}
+void qmpSettingsRO::setOptionString(std::string key,std::string val)
+{
+ if(options.find(key)!=options.end()&&
+ (options[key].type==qmpOptionR::ParameterType::parameter_str||options[key].type==qmpOptionR::ParameterType::parameter_url))
+ settings.insert(QString(key.c_str()),QString(val.c_str()));
+}
+
+void qmpSettingsRO::registerOptionEnumInt(std::string tab,std::string desc,std::string key,std::vector<std::string> enumlist,int defaultval)
+{
+ Q_UNUSED(tab)
+ optionlist.push_back(key);
+ options[key]=qmpOptionR(desc,qmpOptionR::ParameterType::parameter_enum,defaultval);
+ options[key].enumlist=enumlist;
+}
+int qmpSettingsRO::getOptionEnumInt(std::string key)
+{
+ if(options.find(key)!=options.end()&&options[key].type==qmpOptionR::ParameterType::parameter_enum)
+ {
+ std::string curitm=settings.value(QString(key.c_str()),options[key].defaultval).toString().toStdString();
+ auto curidx=std::find(options[key].enumlist.begin(),options[key].enumlist.end(),curitm);
+ if(curidx!=options[key].enumlist.end())
+ return static_cast<int>(curidx-options[key].enumlist.begin());
+ else
+ {
+ return options[key].defaultval.toInt();
+ }
+ }
+ return options[key].defaultval.toInt();
+}
+std::string qmpSettingsRO::getOptionEnumIntOptName(std::string key)
+{
+ if(options.find(key)!=options.end()&&options[key].type==qmpOptionR::ParameterType::parameter_enum)
+ {
+ std::string curitm=settings.value(QString(key.c_str()),options[key].defaultval).toString().toStdString();
+ auto curidx=std::find(options[key].enumlist.begin(),options[key].enumlist.end(),curitm);
+ if(curidx!=options[key].enumlist.end())
+ return curitm;
+ else
+ {
+ return options[key].enumlist[static_cast<size_t>(options[key].defaultval.toInt())];
+ }
+ }
+ return options[key].enumlist[static_cast<size_t>(options[key].defaultval.toInt())];
+}
+void qmpSettingsRO::setOptionEnumInt(std::string key,int val)
+{
+ if(options.find(key)!=options.end()&&options[key].type==qmpOptionR::ParameterType::parameter_enum)
+ {
+ if(static_cast<size_t>(val)<options[key].enumlist.size())
+ settings.insert(QString(key.c_str()),QString(options[key].enumlist[static_cast<size_t>(val)].c_str()));
+ }
+}
+void qmpSettingsRO::setOptionEnumIntOptName(std::string key,std::string valname)
+{
+ if(options.find(key)!=options.end()&&options[key].type==qmpOptionR::ParameterType::parameter_enum)
+ {
+ auto curidx=std::find(options[key].enumlist.begin(),options[key].enumlist.end(),valname);
+ if(curidx!=options[key].enumlist.end())
+ settings.insert(QString(key.c_str()),QString(valname.c_str()));
+ }
+}
+
+void qmpSettingsRO::load(const char *path)
+{
+ QScopedPointer<QSettings> qsettings(new QSettings(path,QSettings::Format::IniFormat));
+ settings.clear();
+ for(QString&k:qsettings->allKeys())
+ {
+ settings.insert(k,qsettings->value(k));
+ }
+}
diff --git a/visualization/renderer/qmpsettingsro.hpp b/visualization/renderer/qmpsettingsro.hpp
new file mode 100644
index 0000000..30ab9b6
--- /dev/null
+++ b/visualization/renderer/qmpsettingsro.hpp
@@ -0,0 +1,73 @@
+#ifndef QMPSETTINGSRO_H
+#define QMPSETTINGSRO_H
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <QVariant>
+#include <QPointer>
+
+struct qmpOptionR
+{
+ enum ParameterType{
+ parameter_int=0,
+ parameter_uint,
+ parameter_bool,
+ parameter_double,
+ parameter_str,
+ parameter_enum,
+ parameter_url,
+ parameter_custom=0x100
+ };
+
+ std::string desc;
+ ParameterType type;
+ QVariant defaultval,minv,maxv;
+ std::vector<std::string> enumlist;
+
+ qmpOptionR(){}
+ qmpOptionR(
+ std::string _desc,
+ ParameterType _t,QVariant _def=QVariant(),
+ QVariant _min=QVariant(),QVariant _max=QVariant()):
+ desc(_desc),
+ type(_t),
+ defaultval(_def),
+ minv(_min),
+ maxv(_max){}
+};
+
+class qmpSettingsRO
+{
+public:
+ qmpSettingsRO();
+ void registerOptionInt(std::string tab,std::string desc,std::string key,int min,int max,int defaultval);
+ int getOptionInt(std::string key);
+ void setOptionInt(std::string key,int val);
+ void registerOptionUint(std::string tab,std::string desc,std::string key,unsigned min,unsigned max,unsigned defaultval);
+ unsigned getOptionUint(std::string key);
+ void setOptionUint(std::string key,unsigned val);
+ void registerOptionBool(std::string tab,std::string desc,std::string key,bool defaultval);
+ bool getOptionBool(std::string key);
+ void setOptionBool(std::string key,bool val);
+ void registerOptionDouble(std::string tab,std::string desc,std::string key,double min,double max,double defaultval);
+ double getOptionDouble(std::string key);
+ void setOptionDouble(std::string key,double val);
+ void registerOptionString(std::string tab,std::string desc,std::string key,std::string defaultval,bool is_url);
+ std::string getOptionString(std::string key);
+ void setOptionString(std::string key,std::string val);
+ void registerOptionEnumInt(std::string tab,std::string desc,std::string key,std::vector<std::string> enumlist,int defaultval);
+ int getOptionEnumInt(std::string key);
+ std::string getOptionEnumIntOptName(std::string key);
+ void setOptionEnumInt(std::string key,int val);
+ void setOptionEnumIntOptName(std::string key,std::string valname);
+
+ void load(const char* path);
+private:
+ std::map<std::string,qmpOptionR> options;
+ std::vector<std::string> optionlist;
+ QVariantMap settings;
+};
+
+#endif
diff --git a/visualization/renderer/qmpvisrendercore.cpp b/visualization/renderer/qmpvisrendercore.cpp
new file mode 100644
index 0000000..fb1a7ef
--- /dev/null
+++ b/visualization/renderer/qmpvisrendercore.cpp
@@ -0,0 +1,88 @@
+#include "qmpvisrendercore.hpp"
+#include "qmppluginapistub.hpp"
+#include "qmpsettingsro.hpp"
+#include "qmpmidiplay.hpp"
+#include "qmpcorepublic.hpp"
+
+#include <cassert>
+#include <dlfcn.h>
+
+#include <QProcess>
+#include <QDebug>
+#include <QThread>
+qmpVisRenderCore *qmpVisRenderCore::inst=nullptr;
+
+qmpVisRenderCore::qmpVisRenderCore():QObject(nullptr)
+{
+ inst=this;
+ player=new CMidiPlayer();
+ api=new qmpPluginAPIStub(this);
+ msettings=new qmpSettingsRO();
+ msettings->registerOptionEnumInt("MIDI","Text encoding","Midi/TextEncoding",{"Unicode","Big5","Big5-HKSCS","CP949","EUC-JP","EUC-KR","GB18030","KOI8-R","KOI8-U","Macintosh","Shift-JIS"},0);
+}
+
+void qmpVisRenderCore::loadVisualizationLibrary()
+{
+ mp=dlopen("libvisualization.so",RTLD_LAZY);
+ if(!mp)fprintf(stderr,"failed to load visualization module!\n");
+ GetInterface_func getintf=reinterpret_cast<GetInterface_func>(dlsym(mp,"qmpPluginGetInterface"));
+ SwitchMode_func switchmode=reinterpret_cast<SwitchMode_func>(dlsym(mp,"switchToRenderMode"));
+ vf=nullptr;
+ vp=getintf(api);
+ switchmode(&qmpVisRenderCore::framefunc,false);
+ vp->init();
+ msettings->load("/home/chrisoft/.config/qmprc");
+}
+
+void qmpVisRenderCore::unloadVisualizationLibrary()
+{
+ vp->deinit();
+ dlclose(mp);
+}
+
+void qmpVisRenderCore::setMIDIFile(const char *url)
+{
+ player->playerLoadFile(url);
+}
+
+void qmpVisRenderCore::startRender()
+{
+ assert(vf);
+ ffmpegproc=new QProcess();
+ ffmpegproc->setProgram("ffmpeg");
+ QStringList arguments;
+ arguments
+ <<"-f"<<"rawvideo"
+ <<"-pixel_format"<<"rgba"
+ <<"-video_size"<<"1600x900"
+ <<"-framerate"<<"60"
+ <<"-i"<<"pipe:";
+ arguments
+ <<"-vf"<<"vflip"
+ <<"-pix_fmt"<<"yuv420p"
+ <<"-c:v"<<"libx264"
+ <<"-preset"<<"fast"
+ <<"-crf"<<"22";
+ arguments<<"output.mp4";
+ ffmpegproc->setArguments(arguments);
+ ffmpegproc->start();
+ connect(ffmpegproc,QOverload<int,QProcess::ExitStatus>::of(&QProcess::finished),
+ [this](int x,QProcess::ExitStatus){qDebug("%d",x);qDebug()<<this->ffmpegproc->readAllStandardError();});
+ vf->show();
+ startcb(nullptr,nullptr);
+}
+
+void qmpVisRenderCore::framefunc(void *px, size_t sz)
+{
+ if(sz)
+ {
+ inst->ffmpegproc->write((const char*)px,sz);
+ while(inst->ffmpegproc->bytesToWrite()>1<<26)
+ {
+ inst->ffmpegproc->waitForBytesWritten();
+ QThread::yieldCurrentThread();
+ }
+ }
+ else
+ inst->ffmpegproc->closeWriteChannel();
+}
diff --git a/visualization/renderer/qmpvisrendercore.hpp b/visualization/renderer/qmpvisrendercore.hpp
new file mode 100644
index 0000000..0c024f1
--- /dev/null
+++ b/visualization/renderer/qmpvisrendercore.hpp
@@ -0,0 +1,44 @@
+#ifndef QMPVISRENDERCORE_HPP
+#define QMPVISRENDERCORE_HPP
+
+#include <cstddef>
+
+#include <QObject>
+#include "qmpcorepublic.hpp"
+
+class qmpPluginAPIStub;
+class CMidiPlayer;
+class qmpSettingsRO;
+
+class QProcess;
+
+class qmpVisRenderCore:public QObject
+{
+ Q_OBJECT
+public:
+ qmpVisRenderCore();
+ void loadVisualizationLibrary();
+ void unloadVisualizationLibrary();
+ void setMIDIFile(const char* url);
+ void startRender();
+
+ qmpSettingsRO* settings(){return msettings;}
+
+private:
+ qmpPluginIntf *vp;
+ qmpFuncBaseIntf *vf;
+ callback_t startcb;
+ void *mp;
+ qmpPluginAPIStub *api;
+ CMidiPlayer *player;
+ qmpSettingsRO *msettings;
+ QProcess *ffmpegproc;
+ typedef qmpPluginIntf*(*GetInterface_func)(qmpPluginAPI*);
+ typedef void(*SwitchMode_func)(void(*frameCallback)(void*,size_t),bool hidewindow);
+
+ friend class qmpPluginAPIStub;
+ static void framefunc(void* px,size_t sz);
+ static qmpVisRenderCore *inst;
+};
+
+#endif // QMPVISRENDERCORE_HPP