From 70fd2d85c1bd6fb8732e680e8fda9d36c317c732 Mon Sep 17 00:00:00 2001 From: Chris Xiong Date: Wed, 6 Apr 2016 16:54:29 +0800 Subject: Too young too simple. --- qmidiplayer-lite/deployment.pri | 13 ++ qmidiplayer-lite/main.cpp | 15 ++ qmidiplayer-lite/main.qml | 144 ++++++++++++ qmidiplayer-lite/qmidiplayer-lite.pro | 24 ++ qmidiplayer-lite/qml.qrc | 5 + qmidiplayer-lite/qmpcorewrapper.hpp | 58 +++++ qmidiplayer-lite/qmpmidiplay.cpp | 406 ++++++++++++++++++++++++++++++++++ qmidiplayer-lite/qmpmidiplay.hpp | 137 ++++++++++++ qmidiplayer-lite/qmpmidiread.cpp | 240 ++++++++++++++++++++ 9 files changed, 1042 insertions(+) create mode 100644 qmidiplayer-lite/deployment.pri create mode 100644 qmidiplayer-lite/main.cpp create mode 100644 qmidiplayer-lite/main.qml create mode 100644 qmidiplayer-lite/qmidiplayer-lite.pro create mode 100644 qmidiplayer-lite/qml.qrc create mode 100644 qmidiplayer-lite/qmpcorewrapper.hpp create mode 100644 qmidiplayer-lite/qmpmidiplay.cpp create mode 100644 qmidiplayer-lite/qmpmidiplay.hpp create mode 100644 qmidiplayer-lite/qmpmidiread.cpp (limited to 'qmidiplayer-lite') diff --git a/qmidiplayer-lite/deployment.pri b/qmidiplayer-lite/deployment.pri new file mode 100644 index 0000000..57bdd3a --- /dev/null +++ b/qmidiplayer-lite/deployment.pri @@ -0,0 +1,13 @@ +unix:!android { + isEmpty(target.path) { + qnx { + target.path = /tmp/$${TARGET}/bin + } else { + target.path = /usr/bin + } + export(target.path) + } + INSTALLS += target +} + +export(INSTALLS) diff --git a/qmidiplayer-lite/main.cpp b/qmidiplayer-lite/main.cpp new file mode 100644 index 0000000..1a87b33 --- /dev/null +++ b/qmidiplayer-lite/main.cpp @@ -0,0 +1,15 @@ +#include +#include +#include +#include "qmpcorewrapper.hpp" + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + qmlRegisterType("org.chrisoft.qmpcore",1,0,"CQMPCoreWrapper"); + + QQmlApplicationEngine engine; + engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); + + return app.exec(); +} diff --git a/qmidiplayer-lite/main.qml b/qmidiplayer-lite/main.qml new file mode 100644 index 0000000..0ec0975 --- /dev/null +++ b/qmidiplayer-lite/main.qml @@ -0,0 +1,144 @@ +import QtQuick 2.3 +import QtQuick.Window 2.2 +import QtQuick.Controls 1.0 +import QtQuick.Dialogs 1.0 +import org.chrisoft.qmpcore 1.0 + +Window { + id: window1 + width: 420 + height: 240 + title: "QMidiPlayer Lite" + visible: true + property bool playing + playing: false + + MouseArea { + id: mouseArea1 + anchors.rightMargin: 0 + anchors.bottomMargin: 0 + anchors.leftMargin: 0 + anchors.topMargin: 0 + anchors.fill: parent + onClicked: { + //Qt.quit(); + } + + Button { + id: button2 + x: 170 + text: qsTr("Play") + anchors.top: parent.top + anchors.topMargin: 172 + anchors.horizontalCenter: parent.horizontalCenter + onClicked: { + if(!playing) + { + qmpcore.loadFile(fileName.text); + qmpcore.initFluidSynth(); + qmpcore.playFile(); + playing=true; + uiTimer.start(); + text="Stop"; + } + else + { + qmpcore.stop(); + playing=false; + text="Play"; + hsTimer.value=0;uiTimer.stop(); + } + } + } + + Slider { + id: hsTimer + y: 210 + height: 22 + anchors.right: parent.right + anchors.rightMargin: 8 + anchors.left: parent.left + anchors.leftMargin: 8 + anchors.bottom: parent.bottom + anchors.bottomMargin: 8 + tickmarksEnabled: false + stepSize: 1 + maximumValue: 100 + property bool autovalchange: false + onValueChanged: { + if(autovalchange||pressed)return; + if(playing){qmpcore.setTCeptr(value);qmpcore.panic();} + } + onPressedChanged: { + if(!pressed) + { + /*if(playing) + { + if(ui->hsTimer->value()==100){on_pbNext_clicked();return;} + player->setTCeptr(player->getStamp(ui->hsTimer->value()),ui->hsTimer->value()); + player->playerPanic(); + offset=ui->hsTimer->value()/100.*player->getFtime(); + st=std::chrono::steady_clock::now(); + } + else + { + player->setTCeptr(player->getStamp(ui->hsTimer->value()),ui->hsTimer->value()); + offset=ui->hsTimer->value()/100.*player->getFtime(); + char ts[100]; + sprintf(ts,"%02d:%02d",(int)(offset)/60,(int)(offset)%60); + ui->lbCurTime->setText(ts); + }*/ + if(playing){qmpcore.setTCeptr(value);qmpcore.panic();} + } + } + } + } + + CQMPCoreWrapper { + id: qmpcore + } + Timer { + id: uiTimer + interval: 100 + running: false + repeat: true + onTriggered: { + if(!hsTimer.pressed) + { + hsTimer.autovalchange=true; + hsTimer.value=qmpcore.getProgress(); + hsTimer.autovalchange=false; + } + } + } + + Text { + id: fileName + text: qsTr("...") + anchors.centerIn: parent + } + + Button { + id: button1 + x: 170 + width: 80 + height: 27 + text: qsTr("Open") + anchors.top: parent.top + anchors.topMargin: 142 + anchors.horizontalCenter: parent.horizontalCenter + onClicked: { + fileDialog.open(); + } + } + + FileDialog { + id: fileDialog + title: qsTr("Select midi file") + nameFilters: ["MIDI Files (*.mid *.midi)"] + onAccepted: { + fileName.text=fileUrl; + } + } +} + diff --git a/qmidiplayer-lite/qmidiplayer-lite.pro b/qmidiplayer-lite/qmidiplayer-lite.pro new file mode 100644 index 0000000..9fb8712 --- /dev/null +++ b/qmidiplayer-lite/qmidiplayer-lite.pro @@ -0,0 +1,24 @@ +TEMPLATE = app + +QT += qml quick +unix:QT += widgets +win32:QT += widgets +CONFIG += c++11 + +SOURCES += main.cpp \ + ../core/qmpmidiplay.cpp \ + ../core/qmpmidiread.cpp + +RESOURCES += qml.qrc + +# Additional import path used to resolve QML modules in Qt Creator's code model +QML_IMPORT_PATH = + +# Default rules for deployment. +include(deployment.pri) + +HEADERS += \ + ../core/qmpmidiplay.hpp \ + qmpcorewrapper.hpp + +LIBS += -lfluidsynth diff --git a/qmidiplayer-lite/qml.qrc b/qmidiplayer-lite/qml.qrc new file mode 100644 index 0000000..5f6483a --- /dev/null +++ b/qmidiplayer-lite/qml.qrc @@ -0,0 +1,5 @@ + + + main.qml + + diff --git a/qmidiplayer-lite/qmpcorewrapper.hpp b/qmidiplayer-lite/qmpcorewrapper.hpp new file mode 100644 index 0000000..ed024df --- /dev/null +++ b/qmidiplayer-lite/qmpcorewrapper.hpp @@ -0,0 +1,58 @@ +#ifndef QMPCOREWRAPPER_H +#define QMPCOREWRAPPER_H +#include +#include +#include +#include +#include "../core/qmpmidiplay.hpp" +class CQMPCoreWrapper:public QObject +{ + Q_OBJECT +private: + CMidiPlayer *mp; + std::thread *playerTh; + int curprog; +public: + explicit CQMPCoreWrapper(QObject* parent=0):QObject(parent) + { + mp=new CMidiPlayer(false); + } + ~CQMPCoreWrapper(){delete mp;} + Q_INVOKABLE void initFluidSynth() + { + fluid_settings_t *fsettings=mp->getFluidSettings(); + fluid_settings_setstr(fsettings,"audio.driver","pulseaudio"); + mp->fluidInitialize(); + mp->pushSoundFont("/media/Files/FluidR3_Ext.sf2"); + } + Q_INVOKABLE void deinitFluidSynth() + { + mp->fluidDeinitialize(); + } + Q_INVOKABLE void loadFile(QUrl file) + { + mp->playerLoadFile(file.toLocalFile().toStdString().c_str()); + mp->playerInit();curprog=0; + } + Q_INVOKABLE void playFile() + { + playerTh=new std::thread(&CMidiPlayer::playerThread,mp); + } + Q_INVOKABLE void stop() + { + mp->playerDeinit();playerTh->join();delete playerTh; + } + Q_INVOKABLE int getProgress() + { + while(!mp->isFinished()&&mp->getTCeptr()>mp->getStamp(curprog) + &&curprog<=100) + ++curprog; + return curprog; + } + Q_INVOKABLE void panic(){mp->playerPanic();} + Q_INVOKABLE void setTCeptr(int perct) + { + mp->setTCeptr(mp->getStamp(perct),perct);curprog=perct; + } +}; +#endif // QMPCOREWRAPPER_H diff --git a/qmidiplayer-lite/qmpmidiplay.cpp b/qmidiplayer-lite/qmpmidiplay.cpp new file mode 100644 index 0000000..baec9a2 --- /dev/null +++ b/qmidiplayer-lite/qmpmidiplay.cpp @@ -0,0 +1,406 @@ +#include +#include +#include +#include +#include "qmpmidiplay.hpp" +#ifdef _WIN32 +#include +uint64_t pf; +#endif +void CMidiPlayer::fluidPreInitialize() +{ + settings=new_fluid_settings(); +} +void CMidiPlayer::fluidInitialize() +{ + synth=new_fluid_synth(settings); + adriver=new_fluid_audio_driver(settings,synth); + fluid_synth_set_chorus(synth,FLUID_CHORUS_DEFAULT_N,FLUID_CHORUS_DEFAULT_LEVEL, + FLUID_CHORUS_DEFAULT_SPEED,FLUID_CHORUS_DEFAULT_DEPTH, + FLUID_CHORUS_DEFAULT_TYPE); +#ifndef _WIN32 + if(!singleInstance) + { + if(midiFile->getStandard()==4) + fluid_synth_set_channel_type(synth,9,CHANNEL_TYPE_MELODIC); + else if(midiFile->getStandard()==1) + fluid_synth_set_channel_type(synth,9,CHANNEL_TYPE_DRUM); + else + { + fluid_synth_set_channel_type(synth,9,CHANNEL_TYPE_DRUM); + fluid_synth_bank_select(synth,9,128); + } + } +#endif +} +void CMidiPlayer::fluidDeinitialize() +{ + if(!synth||!adriver||!settings)return; + delete_fluid_settings(settings); + delete_fluid_audio_driver(adriver); + delete_fluid_synth(synth); + settings=NULL;synth=NULL;adriver=NULL; +} +void CMidiPlayer::processEvent(const SEvent *e) +{ + switch(e->type&0xF0) + { + case 0x80://Note off + fluid_synth_noteoff(synth,e->type&0x0F,e->p1); + break; + case 0x90://Note on + if((mute>>(e->type&0x0F))&1)break;//muted + if(solo&&!((solo>>(e->type&0x0F))&1))break; + fluid_synth_noteon(synth,e->type&0x0F,e->p1,e->p2); + break; + case 0xB0://CC + if(e->p1==100)rpnid=e->p2; + if(e->p1==6)rpnval=e->p2; + if(~rpnid&&~rpnval) + { + if(rpnid==0)fluid_synth_pitch_wheel_sens(synth,e->type&0x0F,rpnval); + rpnid=rpnval=-1; + } + fluid_synth_cc(synth,e->type&0x0F,e->p1,e->p2); + break; + case 0xC0://PC + fluid_synth_program_change(synth,e->type&0x0F,e->p1); + break; + case 0xE0://PW + fluid_synth_pitch_bend(synth,e->type&0x0F,e->p1); + break; + case 0xF0://Meta/SysEx + if((e->type&0x0F)==0x0F) + { + switch(e->p1) + { + case 0x51: + ctempo=e->p2;dpt=ctempo*1000/divs; + break; + case 0x58: + ctsn=e->p2>>24; + ctsd=1<<((e->p2>>16)&0xFF); + break; + case 0x59: + cks=e->p2; + break; + case 0x01:case 0x02:case 0x03: + case 0x04:case 0x05:case 0x06: + case 0x07: + if(e->str)puts(e->str); + break; + } + } + if((e->type&0x0F)==0x00||(e->type&0x0F)==07) + { + int io=0; + if(sendSysEx)fluid_synth_sysex(synth,e->str,e->p1,NULL,&io,NULL,0); + } + break; + } +} +void CMidiPlayer::processEventStub(const SEvent *e) +{ + switch(e->type&0xF0) + { + case 0xB0://CC + if(e->p1==100)rpnid=e->p2; + if(e->p1==6)rpnval=e->p2; + if(~rpnid&&~rpnval) + { + if(rpnid==0)ccc[e->type&0x0F][134]=rpnval; + rpnid=rpnval=-1; + } + ccc[e->type&0x0F][e->p1]=e->p2; + break; + case 0xC0://PC + ccc[e->type&0x0F][128]=e->p1; + break; + case 0xD0://CP + ccc[e->type&0x0F][129]=e->p1; + break; + case 0xE0://PW + ccc[e->type&0x0F][130]=e->p1; + break; + case 0xF0://Meta/SysEx + if((e->type&0x0F)==0x0F) + { + switch(e->p1) + { + case 0x51: + ctempo=e->p2;dpt=ctempo*1000/divs; + ccc[0][131]=dpt; + break; + case 0x58: + ccc[0][132]=e->p2; + break; + case 0x59: + ccc[0][133]=e->p2; + break; + } + } + break; + } +} +#ifdef _WIN32 +void w32usleep(uint64_t t) +{ + uint64_t st=0,ct=0; + QueryPerformanceCounter((LARGE_INTEGER*)&st); + do{ + if(t>10000+(ct-st)*1000000/pf)Sleep((t-(ct-st)*1000000/pf)/2000); + else if(t>5000+(ct-st)*1000000/pf)Sleep(1); + else std::this_thread::yield(); + QueryPerformanceCounter((LARGE_INTEGER*)&ct); + }while((ct-st)*1000000getEvent(0)->time;tceptrgetEventCount();) + { + while(tcpaused)std::this_thread::sleep_for(std::chrono::milliseconds(100)); + while(!tcstop&&midiFile&&tceptrgetEventCount()&&ct==midiFile->getEvent(tceptr)->time) + processEvent(midiFile->getEvent(tceptr++)); + if(tcstop||!midiFile||tceptr>=midiFile->getEventCount())break; + if(resumed)resumed=false; + else +#ifdef _WIN32 + w32usleep((midiFile->getEvent(tceptr)->time-ct)*(dpt/1000)); +#else + std::this_thread::sleep_for(std::chrono::nanoseconds(midiFile->getEvent(tceptr)->time-ct)*dpt); +#endif + if(tcstop||!midiFile)break; + ct=midiFile->getEvent(tceptr)->time; + } + while(!tcstop&&synth&&(waitvoice&&fluid_synth_get_active_voice_count(synth)>0))std::this_thread::sleep_for(std::chrono::milliseconds(2)); + finished=1; +} +void CMidiPlayer::fileTimer1Pass() +{ + ftime=.0;ctempo=0x7A120;dpt=ctempo*1000/divs; + for(uint32_t eptr=0,ct=midiFile->getEvent(0)->time;eptrgetEventCount();) + { + while(eptrgetEventCount()&&ct==midiFile->getEvent(eptr)->time) + processEventStub(midiFile->getEvent(eptr++)); + if(eptr>=midiFile->getEventCount())break; + ftime+=(midiFile->getEvent(eptr)->time-ct)*dpt/1e9; + ct=midiFile->getEvent(eptr)->time; + } +} +void CMidiPlayer::fileTimer2Pass() +{ + double ctime=.0;uint32_t c=1;ctempo=0x7A120;dpt=ctempo*1000/divs; + memset(stamps,0,sizeof(stamps));memset(ccstamps,0,sizeof(ccstamps)); + memset(ccc,0,sizeof(ccc));rpnid=rpnval=-1;for(int i=0;i<16;++i) + { + ccc[i][7]=100;ccc[i][10]=64;ccc[i][11]=127; + ccc[i][11]=127;ccc[i][71]=64;ccc[i][72]=64; + ccc[i][73]=64;ccc[i][74]=64;ccc[i][75]=64; + ccc[i][76]=64;ccc[i][77]=64;ccc[i][78]=64; + ccc[0][131]=dpt;ccc[0][132]=0x04021808; + ccc[0][133]=0;ccc[0][134]=2; + }if(midiFile->getStandard()!=4)ccc[9][0]=128; + for(int i=0;i<16;++i)for(int j=0;j<135;++j) + ccstamps[0][i][j]=ccc[i][j]; + for(uint32_t eptr=0,ct=midiFile->getEvent(0)->time;eptrgetEventCount();) + { + while(eptrgetEventCount()&&ct==midiFile->getEvent(eptr)->time) + processEventStub(midiFile->getEvent(eptr++)); + if(eptr>=midiFile->getEventCount())break; + ctime+=(midiFile->getEvent(eptr)->time-ct)*dpt/1e9; + while(ctime>ftime*c/100.) + { + for(int i=0;i<16;++i)for(int j=0;j<135;++j) + ccstamps[c][i][j]=ccc[i][j]; + stamps[c++]=eptr; + if(c>100)break; + } + ct=midiFile->getEvent(eptr)->time; + } + while(c<101) + { + for(int i=0;i<16;++i)for(int j=0;j<135;++j) + ccstamps[c][i][j]=ccc[i][j]; + stamps[c++]=midiFile->getEventCount(); + } +} +CMidiPlayer::CMidiPlayer(bool singleInst) +{ + midiFile=NULL;resumed=false;singleInstance=singleInst; + settings=NULL;synth=NULL;adriver=NULL;waitvoice=true; +#ifdef _WIN32 + QueryPerformanceFrequency((LARGE_INTEGER*)&pf); + timeBeginPeriod(1); +#endif +} +CMidiPlayer::~CMidiPlayer() +{ + if(singleInstance)fluidDeinitialize(); +#ifdef _WIN32 + timeEndPeriod(1); +#endif +} +void CMidiPlayer::playerPanic(bool reset) +{ + if(reset)for(int i=0;i<16;++i) + { + fluid_synth_pitch_bend(synth,i,8192); + fluid_synth_cc(synth,i,7,100); + fluid_synth_cc(synth,i,10,64); + fluid_synth_cc(synth,i,11,127); + } + for(int i=0;i<16;++i)fluid_synth_all_notes_off(synth,i); +} +bool CMidiPlayer::playerLoadFile(const char* fn) +{ + midiFile=new CMidiFile(fn); + if(!midiFile->isValid())return false; + divs=midiFile->getDivision(); + fileTimer1Pass(); + fileTimer2Pass(); + return true; +} +void CMidiPlayer::playerInit() +{ + ctempo=0x7A120;ctsn=4;ctsd=4;cks=0;dpt=ctempo*1000/divs; + tceptr=0;tcstop=0;tcpaused=0;finished=0;mute=solo=0; + sendSysEx=true;rpnid=rpnval=-1; + if(!singleInstance)fluidPreInitialize(); +} +void CMidiPlayer::playerDeinit() +{ + tceptr=0;tcstop=1;tcpaused=0; + delete midiFile;midiFile=NULL; + if(!singleInstance)fluidDeinitialize(); +} +void CMidiPlayer::playerThread() +{ + playEvents(); +} + +void CMidiPlayer::rendererLoadFile(const char* ofn) +{ + settings=new_fluid_settings(); + fluid_settings_setstr(settings,"audio.file.name",ofn); +} +void CMidiPlayer::rendererInit(const char* fn) +{ + finished=0; + synth=new_fluid_synth(settings); + player=new_fluid_player(synth); + fluid_player_add(player,fn); +} +void CMidiPlayer::rendererThread() +{ + fluid_file_renderer_t* renderer=new_fluid_file_renderer(synth); + fluid_player_play(player); + while(fluid_player_get_status(player)==FLUID_PLAYER_PLAYING) + if(fluid_file_renderer_process_block(renderer)!=FLUID_OK)break; + delete_fluid_file_renderer(renderer); + finished=1; +} +void CMidiPlayer::rendererDeinit() +{ + delete_fluid_player(player); + delete_fluid_synth(synth); + delete_fluid_settings(settings); + player=NULL;synth=NULL;settings=NULL; +} + +void CMidiPlayer::sendSysX(bool send){sendSysEx=send;} +uint32_t CMidiPlayer::getStamp(int id){return stamps[id];} +uint32_t CMidiPlayer::getTCeptr(){return tceptr;} +void CMidiPlayer::setTCeptr(uint32_t ep,uint32_t st) +{ + resumed=true; + if(ep==midiFile->getEventCount())tcstop=1;else tceptr=ep; + for(int i=0;i<16;++i) + { + for(int j=0;j<120;++j)fluid_synth_cc(synth,i,j,ccstamps[st][i][j]); + fluid_synth_program_change(synth,i,ccstamps[st][i][128]); + //fluid_synth_pitch_bend(synth,i,ccstamps[st][i][130]); + dpt=ccstamps[st][0][131];ctempo=dpt*divs/1000; + ctsn=ccstamps[st][0][132]>>24;ctsd=1<<((ccstamps[st][0][132]>>16)&0xFF); + cks=ccstamps[st][0][133]; + } +} +double CMidiPlayer::getFtime(){return ftime;} +void CMidiPlayer::getCurrentTimeSignature(int *n,int *d){*n=ctsn;*d=ctsd;} +void CMidiPlayer::getCurrentKeySignature(int *ks){*ks=cks;} +uint32_t CMidiPlayer::getFileNoteCount(){return midiFile?midiFile->getNoteCount():0;} +uint32_t CMidiPlayer::getFileStandard(){return midiFile?midiFile->getStandard():0;} +const char* CMidiPlayer::getTitle(){return midiFile?midiFile->getTitle():"";} +const char* CMidiPlayer::getCopyright(){return midiFile?midiFile->getCopyright():"";} +double CMidiPlayer::getTempo(){return 60./(ctempo/1e6)/**ctsd/4.*/;} +uint32_t CMidiPlayer::getTCpaused(){return tcpaused;} +void CMidiPlayer::setTCpaused(uint32_t ps){tcpaused=ps;} +uint32_t CMidiPlayer::isFinished(){return finished;} +void CMidiPlayer::setResumed(){resumed=true;} +void CMidiPlayer::setWaitVoice(bool wv){waitvoice=wv;} +void CMidiPlayer::setGain(double gain){if(settings)fluid_settings_setnum(settings,"synth.gain",gain);} +int CMidiPlayer::getPolyphone(){return synth?fluid_synth_get_active_voice_count(synth):0;} +int CMidiPlayer::getMaxPolyphone(){return synth?fluid_synth_get_polyphony(synth):0;} +void CMidiPlayer::setMaxPolyphone(int p){if(synth)fluid_synth_set_polyphony(synth,p);} + +void CMidiPlayer::getChannelPreset(int ch,int *b,int *p,char *name) +{ + if(!synth)return(void)(b=0,p=0,strcpy(name,"")); + fluid_synth_channel_info_t info; + fluid_synth_get_channel_info(synth,ch,&info); + *b=info.bank;*p=info.program; + strcpy(name,info.name); +} +void CMidiPlayer::setChannelPreset(int ch,int b,int p) +{ + if(!synth)return; + fluid_synth_bank_select(synth,ch,b); + fluid_synth_program_change(synth,ch,p); +} +//16MSB..LSB1 +void CMidiPlayer::setBit(uint16_t &n, uint16_t bn, uint16_t b) +{n^=(-b^n)&(1< +#include +#include +#include +struct SEvent +{ + uint32_t iid,time,p1,p2; + uint8_t type; + char *str; + SEvent(){time=p1=p2=0;type=0;str=NULL;} + SEvent(uint32_t _iid,uint32_t _t,char _tp,uint32_t _p1,uint32_t _p2,const char* s=NULL) + { + iid=_iid;time=_t;type=_tp; + p1=_p1;p2=_p2; + if(s){str=new char[strlen(s)+2];strcpy(str,s);}else str=NULL; + } +}; +class CMidiFile +{ + private: + SEvent *eventList[10000000]; + char *title,*copyright; + uint32_t eventc,std;//standard 0=? 1=GM 2=GM2 3=GS 4=XG + uint32_t fmt,trk,divs; + FILE *f; + int byteread,valid; + uint32_t notes,curt,curid; + + void error(int fatal,const char* format,...); + uint32_t readSW(); + uint32_t readDW(); + uint32_t readVL(); + int eventReader(); + void trackChunkReader(); + void headerChunkReader(); + int chunkReader(int hdrXp); + public: + CMidiFile(const char* fn); + ~CMidiFile(); + const SEvent* getEvent(uint32_t id); + uint32_t getEventCount(); + uint32_t getDivision(); + uint32_t getNoteCount(); + uint32_t getStandard(); + const char* getTitle(); + const char* getCopyright(); + bool isValid(); +}; +class CMidiPlayer +{ + private: + CMidiFile *midiFile; + uint32_t stamps[101]; + uint32_t ccstamps[101][16][135],ccc[16][135]; + //0..127:cc 128:pc 129:cp 130:pb 131:tempo 132:ts 133:ks 134:pbr + int32_t rpnid,rpnval; + uint16_t mute,solo; + double ftime; + bool sendSysEx,singleInstance,waitvoice; + fluid_settings_t* settings; + fluid_synth_t* synth; + fluid_audio_driver_t* adriver; + fluid_player_t* player; + uint32_t ctempo,ctsn,ctsd,dpt,divs,cks; + //raw tempo, timesig num., timesig den., delay per tick, division, keysig + //thread control + uint32_t tceptr,tcpaused,tcstop; + uint32_t finished,resumed; + + void setBit(uint16_t &n,uint16_t bn,uint16_t b); + void processEvent(const SEvent *e); + void processEventStub(const SEvent *e); + void playEvents(); + void fileTimer1Pass(); + void fileTimer2Pass(); + public: + CMidiPlayer(bool singleInst=false); + ~CMidiPlayer(); + bool playerLoadFile(const char* fn); + void playerInit(); + void fluidPreInitialize(); + void fluidInitialize(); + void fluidDeinitialize(); + void playerDeinit(); + void playerThread(); + void playerPanic(bool reset=false); + + void rendererLoadFile(const char* ofn); + void rendererInit(const char *fn); + void rendererThread(); + void rendererDeinit(); + + //playing control methods + uint32_t getStamp(int id); + uint32_t getTCeptr(); + void setTCeptr(uint32_t ep,uint32_t st); + uint32_t getTCpaused(); + void setTCpaused(uint32_t ps); + uint32_t isFinished(); + void setResumed(); + void setWaitVoice(bool wv); + + double getFtime(); + void getCurrentTimeSignature(int *n,int *d); + void getCurrentKeySignature(int *ks); + uint32_t getFileNoteCount(); + uint32_t getFileStandard(); + double getTempo(); + const char* getTitle(); + const char* getCopyright(); + + void setGain(double gain); + void sendSysX(bool send); + int getPolyphone(); + int getMaxPolyphone(); + void setMaxPolyphone(int p); + + void setChannelPreset(int ch,int b,int p); + void getChannelPreset(int ch,int *b,int *p,char *name); + void setMute(int ch,bool m); + void setSolo(int ch,bool s); + int getCC(int ch,int id); + void setCC(int ch,int id,int val); + void getReverbPara(double *r,double *d,double *w,double *l); + void setReverbPara(int e,double r,double d,double w,double l); + void getChorusPara(int *fb,double *l,double *r,double *d,int *type); + void setChorusPara(int e,int fb,double l,double r,double d,int type); + + fluid_settings_t* getFluidSettings(); + void pushSoundFont(const char* sf); + int getSFCount(); + fluid_sfont_t* getSFPtr(int sfid); +}; +#endif diff --git a/qmidiplayer-lite/qmpmidiread.cpp b/qmidiplayer-lite/qmpmidiread.cpp new file mode 100644 index 0000000..0bcf597 --- /dev/null +++ b/qmidiplayer-lite/qmpmidiread.cpp @@ -0,0 +1,240 @@ +//CLI Midi file player based on libfluidsynth +//Midi file reading module +//Written by Chris Xiong, 2015 +#include +#include +#include +#include +#include +#include +#include "qmpmidiplay.hpp" +const char* GM1SysX={"\xF0\x7E\x7F\x09\x01\xF7"}; +const char* GM2SysX={"\xF0\x7E\x7F\x09\x03\xF7"}; +const char* GSSysEx={"\xF0\x41\x10\x42\x12\x40\x00\x7F\x00\x41\xF7"}; +const char* XGSysEx={"\xF0\x43\x10\x4C\x00\x00\x7E\x00\xF7"}; +bool cmp(SEvent *a,SEvent *b){return a->time-b->time?a->timetime:a->iidiid;} +void CMidiFile::error(int fatal,const char* format,...) +{ + va_list ap; + va_start(ap,format);vfprintf(stderr,format,ap);va_end(ap); + fprintf(stderr," at %#lx\n",ftell(f)); + if(fatal)throw 2; +} +uint32_t CMidiFile::readSW() +{ + byteread+=2; + uint32_t ret=0; + for(int i=0;i<2;++i){ret<<=8;ret|=((uint32_t)fgetc(f))&0xFF;} + return ret; +} +uint32_t CMidiFile::readDW() +{ + byteread+=4; + uint32_t ret=0; + for(int i=0;i<4;++i){ret<<=8;ret|=((uint32_t)fgetc(f))&0xFF;} + return ret; +} +uint32_t CMidiFile::readVL() +{ + uint32_t ret=0,t,c=0; + do + { + t=fgetc(f); + if(++c>4)error(1,"E: Variable length type overflow."); + ret<<=7;ret|=(t&0x7F); + }while(t&0x80); + byteread+=c; + return ret; +} +int CMidiFile::eventReader()//returns 0 if End of Track encountered +{ + uint32_t delta=readVL();curt+=delta; + char type=fgetc(f);++byteread;uint32_t p1,p2; + static char lasttype; +retry: + switch(type&0xF0) + { + case 0x80://Note Off + p1=fgetc(f);p2=fgetc(f);byteread+=2; + eventList[eventc++]=new SEvent(curid,curt,type,p1,p2); + break; + case 0x90://Note On + p1=fgetc(f);p2=fgetc(f);byteread+=2; + if(p2) + { + ++notes; + eventList[eventc++]=new SEvent(curid,curt,type,p1,p2); + } + else + eventList[eventc++]=new SEvent(curid,curt,(type&0x0F)|0x80,p1,p2); + break; + case 0xA0://Note Aftertouch + p1=fgetc(f);p2=fgetc(f);byteread+=2; + eventList[eventc++]=new SEvent(curid,curt,type,p1,p2); + break; + case 0xB0://Controller Change + p1=fgetc(f);p2=fgetc(f);byteread+=2; + eventList[eventc++]=new SEvent(curid,curt,type,p1,p2); + break; + case 0xC0://Patch Change + p1=fgetc(f);++byteread; + eventList[eventc++]=new SEvent(curid,curt,type,p1,0); + break; + case 0xD0://Channel Aftertouch + p1=fgetc(f);++byteread; + eventList[eventc++]=new SEvent(curid,curt,type,p1,0); + break; + case 0xE0://Pitch wheel + p1=fgetc(f);p2=fgetc(f);byteread+=2; + eventList[eventc++]=new SEvent(curid,curt,type,(p1|(p2<<7))&0x3FFF,0); + break; + case 0xF0: + if((type&0x0F)==0x0F)//Meta Event + { + char metatype=fgetc(f);++byteread; + switch(metatype) + { + case 0x00://Sequence Number + fgetc(f);fgetc(f);fgetc(f); + byteread+=3; + break; + case 0x20://Channel Prefix + fgetc(f);fgetc(f);byteread+=2; + break; + case 0x2F://End of Track + fgetc(f);++byteread; + return 0; + break; + case 0x51://Set Tempo + p1=readDW();p1&=0x00FFFFFF; + eventList[eventc++]=new SEvent(curid,curt,type,metatype,p1); + break; + case 0x54://SMTPE offset, not handled. + fgetc(f);fgetc(f);fgetc(f); + fgetc(f);fgetc(f);fgetc(f); + byteread+=6; + break; + case 0x58://Time signature + fgetc(f);++byteread; + p1=readDW(); + eventList[eventc++]=new SEvent(curid,curt,type,metatype,p1); + break; + case 0x59://Key signature + fgetc(f);++byteread; + p1=readSW(); + eventList[eventc++]=new SEvent(curid,curt,type,metatype,p1); + break; + case 0x01:case 0x02:case 0x03: + case 0x04:case 0x05:case 0x06: + case 0x07:case 0x7F:default://text-like meta + { + uint32_t len=readVL(),c;char* str=NULL; + if(len<=1024&&len>0)str=new char[len+8]; + for(c=0;c0)delete[] str; + } + } + } + else if((type&0x0F)==0x00||(type&0x0F)==0x07)//SysEx + { + uint32_t len=readVL(),c;char* str=NULL; + str=new char[len+8]; + if((type&0x0F)==0x00) + { + str[0]=0xF0;++len; + for(c=1;cchnklen) + { + error(1,"E: Read past end of track."); + }*/ +} +void CMidiFile::headerChunkReader() +{ + int chnklen=readDW();byteread=0; + if(chnklen<6)error(1,"E: Header chunk too short."); + if(chnklen>6)error(0,"W: Header chunk length longer than expected. Ignoring extra bytes."); + fmt=readSW();trk=readSW();divs=readSW(); + if(divs&0x8000)error(1,"E: SMTPE format is not supported."); + for(;byteread0;--chnklen)fgetc(f);return 0; + } + else return trackChunkReader(),1; +} +CMidiFile::CMidiFile(const char* fn) +{ + title=copyright=NULL;notes=eventc=0;std=0;valid=1; + try + { + if(!(f=fopen(fn,"rb")))throw (fprintf(stderr,"E: file %s doesn't exist!\n",fn),2); + chunkReader(1); + for(uint32_t i=0;i