From 40ea6580aaf3d19aa77f43551185a55013d216d9 Mon Sep 17 00:00:00 2001 From: Chris Xiong Date: Mon, 28 Dec 2015 22:02:45 +0800 Subject: Last Remote.(WTF) --- ChangeLog | 4 ++ README.md | 12 ++--- qmidiplayer.pro | 9 ++-- qmpchannelswindow.cpp | 88 ++++++++++++++++++++++++++++++ qmpchannelswindow.hpp | 33 ++++++++++++ qmpchannelswindow.ui | 146 ++++++++++++++++++++++++++++++++++++++++++++++++++ qmpmainwindow.cpp | 16 ++++-- qmpmainwindow.hpp | 5 ++ qmpmainwindow.ui | 3 ++ qmpmidiplay.cpp | 94 ++++++++++++++++++++++++++++---- qmpmidiplay.hpp | 9 +++- qmpmidiread.cpp | 13 ----- 12 files changed, 395 insertions(+), 37 deletions(-) create mode 100644 qmpchannelswindow.cpp create mode 100644 qmpchannelswindow.hpp create mode 100644 qmpchannelswindow.ui diff --git a/ChangeLog b/ChangeLog index 3e6ead8..a4ea71e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +2015-12-28 primitive version +Partially implemented the channel window. +Fixed a seeking bug. + 2015-12-27 primitive version Implemented the play list. diff --git a/README.md b/README.md index fa0c25a..50eb3cd 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ A cross-platform midi file player based on libfluidsynth and Qt. Currently it's still very incomplete and not suitable for everyday use. Planned features: -> Channel mute/solo (not implemented) -> Editing channel parameters on-the-fly (not implemented) -> Playlists (partially implemented) -> Editing synthesizer effects (not implemented) -> Visualization (not implemented) -> Rendering midi to wave file (not implemented) +* Channel mute/solo (not implemented) +* Editing channel parameters on-the-fly (not implemented) +* Playlists (partially implemented) +* Editing synthesizer effects (not implemented) +* Visualization (not implemented) +* Rendering midi to wave file (not implemented) diff --git a/qmidiplayer.pro b/qmidiplayer.pro index 386ea72..12745ee 100644 --- a/qmidiplayer.pro +++ b/qmidiplayer.pro @@ -16,14 +16,17 @@ SOURCES += main.cpp\ qmpmainwindow.cpp \ qmpmidiplay.cpp \ qmpmidiread.cpp \ - qmpplistwindow.cpp + qmpplistwindow.cpp \ + qmpchannelswindow.cpp HEADERS += qmpmainwindow.hpp \ qmpmidiplay.hpp \ - qmpplistwindow.hpp + qmpplistwindow.hpp \ + qmpchannelswindow.hpp FORMS += qmpmainwindow.ui \ - qmpplistwindow.ui + qmpplistwindow.ui \ + qmpchannelswindow.ui QMAKE_CXXFLAGS += -std=c++11 -Wall LIBS += -lfluidsynth diff --git a/qmpchannelswindow.cpp b/qmpchannelswindow.cpp new file mode 100644 index 0000000..6aca878 --- /dev/null +++ b/qmpchannelswindow.cpp @@ -0,0 +1,88 @@ +#include +#include +#include +#include +#include "qmpchannelswindow.hpp" +#include "ui_qmpchannelswindow.h" +#include "qmpmainwindow.hpp" + +qmpchannelswindow::qmpchannelswindow(QWidget *parent) : + QDialog(parent), + ui(new Ui::qmpchannelswindow) +{ + ui->setupUi(this); + connect(this,SIGNAL(dialogClosing()),parent,SLOT(dialogClosed())); + for(int i=0;i<16;++i) + { + ui->twChannels->setCellWidget(i,0,new QCheckBox("")); + connect(ui->twChannels->cellWidget(i,0),SIGNAL(stateChanged(int)),this,SLOT(channelMSChanged())); + ui->twChannels->setCellWidget(i,1,new QCheckBox("")); + connect(ui->twChannels->cellWidget(i,1),SIGNAL(stateChanged(int)),this,SLOT(channelMSChanged())); + ui->twChannels->setCellWidget(i,2,new QComboBox()); + QComboBox *cb=(QComboBox*)ui->twChannels->cellWidget(i,2); + //stub + cb->addItem("Internal fluidsynth"); + ui->twChannels->setCellWidget(i,3,new QLabel("")); + ui->twChannels->setCellWidget(i,4,new QPushButton("...")); + } + ui->twChannels->setColumnWidth(0,32); + ui->twChannels->setColumnWidth(1,32); + ui->twChannels->setColumnWidth(2,192); + ui->twChannels->setColumnWidth(3,192); + ui->twChannels->setColumnWidth(4,32); +} + +void qmpchannelswindow::closeEvent(QCloseEvent *event) +{ + setVisible(false); + emit dialogClosing(); + event->accept(); +} + +void qmpchannelswindow::channelWindowsUpdate() +{ + for(int i=0;i<16;++i) + { + char data[128],nm[24]; + int b,p; + ((qmpMainWindow*)this->parent())->getPlayer()->getChannelPreset(i,&b,&p,nm); + sprintf(data,"%d:%d %s",b,p,nm); + ((QLabel*)ui->twChannels->cellWidget(i,3))->setText(data); + } +} + +void qmpchannelswindow::channelMSChanged() +{ + for(int i=0;i<16;++i) + { + QCheckBox *m,*s; + m=(QCheckBox*)ui->twChannels->cellWidget(i,0); + s=(QCheckBox*)ui->twChannels->cellWidget(i,1); + if(m->isChecked()&&s->isChecked())s->setChecked(false); + ((qmpMainWindow*)this->parent())->getPlayer()->setMute(i,m->isChecked()); + ((qmpMainWindow*)this->parent())->getPlayer()->setSolo(i,s->isChecked()); + } +} + +qmpchannelswindow::~qmpchannelswindow() +{ + delete ui; +} + +void qmpchannelswindow::on_pbUnmute_clicked() +{ + for(int i=0;i<16;++i) + { + ((QCheckBox*)ui->twChannels->cellWidget(i,0))->setChecked(false); + ((qmpMainWindow*)this->parent())->getPlayer()->setMute(i,false); + } +} + +void qmpchannelswindow::on_pbUnsolo_clicked() +{ + for(int i=0;i<16;++i) + { + ((QCheckBox*)ui->twChannels->cellWidget(i,1))->setChecked(false); + ((qmpMainWindow*)this->parent())->getPlayer()->setSolo(i,false); + } +} diff --git a/qmpchannelswindow.hpp b/qmpchannelswindow.hpp new file mode 100644 index 0000000..4bf3ad2 --- /dev/null +++ b/qmpchannelswindow.hpp @@ -0,0 +1,33 @@ +#ifndef QMPCHANNELSWINDOW_H +#define QMPCHANNELSWINDOW_H + +#include +#include + +namespace Ui { + class qmpchannelswindow; +} + +class qmpchannelswindow : public QDialog +{ + Q_OBJECT + + public: + explicit qmpchannelswindow(QWidget *parent = 0); + ~qmpchannelswindow(); + void closeEvent(QCloseEvent *event); + signals: + void dialogClosing(); + public slots: + void channelWindowsUpdate(); + void channelMSChanged(); + private slots: + void on_pbUnmute_clicked(); + + void on_pbUnsolo_clicked(); + + private: + Ui::qmpchannelswindow *ui; +}; + +#endif // QMPCHANNELSWINDOW_H diff --git a/qmpchannelswindow.ui b/qmpchannelswindow.ui new file mode 100644 index 0000000..dba6009 --- /dev/null +++ b/qmpchannelswindow.ui @@ -0,0 +1,146 @@ + + + qmpchannelswindow + + + + 0 + 0 + 580 + 410 + + + + + 580 + 410 + + + + + 580 + 16777215 + + + + Channels + + + + + + + + + 0 + 0 + + + + true + + + QAbstractItemView::NoSelection + + + false + + + 16 + + + 5 + + + + + + + + + + + + + + + + + + + + M + + + + + S + + + + + Device + + + + + Preset + + + + + ... + + + + + + + + + + + + + + + + + Save + + + + 16 + 16 + + + + + + + + Load + + + + + + + Unmute All + + + + + + + Unsolo All + + + + + + + + + + diff --git a/qmpmainwindow.cpp b/qmpmainwindow.cpp index 7d2bf0c..69cf9df 100644 --- a/qmpmainwindow.cpp +++ b/qmpmainwindow.cpp @@ -11,9 +11,11 @@ qmpMainWindow::qmpMainWindow(QWidget *parent) : ui->setupUi(this);player=new CMidiPlayer(); playing=false;stopped=true;dragging=false; plistw=new qmpplistwindow(this); + chnlw=new qmpchannelswindow(this); ui->lbFileName->setText(""); timer=new QTimer(this); connect(timer,SIGNAL(timeout()),this,SLOT(updateWidgets())); + connect(timer,SIGNAL(timeout()),chnlw,SLOT(channelWindowsUpdate())); } qmpMainWindow::~qmpMainWindow() @@ -120,14 +122,14 @@ void qmpMainWindow::on_hsTimer_sliderReleased() if(playing) { if(ui->hsTimer->value()==100){on_pbNext_clicked();return;} - player->setTCeptr(player->getStamp(ui->hsTimer->value())); + 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())); + 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); @@ -157,6 +159,7 @@ void qmpMainWindow::on_pbStop_clicked() void qmpMainWindow::dialogClosed() { if(!plistw->isVisible())ui->pbPList->setChecked(false); + if(!chnlw->isVisible())ui->pbChannels->setChecked(false); } void qmpMainWindow::on_pbPList_clicked() @@ -164,12 +167,17 @@ void qmpMainWindow::on_pbPList_clicked() if(ui->pbPList->isChecked())plistw->show();else plistw->close(); } +void qmpMainWindow::on_pbChannels_clicked() +{ + if(ui->pbChannels->isChecked())chnlw->show();else chnlw->close(); +} + void qmpMainWindow::on_pbPrev_clicked() { timer->stop();player->playerDeinit(); if(playerTh){playerTh->join();delete playerTh;playerTh=NULL;} ui->hsTimer->setValue(0); - QString fns=plistw->getPrevItem(); + QString fns=plistw->getPrevItem();if(fns.length()==0)return on_pbStop_clicked(); ui->lbFileName->setText(QUrl(fns).fileName()); player->playerLoadFile(fns.toStdString().c_str()); char ts[100]; @@ -186,7 +194,7 @@ void qmpMainWindow::on_pbNext_clicked() timer->stop();player->playerDeinit(); if(playerTh){playerTh->join();delete playerTh;playerTh=NULL;} ui->hsTimer->setValue(0); - QString fns=plistw->getNextItem(); + QString fns=plistw->getNextItem();if(fns.length()==0)return on_pbStop_clicked(); ui->lbFileName->setText(QUrl(fns).fileName()); player->playerLoadFile(fns.toStdString().c_str()); char ts[100]; diff --git a/qmpmainwindow.hpp b/qmpmainwindow.hpp index cfb740d..8d64f37 100644 --- a/qmpmainwindow.hpp +++ b/qmpmainwindow.hpp @@ -8,6 +8,7 @@ #include #include "qmpmidiplay.hpp" #include "qmpplistwindow.hpp" +#include "qmpchannelswindow.hpp" namespace Ui { class qmpMainWindow; @@ -21,6 +22,7 @@ class qmpMainWindow : public QMainWindow explicit qmpMainWindow(QWidget *parent = 0); void closeEvent(QCloseEvent *event); ~qmpMainWindow(); + CMidiPlayer* getPlayer(){return player;} private slots: void on_pbPlayPause_clicked(); @@ -39,6 +41,8 @@ class qmpMainWindow : public QMainWindow void on_pbNext_clicked(); + void on_pbChannels_clicked(); + public slots: void dialogClosed(); void selectionChanged(); @@ -52,6 +56,7 @@ class qmpMainWindow : public QMainWindow double offset; CMidiPlayer *player; qmpplistwindow *plistw; + qmpchannelswindow *chnlw; }; diff --git a/qmpmainwindow.ui b/qmpmainwindow.ui index e1bbd29..1cce37d 100644 --- a/qmpmainwindow.ui +++ b/qmpmainwindow.ui @@ -210,6 +210,9 @@ 32 + + true + diff --git a/qmpmidiplay.cpp b/qmpmidiplay.cpp index 9760f81..b676c99 100644 --- a/qmpmidiplay.cpp +++ b/qmpmidiplay.cpp @@ -7,7 +7,7 @@ void CMidiPlayer::fluidInitialize(const char* sf) { settings=new_fluid_settings(); fluid_settings_setstr(settings,"audio.driver","pulseaudio"); - fluid_settings_setint(settings,"synth.cpu-cores",1); + fluid_settings_setint(settings,"synth.cpu-cores",4); fluid_settings_setint(settings,"synth.min-note-length",0); fluid_settings_setint(settings,"synth.polyphony",256); synth=new_fluid_synth(settings); @@ -30,6 +30,8 @@ void CMidiPlayer::processEvent(const SEvent *e) 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 @@ -65,8 +67,33 @@ void CMidiPlayer::processEvent(const SEvent *e) } void CMidiPlayer::processEventStub(const SEvent *e) { - if(e->type==0xFF&&e->p1==0x51) - {ctempo=e->p2;dpt=ctempo*1000/divs;} + switch(e->type&0xF0) + { + case 0xB0://CC + 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; + } + } + break; + } } void CMidiPlayer::playEvents() { @@ -98,18 +125,37 @@ void CMidiPlayer::fileTimer1Pass() } void CMidiPlayer::fileTimer2Pass() { - memset(stamps,0,sizeof(stamps)); 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));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; + } 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.){stamps[c++]=eptr;if(c>100)throw;} + while(ctime>ftime*c/100.) + { + for(int i=0;i<16;++i)for(int j=0;j<132;++j) + ccstamps[c][i][j]=ccc[i][j]; + stamps[c++]=eptr; + if(c>100)throw; + } ct=midiFile->getEvent(eptr)->time; } - while(c<101)stamps[c++]=midiFile->getEventCount(); + while(c<101) + { + for(int i=0;i<16;++i)for(int j=0;j<132;++j) + ccstamps[c][i][j]=ccc[i][j]; + stamps[c++]=midiFile->getEventCount(); + } } CMidiPlayer::CMidiPlayer() { @@ -118,8 +164,6 @@ CMidiPlayer::CMidiPlayer() } void CMidiPlayer::playerPanic() { - //for(int i=0;i<16;++i)fluid_synth_cc(synth,i,123,0);//not supported? - //fluid_synth_system_reset(synth);//reverts to default presets... for(int i=0;i<16;++i)fluid_synth_all_notes_off(synth,i); //for(int i=0;i<16;++i)for(int j=0;j<128;++j)fluid_synth_noteoff(synth,i,j); } @@ -133,7 +177,7 @@ void CMidiPlayer::playerLoadFile(const char* fn) void CMidiPlayer::playerInit() { ctempo=0x7A120;ctsn=4;ctsd=2;dpt=ctempo*1000/divs; - tceptr=0;tcstop=0;tcpaused=0;finished=0; + tceptr=0;tcstop=0;tcpaused=0;finished=0;mute=solo=0; fluidInitialize("/media/Files/FluidR3_Ext.sf2"); } void CMidiPlayer::playerDeinit() @@ -147,7 +191,17 @@ void CMidiPlayer::playerThread() } uint32_t CMidiPlayer::getStamp(int id){return stamps[id];} uint32_t CMidiPlayer::getTCeptr(){return tceptr;} -void CMidiPlayer::setTCeptr(uint32_t ep){tceptr=ep;} +void CMidiPlayer::setTCeptr(uint32_t ep,uint32_t st) +{ + 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]; + } +} double CMidiPlayer::getFtime(){return ftime;} uint32_t CMidiPlayer::getTCpaused(){return tcpaused;} void CMidiPlayer::setTCpaused(uint32_t ps){tcpaused=ps;} @@ -157,3 +211,23 @@ void CMidiPlayer::setGain(double gain){if(settings)fluid_settings_setnum(setting 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; + fluid_synth_channel_info_t info; + fluid_synth_get_channel_info(synth,ch,&info); + *b=info.bank;*p=info.program; + strcpy(name,info.name); +} +//16MSB..LSB1 +void CMidiPlayer::setBit(uint16_t &n, uint16_t bn, uint16_t b) +{n^=(-b^n)&(1<