aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Chris Xiong <chirs241097@gmail.com> 2017-06-22 11:26:48 +0800
committerGravatar Chris Xiong <chirs241097@gmail.com> 2017-06-22 11:26:48 +0800
commitb03133b80b268c74d1dd5c92e2af6907b51c91b2 (patch)
tree2700bf29cecd5ae89fd7d99e3d6acb88ebb44e61
parenta97307ba6e625468a1d8ad1049e6d9db0ad42d4d (diff)
downloadQMidiPlayer-b03133b80b268c74d1dd5c92e2af6907b51c91b2.tar.xz
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.
-rw-r--r--ChangeLog7
-rw-r--r--README.md6
-rw-r--r--core/qmpmidioutrtmidi.cpp2
-rw-r--r--core/qmpmidiplay.cpp26
-rw-r--r--core/qmpmidiread.cpp92
-rw-r--r--doc/APIdoc.md28
-rw-r--r--midifmt-plugin/midifmtplugin.cpp5
-rw-r--r--qmidiplayer-desktop/qmpmainwindow.cpp2
-rw-r--r--qmidiplayer-desktop/qmpplistwindow.cpp10
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;c<len;++c)
- {
- ++byteread;if(str)str[c]=fgetc(f);else fgetc(f);
- }
+ if(str)str[c]=fgetc(f);else fgetc(f);
if(str)str[c]='\0';
curTrack->appendEvent(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;c<len;++c){++byteread;str[c]=fgetc(f);}
+ for(c=1;c<len;++c){str[c]=fgetc(f);}
}
- else for(c=0;c<len;++c){++byteread;str[c]=fgetc(f);}
+ else for(c=0;c<len;++c){str[c]=fgetc(f);}
curTrack->appendEvent(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(/*byteread<chnklen&&*/eventReader());
+ int chnklen=readDW();byteread=ftell(f);curt=0;curid=0;
+ while(eventReader());
+ byteread=ftell(f)-byteread;
if(byteread<chnklen)
{
- error(0,"W: Extra bytes after EOT event.");
- while(byteread<chnklen){fgetc(f);++byteread;}
+ error(0,"Extra bytes after EOT event");
+ for(;byteread<chnklen;++byteread)fgetc(f);
}
- /*if(byteread>chnklen)
- {
- 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(;byteread<chnklen;++byteread){fgetc(f);}
+ if(ret->divs&0x8000)error(1,"SMTPE format is not supported");
+ for(byteread=ftell(f)-byteread;byteread<chnklen;++byteread){fgetc(f);}
}
int CSMFReader::chunkReader(int hdrXp)
{
char hdr[6];
fread(hdr,1,4,f);
- if(feof(f))error(1,"E: Unexpected EOF.");
+ if(feof(f))error(1,"Unexpected EOF");
if(hdrXp)
{
if(!strncmp(hdr,"RIFF",4))
{
fseek(f,4,SEEK_CUR);
fread(hdr,1,4,f);
- if(strncmp(hdr,"RMID",4)){error(1,"E: Wrong file type in RIFF container.");throw std::runtime_error("Wrong file type in RIFF container");}
+ if(strncmp(hdr,"RMID",4)){error(1,"Wrong file type in RIFF container");}
fseek(f,8,SEEK_CUR);
fread(hdr,1,4,f);
}
- if(strncmp(hdr,"MThd",4)){error(1,"E: Wrong MIDI header.");throw std::runtime_error("Wrong MIDI header");}
+ if(strncmp(hdr,"MThd",4)){error(1,"Wrong MIDI header.");}
else return headerChunkReader(),0;
}
else
if(strncmp(hdr,"MTrk",4))
{
- error(0,"W: Wrong track chunk header. Ignoring the whole chunk.");
- for(int chnklen=readDW();chnklen>0;--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;i<trk;i+=chunkReader(0));
fclose(f);f=NULL;
}
- catch(std::runtime_error&){fprintf(stderr,"E: %s is not a supported file.\n",fn);ret->valid=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<SEvent> 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;
}