From 0c3fe540e925829022d9d8aa567c2dc2bd3d33d5 Mon Sep 17 00:00:00 2001 From: Chris Xiong Date: Wed, 21 Jun 2017 13:03:30 +0800 Subject: Complete rewrite of the MIDI mapper. Enforces single fluidsynth instance. Documentation update. Minor changes to make lite version work. --- ChangeLog | 12 + core/qmpmidimapperrtmidi.cpp | 105 --------- core/qmpmidimappers.hpp | 25 --- core/qmpmidioutfluid.cpp | 282 +++++++++++++++++++++++ core/qmpmidioutfluid.hpp | 80 +++++++ core/qmpmidioutrtmidi.cpp | 115 ++++++++++ core/qmpmidioutrtmidi.hpp | 36 +++ core/qmpmidiplay.cpp | 335 ++++++++++------------------ core/qmpmidiplay.hpp | 54 ++--- debian/changelog | 7 + debian/qmidiplayer-plugin-midifmt.install | 1 + doc/APIdoc.md | 130 +++++++++-- doc/optionsdialog.html | 5 +- include/qmpcorepublic.hpp | 49 ++-- qmidiplayer-desktop/qmidiplayer-desktop.pro | 10 +- qmidiplayer-desktop/qmpchannelswindow.cpp | 22 +- qmidiplayer-desktop/qmpchannelswindow.hpp | 3 +- qmidiplayer-desktop/qmpefxwindow.cpp | 9 +- qmidiplayer-desktop/qmpefxwindow.hpp | 2 +- qmidiplayer-desktop/qmpmainwindow.cpp | 109 +++++---- qmidiplayer-desktop/qmpmainwindow.hpp | 4 +- qmidiplayer-desktop/qmpplugin.cpp | 8 +- qmidiplayer-desktop/qmppresetselect.cpp | 17 +- qmidiplayer-desktop/qmpsettingswindow.ui | 5 - qmidiplayer-lite/main.qml | 36 ++- qmidiplayer-lite/qmidiplayer-lite.pro | 6 +- qmidiplayer-lite/qmpcorewrapper.hpp | 16 +- 27 files changed, 950 insertions(+), 533 deletions(-) delete mode 100644 core/qmpmidimapperrtmidi.cpp delete mode 100644 core/qmpmidimappers.hpp create mode 100644 core/qmpmidioutfluid.cpp create mode 100644 core/qmpmidioutfluid.hpp create mode 100644 core/qmpmidioutrtmidi.cpp create mode 100644 core/qmpmidioutrtmidi.hpp create mode 100644 debian/qmidiplayer-plugin-midifmt.install diff --git a/ChangeLog b/ChangeLog index ab665b6..c7108b1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,15 @@ +2017-06-21 0.8.6 indev +Code refactoring: + Complete rewrite of the MIDI mapper. The new + architecture allows implementation of custom MIDI + output devices. +Enforces single fluidsynth instance. Deprecating +the option. +Documentation uptate. +The lite version no longer have hardcoded soundfont +path. +More testing needed for this revision. + 2017-06-16 0.8.6 indev Code refactoring: Tracks information are now preserved. diff --git a/core/qmpmidimapperrtmidi.cpp b/core/qmpmidimapperrtmidi.cpp deleted file mode 100644 index 263cada..0000000 --- a/core/qmpmidimapperrtmidi.cpp +++ /dev/null @@ -1,105 +0,0 @@ -#include -#include -#include -#include RT_MIDI_H -#include "qmpmidimappers.hpp" -RtMidiOut* qmpMidiMapperRtMidi::dummy=NULL; -qmpMidiMapperRtMidi::qmpMidiMapperRtMidi() -{ - try{dummy=new RtMidiOut();} - catch(RtMidiError &e) - { - printf("Failed to initialize the dummy device: %s\n",e.what()); - dummy=NULL; - } - memset(ports,0,sizeof(ports)); -} -qmpMidiMapperRtMidi::~qmpMidiMapperRtMidi() -{ - delete dummy;dummy=NULL; - for(int i=0;i<16;++i)if(ports[i])delete ports[i]; -} -int qmpMidiMapperRtMidi::enumDevices() -{ - return dummy?dummy->getPortCount():0; -} -std::string qmpMidiMapperRtMidi::deviceName(int id) -{ - return dummy?dummy->getPortName(id):""; -} -int qmpMidiMapperRtMidi::deviceInit(int id) -{ - int i=0;for(;ports[i]&&i<16;++i); - if(i==16)return -1; - try - { - ports[i]=new RtMidiOut(); - ports[i]->openPort(id); - } - catch(RtMidiError &e) - { - printf("Device initialization failure: %s\n",e.what()); - ports[i]=NULL; - return -1; - } - return i; -} -void qmpMidiMapperRtMidi::deviceDeinit(int iid) -{ - if(ports[iid]){ports[iid]->closePort();delete ports[iid];ports[iid]=NULL;} -} -void qmpMidiMapperRtMidi::noteOn(int iid,int ch,int key,int vel) -{ - if(!ports[iid])return;ch&=0x0F; - std::vectormessage; - message.push_back(0x90|ch); - message.push_back(key); - message.push_back(vel); - ports[iid]->sendMessage(&message); -} -void qmpMidiMapperRtMidi::noteOff(int iid,int ch,int key) -{ - if(!ports[iid])return;ch&=0x0F; - std::vectormessage; - message.push_back(0x80|ch);message.push_back(key);message.push_back(0); - ports[iid]->sendMessage(&message); -} -void qmpMidiMapperRtMidi::ctrlChange(int iid,int ch,int cc,int val) -{ - if(!ports[iid])return;ch&=0x0F; - std::vectormessage; - message.push_back(0xB0|ch);message.push_back(cc);message.push_back(val); - ports[iid]->sendMessage(&message); -} -void qmpMidiMapperRtMidi::progChange(int iid,int ch,int val) -{ - if(!ports[iid])return;ch&=0x0F; - std::vectormessage; - message.push_back(0xC0|ch);message.push_back(val); - ports[iid]->sendMessage(&message); -} -void qmpMidiMapperRtMidi::pitchBend(int iid,int ch,int val) -{ - if(!ports[iid])return;ch&=0x0F; - std::vectormessage; - message.push_back(0xE0|ch);message.push_back(val&0x7F); - message.push_back(val>>7);ports[iid]->sendMessage(&message); -} -void qmpMidiMapperRtMidi::sysEx(int iid,int length,const char *data) -{ - if(!ports[iid])return; - std::vectormessage(data,data+length); - ports[iid]->sendMessage(&message); -} -void qmpMidiMapperRtMidi::panic(int iid,int ch) -{ - //maybe all notes off is more close to panic? - pitchBend(iid,ch,8192); - ctrlChange(iid,ch,120,0); - //ctrlChange(iid,ch,123,0); -} -void qmpMidiMapperRtMidi::reset(int iid,int ch) -{ - ctrlChange(iid,ch,120,0); - ctrlChange(iid,ch,121,0); -} diff --git a/core/qmpmidimappers.hpp b/core/qmpmidimappers.hpp deleted file mode 100644 index 7b9ff0e..0000000 --- a/core/qmpmidimappers.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef QMPMIDIMAPPERS_H -#define QMPMIDIMAPPERS_H -#include RT_MIDI_H -class qmpMidiMapperRtMidi -{ -private: - RtMidiOut *ports[16]; - static RtMidiOut *dummy; -public: - qmpMidiMapperRtMidi(); - ~qmpMidiMapperRtMidi(); - int deviceInit(int id); - void deviceDeinit(int iid); - void noteOn(int iid,int ch,int key,int vel); - void noteOff(int iid,int ch,int key); - void ctrlChange(int iid,int ch,int cc,int val); - void progChange(int iid,int ch,int val); - void pitchBend(int iid,int ch,int val); - void sysEx(int iid,int length,const char* data); - void panic(int iid,int ch); - void reset(int iid,int ch); - int enumDevices(); - std::string deviceName(int id); -}; -#endif // QMPMIDIMAPPERS_H diff --git a/core/qmpmidioutfluid.cpp b/core/qmpmidioutfluid.cpp new file mode 100644 index 0000000..da3282f --- /dev/null +++ b/core/qmpmidioutfluid.cpp @@ -0,0 +1,282 @@ +#include +#include +#include +#include "qmpmidioutfluid.hpp" +qmpMidiOutFluid::qmpMidiOutFluid() +{ + settings=new_fluid_settings(); + synth=NULL;adriver=NULL; +} +qmpMidiOutFluid::~qmpMidiOutFluid() +{ + delete_fluid_settings(settings); + settings=NULL; +} +void qmpMidiOutFluid::deviceInit() +{ + synth=new_fluid_synth(settings); + if(!synth){fputs("Error creating fluidsynth instance!",stderr);return;} + fluid_set_log_function(FLUID_DBG,NULL,NULL); + fluid_set_log_function(FLUID_INFO,NULL,NULL); + fluid_set_log_function(FLUID_WARN,NULL,NULL); + fluid_set_log_function(FLUID_ERR,fluid_default_log_function,NULL); + fluid_set_log_function(FLUID_PANIC,fluid_default_log_function,NULL); + adriver=new_fluid_audio_driver(settings,synth); + if(!adriver) + { + fputs("Error creating fluidsynth audio driver!",stderr); + delete_fluid_synth(synth);synth=NULL; + return; + } + 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); + /*if(midiFile->std==4) + fluid_synth_set_channel_type(synth,9,CHANNEL_TYPE_MELODIC); + else if(midiFile->std==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); + }*/ +} +void qmpMidiOutFluid::deviceDeinit(){deviceDeinit(false);} +void qmpMidiOutFluid::deviceDeinit(bool freshsettings) +{ + if(!synth||!adriver)return; + delete_fluid_audio_driver(adriver); + delete_fluid_synth(synth); + synth=NULL;adriver=NULL; + if(freshsettings) + { + delete_fluid_settings(settings); + settings=new_fluid_settings(); + } +} +void qmpMidiOutFluid::basicMessage(uint8_t type,uint8_t p1,uint8_t p2) +{ + uint8_t chan=type&0x0F; + switch(type&0xF0) + { + case 0x80: + fluid_synth_noteoff(synth,chan,p1); + break; + case 0x90: + if(p2)fluid_synth_noteon(synth,chan,p1,p2); + else fluid_synth_noteoff(synth,chan,p1); + break; + case 0xB0: + fluid_synth_cc(synth,chan,p1,p2); + break; + case 0xC0: + fluid_synth_program_change(synth,chan,p1); + break; + case 0xE0: + fluid_synth_pitch_bend(synth,chan,(p1|p2<<7)&0x3fff); + break; + } +} +void qmpMidiOutFluid::extendedMessage(uint8_t length,const char *data) +{ + int rlen=0; + fluid_synth_sysex(synth,data,length,NULL,&rlen,NULL,0); +} +void qmpMidiOutFluid::rpnMessage(uint8_t ch,uint16_t type,uint16_t val) +{ + if(type==0)fluid_synth_pitch_wheel_sens(synth,ch,val>>7); + else + { + fluid_synth_cc(synth,ch,0x64,type&0x7F); + fluid_synth_cc(synth,ch,0x65,type>>7); + fluid_synth_cc(synth,ch,0x06,val>>7); + fluid_synth_cc(synth,ch,0x26,val&0x7F); + } +} +void qmpMidiOutFluid::nrpnMessage(uint8_t ch,uint16_t type,uint16_t val) +{ + fluid_synth_cc(synth,ch,0x62,type&0x7F); + fluid_synth_cc(synth,ch,0x63,type>>7); + fluid_synth_cc(synth,ch,0x06,val>>7); + fluid_synth_cc(synth,ch,0x26,val&0x7F); +} +void qmpMidiOutFluid::panic(uint8_t ch) +{ + fluid_synth_cc(synth,ch,64,0); + fluid_synth_pitch_bend(synth,ch,8192); + fluid_synth_all_notes_off(synth,ch); +} +void qmpMidiOutFluid::reset(uint8_t ch) +{ + this->panic(ch); + fluid_synth_cc(synth,ch,0,0); + fluid_synth_cc(synth,ch,7,100); + fluid_synth_cc(synth,ch,10,64); + fluid_synth_cc(synth,ch,11,127); + fluid_synth_cc(synth,ch,32,0); + fluid_synth_pitch_wheel_sens(synth,ch,2); +} +void qmpMidiOutFluid::onMapped(uint8_t,int) +{ +} +void qmpMidiOutFluid::onUnmapped(uint8_t,int) +{ +} +void qmpMidiOutFluid::setOptStr(const char *opt,const char *val) +{ + fluid_settings_setstr(settings,opt,val); +} +void qmpMidiOutFluid::setOptInt(const char *opt,int val) +{ + fluid_settings_setint(settings,opt,val); +} +void qmpMidiOutFluid::setOptNum(const char *opt,double val) +{ + fluid_settings_setnum(settings,opt,val); +} +void qmpMidiOutFluid::loadSFont(const char *path) +{ + if(synth)fluid_synth_sfload(synth,path,1); +} +int qmpMidiOutFluid::getSFCount() +{ + return synth?fluid_synth_sfcount(synth):0; +} +std::vector,std::string>> qmpMidiOutFluid::listPresets() +{ + std::vector,std::string>> ret; + std::map,std::string> pmap; + for(int i=getSFCount()-1;i>=0;--i) + { + fluid_sfont_t* psf=fluid_synth_get_sfont(synth,i); + fluid_preset_t preset; + psf->iteration_start(psf); + while(psf->iteration_next(psf,&preset)) + pmap[std::make_pair( + preset.get_banknum(&preset), + preset.get_num(&preset) + )]=preset.get_name(&preset); + } + for(auto i=pmap.begin();i!=pmap.end();++i) + ret.push_back(std::make_pair(i->first,i->second)); + return ret; +} +int qmpMidiOutFluid::getPolyphone() +{ + return synth?fluid_synth_get_active_voice_count(synth):0; +} +int qmpMidiOutFluid::getMaxPolyphone() +{ + return synth?fluid_synth_get_polyphony(synth):0; +} +void qmpMidiOutFluid::setGain(double gain) +{ + if(settings)fluid_settings_setnum(settings,"synth.gain",gain); +} +void qmpMidiOutFluid::getChannelInfo(int ch,int *b, int *p, char *s) +{ + if(!synth)return; + fluid_synth_channel_info_t chinfo; + fluid_synth_get_channel_info(synth,ch,&chinfo); + *b=chinfo.bank;*p=chinfo.program; + strcpy(s,chinfo.name); +} +void qmpMidiOutFluid::getReverbPara(double *r,double *d,double *w,double *l) +{ + if(!synth)return; + *r=fluid_synth_get_reverb_roomsize(synth); + *d=fluid_synth_get_reverb_damp(synth); + *w=fluid_synth_get_reverb_width(synth); + *l=fluid_synth_get_reverb_level(synth); +} +void qmpMidiOutFluid::setReverbPara(int e,double r,double d,double w,double l) +{ + if(!synth)return; + fluid_synth_set_reverb_on(synth,e); + fluid_synth_set_reverb(synth,r,d,w,l); +} +void qmpMidiOutFluid::getChorusPara(int *fb,double *l,double *r,double *d,int *type) +{ + if(!synth)return; + *fb=fluid_synth_get_chorus_nr(synth); + *l=fluid_synth_get_chorus_level(synth); + *r=fluid_synth_get_chorus_speed_Hz(synth); + *d=fluid_synth_get_chorus_depth_ms(synth); + *type=fluid_synth_get_chorus_type(synth); +} +void qmpMidiOutFluid::setChorusPara(int e,int fb,double l,double r,double d,int type) +{ + if(!synth)return; + fluid_synth_set_chorus_on(synth,e); + fluid_synth_set_chorus(synth,fb,l,r,d,type); +} + +qmpFileRendererFluid::qmpFileRendererFluid(const char *_fn,const char *_ofn) +{ + settings=new_fluid_settings(); + fluid_settings_setstr(settings,"audio.file.name",_ofn); + fn=std::string(_fn); + finished=false; +} +qmpFileRendererFluid::~qmpFileRendererFluid() +{ + if(player||synth||settings)renderDeinit(); +} +void qmpFileRendererFluid::renderInit() +{ + synth=new_fluid_synth(settings); + player=new_fluid_player(synth); + fluid_player_add(player,fn.c_str()); +} +void qmpFileRendererFluid::renderDeinit() +{ + delete_fluid_player(player); + delete_fluid_synth(synth); + delete_fluid_settings(settings); + player=NULL;synth=NULL;settings=NULL; +} +void qmpFileRendererFluid::renderWorker() +{ + 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=true; +} +void qmpFileRendererFluid::setOptStr(const char *opt,const char *val) +{ + fluid_settings_setstr(settings,opt,val); +} +void qmpFileRendererFluid::setOptInt(const char *opt,int val) +{ + fluid_settings_setint(settings,opt,val); +} +void qmpFileRendererFluid::setOptNum(const char *opt,double val) +{ + fluid_settings_setnum(settings,opt,val); +} +void qmpFileRendererFluid::loadSFont(const char *path) +{ + if(synth)fluid_synth_sfload(synth,path,1); +} +bool qmpFileRendererFluid::isFinished() +{ + return finished; +} +void qmpFileRendererFluid::setGain(double gain) +{ + if(settings)fluid_settings_setnum(settings,"synth.gain",gain); +} +void qmpFileRendererFluid::setReverbPara(int e,double r,double d,double w,double l) +{ + if(!synth)return; + fluid_synth_set_reverb_on(synth,e); + fluid_synth_set_reverb(synth,r,d,w,l); +} +void qmpFileRendererFluid::setChorusPara(int e,int fb,double l,double r,double d,int type) +{ + if(!synth)return; + fluid_synth_set_chorus_on(synth,e); + fluid_synth_set_chorus(synth,fb,l,r,d,type); +} diff --git a/core/qmpmidioutfluid.hpp b/core/qmpmidioutfluid.hpp new file mode 100644 index 0000000..963f9df --- /dev/null +++ b/core/qmpmidioutfluid.hpp @@ -0,0 +1,80 @@ +#ifndef QMPMIDIOUTFLUID_H +#define QMPMIDIOUTFLUID_H +#include +#include +#include +#include "../include/qmpcorepublic.hpp" +#include +class IFluidSettings +{ + public: + virtual void setOptStr(const char* opt,const char* val)=0; + virtual void setOptInt(const char* opt,int val)=0; + virtual void setOptNum(const char* opt,double val)=0; + virtual void loadSFont(const char* path)=0; + virtual void setGain(double gain)=0; + virtual void setReverbPara(int e,double r,double d,double w,double l)=0; + virtual void setChorusPara(int e,int fb,double l,double r,double d,int type)=0; +}; +class qmpMidiOutFluid:public qmpMidiOutDevice,public IFluidSettings +{ + private: + fluid_settings_t* settings; + fluid_synth_t* synth; + fluid_audio_driver_t* adriver; + public: + qmpMidiOutFluid(); + ~qmpMidiOutFluid(); + void deviceInit(); + void deviceDeinit(); + void deviceDeinit(bool freshsettings); + void basicMessage(uint8_t type,uint8_t p1,uint8_t p2); + void extendedMessage(uint8_t length,const char *data); + void rpnMessage(uint8_t ch,uint16_t type,uint16_t val); + void nrpnMessage(uint8_t ch,uint16_t type,uint16_t val); + void panic(uint8_t ch); + void reset(uint8_t ch); + void onMapped(uint8_t ch,int refcnt); + void onUnmapped(uint8_t ch,int refcnt); + //FluidSynth specific stuff + void setOptStr(const char* opt,const char* val); + void setOptInt(const char* opt,int val); + void setOptNum(const char* opt,double val); + void loadSFont(const char* path); + int getSFCount(); + std::vector,std::string>> listPresets(); + + int getPolyphone(); + int getMaxPolyphone(); + void setGain(double gain); + void getChannelInfo(int ch,int *b,int *p,char *s); + 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); +}; +class qmpFileRendererFluid:public IFluidSettings +{ + private: + fluid_settings_t* settings; + fluid_synth_t* synth; + fluid_player_t* player; + bool finished; + std::string fn; + public: + qmpFileRendererFluid(const char* _fn,const char* _ofn); + ~qmpFileRendererFluid(); + void renderInit(); + void renderDeinit(); + void renderWorker(); + void setOptStr(const char* opt,const char* val); + void setOptInt(const char* opt,int val); + void setOptNum(const char* opt,double val); + void loadSFont(const char *path); + bool isFinished(); + void setGain(double gain); + void setReverbPara(int e,double r,double d,double w,double l); + void setChorusPara(int e,int fb,double l,double r,double d,int type); + +}; +#endif // QMPMIDIOUTFLUID_H diff --git a/core/qmpmidioutrtmidi.cpp b/core/qmpmidioutrtmidi.cpp new file mode 100644 index 0000000..776eb6b --- /dev/null +++ b/core/qmpmidioutrtmidi.cpp @@ -0,0 +1,115 @@ +#include +#include +#include +#include RT_MIDI_H +#include "qmpmidioutrtmidi.hpp" +qmpMidiOutRtMidi::qmpMidiOutRtMidi(unsigned _portid) +{ + portid=_portid; + outport=NULL; +} +qmpMidiOutRtMidi::~qmpMidiOutRtMidi() +{ + if(!outport)return; + if(outport->isPortOpen())outport->closePort(); + delete outport;outport=NULL; +} +void qmpMidiOutRtMidi::deviceInit() +{ + try + { + outport=new RtMidiOut(); + } + catch(RtMidiError &e) + { + printf("Cannot create RtMidi Output instance: %s\n",e.what()); + outport=NULL; + } +} +void qmpMidiOutRtMidi::deviceDeinit() +{ + if(!outport||!outport->isPortOpen())return; + outport->closePort(); +} +void qmpMidiOutRtMidi::basicMessage(uint8_t type,uint8_t p1,uint8_t p2) +{ + if(!outport||!outport->isPortOpen())return; + std::vectormsg; + msg.push_back(type); + msg.push_back(p1); + msg.push_back(p2); + outport->sendMessage(&msg); +} +void qmpMidiOutRtMidi::extendedMessage(uint8_t length,const char *data) +{ + if(!outport||!outport->isPortOpen())return; + std::vectormsg(data,data+length); + outport->sendMessage(&msg); +} +void qmpMidiOutRtMidi::rpnMessage(uint8_t ch,uint16_t type,uint16_t val) +{ + basicMessage(0xB0|ch,0x64,type&0x7F); + basicMessage(0xB0|ch,0x65,type>>7); + basicMessage(0xB0|ch,0x06,val>>7); + basicMessage(0xB0|ch,0x26,val&0x7F); +} +void qmpMidiOutRtMidi::nrpnMessage(uint8_t ch,uint16_t type,uint16_t val) +{ + basicMessage(0xB0|ch,0x62,type&0x7F); + basicMessage(0xB0|ch,0x63,type>>7); + basicMessage(0xB0|ch,0x06,val>>7); + basicMessage(0xB0|ch,0x26,val&0x7F); +} +void qmpMidiOutRtMidi::panic(uint8_t ch) +{ + //maybe all notes off is more close to panic? + basicMessage(0xE0|ch,0x0,0x40); + basicMessage(0xB0|ch,123,0); +} +void qmpMidiOutRtMidi::reset(uint8_t ch) +{ + basicMessage(0xB0|ch,120,0); + basicMessage(0xB0|ch,121,0); +} +void qmpMidiOutRtMidi::onMapped(uint8_t,int refcnt) +{ + if(!outport||outport->isPortOpen()||refcnt)return; + try + { + outport->openPort(portid); + } + catch(RtMidiError &e) + { + printf("Device initialization failure: %s\n",e.what()); + } + +} +void qmpMidiOutRtMidi::onUnmapped(uint8_t ch,int refcnt) +{ + panic(ch); + if(!refcnt)outport->closePort(); +} + +RtMidiOut* qmpRtMidiManager::dummy=NULL; +void qmpRtMidiManager::createDevices() +{ + try{dummy=new RtMidiOut();} + catch(RtMidiError &e) + { + printf("Failed to initialize the dummy device: %s\n",e.what()); + dummy=NULL; + } + for(unsigned i=0;igetPortCount();++i) + devices.push_back(std::make_pair(new qmpMidiOutRtMidi(i),dummy->getPortName(i))); +} +void qmpRtMidiManager::deleteDevices() +{ + for(size_t i=0;i> qmpRtMidiManager::getDevices() +{ + return devices; +} diff --git a/core/qmpmidioutrtmidi.hpp b/core/qmpmidioutrtmidi.hpp new file mode 100644 index 0000000..48ee0cf --- /dev/null +++ b/core/qmpmidioutrtmidi.hpp @@ -0,0 +1,36 @@ +#ifndef QMPMIDIMAPPERS_H +#define QMPMIDIMAPPERS_H +#include +#define QMP_MAIN +#include "../include/qmpcorepublic.hpp" +#include RT_MIDI_H +class qmpMidiOutRtMidi:public qmpMidiOutDevice +{ +private: + unsigned portid; + RtMidiOut* outport; +public: + qmpMidiOutRtMidi(unsigned _portid); + ~qmpMidiOutRtMidi(); + void deviceInit(); + void deviceDeinit(); + void basicMessage(uint8_t type,uint8_t p1,uint8_t p2); + void extendedMessage(uint8_t length,const char *data); + void rpnMessage(uint8_t ch,uint16_t type,uint16_t val); + void nrpnMessage(uint8_t ch,uint16_t type,uint16_t val); + void panic(uint8_t ch); + void reset(uint8_t ch); + void onMapped(uint8_t ch,int refcnt); + void onUnmapped(uint8_t ch,int refcnt); +}; +class qmpRtMidiManager +{ + private: + static RtMidiOut* dummy; + std::vector> devices; + public: + void createDevices(); + void deleteDevices(); + std::vector> getDevices(); +}; +#endif // QMPMIDIMAPPERS_H diff --git a/core/qmpmidiplay.cpp b/core/qmpmidiplay.cpp index c8169ca..cca77d0 100644 --- a/core/qmpmidiplay.cpp +++ b/core/qmpmidiplay.cpp @@ -10,99 +10,42 @@ uint64_t pf; #endif CMidiPlayer* CMidiPlayer::ref=NULL; -void CMidiPlayer::fluidPreInitialize() -{ - settings=new_fluid_settings(); -} -void CMidiPlayer::fluidInitialize() -{ - synth=new_fluid_synth(settings); - fluid_set_log_function(FLUID_DBG,NULL,NULL); - fluid_set_log_function(FLUID_INFO,NULL,NULL); - fluid_set_log_function(FLUID_WARN,NULL,NULL); - fluid_set_log_function(FLUID_ERR,fluid_default_log_function,NULL); - fluid_set_log_function(FLUID_PANIC,fluid_default_log_function,NULL); - 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->std==4) - fluid_synth_set_channel_type(synth,9,CHANNEL_TYPE_MELODIC); - else if(midiFile->std==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) +bool CMidiPlayer::processEvent(const SEvent *e) { 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]); + uint8_t ch=e->type&0x0F; switch(e->type&0xF0) { case 0x80://Note off - if(mappedoutput[e->type&0x0F]) - mapper->noteOff(mappedoutput[e->type&0x0F]-1,e->type&0x0F,e->p1); - else - fluid_synth_noteoff(synth,e->type&0x0F,e->p1); - break; + return true; case 0x90://Note on - if((mute>>(e->type&0x0F))&1)break;//muted - if(solo&&!((solo>>(e->type&0x0F))&1))break; - if(mappedoutput[e->type&0x0F]) - mapper->noteOn(mappedoutput[e->type&0x0F]-1,e->type&0x0F,e->p1,e->p2); - else - fluid_synth_noteon(synth,e->type&0x0F,e->p1,e->p2); - chstate[e->type&0x0F]=1; - break; + if((mute>>ch)&1&&e->p2)return false;//muted + if(solo&&!((solo>>ch)&1)&&e->p2)return false;//excluded by solo flags + if(e->p2)chstate[ch]=1; + return true; case 0xB0://CC - 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(e->p1==100)rpnid[ch]=e->p2; + if(e->p1==6)rpnval[ch]=e->p2; + if(~rpnid[ch]&&~rpnval[ch]) { - if(rpnid[e->type&0x0F]==0) + if(rpnid[ch]==0) { - fluid_synth_pitch_wheel_sens(synth,e->type&0x0F,rpnval[e->type&0x0F]); - pbr[e->type&0x0F]=rpnval[e->type&0x0F]; + internalFluid->rpnMessage(ch,0,rpnval[ch]<<7); + mididev[mappedoutput[ch]].dev->rpnMessage(ch,0,rpnval[ch]<<7); + pbr[ch]=rpnval[ch]; } - rpnid[e->type&0x0F]=rpnval[e->type&0x0F]=-1; + rpnid[ch]=rpnval[ch]=-1; } - chstatus[e->type&0x0F][e->p1]=e->p2; - if(mappedoutput[e->type&0x0F]) - mapper->ctrlChange(mappedoutput[e->type&0x0F]-1,e->type&0x0F,e->p1,e->p2); - else - fluid_synth_cc(synth,e->type&0x0F,e->p1,e->p2); - break; + chstatus[ch][e->p1]=e->p2; + return true; case 0xC0://PC - chstatus[e->type&0x0F][128]=e->p1; - if(mappedoutput[e->type&0x0F]) - mapper->progChange(mappedoutput[e->type&0x0F]-1,e->type&0x0F,e->p1); - else - fluid_synth_program_change(synth,e->type&0x0F,e->p1); - break; + chstatus[ch][128]=e->p1; + return true; case 0xE0://PW - pbv[e->type&0x0F]=(e->p1|(e->p2<<7))&0x3FFF;; - if(mappedoutput[e->type&0x0F]) - mapper->pitchBend(mappedoutput[e->type&0x0F]-1,e->type&0x0F,pbv[e->type&0x0F]); - else - fluid_synth_pitch_bend(synth,e->type&0x0F,pbv[e->type&0x0F]); - break; + pbv[ch]=(e->p1|(e->p2<<7))&0x3FFF; + return true; case 0xF0://Meta/SysEx if((e->type&0x0F)==0x0F) { @@ -127,15 +70,28 @@ void CMidiPlayer::processEvent(const SEvent *e) } if((e->type&0x0F)==0x00||(e->type&0x0F)==07) { - int io=0; if(sendSysEx) { - for(int i=0;i<16;++i)if(deviceusage[i])mapper->sysEx(i,e->p1,e->str.c_str()); - fluid_synth_sysex(synth,e->str.c_str(),e->p1,NULL,&io,NULL,0); + int l=e->p1;char *rmsg; + if(e->type&0x0F==0x00) + { + rmsg=(char*)malloc((++l)*sizeof(char)); + rmsg[0]=0xF0;rmsg[1]=0; + } + else + { + rmsg=(char*)malloc(l*sizeof(char)); + rmsg[0]=0; + } + strcat(rmsg,e->str.c_str()); + for(auto& i:mididev) + if(i.refcnt) + i.dev->extendedMessage(l,rmsg); } } - break; + return false; } + return false; } void CMidiPlayer::processEventStub(const SEvent *e) { @@ -203,8 +159,9 @@ SEvent* CMidiPlayer::getEvent(int id) void CMidiPlayer::prePlayInit() { playerPanic(true); - for(int i=0;i<16;++i)if(deviceusage[i]) - for(int j=0;j<16;++j)mapper->reset(i,j); + for(size_t i=0;ireset(j); } void CMidiPlayer::playEvents() { @@ -213,8 +170,12 @@ void CMidiPlayer::playEvents() while(tcpaused)std::this_thread::sleep_for(std::chrono::milliseconds(100)); using namespace std::chrono; high_resolution_clock::time_point b=high_resolution_clock::now(); - while(!tcstop&&midiReaders&&tceptrtime) - processEvent(getEvent(tceptr++)); + for(;!tcstop&&midiReaders&&tceptrtime;++tceptr) + if(processEvent(getEvent(tceptr))) + { + SEvent* e=getEvent(tceptr); + mididev[mappedoutput[e->type&0x0F]].dev->basicMessage(e->type,e->p1,e->p2); + } if(tcstop||!midiReaders||tceptr>=ecnt)break; high_resolution_clock::time_point a=high_resolution_clock::now(); auto sendtime=a-b; @@ -229,7 +190,7 @@ void CMidiPlayer::playEvents() if(tcstop||!midiReaders)break; ct=getEvent(tceptr)->time; } - while(!tcstop&&synth&&(waitvoice&&fluid_synth_get_active_voice_count(synth)>0))std::this_thread::sleep_for(std::chrono::milliseconds(2)); + while(!tcstop&&(waitvoice&&internalFluid->getPolyphone()>0))std::this_thread::sleep_for(std::chrono::milliseconds(2)); finished=1; } void CMidiPlayer::fileTimer1Pass() @@ -282,20 +243,20 @@ void CMidiPlayer::fileTimer2Pass() stamps[c++]=ecnt; } } -CMidiPlayer::CMidiPlayer(bool singleInst) + +CMidiPlayer::CMidiPlayer() { midiReaders=new CMidiFileReaderCollection(); - resumed=false;singleInstance=singleInst;midiFile=NULL; - settings=NULL;synth=NULL;adriver=NULL;waitvoice=true; + resumed=false;midiFile=NULL;internalFluid=new qmpMidiOutFluid(); + registerMidiOutDevice(internalFluid,"Internal FluidSynth"); + waitvoice=true; memset(eventHandlerCB,0,sizeof(eventHandlerCB)); memset(eventHandlerCBuserdata,0,sizeof(eventHandlerCBuserdata)); memset(eventReaderCB,0,sizeof(eventReaderCB)); memset(eventReaderCBuserdata,0,sizeof(eventReaderCBuserdata)); memset(fileReadFinishCB,0,sizeof(fileReadFinishCB)); memset(fileReadFinishCBuserdata,0,sizeof(fileReadFinishCBuserdata)); - memset(mappedoutput,0,sizeof(mappedoutput)); - memset(deviceusage,0,sizeof(deviceusage)); - mapper=new qmpMidiMapperRtMidi(); + memset(mappedoutput,0xFF,sizeof(mappedoutput)); memset(chstatus,0,sizeof(chstatus)); for(int i=0;i<16;++i) chstatus[i][7]=100,chstatus[i][11]=127, @@ -309,29 +270,21 @@ CMidiPlayer::CMidiPlayer(bool singleInst) } CMidiPlayer::~CMidiPlayer() { - if(singleInstance||settings||synth||adriver)fluidDeinitialize(); - if(midiFile)delete midiFile;delete midiReaders;delete mapper; + if(internalFluid) + { + internalFluid->deviceDeinit(); + delete internalFluid; + internalFluid=NULL; + } + if(midiFile)delete midiFile;delete midiReaders; } void CMidiPlayer::playerPanic(bool reset) { - for(int i=0;i<16;++i) + for(auto& i:mididev) + if(i.refcnt) { - if(synth){ - if(reset){ - fluid_synth_cc(synth,i,0,0); - fluid_synth_cc(synth,i,7,100); - fluid_synth_cc(synth,i,10,64); - fluid_synth_cc(synth,i,11,127); - fluid_synth_cc(synth,i,32,0); - fluid_synth_pitch_wheel_sens(synth,i,2); - } - fluid_synth_cc(synth,i,64,0); - fluid_synth_pitch_bend(synth,i,8192); - //all sounds off causes the minus polyphone bug... - fluid_synth_all_notes_off(synth,i); - } - if(deviceusage[i])for(int j=0;j<16;++j) - reset?mapper->reset(i,j):mapper->panic(i,j); + for(uint8_t j=0;j<16;++j) + reset?i.dev->reset(j):i.dev->panic(j); } } bool CMidiPlayer::playerLoadFile(const char* fn) @@ -372,13 +325,11 @@ void CMidiPlayer::playerInit() chstatus[i][10]=chstatus[i][71]=chstatus[i][72]= chstatus[i][73]=chstatus[i][74]=chstatus[i][75]= chstatus[i][76]=chstatus[i][77]=chstatus[i][78]=64; - if(!singleInstance)fluidPreInitialize(); } void CMidiPlayer::playerDeinit() { tceptr=0;tcstop=1;tcpaused=0; delete midiFile;midiFile=NULL; - if(!singleInstance)fluidDeinitialize(); } void CMidiPlayer::playerThread() { @@ -386,35 +337,6 @@ 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;} @@ -424,10 +346,11 @@ void CMidiPlayer::setTCeptr(uint32_t ep,uint32_t st) if(ep==ecnt)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]); + for(int j=0;j<120;++j) + internalFluid->basicMessage(0xB0|i,j,ccstamps[st][i][j]); + internalFluid->basicMessage(0xC0|i,ccstamps[st][i][128],0); //fluid_synth_pitch_bend(synth,i,ccstamps[st][i][130]); - fluid_synth_pitch_wheel_sens(synth,i,ccstamps[st][i][134]); + internalFluid->rpnMessage(i,0,ccstamps[st][i][134]<<7); pbr[i]=ccstamps[st][i][134]; dpt=ccstamps[st][0][131];ctempo=dpt*divs/1000; ctsn=ccstamps[st][0][132]>>24;ctsd=1<<((ccstamps[st][0][132]>>16)&0xFF); @@ -452,14 +375,9 @@ 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,"")); if(mappedoutput[ch]) { *b=((int)chstatus[ch][0]<<7)|chstatus[ch][32]; @@ -468,29 +386,27 @@ void CMidiPlayer::getChannelPreset(int ch,int *b,int *p,char *name) } else { - fluid_synth_channel_info_t info; - fluid_synth_get_channel_info(synth,ch,&info); - *b=info.bank;*p=info.program; - strcpy(name,info.name); + internalFluid->getChannelInfo(ch,b,p,name); } } void CMidiPlayer::setChannelPreset(int ch,int b,int p) { - if(!synth)return; chstatus[ch][128]=p; if(mappedoutput[ch]) { //external device mode? chstatus[ch][0]=b>>7;chstatus[ch][32]=b&0x7F; - mapper->ctrlChange(mappedoutput[ch]-1,ch,0,b>>7); - mapper->ctrlChange(mappedoutput[ch]-1,ch,32,b&0x7F); - mapper->progChange(mappedoutput[ch]-1,ch,p); + qmpMidiOutDevice* d=mididev[mappedoutput[ch]].dev; + d->basicMessage(0xB0|ch,0x00,b>>7); + d->basicMessage(0xB0|ch,0x20,b&0x7F); + d->basicMessage(0xC0|ch,p,0); } else { - chstatus[ch][0]=b;//!!FIXME: This is not correct... - fluid_synth_bank_select(synth,ch,b); - fluid_synth_program_change(synth,ch,p); + chstatus[ch][0]=b;//Assuming GS. !!FIXME: This is not correct... + qmpMidiOutDevice* d=mididev[mappedoutput[ch]].dev; + d->basicMessage(0xB0|ch,0x00,b); + d->basicMessage(0xC0|ch,p,0); } } void CMidiPlayer::dumpFile() @@ -516,85 +432,60 @@ bool CMidiPlayer::getChannelMask(int ch) {return((mute>>ch)&1)||(solo&&!((solo>>ch)&1));} int CMidiPlayer::getCC(int ch,int id) { - int ret=0; - if(mappedoutput[ch]) - ret=chstatus[ch][id]; - else - if(synth)fluid_synth_get_cc(synth,ch,id,&ret); - return ret; + return chstatus[ch][id]; } void CMidiPlayer::setCC(int ch,int id,int val) { - if(!synth)return; chstatus[ch][id]=val; - mappedoutput[ch]?mapper->ctrlChange(mappedoutput[ch]-1,ch,id,val): - (void)fluid_synth_cc(synth,ch,id,val); + mididev[mappedoutput[ch]].dev->basicMessage(0xB0|ch,id,val); } -void CMidiPlayer::getReverbPara(double *r,double *d,double *w,double *l) -{ - if(!synth)return; - *r=fluid_synth_get_reverb_roomsize(synth); - *d=fluid_synth_get_reverb_damp(synth); - *w=fluid_synth_get_reverb_width(synth); - *l=fluid_synth_get_reverb_level(synth); -} -void CMidiPlayer::setReverbPara(int e,double r,double d,double w,double l) + +qmpMidiOutFluid* CMidiPlayer::fluid(){return internalFluid;} + +void CMidiPlayer::registerMidiOutDevice(qmpMidiOutDevice* dev,std::string name) { - if(!synth)return; - fluid_synth_set_reverb_on(synth,e); - fluid_synth_set_reverb(synth,r,d,w,l); + SMidiDev d; + d.dev=dev;d.name=name; + d.refcnt=0; + mididev.push_back(d); } -void CMidiPlayer::getChorusPara(int *fb,double *l,double *r,double *d,int *type) +void CMidiPlayer::unregisterMidiOutDevice(std::string name) { - if(!synth)return; - *fb=fluid_synth_get_chorus_nr(synth); - *l=fluid_synth_get_chorus_level(synth); - *r=fluid_synth_get_chorus_speed_Hz(synth); - *d=fluid_synth_get_chorus_depth_ms(synth); - *type=fluid_synth_get_chorus_type(synth); + for(auto i=mididev.begin();i!=mididev.end();++i) + if(i->name==name) + { + i->dev->deviceDeinit(); + mididev.erase(i); + break; + } } -void CMidiPlayer::setChorusPara(int e,int fb,double l,double r,double d,int type) +std::vector CMidiPlayer::getMidiOutDevices() { - if(!synth)return; - fluid_synth_set_chorus_on(synth,e); - fluid_synth_set_chorus(synth,fb,l,r,d,type); + std::vector ret; + for(auto &i:mididev) + ret.push_back(i.name); + return ret; } -fluid_settings_t* CMidiPlayer::getFluidSettings(){return settings;} -void CMidiPlayer::pushSoundFont(const char *sf) -{fluid_synth_sfload(synth,sf,1);} -int CMidiPlayer::getSFCount() -{return synth?fluid_synth_sfcount(synth):0;} -fluid_sfont_t* CMidiPlayer::getSFPtr(int sfid) -{return synth&&sfid0) - { - if(!deviceusage[devid-1])deviceiid[devid]=newoutput=mapper->deviceInit(devid-1); - ++deviceusage[deviceiid[devid]];mapper->progChange(deviceiid[devid],ch,chstatus[ch][128]); - for(int i=0;i<128;++i)if(i!=100&&i!=101)mapper->ctrlChange(deviceiid[devid],ch,i,chstatus[ch][i]); - } - else + SMidiDev& dnew=mididev[outid]; + dnew.dev->onMapped(ch,++dnew.refcnt); + for(int i=0;i<128;++i) + if(i!=6&&i!=38&&i!=100&&i!=101)//avoid sending RPN/NRPN + dnew.dev->basicMessage(0xB0|ch,i,chstatus[ch][i]); + dnew.dev->basicMessage(0xC0|ch,chstatus[ch][128],0); + mappedoutput[ch]=outid; + if(~origoutput) { - fluid_synth_bank_select(synth,ch,chstatus[ch][0]); - fluid_synth_program_change(synth,ch,chstatus[ch][128]); - for(int i=0;i<128;++i)if(i!=100&&i!=101&&i!=0&&i!=32) - fluid_synth_cc(synth,ch,i,chstatus[ch][i]); + SMidiDev& dold=mididev[origoutput]; + dold.dev->onUnmapped(ch,--dold.refcnt); + } - mappedoutput[ch]=devid?deviceiid[devid]+1:0; - if(origoutput>0) - { - --deviceusage[origoutput-1];mapper->panic(origoutput-1,ch); - if(!deviceusage[origoutput-1])mapper->deviceDeinit(origoutput-1); - }else if(synth)fluid_synth_all_notes_off(synth,ch); } uint8_t* CMidiPlayer::getChstates(){return chstate;} int CMidiPlayer::setEventHandlerCB(IMidiCallBack *cb,void *userdata) diff --git a/core/qmpmidiplay.hpp b/core/qmpmidiplay.hpp index 8a146a7..c935084 100644 --- a/core/qmpmidiplay.hpp +++ b/core/qmpmidiplay.hpp @@ -5,10 +5,10 @@ #include #include #include -#include #define QMP_MAIN #include "../include/qmpcorepublic.hpp" -#include "qmpmidimappers.hpp" +#include "qmpmidioutrtmidi.hpp" +#include "qmpmidioutfluid.hpp" class CMidiPlayer; class CSMFReader:public IMidiFileReader { @@ -60,20 +60,23 @@ class CMidiPlayer int32_t rpnid[16],rpnval[16]; 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; + bool sendSysEx,waitvoice; + uint8_t chstate[16],chstatus[16][130];//0..127: cc 128: pc + qmpMidiOutFluid* internalFluid; 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,ct; uint32_t finished,resumed; uint32_t pbr[16],pbv[16]; - qmpMidiMapperRtMidi *mapper; - int mappedoutput[16],deviceusage[16],deviceiid[128]; - uint8_t chstate[16],chstatus[16][130];//0..127: cc 128: pc + struct SMidiDev + { + std::string name; + qmpMidiOutDevice* dev; + int refcnt; + }; + std::vector mididev; + int mappedoutput[16]; IMidiCallBack* eventHandlerCB[16]; IMidiCallBack* eventReaderCB[16]; IMidiCallBack* fileReadFinishCB[16]; @@ -85,29 +88,21 @@ class CMidiPlayer SEvent *getEvent(int id); void dumpFile(); void setBit(uint16_t &n,uint16_t bn,uint16_t b); - void processEvent(const SEvent *e); + bool processEvent(const SEvent *e); void processEventStub(const SEvent *e); void prePlayInit(); void playEvents(); void fileTimer1Pass(); void fileTimer2Pass(); public: - CMidiPlayer(bool singleInst=false); + CMidiPlayer(); ~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(); @@ -132,11 +127,7 @@ class CMidiPlayer 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); @@ -145,19 +136,14 @@ class CMidiPlayer bool getChannelMask(int ch); 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); + qmpMidiOutFluid* fluid(); - qmpMidiMapperRtMidi* getMidiMapper(); + void registerMidiOutDevice(qmpMidiOutDevice* dev,std::string name); + void unregisterMidiOutDevice(std::string name); + std::vector getMidiOutDevices(); int getChannelOutput(int ch); - void setChannelOutput(int ch,int devid); + void setChannelOutput(int ch,int outid); uint8_t* getChstates(); int setEventHandlerCB(IMidiCallBack *cb,void *userdata); void unsetEventHandlerCB(int id); diff --git a/debian/changelog b/debian/changelog index 1eb0adc..c9eab4e 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,10 @@ +qmidiplayer (0.8.6-1) UNRELEASED; urgency=low + + * New upstream release. + + -- chrisoft Wed, 21 Jun 2017 10:40:08 +0800 + + qmidiplayer (0.8.5-1) UNRELEASED; urgency=low * New upstream release. diff --git a/debian/qmidiplayer-plugin-midifmt.install b/debian/qmidiplayer-plugin-midifmt.install new file mode 100644 index 0000000..dca2499 --- /dev/null +++ b/debian/qmidiplayer-plugin-midifmt.install @@ -0,0 +1 @@ +usr/lib/qmidiplayer/libmidifmt-plugin.* diff --git a/doc/APIdoc.md b/doc/APIdoc.md index 5569e49..c050869 100644 --- a/doc/APIdoc.md +++ b/doc/APIdoc.md @@ -92,18 +92,122 @@ When the user requests to open a file, the core tries to load the file with regi accepts the first valid result. Therefore you can implement your own file reader, which may even add eXtended Module support to QMidiPlayer! -# 5. Reference +## 4.4 MIDI Output Device +This is not a functionality either. By implementing the interface qmpMidiOutDevice and registering it, you can +add custom MIDI Devices in the built-in MIDI mapper. + +# 5. Generic Considerations + +1. If you implemented a API that returns a pointer to something, you can forget about the pointer after returning +it. The core will free its memory after it is no longer used. You shouldn't extend the class pointed to because +if you do so, the core will not be able to destruct it correctly. Examples include IMidiFileReader::readFile which +returns a pointer to a CMidiFile class. +2. However if you passed a pointer to the core through a function in qmpPluginAPI, you cannot forget about the pointer +later. As these pointers are mostly polymorphic, the core cannot handle their destruction. You have to delete them +yourself in the deinit() function of your plugin. + +# 6. Reference Well, everything above is just nonsense compared to this one. The full reference of the API is here. -struct SEvent: -members: - -- iid (uint32_t): internal id. Usually set to the size of the event pool when current event is read. -- time (uint32_t): event time in tick. -- p1 (uint32_t): parameter 1 for the midi device. -- p2 (uint32_t): parameter 2 for the midi device. -- type (uint8_t): type of the event together with the channel this event goes to. -- str (std::string): Contains the raw data for string-like events. -- default constructor: sets everything to zero or empty. -- constructor with parameters -- friend bool operator <(const SEvent& a,const SEvent& b) +## Structures & Classes + +### struct SEvent +Describes an MIDI event. + +- `uint32_t iid` +internal id. Usually set to the size of the event pool when current event is read. +- `uint32_t time` +timestamp of the event in MIDI tick. +- `uint32_t p1` +parameter 1 for the midi event. +- `uint32_t p2` +parameter 2 for the midi event. +- `uint8_t type` +type of the event together with the channel this event goes to. +- `std::string str` +Contains the raw data for string-like events. +- default constructor: `SEvent()` +sets everything to zero or empty. +- constructor with parameters: `SEvent(uint32_t _iid,uint32_t _t,char _tp,uint32_t _p1,uint32_t _p2,const char* s=NULL)` +fills the event with the parameters given. +- `friend bool operator <(const SEvent& a,const SEvent& b)` +compares events by their timestamps. Ties are broken by comparing precedence in file. + +### struct SEventCallBackData +A stripped down version of SEvent that is used to pass event data in APIs. + +- `uint32_t time` +- `uint32_t type` +- `uint32_t p1` +- `uint32_t p2` + +### class CMidiTrack +Describes a single MIDI track. A MIDI track consists of multiple MIDI events. + +- `std::vector eventList` +Vector of SEvent's. +- `void appendEvent(SEvent e)` +Append an event to the end of the event list. +- `SEvent& operator[](size_t sub)` +Get the reference to the contained event with the given index. + +### class CMidiFile +Describes a MIDI file. A MIDI file consists of multiple MIDI tracks. + +- `bool valid` +Is the MIDI file a valid one? +- `char* title` +Title of the MIDI file. +- `char* copyright` +Copyright information of the MIDI file. +- `std::vector tracks` +Tracks in the MIDI file. +- `uint32_t std` +File standard of the MIDI file. + - 0: unknown + - 1: GM Level 1 + - 2: GM Level 2 + - 3: GS + - 4: XG +- `uint32_t div` +Ticks per quarter note. SMTPE format is not supported by QMidiPlayer. +- `~CMidiFile()` +Frees memory occupied by the title and copyright string. + +### class IMidiCallBack +Generic callback function that can be used for hooking the core. + +- `IMidiCallBack()` +Default empty constructor. +- `virtual void callBack(void* callerdata,void* userdata)=0;` +Abstract function of the callback. callerdata is filled by the caller and +userdata is set to whatever you asked for when registering the callback. +- `virtual ~IMidiCallBack()` +Virtual empty destructor. + +### class IMidiFileReader +MIDI file reader interface. Use this to implement your file importer. + +- `IMidiFileReader()` +Default empty constructor. +- `virtual ~IMidiFileReader()` +Virtual empty destructor. +- `virtual CMidiFile* readFile(const char* fn)=0` +Abstract function to be implemented by the plugin. +This function should read file from the given path (fn) and return a +pointer to the resulting CMidiFile structure. You shoudn't handle the +destruction of the resulting structure as the core will handle it. +Read 5.1 for more details. +After reading each event, you should call qmpPluginAPI::callEventReaderCB +to invoke event reader callbacks and process their requests. +- `virtual void discardCurrentEvent()=0` +Only called by event reader callbacks. +Expected behavior: Sets the discard flag for the last event you have read. +If an event has its discard flag set, it shouldn't be pushed into its track. +discardCurrentEvent may be called multiple times by different event reader +callbacks. In such case, there's still only one event discarded. +- `virtual void commitEventChange(SEventCallBackData d)=0` +Only called by event reader callbacks. +Expected behavior: modifies the last event you have read to the provided data. +commitEventChange may be called multiple times by different event reader callbacks. +In such case, only the last modification to the current event counts. diff --git a/doc/optionsdialog.html b/doc/optionsdialog.html index c512447..d90cf45 100644 --- a/doc/optionsdialog.html +++ b/doc/optionsdialog.html @@ -70,9 +70,10 @@ same folder to the playlist when started with a file.
  • Save dialog status*: Save dialog positions and restore on start up.
  • Save parameters in effects window
  • -
  • Persistent fluidsynth instance*: If checked, the synthesizer engine will keep running - through the whole session. Otherwise it is restarted every time when a new song is played.
  • +
  • Persistent fluidsynth instance: Deprecated. Removing before 0.9.
  • Icon Theme: Leave it unchanged unless you can't see the icons on those buttons.
  • +
  • Customize toolbar: customize the functionalities shown in the main window.
  • +
  • Customize actions: customize the functionalities shown in the file action menu.

  • Plugin manager. View details of plugins, enable or disable them here.
    diff --git a/include/qmpcorepublic.hpp b/include/qmpcorepublic.hpp index ba38f4e..3607976 100644 --- a/include/qmpcorepublic.hpp +++ b/include/qmpcorepublic.hpp @@ -8,7 +8,7 @@ #else #define EXPORTSYM __attribute__ ((visibility ("default"))) #endif -#define QMP_PLUGIN_API_REV "1+indev4" +#define QMP_PLUGIN_API_REV "1+indev5" //MIDI Event structure struct SEvent { @@ -26,9 +26,8 @@ struct SEvent }; //This struct is used by event reader callbacks and event handler callbacks //as caller data struct -class SEventCallBackData +struct SEventCallBackData { -public: 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;} }; @@ -71,17 +70,7 @@ class IMidiFileReader virtual void discardCurrentEvent()=0; virtual void commitEventChange(SEventCallBackData d)=0; }; -//Main plugin interface. -class qmpPluginIntf -{ - public: - qmpPluginIntf(){} - virtual ~qmpPluginIntf(){} - virtual void init(){} - virtual void deinit(){} - virtual const char* pluginGetName(){return "";} - virtual const char* pluginGetVersion(){return "";} -}; +//Functionality interface. class qmpFuncBaseIntf { public: @@ -90,7 +79,7 @@ class qmpFuncBaseIntf virtual void close()=0; virtual ~qmpFuncBaseIntf(){} }; -//Visualization plugin pinterface. If your plugin implements a visualization, +//Visualization plugin interface. If your plugin implements a visualization, //you should implement this pinterface. class qmpVisualizationIntf:public qmpFuncBaseIntf { @@ -104,6 +93,34 @@ class qmpVisualizationIntf:public qmpFuncBaseIntf virtual void reset()=0; virtual ~qmpVisualizationIntf(){} }; +//Midi mapper plugin interface. +class qmpMidiOutDevice +{ + public: + qmpMidiOutDevice(){} + virtual void deviceInit()=0; + virtual void deviceDeinit()=0; + virtual void basicMessage(uint8_t type,uint8_t p1,uint8_t p2)=0; + virtual void extendedMessage(uint8_t length,const char* data)=0; + virtual void rpnMessage(uint8_t ch,uint16_t type,uint16_t val)=0; + virtual void nrpnMessage(uint8_t ch,uint16_t type,uint16_t val)=0; + virtual void panic(uint8_t ch)=0; + virtual void reset(uint8_t ch)=0; + virtual void onMapped(uint8_t ch,int refcnt)=0; + virtual void onUnmapped(uint8_t ch,int refcnt)=0; + virtual ~qmpMidiOutDevice(){} +}; +//Main plugin interface. +class qmpPluginIntf +{ + public: + qmpPluginIntf(){} + virtual ~qmpPluginIntf(){} + virtual void init(){} + virtual void deinit(){} + virtual const char* pluginGetName(){return "";} + virtual const char* pluginGetVersion(){return "";} +}; #ifdef QMP_MAIN extern "C"{ #endif @@ -151,6 +168,8 @@ class qmpPluginAPI virtual void unregisterFunctionality(std::string name); virtual void registerVisualizationIntf(qmpVisualizationIntf* intf,std::string name,std::string desc,const char* icon,int iconlen); virtual void unregisterVisualizationIntf(std::string name); + virtual void registerMidiOutDevice(qmpMidiOutDevice* dev,std::string name); + virtual void unregisterMidiOutDevice(std::string name); virtual int registerEventReaderIntf(IMidiCallBack* cb,void* userdata); virtual void unregisterEventReaderIntf(int intfhandle); virtual int registerEventHandlerIntf(IMidiCallBack* cb,void* userdata); diff --git a/qmidiplayer-desktop/qmidiplayer-desktop.pro b/qmidiplayer-desktop/qmidiplayer-desktop.pro index 427a2ff..74317c2 100644 --- a/qmidiplayer-desktop/qmidiplayer-desktop.pro +++ b/qmidiplayer-desktop/qmidiplayer-desktop.pro @@ -26,9 +26,10 @@ SOURCES += main.cpp\ qmpsettingswindow.cpp \ qmphelpwindow.cpp \ qdialskulpturestyle.cpp \ - ../core/qmpmidimapperrtmidi.cpp \ qmpplugin.cpp \ - qmpcustomizewindow.cpp + qmpcustomizewindow.cpp \ + ../core/qmpmidioutrtmidi.cpp \ + ../core/qmpmidioutfluid.cpp HEADERS += qmpmainwindow.hpp \ ../core/qmpmidiplay.hpp \ @@ -41,10 +42,11 @@ HEADERS += qmpmainwindow.hpp \ qmpsettingswindow.hpp \ qmphelpwindow.hpp \ qdialskulpturestyle.hpp \ - ../core/qmpmidimappers.hpp \ ../include/qmpcorepublic.hpp \ qmpplugin.hpp \ - qmpcustomizewindow.hpp + qmpcustomizewindow.hpp \ + ../core/qmpmidioutrtmidi.hpp \ + ../core/qmpmidioutfluid.hpp FORMS += qmpmainwindow.ui \ qmpplistwindow.ui \ diff --git a/qmidiplayer-desktop/qmpchannelswindow.cpp b/qmidiplayer-desktop/qmpchannelswindow.cpp index 9b6df60..0626697 100644 --- a/qmidiplayer-desktop/qmpchannelswindow.cpp +++ b/qmidiplayer-desktop/qmpchannelswindow.cpp @@ -15,18 +15,18 @@ qmpChannelsWindow::qmpChannelsWindow(QWidget *parent) : setMaximumWidth(w);setMaximumHeight(h);setMinimumWidth(w);setMinimumHeight(h); pselectw=new qmpPresetSelector(this); ceditw=new qmpChannelEditor(this); - mapper=qmpMainWindow::getInstance()->getPlayer()->getMidiMapper(); cha=new QIcon(":/img/ledon.svg");chi=new QIcon(":/img/ledoff.svg"); cb=new qmpCWNoteOnCB();fused=callbacksc=cbcnt=0; qmpMainWindow::getInstance()->getPlayer()->setEventHandlerCB(cb,NULL); connect(cb,SIGNAL(onNoteOn()),this,SLOT(updateChannelActivity())); - int devc=mapper->enumDevices(); + std::vector devs=qmpMainWindow::getInstance()->getPlayer()->getMidiOutDevices(); + size_t devc=devs.size(); //We setup default output here... //Pretty strange... - for(int i=0;iaddItem(mapper->deviceName(i).c_str()); - if(!QString(mapper->deviceName(i).c_str()).compare(qmpSettingsWindow::getSettingsIntf()-> + qmpSettingsWindow::getDefaultOutWidget()->addItem(devs[i].c_str()); + if(!QString(devs[i].c_str()).compare(qmpSettingsWindow::getSettingsIntf()-> value("Midi/DefaultOutput","Internal FluidSynth").toString())) qmpSettingsWindow::getDefaultOutWidget()->setCurrentIndex(i+1); } @@ -44,16 +44,16 @@ qmpChannelsWindow::qmpChannelsWindow(QWidget *parent) : connect(ui->twChannels->cellWidget(i,2),SIGNAL(stateChanged(int)),this,SLOT(channelMSChanged())); ui->twChannels->setCellWidget(i,3,new QDCComboBox()); QDCComboBox *cb=(QDCComboBox*)ui->twChannels->cellWidget(i,3); - cb->addItem("Internal FluidSynth");cb->setID(i); - for(int j=0;jsetID(i); + for(size_t j=0;jaddItem(mapper->deviceName(j).c_str()); + cb->addItem(devs[j].c_str()); if(!qmpSettingsWindow::getSettingsIntf()-> value("Midi/DefaultOutput","Internal FluidSynth").toString().compare( - QString(mapper->deviceName(j).c_str()))) + QString(devs[j].c_str()))) { - cb->setCurrentIndex(j+1); - changeMidiMapping(i,j+1); + cb->setCurrentIndex(j); + changeMidiMapping(i,j); } } if(qmpSettingsWindow::getSettingsIntf()->value("Midi/DisableMapping",0).toInt()) diff --git a/qmidiplayer-desktop/qmpchannelswindow.hpp b/qmidiplayer-desktop/qmpchannelswindow.hpp index 9db3689..3d0444b 100644 --- a/qmidiplayer-desktop/qmpchannelswindow.hpp +++ b/qmidiplayer-desktop/qmpchannelswindow.hpp @@ -11,7 +11,7 @@ #include "qmppresetselect.hpp" #include "qmpchanneleditor.hpp" #include "../core/qmpmidiplay.hpp" -#include "../core/qmpmidimappers.hpp" +#include "../core/qmpmidioutrtmidi.hpp" namespace Ui { class qmpChannelsWindow; @@ -105,7 +105,6 @@ class qmpChannelsWindow:public QDialog Ui::qmpChannelsWindow *ui; qmpPresetSelector *pselectw; qmpChannelEditor *ceditw; - qmpMidiMapperRtMidi *mapper; QIcon *cha,*chi; qmpCWNoteOnCB *cb; qmpChannelFunc *chnlf; diff --git a/qmidiplayer-desktop/qmpefxwindow.cpp b/qmidiplayer-desktop/qmpefxwindow.cpp index 6546547..e9f19bd 100644 --- a/qmidiplayer-desktop/qmpefxwindow.cpp +++ b/qmidiplayer-desktop/qmpefxwindow.cpp @@ -95,7 +95,7 @@ void qmpEfxWindow::showEvent(QShowEvent *event) event->accept(); } -void qmpEfxWindow::sendEfxChange() +void qmpEfxWindow::sendEfxChange(void *_fs) { if(!qmpMainWindow::getInstance()||!initialized)return; rr=ui->sbRoom->value()/100.;rd=ui->sbDamp->value()/100.; @@ -103,9 +103,10 @@ void qmpEfxWindow::sendEfxChange() ct=ui->rbSine->isChecked()?FLUID_CHORUS_MOD_SINE:FLUID_CHORUS_MOD_TRIANGLE; cfb=ui->sbFeedBack->value();cl=ui->sbLevelC->value()/100.; cr=ui->sbRate->value();cd=ui->sbDepth->value(); - CMidiPlayer* player=qmpMainWindow::getInstance()->getPlayer(); - player->setReverbPara(ui->cbEnabledR->isChecked()?1:0,rr,rd,rw,rl); - player->setChorusPara(ui->cbEnabledC->isChecked()?1:0,cfb,cl,cr,cd,ct); + IFluidSettings* fs=(IFluidSettings*)_fs; + if(!_fs)fs=qmpMainWindow::getInstance()->getPlayer()->fluid(); + fs->setReverbPara(ui->cbEnabledR->isChecked()?1:0,rr,rd,rw,rl); + fs->setChorusPara(ui->cbEnabledC->isChecked()?1:0,cfb,cl,cr,cd,ct); QSettings *settings=qmpSettingsWindow::getSettingsIntf(); settings->setValue("Effects/ChorusEnabled",ui->cbEnabledC->isChecked()?1:0); diff --git a/qmidiplayer-desktop/qmpefxwindow.hpp b/qmidiplayer-desktop/qmpefxwindow.hpp index b016254..3bc8e40 100644 --- a/qmidiplayer-desktop/qmpefxwindow.hpp +++ b/qmidiplayer-desktop/qmpefxwindow.hpp @@ -34,7 +34,7 @@ class qmpEfxWindow : public QDialog ~qmpEfxWindow(); void closeEvent(QCloseEvent *event); void showEvent(QShowEvent *event); - void sendEfxChange(); + void sendEfxChange(void *_fs=NULL); private slots: void on_dRoom_valueChanged(); diff --git a/qmidiplayer-desktop/qmpmainwindow.cpp b/qmidiplayer-desktop/qmpmainwindow.cpp index d2c2d0d..1c4d7d3 100644 --- a/qmidiplayer-desktop/qmpmainwindow.cpp +++ b/qmidiplayer-desktop/qmpmainwindow.cpp @@ -23,10 +23,10 @@ char* wcsto8bit(const wchar_t* s) WideCharToMultiByte(CP_OEMCP,WC_NO_BEST_FIT_CHARS,s,-1,c,size,0,0); return c; } -#define LOAD_SOUNDFONT \ +#define LOAD_SOUNDFONT(a) \ {\ char* c=wcsto8bit(settingsw->getSFWidget()->item(i,1)->text().toStdWString().c_str());\ - player->pushSoundFont(c);\ + a->loadSFont(c);\ free(c);\ } #define LOAD_FILE \ @@ -37,8 +37,8 @@ char* wcsto8bit(const wchar_t* s) free(c);\ } #else -#define LOAD_SOUNDFONT \ - player->pushSoundFont(settingsw->getSFWidget()->item(i,1)->text().toStdString().c_str()) +#define LOAD_SOUNDFONT(a) \ + a->loadSFont(settingsw->getSFWidget()->item(i,1)->text().toStdString().c_str()) #define LOAD_FILE \ {\ for(auto i=mfunc.begin();i!=mfunc.end();++i)if(i->second.isVisualization())((qmpVisualizationIntf*)(i->second.i()))->reset();\ @@ -64,7 +64,7 @@ qmpMainWindow::qmpMainWindow(QWidget *parent) : //setButtonHeight(ui->pbEfx,36);setButtonHeight(ui->pbVisualization,36); playing=false;stopped=true;dragging=false;fin=false; settingsw=new qmpSettingsWindow(this);pmgr=new qmpPluginManager(); - plistw=new qmpPlistWindow(this);player=NULL;timer=NULL; + plistw=new qmpPlistWindow(this);player=NULL;timer=NULL;fluidrenderer=NULL; singleFS=qmpSettingsWindow::getSettingsIntf()->value("Behavior/SingleInstance",0).toInt(); } @@ -77,6 +77,9 @@ qmpMainWindow::~qmpMainWindow() delete a[i]; } pmgr->deinitPlugins(); + std::vector> rtdev=rtmididev->getDevices(); + for(auto &i:rtdev)player->unregisterMidiOutDevice(i.second); + rtmididev->deleteDevices(); delete pmgr;if(player)delete player; if(timer)delete timer; delete helpw;helpw=NULL; @@ -93,7 +96,11 @@ qmpMainWindow::~qmpMainWindow() void qmpMainWindow::init() { - player=new CMidiPlayer(singleFS); + player=new CMidiPlayer(); + rtmididev=new qmpRtMidiManager(); + rtmididev->createDevices(); + std::vector> rtdev=rtmididev->getDevices(); + for(auto &i:rtdev)player->registerMidiOutDevice(i.first,i.second); chnlw=new qmpChannelsWindow(this); efxw=new qmpEfxWindow(this); infow=new qmpInfoWindow(this); @@ -106,10 +113,10 @@ void qmpMainWindow::init() registerFunctionality(panicf,"Panic",tr("Panic").toStdString(),getThemedIconc(":/img/panic.svg"),0,false); registerFunctionality(reloadsynf,"ReloadSynth",tr("Restart fluidsynth").toStdString(),getThemedIconc(":/img/repeat-base.svg"),0,false); pmgr->scanPlugins();settingsw->updatePluginList(pmgr);pmgr->initPlugins(); - if(singleFS){player->fluidPreInitialize();playerSetup();player->fluidInitialize(); - for(int i=settingsw->getSFWidget()->rowCount()-1;i>=0;--i){if(!((QCheckBox*)settingsw->getSFWidget()->cellWidget(i,0))->isChecked())continue; - LOAD_SOUNDFONT; - }} + playerSetup(player->fluid());player->fluid()->deviceInit(); + for(int i=settingsw->getSFWidget()->rowCount()-1;i>=0;--i){if(!((QCheckBox*)settingsw->getSFWidget()->cellWidget(i,0))->isChecked())continue; + LOAD_SOUNDFONT(player->fluid()); + } if(qmpSettingsWindow::getSettingsIntf()->value("Behavior/DialogStatus",0).toInt()) { QRect g=qmpSettingsWindow::getSettingsIntf()->value("DialogStatus/MainW",QRect(-999,-999,999,999)).toRect(); @@ -242,7 +249,7 @@ void qmpMainWindow::updateWidgets() chnlw->resetAcitivity(); player->playerDeinit();playerTh->join(); delete playerTh;playerTh=NULL; - if(singleFS)player->playerPanic(true); + player->playerPanic(true); chnlw->on_pbUnmute_clicked();chnlw->on_pbUnsolo_clicked(); ui->pbPlayPause->setIcon(QIcon(getThemedIcon(":/img/play.svg"))); ui->hsTimer->setValue(0); @@ -254,21 +261,13 @@ void qmpMainWindow::updateWidgets() } if(renderTh) { - if(player->isFinished()) + if(fluidrenderer->isFinished()) { renderTh->join();timer->stop(); ui->centralWidget->setEnabled(true); delete renderTh;renderTh=NULL; - player->rendererDeinit(); - if(singleFS) - { - player->fluidPreInitialize(); - playerSetup(); - player->fluidInitialize(); - for(int i=settingsw->getSFWidget()->rowCount()-1;i>=0;--i){if(!((QCheckBox*)settingsw->getSFWidget()->cellWidget(i,0))->isChecked())continue; - LOAD_SOUNDFONT; - } - } + fluidrenderer->renderDeinit(); + delete fluidrenderer;fluidrenderer=NULL; } } while(!player->isFinished()&&player->getTCeptr()>player->getStamp(ui->hsTimer->value()) @@ -281,8 +280,8 @@ void qmpMainWindow::updateWidgets() char ts[100]; sprintf(ts,"%02d:%02d",(int)(elapsed.count()+offset)/60,(int)(elapsed.count()+offset)%60); ui->lbCurTime->setText(ts); - ui->lbCurPoly->setText(QString("%1").arg(player->getPolyphone(),5,10,QChar('0'))); - ui->lbMaxPoly->setText(QString("%1").arg(player->getMaxPolyphone(),5,10,QChar('0'))); + ui->lbCurPoly->setText(QString("%1").arg(player->fluid()->getPolyphone(),5,10,QChar('0'))); + ui->lbMaxPoly->setText(QString("%1").arg(player->fluid()->getMaxPolyphone(),5,10,QChar('0'))); } } @@ -295,7 +294,7 @@ void qmpMainWindow::switchTrack(QString s) timer->stop();player->playerDeinit(); for(auto i=mfunc.begin();i!=mfunc.end();++i)if(i->second.isVisualization())((qmpVisualizationIntf*)(i->second.i()))->stop(); if(playerTh){playerTh->join();delete playerTh;playerTh=NULL;} - if(singleFS)player->playerPanic(true); + player->playerPanic(true); ui->hsTimer->setValue(0); chnlw->on_pbUnmute_clicked();chnlw->on_pbUnsolo_clicked(); QString fns=s;setWindowTitle(QUrl::fromLocalFile(fns).fileName().left(QUrl::fromLocalFile(fns).fileName().lastIndexOf('.'))+" - QMidiPlayer"); @@ -305,12 +304,9 @@ void qmpMainWindow::switchTrack(QString s) char ts[100]; sprintf(ts,"%02d:%02d",(int)player->getFtime()/60,(int)player->getFtime()%60); ui->lbFinTime->setText(ts); - player->playerInit();if(!singleFS){playerSetup();player->fluidInitialize(); - for(int i=settingsw->getSFWidget()->rowCount()-1;i>=0;--i){if(!((QCheckBox*)settingsw->getSFWidget()->cellWidget(i,0))->isChecked())continue; - LOAD_SOUNDFONT; - }} + player->playerInit(); for(auto i=mfunc.begin();i!=mfunc.end();++i)if(i->second.isVisualization())((qmpVisualizationIntf*)(i->second.i()))->start(); - player->setGain(ui->vsMasterVol->value()/250.);efxw->sendEfxChange(); + player->fluid()->setGain(ui->vsMasterVol->value()/250.);efxw->sendEfxChange(); player->setWaitVoice(qmpSettingsWindow::getSettingsIntf()->value("Midi/WaitVoice",1).toInt()); playerTh=new std::thread(&CMidiPlayer::playerThread,player); #ifdef _WIN32 @@ -340,19 +336,18 @@ std::wstring qmpMainWindow::getWTitle() toUnicode(player->getTitle()).toStdWString(); } -void qmpMainWindow::playerSetup() +void qmpMainWindow::playerSetup(IFluidSettings* fs) { - fluid_settings_t* fsettings=player->getFluidSettings(); QSettings* settings=qmpSettingsWindow::getSettingsIntf(); - fluid_settings_setstr(fsettings,"audio.driver",settings->value("Audio/Driver","").toString().toStdString().c_str()); - fluid_settings_setint(fsettings,"audio.period-size",settings->value("Audio/BufSize","").toInt()); - fluid_settings_setint(fsettings,"audio.periods",settings->value("Audio/BufCnt","").toInt()); - fluid_settings_setstr(fsettings,"audio.sample-format",settings->value("Audio/Format","").toString().toStdString().c_str()); - fluid_settings_setint(fsettings,"synth.sample-rate",settings->value("Audio/Frequency","").toInt()); - fluid_settings_setint(fsettings,"synth.polyphony",settings->value("Audio/Polyphony","").toInt()); - fluid_settings_setint(fsettings,"synth.cpu-cores",settings->value("Audio/Threads","").toInt()); + fs->setOptStr("audio.driver",settings->value("Audio/Driver","").toString().toStdString().c_str()); + fs->setOptInt("audio.period-size",settings->value("Audio/BufSize","").toInt()); + fs->setOptInt("audio.periods",settings->value("Audio/BufCnt","").toInt()); + fs->setOptStr("audio.sample-format",settings->value("Audio/Format","").toString().toStdString().c_str()); + fs->setOptInt("synth.sample-rate",settings->value("Audio/Frequency","").toInt()); + fs->setOptInt("synth.polyphony",settings->value("Audio/Polyphony","").toInt()); + fs->setOptInt("synth.cpu-cores",settings->value("Audio/Threads","").toInt()); char bsmode[4]; - if(!singleFS&&settings->value("Audio/AutoBS",1).toInt()&&player->getFileStandard()) + if(settings->value("Audio/AutoBS",1).toInt()&&player->getFileStandard()) switch(player->getFileStandard()) { case 1:strcpy(bsmode,"gm");break; @@ -371,7 +366,7 @@ void qmpMainWindow::playerSetup() if(settings->value("Audio/BankSelect","CC#0").toString()==QString("CC#0*128+CC#32")) strcpy(bsmode,"mma"); } - fluid_settings_setstr(fsettings,"synth.midi-bank-select",bsmode); + fs->setOptStr("synth.midi-bank-select",bsmode); player->sendSysX(settings->value("Midi/SendSysEx",1).toInt()); } @@ -393,13 +388,9 @@ void qmpMainWindow::on_pbPlayPause_clicked() char ts[100]; sprintf(ts,"%02d:%02d",(int)player->getFtime()/60,(int)player->getFtime()%60); ui->lbFinTime->setText(ts); - player->playerInit();if(!singleFS){playerSetup();player->fluidInitialize(); - for(int i=settingsw->getSFWidget()->rowCount()-1;i>=0;--i){if(!((QCheckBox*)settingsw->getSFWidget()->cellWidget(i,0))->isChecked())continue; - LOAD_SOUNDFONT; - } - } + player->playerInit(); for(auto i=mfunc.begin();i!=mfunc.end();++i)if(i->second.isVisualization())((qmpVisualizationIntf*)(i->second.i()))->start(); - player->setGain(ui->vsMasterVol->value()/250.);efxw->sendEfxChange(); + player->fluid()->setGain(ui->vsMasterVol->value()/250.);efxw->sendEfxChange(); player->setWaitVoice(qmpSettingsWindow::getSettingsIntf()->value("Midi/WaitVoice",1).toInt()); playerTh=new std::thread(&CMidiPlayer::playerThread,player); #ifdef _WIN32 @@ -480,7 +471,7 @@ void qmpMainWindow::playerSeek(uint32_t percentage) void qmpMainWindow::on_vsMasterVol_valueChanged() { - if(!stopped)player->setGain(ui->vsMasterVol->value()/250.); + if(!stopped)player->fluid()->setGain(ui->vsMasterVol->value()/250.); qmpSettingsWindow::getSettingsIntf()->setValue("Audio/Gain",ui->vsMasterVol->value()); } @@ -492,7 +483,7 @@ void qmpMainWindow::on_pbStop_clicked() for(auto i=mfunc.begin();i!=mfunc.end();++i)if(i->second.isVisualization())((qmpVisualizationIntf*)(i->second.i()))->stop(); player->playerDeinit(); setFuncEnabled("Render",stopped);setFuncEnabled("ReloadSynth",stopped); - if(singleFS)player->playerPanic(true);chnlw->resetAcitivity(); + player->playerPanic(true);chnlw->resetAcitivity(); if(playerTh){playerTh->join();delete playerTh;playerTh=NULL;} chnlw->on_pbUnmute_clicked();chnlw->on_pbUnsolo_clicked(); ui->pbPlayPause->setIcon(QIcon(getThemedIcon(":/img/play.svg"))); @@ -583,7 +574,6 @@ bool qmpMainWindow::isDarkTheme() void qmpMainWindow::startRender() { - if(singleFS)player->fluidDeinitialize(); #ifdef _WIN32 char* ofstr=wcsto8bit((plistw->getSelectedItem()+QString(".wav")).toStdWString().c_str()); char* ifstr=wcsto8bit(plistw->getSelectedItem().toStdWString().c_str()); @@ -591,22 +581,27 @@ void qmpMainWindow::startRender() playerSetup();player->rendererInit(ifstr); free(ofstr);free(ifstr); #else - player->rendererLoadFile((plistw->getSelectedItem()+QString(".wav")).toStdString().c_str()); - playerSetup();player->rendererInit(plistw->getSelectedItem().toStdString().c_str()); + fluidrenderer=new qmpFileRendererFluid( + plistw->getSelectedItem().toStdString().c_str(), + (plistw->getSelectedItem()+QString(".wav")).toStdString().c_str() + ); + playerSetup(fluidrenderer); + fluidrenderer->renderInit(); #endif ui->centralWidget->setEnabled(false); for(int i=settingsw->getSFWidget()->rowCount()-1;i>=0;--i){if(!((QCheckBox*)settingsw->getSFWidget()->cellWidget(i,0))->isChecked())continue; - LOAD_SOUNDFONT; + LOAD_SOUNDFONT(fluidrenderer); } - player->setGain(ui->vsMasterVol->value()/250.);efxw->sendEfxChange();timer->start(UPDATE_INTERVAL); - renderTh=new std::thread(&CMidiPlayer::rendererThread,player); + fluidrenderer->setGain(ui->vsMasterVol->value()/250.); + efxw->sendEfxChange(fluidrenderer);timer->start(UPDATE_INTERVAL); + renderTh=new std::thread(&qmpFileRendererFluid::renderWorker,fluidrenderer); } void qmpMainWindow::reloadSynth() { - player->fluidDeinitialize();player->fluidPreInitialize();playerSetup();player->fluidInitialize(); + player->fluid()->deviceDeinit(true);playerSetup(player->fluid());player->fluid()->deviceInit(); for(int i=settingsw->getSFWidget()->rowCount()-1;i>=0;--i){if(!((QCheckBox*)settingsw->getSFWidget()->cellWidget(i,0))->isChecked())continue; - LOAD_SOUNDFONT; + LOAD_SOUNDFONT(player->fluid()); } } diff --git a/qmidiplayer-desktop/qmpmainwindow.hpp b/qmidiplayer-desktop/qmpmainwindow.hpp index baadb6d..fd1a5eb 100644 --- a/qmidiplayer-desktop/qmpmainwindow.hpp +++ b/qmidiplayer-desktop/qmpmainwindow.hpp @@ -180,6 +180,8 @@ class qmpMainWindow:public QMainWindow std::chrono::steady_clock::time_point st; double offset; CMidiPlayer *player; + qmpRtMidiManager *rtmididev; + qmpFileRendererFluid *fluidrenderer; qmpPluginManager *pmgr; QPointer plistw; QPointer chnlw; @@ -194,7 +196,7 @@ class qmpMainWindow:public QMainWindow std::vector enabled_buttons,enabled_actions; void onfnChanged(); - void playerSetup(); + void playerSetup(IFluidSettings *fs); private: static qmpMainWindow* ref; diff --git a/qmidiplayer-desktop/qmpplugin.cpp b/qmidiplayer-desktop/qmpplugin.cpp index 5889b9f..2cafa7f 100644 --- a/qmidiplayer-desktop/qmpplugin.cpp +++ b/qmidiplayer-desktop/qmpplugin.cpp @@ -135,9 +135,9 @@ uint32_t qmpPluginAPI::getNoteCount() uint32_t qmpPluginAPI::getMaxTick() {return qmw&&qmw->getPlayer()?qmw->getPlayer()->getMaxTick():0;} uint32_t qmpPluginAPI::getCurrentPolyphone() -{return qmw&&qmw->getPlayer()?qmw->getPlayer()->getPolyphone():0;} +{return qmw&&qmw->getPlayer()?qmw->getPlayer()->fluid()->getPolyphone():0;} uint32_t qmpPluginAPI::getMaxPolyphone() -{return qmw&&qmw->getPlayer()?qmw->getPlayer()->getMaxPolyphone():0;} +{return qmw&&qmw->getPlayer()?qmw->getPlayer()->fluid()->getMaxPolyphone():0;} uint32_t qmpPluginAPI::getCurrentTimeStamp() {return qmw&&qmw->getPlayer()?qmw->getPlayer()->getTick():0;} uint32_t qmpPluginAPI::getCurrentPlaybackPercentage() @@ -182,6 +182,10 @@ void qmpPluginAPI::callEventReaderCB(SEventCallBackData d){if(qmw&&qmw->getPlaye void qmpPluginAPI::setFuncState(std::string name,bool state){if(qmw)qmw->setFuncState(name,state);} void qmpPluginAPI::setFuncEnabled(std::string name,bool enable){if(qmw)qmw->setFuncEnabled(name,enable);} +void qmpPluginAPI::registerMidiOutDevice(qmpMidiOutDevice *dev, std::string name) +{qmw->getPlayer()->registerMidiOutDevice(dev,name);} +void qmpPluginAPI::unregisterMidiOutDevice(std::string name) +{qmw->getPlayer()->unregisterMidiOutDevice(name);} int qmpPluginAPI::registerEventHandlerIntf(IMidiCallBack *cb,void *userdata) {return qmw->getPlayer()->setEventHandlerCB(cb,userdata);} void qmpPluginAPI::unregisterEventHandlerIntf(int intfhandle) diff --git a/qmidiplayer-desktop/qmppresetselect.cpp b/qmidiplayer-desktop/qmppresetselect.cpp index c5132b3..57dbe7f 100644 --- a/qmidiplayer-desktop/qmppresetselect.cpp +++ b/qmidiplayer-desktop/qmppresetselect.cpp @@ -21,16 +21,11 @@ void qmpPresetSelector::showEvent(QShowEvent *e) { memset(presets,0,sizeof(presets)); CMidiPlayer *plyr=qmpMainWindow::getInstance()->getPlayer(); - if(!plyr->getSFCount())return e->ignore(); - int sfc=plyr->getSFCount(); - for(int i=sfc-1;i>=0;--i) - { - fluid_sfont_t* psf=plyr->getSFPtr(i); - fluid_preset_t preset; - psf->iteration_start(psf); - while(psf->iteration_next(psf,&preset)) - strcpy(presets[preset.get_banknum(&preset)][preset.get_num(&preset)],preset.get_name(&preset)); - } + if(!plyr->fluid()->getSFCount())return e->ignore(); + std::vector,std::string>> + presetlist=plyr->fluid()->listPresets(); + for(auto &i:presetlist) + strcpy(presets[i.first.first][i.first.second],i.second.c_str()); ui->lwBankSelect->clear(); ui->lwPresetSelect->clear(); for(int i=0;i<=128;++i) @@ -44,7 +39,7 @@ void qmpPresetSelector::showEvent(QShowEvent *e) void qmpPresetSelector::setupWindow(int chid) { CMidiPlayer *plyr=qmpMainWindow::getInstance()->getPlayer(); - if(!plyr->getSFCount())return; + if(!plyr->fluid()->getSFCount())return; ch=chid;int b=0,p=0,r;char name[64]; sprintf(name,"Preset Selection - Channel #%d",ch+1); setWindowTitle(name); diff --git a/qmidiplayer-desktop/qmpsettingswindow.ui b/qmidiplayer-desktop/qmpsettingswindow.ui index ad359f7..bbeddd4 100644 --- a/qmidiplayer-desktop/qmpsettingswindow.ui +++ b/qmidiplayer-desktop/qmpsettingswindow.ui @@ -53,11 +53,6 @@ 0 - - - Internal FluidSynth - - diff --git a/qmidiplayer-lite/main.qml b/qmidiplayer-lite/main.qml index 0ec0975..ace1427 100644 --- a/qmidiplayer-lite/main.qml +++ b/qmidiplayer-lite/main.qml @@ -8,10 +8,12 @@ Window { id: window1 width: 420 height: 240 - title: "QMidiPlayer Lite" + title: "QMidiPlayer Lite TP" visible: true property bool playing playing: false + property string soundfont + soundfont: "" MouseArea { id: mouseArea1 @@ -20,9 +22,6 @@ Window { anchors.leftMargin: 0 anchors.topMargin: 0 anchors.fill: parent - onClicked: { - //Qt.quit(); - } Button { id: button2 @@ -35,7 +34,7 @@ Window { if(!playing) { qmpcore.loadFile(fileName.text); - qmpcore.initFluidSynth(); + qmpcore.initFluidSynth(soundfont); qmpcore.playFile(); playing=true; uiTimer.start(); @@ -92,6 +91,18 @@ Window { } } } + + Button { + id: button + anchors.top: parent.top + anchors.topMargin: 67 + anchors.horizontalCenter: parent.horizontalCenter + x: 139 + text: qsTr("Load SoundFont") + onClicked: { + sfFileDialog.open() + } + } } CQMPCoreWrapper { @@ -115,17 +126,18 @@ Window { Text { id: fileName text: qsTr("...") - anchors.centerIn: parent + anchors.top: parent.top + anchors.topMargin: 100 + anchors.horizontalCenter: parent.horizontalCenter + x: 139 } Button { id: button1 x: 170 - width: 80 - height: 27 text: qsTr("Open") anchors.top: parent.top - anchors.topMargin: 142 + anchors.topMargin: 130 anchors.horizontalCenter: parent.horizontalCenter onClicked: { fileDialog.open(); @@ -140,5 +152,11 @@ Window { fileName.text=fileUrl; } } + FileDialog { + id: sfFileDialog + title: qsTr("Select SoundFont") + nameFilters: ["SoundFont (*.sf2)"] + onAccepted: {soundfont=fileUrl;} + } } diff --git a/qmidiplayer-lite/qmidiplayer-lite.pro b/qmidiplayer-lite/qmidiplayer-lite.pro index 1534d53..9b02387 100644 --- a/qmidiplayer-lite/qmidiplayer-lite.pro +++ b/qmidiplayer-lite/qmidiplayer-lite.pro @@ -7,8 +7,8 @@ CONFIG += c++11 SOURCES += main.cpp \ ../core/qmpmidiplay.cpp \ - ../core/qmpmidiread.cpp \ - ../core/qmpmidimapperrtmidi.cpp + ../core/qmpmidiread.cpp \ + ../core/qmpmidioutfluid.cpp RESOURCES += qml.qrc @@ -21,7 +21,7 @@ include(deployment.pri) HEADERS += \ ../core/qmpmidiplay.hpp \ qmpcorewrapper.hpp \ - ../core/qmpmidimappers.hpp \ + ../core/qmpmidioutfluid.hpp \ ../include/qmpcorepublic.hpp unix{ LIBS += -lfluidsynth -lrtmidi diff --git a/qmidiplayer-lite/qmpcorewrapper.hpp b/qmidiplayer-lite/qmpcorewrapper.hpp index ed024df..090edc7 100644 --- a/qmidiplayer-lite/qmpcorewrapper.hpp +++ b/qmidiplayer-lite/qmpcorewrapper.hpp @@ -15,19 +15,20 @@ private: public: explicit CQMPCoreWrapper(QObject* parent=0):QObject(parent) { - mp=new CMidiPlayer(false); + mp=new CMidiPlayer(); } ~CQMPCoreWrapper(){delete mp;} - Q_INVOKABLE void initFluidSynth() + Q_INVOKABLE void initFluidSynth(QUrl sfpath) { - fluid_settings_t *fsettings=mp->getFluidSettings(); - fluid_settings_setstr(fsettings,"audio.driver","pulseaudio"); - mp->fluidInitialize(); - mp->pushSoundFont("/media/Files/FluidR3_Ext.sf2"); + mp->fluid()->setOptStr("audio.driver","pulseaudio"); + mp->fluid()->deviceInit(); + mp->fluid()->loadSFont(sfpath.toLocalFile().toStdString().c_str()); + for(int i=0;i<16;++i) + mp->setChannelOutput(i,0); } Q_INVOKABLE void deinitFluidSynth() { - mp->fluidDeinitialize(); + mp->fluid()->deviceDeinit(); } Q_INVOKABLE void loadFile(QUrl file) { @@ -41,6 +42,7 @@ public: Q_INVOKABLE void stop() { mp->playerDeinit();playerTh->join();delete playerTh; + mp->playerPanic(); } Q_INVOKABLE int getProgress() { -- cgit v1.2.3