From 4ffde0ad55d041229793ca4457123bcbcbf8e0c0 Mon Sep 17 00:00:00 2001 From: Chris Xiong Date: Mon, 25 Apr 2016 23:46:17 +0800 Subject: Plugin API now works correctly. Port old visualization code. --- ChangeLog | 7 +- core/qmpmidiplay.cpp | 6 +- core/qmpmidiplay.hpp | 3 +- core/qmpmidiread.cpp | 8 +- include/qmpcorepublic.hpp | 57 +++++----- qmidiplayer-desktop/qmpplugin.cpp | 8 ++ visualization/qmpvisualization.cpp | 214 +++++++++++++++++++++++++++++++++++-- visualization/qmpvisualization.hpp | 61 ++++++++++- visualization/visualization.pro | 6 ++ 9 files changed, 324 insertions(+), 46 deletions(-) diff --git a/ChangeLog b/ChangeLog index fdfc2f1..589b4aa 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,9 @@ -2016-04-23 0.7.8 alpha +2016-04-25 0.7.8 alpha +Plugin API now works correctly. +Port part of the old visualization code to the +new Plugin API. + +2016-04-24 0.7.8 alpha More implementation of plugin support. Migrate channel activity indicator to the new callback system. diff --git a/core/qmpmidiplay.cpp b/core/qmpmidiplay.cpp index 4e28be0..ca2878c 100644 --- a/core/qmpmidiplay.cpp +++ b/core/qmpmidiplay.cpp @@ -49,7 +49,7 @@ void CMidiPlayer::fluidDeinitialize() } void CMidiPlayer::processEvent(const SEvent *e) { - SEventCallBackData cbd(e->type,e->p1,e->p2); + SEventCallBackData cbd(e->type,e->p1,e->p2,tceptr); for(int i=0;i<16;++i)if(eventHandlerCB[i]) eventHandlerCB[i]->callBack(&cbd,eventHandlerCBuserdata[i]); switch(e->type&0xF0) @@ -317,9 +317,7 @@ void CMidiPlayer::playerPanic(bool reset) } bool CMidiPlayer::playerLoadFile(const char* fn) { - midiFile=new CMidiFile(fn); - memcpy(midiFile->eventReaderCB,this->eventReaderCB,sizeof(this->eventReaderCB)); - memcpy(midiFile->eventReaderCBuserdata,this->eventReaderCBuserdata,sizeof(this->eventReaderCBuserdata)); + midiFile=new CMidiFile(fn,this->eventReaderCB,this->eventReaderCBuserdata); if(!midiFile->isValid())return false; divs=midiFile->getDivision(); fileTimer1Pass(); diff --git a/core/qmpmidiplay.hpp b/core/qmpmidiplay.hpp index 35fecea..a89a925 100644 --- a/core/qmpmidiplay.hpp +++ b/core/qmpmidiplay.hpp @@ -23,7 +23,6 @@ struct SEvent }; class CMidiFile { - friend class CMidiPlayer; private: std::vectoreventList; char *title,*copyright; @@ -45,7 +44,7 @@ class CMidiFile int chunkReader(int hdrXp); void dumpEvents(); public: - CMidiFile(const char* fn); + CMidiFile(const char* fn,IMidiCallBack** ercb,void** ercbdata); ~CMidiFile(); const SEvent* getEvent(uint32_t id); uint32_t getEventCount(); diff --git a/core/qmpmidiread.cpp b/core/qmpmidiread.cpp index 762ce5d..380b603 100644 --- a/core/qmpmidiread.cpp +++ b/core/qmpmidiread.cpp @@ -172,6 +172,10 @@ int CMidiFile::eventReader()//returns 0 if End of Track encountered error(0,"W: Unknown event type %#x",type); } lasttype=type;++curid; + SEvent* le=eventList[eventList.size()-1]; + SEventCallBackData cbd(le->type,le->p1,le->p2,le->time); + for(int i=0;i<16;++i)if(eventReaderCB[i]) + eventReaderCB[i]->callBack(&cbd,eventReaderCBuserdata[i]); return 1; } void CMidiFile::trackChunkReader() @@ -222,9 +226,11 @@ void CMidiFile::dumpEvents() printf("type %x #%d @%d p1 %d p2 %d\n",eventList[i]->type, eventList[i]->iid,eventList[i]->time,eventList[i]->p1,eventList[i]->p2); } -CMidiFile::CMidiFile(const char* fn) +CMidiFile::CMidiFile(const char* fn,IMidiCallBack **ercb,void **ercbdata) { title=copyright=NULL;notes=0;std=0;valid=1; + memcpy(eventReaderCB,ercb,sizeof(eventReaderCB)); + memcpy(eventReaderCBuserdata,ercbdata,sizeof(eventReaderCBuserdata)); try { if(!(f=fopen(fn,"rb")))throw (fprintf(stderr,"E: file %s doesn't exist!\n",fn),2); diff --git a/include/qmpcorepublic.hpp b/include/qmpcorepublic.hpp index ad6a165..3654a75 100644 --- a/include/qmpcorepublic.hpp +++ b/include/qmpcorepublic.hpp @@ -4,8 +4,8 @@ #include struct SEventCallBackData { - uint32_t type,p1,p2; - SEventCallBackData(uint32_t _t,uint32_t _p1,uint32_t _p2){type=_t;p1=_p1;p2=_p2;} + uint32_t time,type,p1,p2; + SEventCallBackData(uint32_t _t,uint32_t _p1,uint32_t _p2,uint32_t _tm){type=_t;p1=_p1;p2=_p2;time=_tm;} }; class IMidiCallBack { @@ -17,6 +17,7 @@ class IMidiCallBack class qmpPluginIntf { public: + qmpPluginIntf(){} virtual ~qmpPluginIntf(){} virtual void init(){} virtual void deinit(){} @@ -26,34 +27,40 @@ class qmpPluginIntf class qmpVisualizationIntf { public: - virtual void show(); - virtual void close(); + qmpVisualizationIntf(){} + virtual void show()=0; + virtual void close()=0; + virtual void start()=0; + virtual void stop()=0; + virtual void pause()=0; + virtual void reset()=0; + virtual ~qmpVisualizationIntf(){} }; extern "C"{ class qmpPluginAPI { public: - uint32_t getDivision(); - uint32_t getRawTempo(); - double getRealTempo(); - uint32_t getTimeSig(); - int getKeySig(); - uint32_t getNoteCount(); - uint32_t getCurrentPolyphone(); - uint32_t getMaxPolyphone(); - uint32_t getCurrentTimeStamp(); - int registerVisualizationIntf(qmpVisualizationIntf* i); - void unregisterVisualizationIntf(int intfhandle); - int registerEventReaderIntf(IMidiCallBack* cb,void* userdata); - void unregisterEventReaderIntf(int intfhandle); - int registerEventHandlerIntf(IMidiCallBack* cb,void* userdata); - void unregisterEventHandlerIntf(int intfhandle); - void registerOptionInt(std::string desc,std::string key,int defaultval); - int getOptionInt(std::string key); - void registerOptionDouble(std::string desc,std::string key,double defaultval); - double getOptionDouble(std::string key); - void registerOptionString(std::string desc,std::string key,std::string defaultval); - std::string getOptionString(std::string key); + virtual uint32_t getDivision(); + virtual uint32_t getRawTempo(); + virtual double getRealTempo(); + virtual uint32_t getTimeSig(); + virtual int getKeySig(); + virtual uint32_t getNoteCount(); + virtual uint32_t getCurrentPolyphone(); + virtual uint32_t getMaxPolyphone(); + virtual uint32_t getCurrentTimeStamp(); + virtual int registerVisualizationIntf(qmpVisualizationIntf* i); + virtual void unregisterVisualizationIntf(int intfhandle); + virtual int registerEventReaderIntf(IMidiCallBack* cb,void* userdata); + virtual void unregisterEventReaderIntf(int intfhandle); + virtual int registerEventHandlerIntf(IMidiCallBack* cb,void* userdata); + virtual void unregisterEventHandlerIntf(int intfhandle); + virtual void registerOptionInt(std::string desc,std::string key,int defaultval); + virtual int getOptionInt(std::string key); + virtual void registerOptionDouble(std::string desc,std::string key,double defaultval); + virtual double getOptionDouble(std::string key); + virtual void registerOptionString(std::string desc,std::string key,std::string defaultval); + virtual std::string getOptionString(std::string key); }; } typedef qmpPluginIntf*(*qmpPluginEntry)(qmpPluginAPI*); diff --git a/qmidiplayer-desktop/qmpplugin.cpp b/qmidiplayer-desktop/qmpplugin.cpp index 559b575..aea01da 100644 --- a/qmidiplayer-desktop/qmpplugin.cpp +++ b/qmidiplayer-desktop/qmpplugin.cpp @@ -89,3 +89,11 @@ int qmpPluginAPI::registerEventReaderIntf(IMidiCallBack *cb,void *userdata) {return qmw->getPlayer()->setEventReaderCB(cb,userdata);} void qmpPluginAPI::unregisterEventReaderIntf(int intfhandle) {qmw->getPlayer()->unsetEventReaderCB(intfhandle);} +int qmpPluginAPI::registerVisualizationIntf(qmpVisualizationIntf*){return 0;} +void qmpPluginAPI::unregisterVisualizationIntf(int){} +void qmpPluginAPI::registerOptionInt(std::string,std::string,int){} +int qmpPluginAPI::getOptionInt(std::string){return 0;} +void qmpPluginAPI::registerOptionDouble(std::string,std::string,double){} +double qmpPluginAPI::getOptionDouble(std::string){return 0;} +void qmpPluginAPI::registerOptionString(std::string,std::string,std::string){} +std::string qmpPluginAPI::getOptionString(std::string){return "";} diff --git a/visualization/qmpvisualization.cpp b/visualization/qmpvisualization.cpp index e8bb9fb..2d0b494 100644 --- a/visualization/qmpvisualization.cpp +++ b/visualization/qmpvisualization.cpp @@ -1,34 +1,218 @@ #include +#include +#include +#include #include "qmpvisualization.hpp" +const int viewdist=100; +const int notestretch=100;//length of quarter note +const int minnotelength=100; +DWORD chcolors[]={0XFFFF0000,0XFFFF8000,0XFFFFBF00,0XFFFFFF00, + 0XFFBFFF00,0XFF80FF00,0XFF00FF00,0XFF00FFBF, + 0XFF00FFFF,0XFF333333,0XFF00BFFF,0XFF007FFF, + 0XFF0000FF,0XFF7F00FF,0XFFBF00FF,0XFFFF00BF}; + void CTestCallBack::callBack(void *callerdata,void *) { - if(par->c<3) + SEventCallBackData* cbd=(SEventCallBackData*)callerdata; + switch(cbd->type&0xF0) { - SEventCallBackData* cbd=(SEventCallBackData*)callerdata; - printf("type %#x p1 %d p2 %d\n",cbd->type,cbd->p1,cbd->p2); + case 0x80: + par->pushNoteOff(cbd->time,cbd->type&0x0F,cbd->p1); + break; + case 0x90: + par->pushNoteOn(cbd->time,cbd->type&0x0F,cbd->p1,cbd->p2); + break; } } +void qmpVisualization::show() +{ + sm=smGetInterface(SMELT_APILEVEL); + sm->smVidMode(800,600,true); + sm->smUpdateFunc(h); + sm->smWinTitle("A Stupid Midi Visualization"); + sm->smSetFPS(FPS_VSYNC); + sm->smNoSuspend(true); + sm->smInit(); + sm->smTextureOpt(TPOT_POT,TFLT_LINEAR); + chequer=sm->smTextureLoad("chequerboard.png"); + tdscn=sm->smTargetCreate(800,600); + if(!font.loadTTF("/usr/share/fonts/truetype/freefont/FreeMono.ttf",16)) + printf("W: Font load failed.\n"); + pos[0]=-0;pos[1]=70;pos[2]=20; + rot[0]=0;rot[1]=90;rot[2]=90;ctk=0; + rendererTh=new std::thread(&SMELT::smMainLoop,sm); +} +void qmpVisualization::close() +{ + sm->smFinale(); + font.releaseTTF(); + sm->smTextureFree(chequer); + sm->smTargetFree(tdscn); + sm->smRelease(); +} +void qmpVisualization::reset() +{ + for(unsigned i=0;ismRenderBegin3D(60,tdscn); + sm->sm3DCamera6f2v(pos,rot); + sm->smClrscr(0xFF666666); + sm->smRenderQuad(&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_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); + 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; + //printf("pos: %f %f %f\n",pos[0],pos[1],pos[2]); + //printf("rot: %f %f %f\n",rot[0],rot[1],rot[2]); + double lpt=(double)notestretch/dvs/10.; + for(uint32_t i=0;itcs-ctk)*lpttce-ctk)*lptkey-64),pool[i]->ch*-2.,((double)pool[i]->tce-ctk)*lpt); + smvec3d b(((double)pool[i]->key-64)+.9,pool[i]->ch*-2.+1.6,((double)pool[i]->tcs-ctk)*lpt); + if(((double)pool[i]->tce-pool[i]->tcs)*lpttcs-ctk)*lpt-minnotelength/100.; + drawCube(a,b,SETA(chcolors[pool[i]->ch],pool[i]->vel),0); + } + } + //ctk+=(int)(etps*sm->smGetDelta()); + ctk=api->getCurrentTimeStamp(); + if(ctk>fintk)return true; + sm->smRenderEnd(); + for(int i=0;i<4;++i){q.v[i].col=0xFFFFFFFF;q.v[i].z=0;} + q.tex=sm->smTargetTexture(tdscn); + sm->smRenderBegin2D(); + sm->smClrscr(0xFF000000); + 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=800; + q.v[0].y=q.v[3].y=0;q.v[1].y=q.v[2].y=600; + sm->smRenderQuad(&q); + font.updateString(L"FPS: %.2f",sm->smGetFPS()); + font.render(1,586,0xFFFFFFFF,ALIGN_LEFT); + font.render(0,585,0xFF000000,ALIGN_LEFT); + sm->smRenderEnd(); + return false; +} + +void qmpVisualization::drawCube(smvec3d a,smvec3d b,DWORD col,SMTEX tex) +{ + smQuad q;q.blend=BLEND_ALPHABLEND; + q.tex=tex;for(int i=0;i<4;++i)q.v[i].col=col; + //top + q.v[0].x=a.x;q.v[0].y=a.y;q.v[0].z=a.z; + q.v[1].x=b.x;q.v[1].y=a.y;q.v[1].z=a.z; + q.v[2].x=b.x;q.v[2].y=b.y;q.v[2].z=a.z; + q.v[3].x=a.x;q.v[3].y=b.y;q.v[3].z=a.z; + sm->smRenderQuad(&q); + //bottom + q.v[0].x=a.x;q.v[0].y=a.y;q.v[0].z=b.z; + q.v[1].x=b.x;q.v[1].y=a.y;q.v[1].z=b.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); + //left + q.v[0].x=a.x;q.v[0].y=b.y;q.v[0].z=a.z; + q.v[1].x=a.x;q.v[1].y=b.y;q.v[1].z=b.z; + q.v[2].x=a.x;q.v[2].y=a.y;q.v[2].z=b.z; + q.v[3].x=a.x;q.v[3].y=a.y;q.v[3].z=a.z; + sm->smRenderQuad(&q); + //right + q.v[0].x=b.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=b.z; + q.v[2].x=b.x;q.v[2].y=a.y;q.v[2].z=b.z; + q.v[3].x=b.x;q.v[3].y=a.y;q.v[3].z=a.z; + sm->smRenderQuad(&q); + //front + 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); + //back + q.v[0].x=a.x;q.v[0].y=a.y;q.v[0].z=a.z; + q.v[1].x=b.x;q.v[1].y=a.y;q.v[1].z=a.z; + q.v[2].x=b.x;q.v[2].y=a.y;q.v[2].z=b.z; + q.v[3].x=a.x;q.v[3].y=a.y;q.v[3].z=b.z; + sm->smRenderQuad(&q); +} -qmpVisualization::qmpVisualization(qmpPluginAPI* _api) -{api=_api;} -qmpVisualization::~qmpVisualization() -{api=NULL;} +qmpVisualization::qmpVisualization(qmpPluginAPI* _api){api=_api;} +qmpVisualization::~qmpVisualization(){api=NULL;} void qmpVisualization::init() { puts("hello world from test plugin!"); - puts("I'll try to print the first 3 events from the file!"); - cb=new CTestCallBack(this);c=0; - //!!FIXME: not working properly... + cb=new CTestCallBack(this); + vi=new CDemoVisualization(this); + h=new CMidiVisualHandler(this); + api->registerVisualizationIntf(vi); api->registerEventReaderIntf(cb,NULL); } void qmpVisualization::deinit() -{} +{ + delete cb;delete vi; + delete h; +} const char* qmpVisualization::pluginGetName() {return "QMidiPlayer Default Visualization Plugin";} const char* qmpVisualization::pluginGetVersion() {return "0.7.8";} +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(); + if(tc>fintk)fintk=tc; +} + //dummy implementations of the api... uint32_t qmpPluginAPI::getDivision(){return 0;} uint32_t qmpPluginAPI::getRawTempo(){return 0;} @@ -43,3 +227,11 @@ int qmpPluginAPI::registerEventHandlerIntf(IMidiCallBack*,void*){return -1;} void qmpPluginAPI::unregisterEventHandlerIntf(int){} int qmpPluginAPI::registerEventReaderIntf(IMidiCallBack*,void*){return -1;} void qmpPluginAPI::unregisterEventReaderIntf(int){} +int qmpPluginAPI::registerVisualizationIntf(qmpVisualizationIntf*){return 0;} +void qmpPluginAPI::unregisterVisualizationIntf(int){} +void qmpPluginAPI::registerOptionInt(std::string,std::string,int){} +int qmpPluginAPI::getOptionInt(std::string){return 0;} +void qmpPluginAPI::registerOptionDouble(std::string,std::string,double){} +double qmpPluginAPI::getOptionDouble(std::string){return 0;} +void qmpPluginAPI::registerOptionString(std::string,std::string,std::string){} +std::string qmpPluginAPI::getOptionString(std::string){return "";} diff --git a/visualization/qmpvisualization.hpp b/visualization/qmpvisualization.hpp index 847f37b..df9255c 100644 --- a/visualization/qmpvisualization.hpp +++ b/visualization/qmpvisualization.hpp @@ -1,6 +1,12 @@ #ifndef QMPVISUALIZATION_H #define QMPVISUALIZATION_H +#include +#include +#include +#include +#include +#include #include "../include/qmpcorepublic.hpp" class qmpVisualization; @@ -12,22 +18,73 @@ class CTestCallBack:public IMidiCallBack CTestCallBack(qmpVisualization *_par){par=_par;} void callBack(void *callerdata,void *userdata); }; +struct MidiVisualEvent +{ + uint32_t tcs,tce; + uint32_t key,vel; + uint32_t ch; +}; class qmpVisualization:public qmpPluginIntf { - friend class CTestCallBack; private: qmpPluginAPI* api; - int c; CTestCallBack* cb; + qmpVisualizationIntf* vi; + std::thread* rendererTh; + std::vectorpool; + smHandler* h; + std::stack pendingt[16][128],pendingv[16][128]; + SMELT *sm; + SMTRG tdscn; + SMTEX chequer; + smTTFont font; + float pos[3],rot[3],lastx,lasty; + uint32_t ctc,dvs,ctk,fintk; + double etps; + void drawCube(smvec3d a,smvec3d b,DWORD col,SMTEX tex); public: qmpVisualization(qmpPluginAPI* _api); ~qmpVisualization(); + void pushNoteOn(uint32_t tc,uint32_t ch,uint32_t key,uint32_t vel); + void pushNoteOff(uint32_t tc,uint32_t ch,uint32_t key); + void pushPitchBend(uint32_t tc,uint32_t ch,uint32_t key); + bool update(); + void start(); + void stop(); + void pause(); + void show(); + void close(); + void reset(); + void init(); void deinit(); const char* pluginGetName(); const char* pluginGetVersion(); }; +class CMidiVisualHandler:public smHandler +{ + private: + qmpVisualization *p; + public: + CMidiVisualHandler(qmpVisualization* par){p=par;} + bool handlerFunc(){return p->update();} +}; + +class CDemoVisualization:public qmpVisualizationIntf +{ + private: + qmpVisualization* par; + public: + CDemoVisualization(qmpVisualization *p){par=p;} + void show(){par->show();} + void close(){par->close();} + void start(){par->start();} + void stop(){par->stop();} + void pause(){par->pause();} + void reset(){par->reset();} +}; + extern "C"{ qmpPluginIntf* qmpPluginGetInterface(qmpPluginAPI* api) {return new qmpVisualization(api);} diff --git a/visualization/visualization.pro b/visualization/visualization.pro index 8cce718..10048bd 100644 --- a/visualization/visualization.pro +++ b/visualization/visualization.pro @@ -20,4 +20,10 @@ HEADERS += qmpvisualization.hpp unix { target.path = /usr/lib/qmidiplayer INSTALLS += target + QMAKE_CXXFLAGS += -pthread -fPIC } +#well... +INCLUDEPATH += /home/chrisoft/devel/BulletLabRemixIII/include/ /usr/include/freetype2 +LIBS += -L/home/chrisoft/devel/BulletLabRemixIII/smelt/sdl/ +LIBS += -L/home/chrisoft/devel/BulletLabRemixIII/extensions/ +LIBS += -lstdc++ -lSDL2 -ljpeg -lpng -lfreetype -lz -lsmeltext -lsmelt-dumb -lCxImage -- cgit v1.2.3