/* * Copyright (C) 2002 - David W. Durham * * This file is part of ReZound, an audio editing application. * * ReZound is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2 of the License, * or (at your option) any later version. * * ReZound is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ /* * Information about MIDI's Sample Dump Specification obtained from: * http://www.borg.com/~jglatt/tech/sds.htm */ #include "CMIDISDSSoundTranslator.h" #include #include #include #include #include #include #include #include #include //#include #include #include #include "AStatusComm.h" #include "AFrontendHooks.h" #include "CSound.h" //#define DEBUG_PRINT CMIDISDSSoundTranslator::CMIDISDSSoundTranslator() { } CMIDISDSSoundTranslator::~CMIDISDSSoundTranslator() { } // for MIDI bytes (3 7bit bytes to native int) static int bytesToInt(unsigned char bh,unsigned char bm,unsigned char bl) { return (((int)(bh&0x7f))<<7<<7) + (((int)(bm&0x7f))<<7) + ((int)(bl&0x7f)); } static int full_read(int fd,unsigned char *buffer,int len) { int l=0; while(l0) { // check all bytes read if they match the pattern; as soon as one byte doesn't match, start looking for the beginning again for(int t=0;tmsTimeout) { // timed-out for good fcntl(fd,F_SETFL,origFlags); delete statusBar; return patternPos; } if(statusBar && statusBar->update(tmp++%2)) { // cancelled fcntl(fd,F_SETFL,origFlags); delete statusBar; return -1; } } } // restore original flags fcntl(fd,F_SETFL,origFlags); delete statusBar; return patternPos; } catch(...) { fcntl(fd,F_SETFL,origFlags); delete statusBar; throw; } } // ??? could just return a CSound object and have used the one constructor that takes the meta info // ??? but, then how would I be able to have createWorkingPoolFileIfExists bool CMIDISDSSoundTranslator::onLoadSound(const string filename,CSound *sound) const { int sysExChannel=-1; const bool isDevice=CPath(filename).isDevice(); bool ret=true; #ifdef DEBUG_PRINT printf("opening: %s\n",filename.c_str());fflush(stdout); #endif int fd=open(filename.c_str(),isDevice ? O_RDWR : O_RDONLY); if(fd==-1) throw runtime_error(string(__func__)+" -- error opening file: "+filename+" -- "+strerror(errno)); try { unsigned char buffer[256]; int l; // if we're dealing with a device, then prompt the user for which sample Id to request, otherwise just wait for one bool sampleDumpRequestSent=false; // use this flag to know if we should timeout after not getting anything if(isDevice) { int waveformId=-1; if(!gFrontendHooks->promptForOpenMIDISampleDump(sysExChannel,waveformId)) { close(fd); return false; } if(waveformId>16384) throw runtime_error(string(__func__)+" -- waveform IDs must be 0 to 16384"); if(waveformId>=0) { // send DUMP REQUEST unsigned char buffer[]={0xf0,0x7e,0,0x3,0,0,0xf7}; buffer[2]=sysExChannel; buffer[4]=waveformId&0x7f; // bits 0 to 6 buffer[5]=(waveformId>>7)&0x7f; // bits 7 to 13 if(write(fd,buffer,7)!=7) { const int err=errno; throw runtime_error(string(__func__)+" -- error writing SAMPLE DUMP request to device -- perhaps the disk is full -- "+strerror(err)); } sampleDumpRequestSent=true; } } /* { unsigned char buffer[256]; int l; l=full_read(fd,buffer,21); printf("l: %d\n",l);fflush(stdout); print_hex(buffer,21); return false; } */ // wait for "Dump Header" pattern: F0 7E cc 01 sl sh ee pl pm ph gl gm gh hl hm hh il im ih jj F7 static const int dumpHeaderPattern[]={0xf0,0x7e,-1,0x01,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0xf7}; static const int dumpHeaderPatternLen=sizeof(dumpHeaderPattern)/sizeof(*dumpHeaderPattern); // I would do the line below, but for some dumb reason if I instantiate a CStatusBar and (select or poll)/read on fd, then it drops data reading. I even used ltrace -S to see if I could see FOX inadvertantly reading from fd. I did find that if I instantated the CStatusBar BEFORE I opened fd then it worked. Anyway, for now I'm just passing NULL for the message so that a statusBar won't be created when a DUMP REQUEST was sent //l=wait_for_pattern(fd,buffer,dumpHeaderPattern,dumpHeaderPatternLen,sampleDumpRequestSent ? 2*1000 : 3600*1000,"Waiting For Dump"); if(sampleDumpRequestSent) l=wait_for_pattern(fd,buffer,dumpHeaderPattern,dumpHeaderPatternLen,2*1000,NULL); else l=wait_for_pattern(fd,buffer,dumpHeaderPattern,dumpHeaderPatternLen,3600*1000,"Waiting For Dump"); if(l!=dumpHeaderPatternLen) { if(l==-1) { // cancelled close(fd); return false; } if(sampleDumpRequestSent) { // ??? a CANCEL msg might come if the sample doesn't exist, so check for that too if possible? Error("Sample Dump Request Failed -- that waveform ID may not exist"); close(fd); return false; } else throw runtime_error(string(__func__)+" -- error while waiting for Dump Header -- only read "+istring(l)+" bytes"); } // expecting: F0 7E cc 01 sl sh ee pl pm ph gl gm gh hl hm hh il im ih jj F7 (just a double-check) if(!(buffer[0]==0xf0 && buffer[1]==0x7e && buffer[3]==0x01 && buffer[20]==0xf7)) { #ifdef DEBUG_PRINT print_hex(buffer,21); #endif throw runtime_error(string(__func__)+" -- invalid data in expected Dump Header"); } sysExChannel=buffer[2]; const int waveformId=bytesToInt(0,buffer[5],buffer[4]); const int bitRate=buffer[6]; const unsigned sampleRate= 1000000000/bytesToInt(buffer[9],buffer[8],buffer[7]); // may need to take into account the bitRate when it's not 16 ??? const sample_pos_t length=bytesToInt(buffer[12],buffer[11],buffer[10]); // offset in words // may need to take into account the bitRate when it's not 16 ??? const sample_pos_t loopStart=bytesToInt(buffer[15],buffer[14],buffer[13]); const sample_pos_t loopStop=bytesToInt(buffer[18],buffer[17],buffer[16]); const int loopType=buffer[19]; //#ifdef DEBUG_PRINT printf("---------\n"); printf("sysExChannel: %d\n",sysExChannel); printf("waveformId: %d\n",waveformId); printf("bitRate: %d\n",bitRate); printf("sampleRate: %d\n",sampleRate); printf("length: %d\n",length); printf("loopStart: %d\n",loopStart); printf("loopStop: %d\n",loopStop); printf("loopType: %d\n",loopType); printf("---------\n"); //#endif if(sampleRate<4000 || sampleRate>96000) throw runtime_error(string(__func__)+" -- an unlikely sample rate of "+istring(sampleRate)); if(bitRate!=16) throw runtime_error(string(__func__)+" -- only 16bit data supported, not "+istring(bitRate)+"bit -- I have no way of testing non-16bit data, so contact me if you have a device that can test it"); if(isDevice) { // send WAIT message #ifdef DEBUG_PRINT printf("header received... sending WAIT\n"); #endif unsigned char buffer[]={0xf0,0x7e,0,0x7c,0,0xf7}; buffer[2]=sysExChannel; buffer[4]=0; write(fd,buffer,6); } #ifdef DEBUG_PRINT printf("creating working file\n"); #endif sound->createWorkingPoolFile(filename,sampleRate,1,length); // add some labelled cues about the loop points sound->addCue("Loop Start",loopStart,false); sound->addCue("Loop Stop",loopStop,false); // set some misc data for later saving { TPoolAccesser a=sound->getGeneralDataAccesser("SDS Data"); a.clear(); a.append(3); a[0]=sysExChannel; a[1]=waveformId; a[2]=loopType; } if(isDevice) { // send ACK message to continue after the WAIT #ifdef DEBUG_PRINT printf("sending ACK\n"); #endif unsigned char buffer[]={0xf0,0x7e,0,0x7f,0,0xf7}; buffer[2]=sysExChannel; buffer[4]=0; write(fd,buffer,6); } #ifdef DEBUG_PRINT printf("reading data...\n"); #endif CStatusBar statusBar(_("Downloading Sample"),0,length,true); sample_pos_t lengthRead=0; int expectedPacketNumber=0; while(lengthReadgetAudio(0); sample_pos_t k=0; unsigned char *ptr=buffer+5; for(int t=0;t<120 && lengthRead>5); dest[lengthRead++]=convert_sample(s-0x8000); } // could perform checksum verification } if(isDevice) { // send ACK message #ifdef DEBUG_PRINT printf("sending ACK\n"); #endif unsigned char buffer[]={0xf0,0x7e,0,0x7f,0,0xf7}; buffer[2]=sysExChannel; buffer[4]=expectedPacketNumber; write(fd,buffer,6); } if(statusBar.update(lengthRead)) { // cancelled if(lengthRead<=0) ret=false; else Message(_("Keeping what was received")); if(isDevice) { // send CANCEL message unsigned char buffer[6]={0xf0,0x7e,0,0x7d,0,0xf7}; buffer[2]=sysExChannel; buffer[4]=expectedPacketNumber; write(fd,buffer,6); } break; } // next packet expectedPacketNumber= (expectedPacketNumber+1)%128; } close(fd); return ret; } catch(...) { if(isDevice && sysExChannel>=0) { // send CANCEL message unsigned char buffer[6]={0xf0,0x7e,0,0x7d,0,0xf7}; buffer[2]=sysExChannel; buffer[4]=0; write(fd,buffer,6); } close(fd); throw; } } bool CMIDISDSSoundTranslator::onSaveSound(const string filename,const CSound *sound,const sample_pos_t saveStart,const sample_pos_t saveLength,bool useLastUserPrefs) const { int sysExChannel=-1; int packetSeq=0; const bool isDevice=CPath(filename).isDevice(); bool ret=true; #ifdef DEBUG_PRINT printf("opening: %s\n",filename.c_str()); #endif int fd=open(filename.c_str(),!isDevice ? O_RDWR|O_CREAT|O_TRUNC : O_WRONLY); if(fd==-1) throw runtime_error(string(__func__)+" -- error opening file: "+filename+" -- "+strerror(errno)); try { unsigned char buffer[256]; int l; // prompt for some necessary things sysExChannel= sound->containsGeneralDataPool("SDS Data") ? sound->getGeneralDataAccesser("SDS Data")[0] : -1; int waveformId= sound->containsGeneralDataPool("SDS Data") ? sound->getGeneralDataAccesser("SDS Data")[1] : -1; int loopType= sound->containsGeneralDataPool("SDS Data") ? sound->getGeneralDataAccesser("SDS Data")[2] : -1; int dumpChannel=0; // ??? which channel in the audio file to dump to the receiver if(!gFrontendHooks->promptForSaveMIDISampleDump(sysExChannel,waveformId,loopType)) { close(fd); return false; } size_t cueIndex; resendHeader: // build dump header information buffer[0]=0xf0; buffer[1]=0x7e; buffer[2]=sysExChannel; // cc buffer[3]=0x01; buffer[4]=waveformId&0x7f; // sl buffer[5]=(waveformId>>7)&0x7f; // sh buffer[6]=16; // ee const int samplePeriod=1000000000/sound->getSampleRate(); buffer[7]=samplePeriod&0x7f; // pl buffer[8]=(samplePeriod>>7)&0x7f; // pm buffer[9]=(samplePeriod>>14)&0x7f; // ph const sample_pos_t length=saveLength; // should check size limit ??? buffer[10]=length&0x7f; // gl buffer[11]=(length>>7)&0x7f; // gm buffer[12]=(length>>14)&0x7f; // gh const sample_pos_t loopStart=sound->containsCue("Loop Start",cueIndex) ? sound->getCueTime(cueIndex)-saveStart : 0; buffer[13]=loopStart&0x7f; // hl buffer[14]=(loopStart>>7)&0x7f; // hm buffer[15]=(loopStart>>14)&0x7f; // hh const sample_pos_t loopStop=sound->containsCue("Loop Stop",cueIndex) ? sound->getCueTime(cueIndex)-saveStart : saveLength-1; buffer[16]=loopStop&0x7f; // il buffer[17]=(loopStop>>7)&0x7f; // im buffer[18]=(loopStop>>14)&0x7f; // ih buffer[19]=loopType; // jj buffer[20]=0xf7; // write dump header l=write(fd,buffer,21); if(l!=21) { int err=errno; throw runtime_error(string(__func__)+" -- error writing DUMP HEADER -- "+strerror(err)); } if(isDevice) { // wait for up to 2 seconds for a handshaking response int waitTime=2 *1000; waitForResponse: // expect either an ACK, NAK, WAIT or CANCEL static int dataPacketPattern[]={0xf0,0x7e,0,-1,-1,0xf7}; dataPacketPattern[2]=sysExChannel; static const int dataPacketPatternLen=sizeof(dataPacketPattern)/sizeof(*dataPacketPattern); l=wait_for_pattern(fd,buffer,dataPacketPattern,dataPacketPatternLen,waitTime); if(l!=dataPacketPatternLen) { if(l==0 && waitTime==(2 *1000)) // 2 seconds are up, so start a non-handshaking dump goto startDumping; throw runtime_error(string(__func__)+" -- invalid or no response after sending DUMP HEADER"); } /* not that important I guess if(buffer[4]!=0) throw runtime_error(string(__func__)+" -- invalid packet sequence in response from DUMP HEADER"); */ if(buffer[3]==0x7f) { // ACK #ifdef DEBUG_PRINT printf("DUMP HEADER -- ACK\n"); #endif goto startDumping; } else if(buffer[3]==0x7e) { // NAK #ifdef DEBUG_PRINT printf("DUMP HEADER -- NAK\n"); #endif goto resendHeader; } else if(buffer[3]==0x7d) { // CANCEL #ifdef DEBUG_PRINT printf("DUMP HEADER -- CANCEL\n"); #endif close(fd); return false; } else if(buffer[3]==0x7c) { // WAIT #ifdef DEBUG_PRINT printf("DUMP HEADER -- WAIT\n"); #endif waitTime=3600 *1000; // wait longer now for the same things goto waitForResponse; } else throw runtime_error(string(__func__)+" -- invalid response from DUMP HEADER -- "+istring((int)buffer[3])); } startDumping: const CRezPoolAccesser src=sound->getAudio(dumpChannel); #define SAMPLES_PER_PACK 40 // only 40 16bit samples can fit in the packed 120 7bit bytes const int lastPacketSeq=saveLength/SAMPLES_PER_PACK; CStatusBar statusBar(isDevice ? _("Uploading Sample") : _("Dumping Sample"),0,lastPacketSeq,true); while(packetSeq<=lastPacketSeq) { buffer[0]=0xf0; buffer[1]=0x7e; buffer[2]=sysExChannel; buffer[3]=0x02; buffer[4]=packetSeq%128; const int len=packetSeq(src[saveStart+(packetSeq*SAMPLES_PER_PACK)+t])+0x8000; (*(b++))=(s>>9)&0x7f; (*(b++))=(s>>2)&0x7f; (*(b++))=s&0x3; } // pad with zeros if necessary for(int t=len;t=0) { // send CANCEL message unsigned char buffer[6]={0xf0,0x7e,0,0x7d,0,0xf7}; buffer[2]=sysExChannel; buffer[4]=packetSeq; write(fd,buffer,6); } ret=false; break; } packetSeq++; } close(fd); } catch(...) { if(isDevice && sysExChannel>=0) { // send CANCEL message unsigned char buffer[6]={0xf0,0x7e,0,0x7d,0,0xf7}; buffer[2]=sysExChannel; buffer[4]=packetSeq; write(fd,buffer,6); } close(fd); throw; } if(!ret && !isDevice) unlink(filename.c_str()); // remove the cancelled file return ret; } bool CMIDISDSSoundTranslator::handlesExtension(const string extension,const string filename) const { CPath path(filename); // *.sds or path is a device and the filename part starts with "midi" return extension=="sds" || (path.exists() && path.isDevice() && path.baseName().substr(0,4)=="midi"); } bool CMIDISDSSoundTranslator::supportsFormat(const string filename) const { printf("file: %s\n",filename.c_str()); bool ret=false; // this won't get called from ASoundTranslator::findTranslator if filename is a device // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // should start with "F0 7E xx 01 xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx F7" FILE *f=fopen(filename.c_str(),"r"); if(f!=NULL) { uint8_t buffer[21]={0}; fread(buffer,21,1,f); if(buffer[0]==0xf0 && buffer[1]==0x7e && buffer[3]==0x01 && buffer[20]==0xf7) ret=true; fclose(f); } // perhaps if isDevice, then send a NAK and it will resend the data, if not a device, then we can seek // we'd have to know the waveformId and request it or wait for something.. but if it wasn't a MIDI // then attempting to read a header would mess things up unless we could rewind some return ret; } const vector CMIDISDSSoundTranslator::getFormatNames() const { vector names; names.push_back("MIDI Sample Dump Standard"); return names; } const vector > CMIDISDSSoundTranslator::getFormatFileMasks() const { vector > list; vector fileMasks; fileMasks.clear(); fileMasks.push_back("*.sds"); fileMasks.push_back("midi*"); list.push_back(fileMasks); return list; }