From b03133b80b268c74d1dd5c92e2af6907b51c91b2 Mon Sep 17 00:00:00 2001 From: Chris Xiong Date: Thu, 22 Jun 2017 11:26:48 +0800 Subject: Minor bug fixes. SMF reader finally takes chunk length into account. Do not prepend sysex header to F0h sysex. Let the readers do it. File readers code cleanups and refined error messages. --- ChangeLog | 7 +++ README.md | 6 +-- core/qmpmidioutrtmidi.cpp | 2 +- core/qmpmidiplay.cpp | 26 ++-------- core/qmpmidiread.cpp | 92 ++++++++++++++++------------------ doc/APIdoc.md | 28 ++++++----- midifmt-plugin/midifmtplugin.cpp | 5 +- qmidiplayer-desktop/qmpmainwindow.cpp | 2 +- qmidiplayer-desktop/qmpplistwindow.cpp | 10 ++-- 9 files changed, 83 insertions(+), 95 deletions(-) diff --git a/ChangeLog b/ChangeLog index d749d78..69e76d3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2017-06-22 0.8.6 indev +Minor bug fixes. +SMF reader finally takes chunk length into account. +Do not prepend sysex header to F0h sysex. Let the +readers do it. +File readers code cleanups and refined error messages. + 2017-06-21 0.8.6 indev Fixed FTBFS on Windows. Fixed the default output device option. diff --git a/README.md b/README.md index 8c7632a..aca9cca 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ Features: * Editing channel parameters on-the-fly * Playlists * Editing synthesizer effects -* Rendering midi to wave file -* Visualization using SMELT (highly experimental Windows version now available) -* MIDI mapping (based on RtMidi) +* Rendering midi to wave file (currently fluidsynth only) +* Visualization using SMELT (experimental Windows version now available) +* MIDI mapping (RtMidi and plugins) * Plugin interface for extending the player easily Tested on Debian sid and Windows Vista~10. diff --git a/core/qmpmidioutrtmidi.cpp b/core/qmpmidioutrtmidi.cpp index 295e87b..7f75fda 100644 --- a/core/qmpmidioutrtmidi.cpp +++ b/core/qmpmidioutrtmidi.cpp @@ -69,8 +69,8 @@ void qmpMidiOutRtMidi::panic(uint8_t ch) } void qmpMidiOutRtMidi::reset(uint8_t ch) { - basicMessage(0xB0|ch,120,0); basicMessage(0xB0|ch,121,0); + basicMessage(0xB0|ch,123,0); } void qmpMidiOutRtMidi::onMapped(uint8_t,int) { diff --git a/core/qmpmidiplay.cpp b/core/qmpmidiplay.cpp index c3df381..55b3959 100644 --- a/core/qmpmidiplay.cpp +++ b/core/qmpmidiplay.cpp @@ -69,27 +69,10 @@ bool CMidiPlayer::processEvent(const SEvent *e) break; } } - if((e->type&0x0F)==0x00||(e->type&0x0F)==07) - { - if(sendSysEx) - { - 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); - } - } + if(((e->type&0x0F)==0x00||(e->type&0x0F)==07)&&sendSysEx) + for(auto& i:mididev) + if(i.refcnt) + i.dev->extendedMessage(e->p1,e->str.c_str()); return false; } return false; @@ -298,6 +281,7 @@ bool CMidiPlayer::playerLoadFile(const char* fn) for(CMidiTrack& i:midiFile->tracks) { ecnt+=i.eventList.size(); + if(i.eventList.size()) maxtk=std::max(maxtk,i.eventList.back().time); } for(int i=0;i<16;++i)if(fileReadFinishCB[i]) diff --git a/core/qmpmidiread.cpp b/core/qmpmidiread.cpp index cd3ffa3..6089ac5 100644 --- a/core/qmpmidiread.cpp +++ b/core/qmpmidiread.cpp @@ -14,21 +14,20 @@ 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"; void CSMFReader::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 std::runtime_error("fatal error"); + va_list ap;char buf[1024],bufr[1024]; + va_start(ap,format);vsnprintf(buf,1024,format,ap);va_end(ap); + snprintf(bufr,1024,"%s at %#lx",buf,ftell(f)); + if(fatal)throw std::runtime_error(bufr); + else fprintf(stderr,"CSMFReader W: %s.\n",bufr); } uint32_t CSMFReader::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 CSMFReader::readDW() { - byteread+=4; uint32_t ret=0; for(int i=0;i<4;++i){ret<<=8;ret|=((uint32_t)fgetc(f))&0xFF;} return ret; @@ -39,18 +38,17 @@ uint32_t CSMFReader::readVL() do { t=fgetc(f); - if(++c>4)error(1,"E: Variable length type overflow."); + if(++c>4)error(1,"Variable length type overflow"); ret<<=7;ret|=(t&0x7F); }while(t&0x80); - byteread+=c; return ret; } int CSMFReader::eventReader()//returns 0 if End of Track encountered { uint32_t delta=readVL();curt+=delta; - char type=fgetc(f);++byteread;uint32_t p1,p2; + char type=fgetc(f);uint32_t p1,p2; static char lasttype;eventdiscarded=0; - if(!(type&0x80)){fseek(f,-1,SEEK_CUR);--byteread;type=lasttype;} + if(!(type&0x80)){fseek(f,-1,SEEK_CUR);type=lasttype;} switch(type&0xF0) { case 0x80://Note Off @@ -58,29 +56,28 @@ int CSMFReader::eventReader()//returns 0 if End of Track encountered case 0xA0://Note Aftertouch case 0xB0://Controller Change case 0xE0://Pitch wheel - p1=fgetc(f);p2=fgetc(f);byteread+=2; + p1=fgetc(f);p2=fgetc(f); curTrack->appendEvent(SEvent(curid,curt,type,p1,p2)); break; case 0xC0://Patch Change case 0xD0://Channel Aftertouch - p1=fgetc(f);++byteread; + p1=fgetc(f); curTrack->appendEvent(SEvent(curid,curt,type,p1,0)); break; case 0xF0: if((type&0x0F)==0x0F)//Meta Event { - char metatype=fgetc(f);++byteread; + char metatype=fgetc(f); 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; + fgetc(f);fgetc(f); break; case 0x2F://End of Track - fgetc(f);++byteread; + fgetc(f); return 0; break; case 0x51://Set Tempo @@ -90,16 +87,13 @@ int CSMFReader::eventReader()//returns 0 if End of Track encountered 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(); + fgetc(f);p1=readDW(); curTrack->appendEvent(SEvent(curid,curt,type,metatype,p1)); break; case 0x59://Key signature - fgetc(f);++byteread; - p1=readSW(); + fgetc(f);p1=readSW(); curTrack->appendEvent(SEvent(curid,curt,type,metatype,p1)); break; case 0x01:case 0x02:case 0x03: @@ -109,9 +103,7 @@ int CSMFReader::eventReader()//returns 0 if End of Track encountered uint32_t len=readVL(),c;char* str=NULL; if(len<=1024&&len>0)str=new char[len+8]; for(c=0;cappendEvent(SEvent(curid,curt,type,metatype,0,str)); if(str&&metatype==0x03&&!ret->title) @@ -135,9 +127,9 @@ int CSMFReader::eventReader()//returns 0 if End of Track encountered if((type&0x0F)==0x00) { str[0]=0xF0;++len; - for(c=1;cappendEvent(SEvent(curid,curt,type,len,0,str)); if(!strcmp(str,GM1SysX))ret->std=1; if(!strcmp(str,GM2SysX))ret->std=2; @@ -145,10 +137,10 @@ int CSMFReader::eventReader()//returns 0 if End of Track encountered if(!strcmp(str,XGSysEx))ret->std=4; delete[] str; } - else error(0,"W: Unknown event type %#x",type); + else error(0,"Unknown event type %#x",type); break; default: - error(0,"W: Unknown event type %#x",type); + error(0,"Unknown event type %#x",type); } lasttype=type;++curid; if(curTrack->eventList.size()) @@ -163,50 +155,49 @@ void CSMFReader::trackChunkReader() { ret->tracks.push_back(CMidiTrack()); curTrack=&ret->tracks.back(); - int chnklen=readDW();byteread=0;curt=0;curid=0; - while(/*bytereadchnklen) - { - error(1,"E: Read past end of track."); - }*/ + if(byteread>chnklen) + error(1,"Read past end of track"); } void CSMFReader::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."); + int chnklen=readDW();byteread=ftell(f); + if(chnklen<6)error(1,"Header chunk too short"); + if(chnklen>6)error(0,"Header chunk length longer than expected. Ignoring extra bytes"); fmt=readSW();trk=readSW();ret->divs=readSW(); - if(ret->divs&0x8000)error(1,"E: SMTPE format is not supported."); - for(;bytereaddivs&0x8000)error(1,"SMTPE format is not supported"); + for(byteread=ftell(f)-byteread;byteread0;--chnklen)fgetc(f);return 0; + error(0,"Wrong track chunk header. Ignoring the whole chunk"); + uint32_t chnklen=readDW();fseek(f,chnklen,SEEK_CUR);return 0; } else return trackChunkReader(),1; } @@ -220,12 +211,17 @@ CMidiFile* CSMFReader::readFile(const char* fn) ret->title=ret->copyright=NULL;ret->std=0;ret->valid=1; try { - if(!(f=fopen(fn,"rb")))throw (fprintf(stderr,"E: file %s doesn't exist!\n",fn),std::runtime_error("File doesn't exist")); + if(!(f=fopen(fn,"rb"))) + throw std::runtime_error("File doesn't exist"); chunkReader(1); for(uint32_t i=0;ivalid=0;if(f)fclose(f);f=NULL;} + catch(std::runtime_error& e) + { + fprintf(stderr,"CSMFReader E: %s is not a supported file. Cause: %s.\n",fn,e.what()); + ret->valid=0;if(f)fclose(f);f=NULL; + } return ret; } CSMFReader::~CSMFReader() diff --git a/doc/APIdoc.md b/doc/APIdoc.md index c050869..be7bbc5 100644 --- a/doc/APIdoc.md +++ b/doc/APIdoc.md @@ -105,13 +105,15 @@ 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. +3. Do not throw exceptions to the core. The core doesn't handle exceptions and they will crash the entire program. +Use the return value to indicate failure of a procedure instead. # 6. Reference Well, everything above is just nonsense compared to this one. The full reference of the API is here. ## Structures & Classes -### struct SEvent +### struct `SEvent` Describes an MIDI event. - `uint32_t iid` @@ -133,7 +135,7 @@ 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 +### struct `SEventCallBackData` A stripped down version of SEvent that is used to pass event data in APIs. - `uint32_t time` @@ -141,7 +143,7 @@ A stripped down version of SEvent that is used to pass event data in APIs. - `uint32_t p1` - `uint32_t p2` -### class CMidiTrack +### class `CMidiTrack` Describes a single MIDI track. A MIDI track consists of multiple MIDI events. - `std::vector eventList` @@ -151,7 +153,7 @@ 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 +### class `CMidiFile` Describes a MIDI file. A MIDI file consists of multiple MIDI tracks. - `bool valid` @@ -164,17 +166,17 @@ Copyright information of the MIDI file. 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 + - 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 +### class `IMidiCallBack` Generic callback function that can be used for hooking the core. - `IMidiCallBack()` @@ -185,7 +187,7 @@ userdata is set to whatever you asked for when registering the callback. - `virtual ~IMidiCallBack()` Virtual empty destructor. -### class IMidiFileReader +### class `IMidiFileReader` MIDI file reader interface. Use this to implement your file importer. - `IMidiFileReader()` @@ -199,7 +201,9 @@ 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. +to invoke event reader callbacks and process their requests. +If a file not supported by the reader is provided, this function should +return a CMidiFile* whose `valid` field is `false`. - `virtual void discardCurrentEvent()=0` Only called by event reader callbacks. Expected behavior: Sets the discard flag for the last event you have read. diff --git a/midifmt-plugin/midifmtplugin.cpp b/midifmt-plugin/midifmtplugin.cpp index 05b5b87..a025d29 100644 --- a/midifmt-plugin/midifmtplugin.cpp +++ b/midifmt-plugin/midifmtplugin.cpp @@ -47,9 +47,6 @@ bool CMidiStreamReader::midsBodyReader() else if(e>>24==0)//midishortmsg ev=SEvent(curid,cts,e&0xFF,(e>>8)&0xFF,(e>>16)&0xFF); else return false; - //fprintf(stderr,"ev: @ %x t %x p1 %x p2 %x\n",ev.time,ev.type,ev.p1,ev.p2); - if((ev.type&0xF0)==0x90&&ev.p2==0)//Note on with zero velo - ev.type=(ev.type&0x0F)|0x80; ret->tracks.back().appendEvent(ev);eventdiscarded=0; qmpMidiFmtPlugin::api->callEventReaderCB(SEventCallBackData(ev.type,ev.p1,ev.p2,ev.time)); if(eventdiscarded)ret->tracks.back().eventList.pop_back(); @@ -70,7 +67,7 @@ CMidiFile* CMidiStreamReader::readFile(const char *fn) if(!midsBodyReader())throw std::runtime_error("MIDS data error"); }catch(std::runtime_error& e) { - fprintf(stderr,"MIDI Format plugin: E: %s is not a supported file. Cause: %s.\n",fn,e.what()); + fprintf(stderr,"CMidiStreamReader E: %s is not a supported file. Cause: %s.\n",fn,e.what()); ret->valid=0;if(f)fclose(f);f=NULL; } return ret; diff --git a/qmidiplayer-desktop/qmpmainwindow.cpp b/qmidiplayer-desktop/qmpmainwindow.cpp index 6e10389..e5ad102 100644 --- a/qmidiplayer-desktop/qmpmainwindow.cpp +++ b/qmidiplayer-desktop/qmpmainwindow.cpp @@ -579,7 +579,7 @@ void qmpMainWindow::startRender() char* ifstr=wcsto8bit(plistw->getSelectedItem().toStdWString().c_str()); fluidrenderer=new qmpFileRendererFluid(ifstr,ofstr); playerSetup(fluidrenderer); - fluidrenderer->rendererInit(); + fluidrenderer->renderInit(); free(ofstr);free(ifstr); #else fluidrenderer=new qmpFileRendererFluid( diff --git a/qmidiplayer-desktop/qmpplistwindow.cpp b/qmidiplayer-desktop/qmpplistwindow.cpp index 7185e5e..4fa9f50 100644 --- a/qmidiplayer-desktop/qmpplistwindow.cpp +++ b/qmidiplayer-desktop/qmpplistwindow.cpp @@ -210,15 +210,15 @@ void qmpPlistWindow::on_pbRepeat_clicked() switch(repeat) { case 0: - ui->pbRepeat->setIcon(QIcon(":/img/repeat-non.svg")); + ui->pbRepeat->setIcon(QIcon(getThemedIcon(":/img/repeat-non.svg"))); ui->pbRepeat->setText(tr("Repeat Off")); break; case 1: - ui->pbRepeat->setIcon(QIcon(":/img/repeat-one.svg")); + ui->pbRepeat->setIcon(QIcon(getThemedIcon(":/img/repeat-one.svg"))); ui->pbRepeat->setText(tr("Repeat One")); break; case 2: - ui->pbRepeat->setIcon(QIcon(":/img/repeat-all.svg")); + ui->pbRepeat->setIcon(QIcon(getThemedIcon(":/img/repeat-all.svg"))); ui->pbRepeat->setText(tr("Repeat All")); break; } @@ -230,12 +230,12 @@ void qmpPlistWindow::on_pbShuffle_clicked() switch(shuffle) { case 1: - ui->pbShuffle->setIcon(QIcon(":/img/shuffle.svg")); + ui->pbShuffle->setIcon(QIcon(getThemedIcon(":/img/shuffle.svg"))); ui->pbShuffle->setText(tr("Shuffle On")); break; case 0: default: - ui->pbShuffle->setIcon(QIcon(":/img/shuffle-off.svg")); + ui->pbShuffle->setIcon(QIcon(getThemedIcon(":/img/shuffle-off.svg"))); ui->pbShuffle->setText(tr("Shuffle Off")); break; } -- cgit v1.2.3