/* * 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 */ #include "CSound.h" #include "AStatusComm.h" #include #include // ??? just for console info printfs #include #include #include #include "settings.h" #include "unit_conv.h" // for seconds_to_string() /* TODO: - add try and catch around space modifier methods that call matchUpChannelLengths even upon exception - create MAX_LENGTH for an arbitrary maximum length incase I need to enforce such a limit - I need to rework the bounds checking not to ever add where+len since it could overflow, but do it correctly where it should never have a problem - make some macros for doing this since I do it in several places - I have already fixed some of the methods - the peak chunk mantainance is not absolutly perfect... if I delete a portion of the data and thus remove some of the entries in the peak chunk data, the min and maxes aren't exactly correct now because the PEAK_CHUNK_SIZE samples which were calculated for teh originaly data is now scewed a little on top of the new data... normally not enough to worry about in just a frontend rendering of it... However overtime this error in the peak chunk data accumulates which is why I have a refresh button which recalculates all the peak data. Perhaps there is some other method which could better reduce or eliminate this error without completely recaclulating the peak chunk data anytime an operation is done on a channel... perhaps with undo, I could store the peak chunk data in temp pools just as I did the audio data and could just as readily restore it on undo */ // ??? I could just set peakChunk data for space that I know I just silenced to min=max=0 and not bother setting it dirty #define PEAK_CHUNK_SIZE 500 // ??? probably check a static variable that doesn't require a lock if the application is not threaded #define ASSERT_RESIZE_LOCK \ if(!poolFile.isExclusiveLocked()) \ throw(runtime_error(string(__func__)+" -- this CSound object has not been locked for resize")); #define ASSERT_SIZE_LOCK \ if(!poolFile.isExclusiveLocked() && poolFile.getSharedLockCount()<=0) \ throw(runtime_error(string(__func__)+" -- this CSound object's size has not been locked")); // current pool names for the current version #define FORMAT_INFO_POOL_NAME "Format Info" #define AUDIO_POOL_NAME "Channel " #define PEAK_CHUNK_POOL_NAME "PeakChunk " #define TEMP_AUDIO_POOL_NAME "TempAudioPool_" #define CUES_POOL_NAME "Cues" #define NOTES_POOL_NAME "UserNotes" #define GET_WORKING_FILENAME(workDir,filename) (workDir+CPath::dirDelim+CPath(filename).baseName()+".pf$") CSound::CSound() : poolFile(REZOUND_POOLFILE_BLOCKSIZE,REZOUND_WORKING_POOLFILE_SIGNATURE), size(0), sampleRate(0), channelCount(0), metaInfoPoolID(0), tempAudioPoolKeyCounter(1), _isModified(true), cueAccesser(NULL), adjustCuesOnSpaceChanges(true) { for(unsigned t=0;t0) unlockSize(); */ if(poolFile.isOpen()) { deletePeakChunkAccessers(); deleteCueAccesser(); poolFile.closeFile(false,false); } } void CSound::changeWorkingFilename(const string newOriginalFilename) { const string workDir=CPath(poolFile.getFilename()).dirName(); poolFile.rename(GET_WORKING_FILENAME(workDir,newOriginalFilename)); } void CSound::closeSound() { // ??? probably should get a lock? deletePeakChunkAccessers(); deleteCueAccesser(); poolFile.closeFile(false,true); } // locks to keep the size from changing (multiple locks can be obtained of this type) void CSound::lockSize() const { poolFile.sharedLock(); } bool CSound::trylockSize() const { return(poolFile.sharedTrylock()); } void CSound::unlockSize() const { poolFile.sharedUnlock(); } // locks to be able to change the size (only one lock can be obtained of this type) void CSound::lockForResize() const { poolFile.exclusiveLock(); } bool CSound::trylockForResize() const { return(poolFile.exclusiveTrylock()); } void CSound::unlockForResize() const { poolFile.exclusiveUnlock(); } CSound::CInternalRezPoolAccesser CSound::getAudioInternal(unsigned channel) { return(poolFile.getPoolAccesser(channelPoolIDs[channel])); } CSound::CInternalRezPoolAccesser CSound::getTempDataInternal(unsigned tempAudioPoolKey,unsigned channel) { return(poolFile.getPoolAccesser(createTempAudioPoolName(tempAudioPoolKey,channel))); } CRezPoolAccesser CSound::getAudio(unsigned channel) { if(channel>=channelCount) throw(runtime_error(string(__func__)+" -- invalid channel: "+istring(channel))); return(poolFile.getPoolAccesser(channelPoolIDs[channel])); } const CRezPoolAccesser CSound::getAudio(unsigned channel) const { if(channel>=channelCount) throw(runtime_error(string(__func__)+" -- invalid channel: "+istring(channel))); return(poolFile.getPoolAccesser(channelPoolIDs[channel])); } CRezPoolAccesser CSound::getTempAudio(unsigned tempAudioPoolKey,unsigned channel) { return(poolFile.getPoolAccesser(createTempAudioPoolName(tempAudioPoolKey,channel))); } const CRezPoolAccesser CSound::getTempAudio(unsigned tempAudioPoolKey,unsigned channel) const { return(poolFile.getPoolAccesser(createTempAudioPoolName(tempAudioPoolKey,channel))); } /* * - This method gets the peak chunk information between [dataPos,nextDataPos) * where dataPos and nextDataPos are positions in the sample data. * - That is, the min and max sample value between those data positions. * - This information is used to render the waveform data on screen. * * - This information is stored in a pool of RPeakChunk struct objects in the * pool file for this sound object. And a min and max is stored for every * PEAK_CHUNK_SIZE samples. * - The RPeakChunk struct has a bool flag, 'dirty', that get's set to true * for every chunk that we need to re-calculate the min and max for. This * is necessary when perhaps an action modifies the data, so the view on * screen also needs to change. * * - Sometimes it reads this precalculated peak chunk information, and sometimes * it reads the data directly. If the nextDataPos-dataPos < PEAK_CHUNK_SIZE, then * we should just read the data normally and find the min and max on the fly. * Otherwise, we can use the precalculated information. * * - When using the precalculated information: * - I could simply return peakChunks[floor(dataPos/PEAK_CHUNK_SIZE)], but I * don't since the next time getPeakValue is called, it may have skipped over * more than a chunk size, so the min and max would not necessarily be accurate. * - So, I also have the caller pass in the dataPos of the next time this method * will be called and I combine the mins and maxes of all the chunks that will * be skipped over by the next call. * * - Also, for practical reasons, it is okay if nextDataPos is >= getLength(), it's really * 'what WOULD be the next data position' not than it will be necessarily called with that * position */ RPeakChunk CSound::getPeakData(unsigned channel,sample_pos_t dataPos,sample_pos_t nextDataPos,const CRezPoolAccesser &dataAccesser) const { if(channel>=channelCount) throw(runtime_error(string(__func__)+" -- channel parameter is out of change: "+istring(channel))); /* if(dataPos==getLength()-1) { RPeakChunk p; p.min=MIN_SAMPLE; p.max=MAX_SAMPLE; return(p); } */ // I could check the bounds in dataPos and nextDataPos, but I'll just let the caller have already done that if((nextDataPos-dataPos)dataAccesser.getSize()) nextDataPos=dataAccesser.getSize(); for(sample_pos_t t=dataPos+1;tpeakChunkAccesser.getSize()) lastChunk=peakChunkAccesser.getSize(); // - we combine all the mins and maxes of the peak chunk information between [firstChunk and lastChunk) // - Also, if any chunk is dirty along the way, we recalculate it for(sample_pos_t t=firstChunk;tgetLength()) end=getLength(); sample_t _min=dataAccesser[start]; sample_t _max=dataAccesser[start]; for(sample_pos_t i=start+1;i=channelCount) throw(runtime_error(string(__func__)+" -- channel parameter is out of change: "+istring(channel))); // ??? check ranges of start and stop CPeakChunkRezPoolAccesser &peakChunkAccesser=*(peakChunkAccessers[channel]); // fudge one peak chunk size longer if(stopgetLength()-1) stop=getLength()-1; } const sample_pos_t firstChunk=start/PEAK_CHUNK_SIZE; const sample_pos_t lastChunk=stop/PEAK_CHUNK_SIZE; for(sample_pos_t t=firstChunk;t<=lastChunk;t++) peakChunkAccesser[t].dirty=true; } void CSound::invalidatePeakData(const bool doChannel[MAX_CHANNELS],sample_pos_t start,sample_pos_t stop) { for(sample_pos_t t=0;tMAX_CHANNELS) throw(runtime_error(string(__func__)+" -- adding another channel would exceed the maximum of "+istring(MAX_CHANNELS)+" channels")); const string audioPoolName=AUDIO_POOL_NAME+istring(channelCount+1); const string peakChunkPoolName=PEAK_CHUNK_POOL_NAME+istring(channelCount); peakChunkAccessers[channelCount]=NULL; bool addedToChannelCount=false; try { CInternalRezPoolAccesser audioPool=poolFile.createPool(audioPoolName); channelPoolIDs[channelCount]=poolFile.getPoolIdByName(audioPoolName); CPeakChunkRezPoolAccesser peakChunkPool=poolFile.createPool(peakChunkPoolName); peakChunkAccessers[channelCount]=new CPeakChunkRezPoolAccesser(peakChunkPool); channelCount++; addedToChannelCount=true; if(addAudioSpaceForNewChannel) matchUpChannelLengths(getLength(),doZeroData); } catch(...) { // attempt to recover if(addedToChannelCount) channelCount--; poolFile.removePool(audioPoolName,false); delete peakChunkAccessers[channelCount]; poolFile.removePool(peakChunkPoolName,false); throw; } saveMetaInfo(); } void CSound::addChannels(unsigned where,unsigned count,bool doZeroData) { ASSERT_RESIZE_LOCK if(where>getChannelCount()) throw(runtime_error(string(__func__)+" -- where out of range: "+istring(where)+">"+istring(getChannelCount()))); if((count+getChannelCount())>MAX_CHANNELS) throw(runtime_error(string(__func__)+" -- adding "+istring(count)+" channels would exceed the maximum of "+istring(MAX_CHANNELS)+" channels")); /* * This way may seem a little obtuse, but it's the easiest way without * renaming pools, regetting poolIds, invalidating any outstanding * accessors (because now their poolIds are invalid) and stuff.. simply * add a channel to the end and move it where it needs to be and do this * for each channel to add */ size_t swapCount=getChannelCount()-where; for(unsigned t=0;tgetChannelCount()) throw(runtime_error(string(__func__)+" -- where out of range: "+istring(where)+">"+istring(getChannelCount()))); if(count>(getChannelCount()-where)) throw(runtime_error(string(__func__)+" -- where/count out of range: "+istring(where)+"/"+istring(count))); if(where==0 && count>=getChannelCount()) throw(runtime_error(string(__func__)+" -- removing "+istring(count)+" channels at "+istring(where)+" would cause the channel count to go to zero")); unsigned swapCount=channelCount-where-1; for(unsigned t=0;t0) { for(unsigned i=0;i=getChannelCount()) throw(runtime_error(string(__func__)+" -- whichChannels specifies to remove channel "+istring(t)+" which does not exist")); } if(removeCount>=getChannelCount()) throw(runtime_error(string(__func__)+" -- whichChannels specifies all the channels, which would cause the channel count to go to zero")); // through a series of swaps move all the channels to be removed to become the last channels in the sound removeCount=0; for(unsigned _t=getChannelCount();_t>0;_t--) { unsigned t=_t-1; if(whichChannels[t]) { for(unsigned i=t;iMAX_CHANNELS) throw(runtime_error(string(__func__)+" -- re-adding the channels specified by whichChannels would exceed the maximum of "+istring(MAX_CHANNELS)+" channels")); // make sure all the temp audio pools exist for the channels specified by whichChannels and that they have the correct length // and note that the temp pool channel number would be as if it were at the end (k), because that's where it would have been when removed in moveChannelsToTemp int k=getChannelCount(); for(unsigned t=0;tt;i--) swapChannels(i-1,i,0,getLength()); movedChannelIndex++; } } } static const bool isAllChannels(CSound *sound,const bool whichChannels[MAX_CHANNELS]) { unsigned count=0; for(unsigned t=0;tgetChannelCount();t++) if(whichChannels[t]) count++; return(count==sound->getChannelCount()); } void CSound::addSpace(sample_pos_t where,sample_pos_t length,bool doZeroData) { ASSERT_RESIZE_LOCK bool whichChannels[MAX_CHANNELS]; for(unsigned t=0;tsize) throw(runtime_error(string(__func__)+" -- where parameter out of range: "+istring(where))); /* if(length>MAX_LENGTH) throw(runtime_error(string(__func__)+" -- length parameter out of range: "+istring(length))); */ for(unsigned t=0;tsize) throw(runtime_error(string(__func__)+" -- where parameter out of range: "+istring(where))); if(length>(size-where)) throw(runtime_error(string(__func__)+" -- length parameter out of range: "+istring(length))); for(unsigned t=0;tsize) throw(runtime_error(string(__func__)+" -- where parameter out of range: "+istring(where))); if(length>(size-where)) throw(runtime_error(string(__func__)+" -- length parameter out of range: "+istring(length))); const unsigned tempAudioPoolKey=tempAudioPoolKeyCounter++; for(unsigned t=0;tsize) throw(runtime_error(string(__func__)+" -- where parameter out of range: "+istring(where))); if(length>(size-where)) throw(runtime_error(string(__func__)+" -- length parameter out of range: "+istring(length))); /* if(replaceLength>MAX_LENGTH) throw(runtime_error(string(__func__)+" -- replaceLength parameter out of range: "+istring(replaceLength))); */ const unsigned tempAudioPoolKey=tempAudioPoolKeyCounter++; for(unsigned t=0;tsize || removeLength>(size-removeWhere))) throw(runtime_error(string(__func__)+" -- removeWhere/removeLength parameter out of range: "+istring(removeWhere)+"/"+istring(removeLength))); if(moveWhere>(size-removeLength)) throw(runtime_error(string(__func__)+" -- moveWhere parameter out of range: "+istring(moveWhere)+" for removeWhere "+istring(removeLength))); for(unsigned t=0;t(stop-start)) throw(runtime_error(string(__func__)+" -- amount is greater than the distance between start and stop")); for(unsigned int i=0;i(stop-start)) throw(runtime_error(string(__func__)+" -- amount is greater than the distance between start and stop")); for(unsigned int i=0;i=getChannelCount()) throw(runtime_error(string(__func__)+" -- channelA is out of range: "+istring(channelA)+">="+istring(getChannelCount()))); if(channelB>=getChannelCount()) throw(runtime_error(string(__func__)+" -- channelB is out of range: "+istring(channelB)+">="+istring(getChannelCount()))); if(where>size) throw(runtime_error(string(__func__)+" -- where parameter out of range: "+istring(where))); if(length>(size-where)) throw(runtime_error(string(__func__)+" -- where/length parameter out of range: "+istring(where)+"/"+istring(length))); if(channelA==channelB) return; if(length<=0) return; const unsigned tempAudioPoolKeyA=tempAudioPoolKeyCounter++; const unsigned tempAudioPoolKeyB=tempAudioPoolKeyCounter++; // move data from each channel to its temp pool moveDataOutOfChannel(tempAudioPoolKeyA,channelA,where,length); moveDataOutOfChannel(tempAudioPoolKeyB,channelB,where,length); // move the data back into the channel from each temp pool but swapped (and pass true to remove the temp pool) moveDataIntoChannel(tempAudioPoolKeyA,channelA,channelB,where,length,true); moveDataIntoChannel(tempAudioPoolKeyB,channelB,channelA,where,length,true); } void CSound::addSpaceToChannel(unsigned channel,sample_pos_t where,sample_pos_t length,bool doZeroData) { if(length==0) return; CInternalRezPoolAccesser accesser=getAudioInternal(channel); const sample_pos_t peakChunkCountHave=peakChunkAccessers[channel]==NULL ? 0 : peakChunkAccessers[channel]->getSize(); const sample_pos_t peakChunkCountNeeded=calcPeakChunkCount(accesser.getSize()+length); // modify the audio data pools accesser.insert(where,length); if(doZeroData) accesser.zeroData(where,length); if(peakChunkAccessers[channel]!=NULL) { // add more peak chunks if the needed size is more than we have if(peakChunkCountNeeded>peakChunkCountHave) { const sample_pos_t insertWhere=where/PEAK_CHUNK_SIZE; const sample_pos_t insertCount=peakChunkCountNeeded-peakChunkCountHave; peakChunkAccessers[channel]->insert(insertWhere,insertCount); for(sample_pos_t t=0;tgetSize(); const sample_pos_t peakChunkCountNeeded=calcPeakChunkCount(accesser.getSize()-length); accesser.remove(where,length); if(peakChunkAccessers[channel]!=NULL) { // modify the peak data pools if the size is dropping below the required size if(peakChunkCountHave>peakChunkCountNeeded) { const sample_pos_t removeWhere=where/PEAK_CHUNK_SIZE; const sample_pos_t removeCount=peakChunkCountHave-peakChunkCountNeeded; peakChunkAccessers[channel]->remove(removeWhere,removeCount); } // make the a the peak chunk at where recalculate if((where/PEAK_CHUNK_SIZE)getSize()) (*(peakChunkAccessers[channel]))[where/PEAK_CHUNK_SIZE].dirty=true; } } void CSound::copyDataFromChannel(unsigned tempAudioPoolKey,unsigned channel,sample_pos_t where,sample_pos_t length) { CInternalRezPoolAccesser destAccesser=createTempAudioPool(tempAudioPoolKey,channel); if(length==0) return; CInternalRezPoolAccesser srcAccesser=getAudioInternal(channel); destAccesser.append(length); destAccesser.copyData(0,srcAccesser,where,length); } void CSound::moveDataOutOfChannel(unsigned tempAudioPoolKey,unsigned channel,sample_pos_t where,sample_pos_t length) { CInternalRezPoolAccesser destAccesser=createTempAudioPool(tempAudioPoolKey,channel); if(length==0) return; CInternalRezPoolAccesser srcAccesser=getAudioInternal(channel); const sample_pos_t peakChunkCountHave=peakChunkAccessers[channel]==NULL ? 0 : peakChunkAccessers[channel]->getSize(); const sample_pos_t peakChunkCountNeeded=calcPeakChunkCount(srcAccesser.getSize()-length); destAccesser.moveData(0,srcAccesser,where,length); if(peakChunkAccessers[channel]!=NULL) { // modify the peak data pools if the size is dropping below the required size if(peakChunkCountHave>peakChunkCountNeeded) { const sample_pos_t removeWhere=where/PEAK_CHUNK_SIZE; const sample_pos_t removeCount=peakChunkCountHave-peakChunkCountNeeded; peakChunkAccessers[channel]->remove(removeWhere,removeCount); } // make the a the peak chunk at where recalculate if((where/PEAK_CHUNK_SIZE)getSize()) (*(peakChunkAccessers[channel]))[where/PEAK_CHUNK_SIZE].dirty=true; } } void CSound::moveDataIntoChannel(unsigned tempAudioPoolKey,unsigned channelInTempPool,unsigned channelInAudio,sample_pos_t where,sample_pos_t length,bool removeTempAudioPool) { if(length==0) return; CInternalRezPoolAccesser destAccesser=getAudioInternal(channelInAudio); const sample_pos_t peakChunkCountHave=peakChunkAccessers[channelInAudio]==NULL ? 0 : peakChunkAccessers[channelInAudio]->getSize(); const sample_pos_t peakChunkCountNeeded=calcPeakChunkCount(destAccesser.getSize()+length); CInternalRezPoolAccesser srcAccesser=getTempDataInternal(tempAudioPoolKey,channelInTempPool); if(length>srcAccesser.getSize()) throw(runtime_error(string(__func__)+" -- length parameter out of range: "+istring(length))); destAccesser.moveData(where,srcAccesser,0,length); if(removeTempAudioPool) poolFile.removePool(createTempAudioPoolName(tempAudioPoolKey,channelInTempPool)); if(peakChunkAccessers[channelInAudio]!=NULL) { // add more peak chunks if the needed size is more than we have if(peakChunkCountNeeded>peakChunkCountHave) { const sample_pos_t insertWhere=where/PEAK_CHUNK_SIZE; const sample_pos_t insertCount=peakChunkCountNeeded-peakChunkCountHave; peakChunkAccessers[channelInAudio]->insert(insertWhere,insertCount); for(sample_pos_t t=0;t srcStretcher(src,srcWhere,src.getSize()-srcWhere,length); const sample_pos_t last=where+length; if(showProgressBar) { CStatusBar statusBar(_("Copying/Fitting Data -- Channel ")+istring(channel),where,last); for(sample_pos_t t=where;t srcStretcher(src,srcWhere,(sample_pos_t)((sample_fpos_t)length/destSampleRate*srcSampleRate),length); const sample_pos_t last=where+length; if(showProgressBar) { CStatusBar statusBar(_("Copying Data -- Channel ")+istring(channel),where,last); for(sample_pos_t t=where;t0) { CStatusBar statusBar(_("Copying Data -- Channel ")+istring(channel),0,100,false); for(sample_pos_t t=0;t<100;t++) { dest.copyData(destOffset+(t*(length/100)),src,srcWhere+(t*(length/100)),length/100); statusBar.update(t); } } dest.copyData(destOffset+(100*(length/100)),src,srcWhere+(100*(length/100)),length%100); } break; case mmAdd: if(fitSrc!=sftNone) { if(fitSrc==sftChangeRate) { TSoundStretcher srcStretcher(src,srcWhere,src.getSize()-srcWhere,length); const sample_pos_t last=where+length; if(showProgressBar) { CStatusBar statusBar(_("Mixing/Fitting Data (add) -- Channel ")+istring(channel),where,last); for(sample_pos_t t=where;t srcStretcher(src,srcWhere,(sample_pos_t)((sample_fpos_t)length/destSampleRate*srcSampleRate),length); const sample_pos_t last=where+length; if(showProgressBar) { CStatusBar statusBar(_("Mixing Data (add) -- Channel ")+istring(channel),where,last); for(sample_pos_t t=where;t srcStretcher(src,srcWhere,src.getSize()-srcWhere,length); const sample_pos_t last=where+length; if(showProgressBar) { CStatusBar statusBar(_("Mixing/Fitting Data (subtract) -- Channel ")+istring(channel),where,last); for(sample_pos_t t=where;t srcStretcher(src,srcWhere,(sample_pos_t)((sample_fpos_t)length/destSampleRate*srcSampleRate),length); const sample_pos_t last=where+length; if(showProgressBar) { CStatusBar statusBar(_("Mixing Data (subtract) -- Channel ")+istring(channel),where,last); for(sample_pos_t t=where;t srcStretcher(src,srcWhere,src.getSize()-srcWhere,length); const sample_pos_t last=where+length; if(showProgressBar) { CStatusBar statusBar(_("Mixing/Fitting Data (multiply) -- Channel ")+istring(channel),where,last); for(sample_pos_t t=where;t srcStretcher(src,srcWhere,(sample_pos_t)((sample_fpos_t)length/destSampleRate*srcSampleRate),length); const sample_pos_t last=where+length; if(showProgressBar) { CStatusBar statusBar(_("Mixing Data (multiply) -- Channel ")+istring(channel),where,last); for(sample_pos_t t=where;t srcStretcher(src,srcWhere,src.getSize()-srcWhere,length); const sample_pos_t last=where+length; if(showProgressBar) { CStatusBar statusBar(_("Mixing/Fitting Data (average) -- Channel ")+istring(channel),where,last); for(sample_pos_t t=where;t srcStretcher(src,srcWhere,(sample_pos_t)((sample_fpos_t)length/destSampleRate*srcSampleRate),length); const sample_pos_t last=where+length; if(showProgressBar) { CStatusBar statusBar(_("Mixing Data (average) -- Channel ")+istring(channel),where,last); for(sample_pos_t t=where;t // for sscanf const sample_pos_t CSound::getPositionFromTime(const string time,bool &wasInvalid) const { wasInvalid=false; sample_pos_t samplePos=0; if(istring(time).count(':')==2) { // supposedly HH:MM:SS.sssss unsigned h=0,m=0; double s=0.0; // ??? this may be a potential porting issue sscanf(time.c_str()," %u:%u:%lf ",&h,&m,&s); samplePos=(sample_pos_t)(((sample_fpos_t)h*3600.0+(sample_fpos_t)m*60.0+(sample_fpos_t)s)*(sample_fpos_t)getSampleRate()); } else if(istring(time).count(':')==1) { // supposedly MM:SS.sssss unsigned m=0; double s=0.0; // ??? this may be a potential porting issue sscanf(time.c_str()," %u:%lf ",&m,&s); samplePos=(sample_pos_t)(((sample_fpos_t)m*60.0+(sample_fpos_t)s)*(sample_fpos_t)getSampleRate()); } else if(istring(time).count(':')==0) { // supposedly SSSS.sssss double s=0.0; // ??? this may be a potential porting issue sscanf(time.c_str()," %lf ",&s); samplePos=(sample_pos_t)(((sample_fpos_t)s)*(sample_fpos_t)getSampleRate()); } else { wasInvalid=true; samplePos=0; } return(samplePos); } const string CSound::getAudioDataSize(const sample_pos_t sampleCount) const { sample_fpos_t audioDataSize=sampleCount*sizeof(sample_t)*channelCount; if(audioDataSize>=1024*1024*1024) { // return as gb return(istring(audioDataSize/1024.0/1024.0/1024.0,5,3)+"gb"); } else if(audioDataSize>=1024*1024) { // return as mb return(istring(audioDataSize/1024.0/1024.0,5,2)+"mb"); } else if(audioDataSize>=1024) { // return as kb return(istring(audioDataSize/1024.0,5,1)+"kb"); } else { // return as b return(istring((sample_pos_t)audioDataSize)+"b"); } } const string CSound::getAudioDataSize() const { return(getAudioDataSize(isEmpty() ? 0 : getAudio(0).getSize())); } const string CSound::getPoolFileSize() const { uint64_t iPoolFileSize=poolFile.getFileSize(); if(iPoolFileSize>=1024*1024*1024) { // return as gb return(istring((long double)iPoolFileSize/1024.0/1024.0/1024.0,5,3)+"gb"); } else if(iPoolFileSize>=1024*1024) { // return as mb return(istring((long double)iPoolFileSize/1024.0/1024.0,5,2)+"mb"); } else if(iPoolFileSize>=1024) { // return as kb return(istring((long double)iPoolFileSize/1024.0,5,1)+"kb"); } else { // return as b return(istring(iPoolFileSize)+"b"); } } void CSound::defragPoolFile() { lockForResize(); try { poolFile.defrag(); unlockForResize(); } catch(...) { unlockForResize(); throw; } } void CSound::printSAT() { poolFile.printSAT(); } void CSound::verifySAT() { poolFile.verifyAllBlockInfo(false); } void CSound::flush() { poolFile.flushData(); } /* Finds a working directory for a working file for the given filename. A suitable working directory should be writable and have free space of 110% of the given file's size */ #if defined(rez_OS_SOLARIS) #include #define statfs statvfs #elif defined(rez_OS_BSD) #include /* I think there's a bug in sys/ucred.h because NGROUPS isn't defined by any previous include, so I include this to get it defined */ #include #elif defined(rez_OS_LINUX) #include #else #error complier error imminent no xxxfs.h has been included #endif #include #include #include const string findWorkDir(const string filename) { vector workingDirs; if(gPrimaryWorkDir!="" && CPath(gPrimaryWorkDir).isDirectory()) { workingDirs.push_back(CPath(gPrimaryWorkDir).realPath()); } workingDirs.push_back(CPath(filename).dirName()); // first try the dirname of the given filename workingDirs.push_back(gFallbackWorkDir); // next try to fallback working dir (in the future, this may be a list) for(size_t t=0;tMAX_CHANNELS) throw(runtime_error(string(__func__)+" -- invalid number of channels: "+istring(_channelCount))); channelCount=_channelCount; sampleRate=_sampleRate; size=_size; // determine a suitable place for the working file const string workDir=findWorkDir(originalFilename); const string workingFilename=GET_WORKING_FILENAME(workDir,originalFilename); PoolFile_t::removeFile(workingFilename); poolFile.openFile(workingFilename,true); removeAllTempAudioPools(); CFormatInfoPoolAccesser a=poolFile.createPool(FORMAT_INFO_POOL_NAME); metaInfoPoolID=poolFile.getPoolIdByName(FORMAT_INFO_POOL_NAME); a.append(1); // create an audio pool for each channel for(unsigned t=0;t(poolName); channelPoolIDs[t]=poolFile.getPoolIdByName(poolName); a1.append(size); } // create all dirty peakChunks after we know the data size createPeakChunkAccessers(); createCueAccesser(); /*??? maybe could speed things up by not zeroing the data here */ matchUpChannelLengths(NIL_SAMPLE_POS); saveMetaInfo(); } bool CSound::createFromWorkingPoolFileIfExists(const string originalFilename,bool promptIfFound) { if(poolFile.isOpen()) throw(runtime_error(string(__func__)+" -- poolFile is already opened")); try { vector workingDirs; workingDirs.push_back(CPath(originalFilename).dirName()); workingDirs.push_back(gFallbackWorkDir); // look in all possible working spaces string workingFilename=""; for(size_t t=0;t2gb PoolFile_t::removeFile(workingFilename); return(false); } } poolFile.openFile(workingFilename,false); _isModified=true; removeAllTempAudioPools(); deletePeakChunkAccessers(); deleteCueAccesser(); // now that file is successfully opened, ask user if they want // to try to pick up where they left off or forget all edit // history metaInfoPoolID=poolFile.getPoolIdByName(FORMAT_INFO_POOL_NAME); // check version at the beginning of RFormat and perhaps handle things differently uint32_t version=0xffffffff; poolFile.readPoolRaw(metaInfoPoolID,&version,sizeof(version)); if(version==1) { const CFormatInfoPoolAccesser a=poolFile.getPoolAccesser(metaInfoPoolID); RFormatInfo r; r=a[0]; if(r.size>MAX_LENGTH) { // ??? what should I do? truncate the sound or just error out? } size=r.size; // actually overwritten by matchUpChannelLengths sampleRate=r.sampleRate; channelCount=r.channelCount; } else throw(runtime_error(string(__func__)+" -- unhandled format version: "+istring(version)+" in found working file: "+workingFilename)); if(channelCount<0 || channelCount>MAX_CHANNELS) { deletePeakChunkAccessers(); deleteCueAccesser(); poolFile.closeFile(false,false); throw(runtime_error(string(__func__)+" -- invalid number of channels: "+istring(channelCount)+" in found working file: "+workingFilename)); } for(unsigned t=0;t(metaInfoPoolID); if(b.getSize()==1) { RFormatInfo &r=b[0]; // always write the newest format r.version=1; r.size=size; r.sampleRate=sampleRate; r.channelCount=channelCount; } else { RFormatInfo r; // always write the newest format r.version=1; r.size=size; r.sampleRate=sampleRate; r.channelCount=channelCount; poolFile.clearPool(metaInfoPoolID); poolFile.setPoolAlignment(metaInfoPoolID,sizeof(RFormatInfo)); CFormatInfoPoolAccesser b=poolFile.getPoolAccesser(metaInfoPoolID); b.append(1); b[0]=r; } // really slows things down especially for recording // flush(); } void CSound::createPeakChunkAccessers() { if(poolFile.isOpen()) { sample_pos_t peakCount=calcPeakChunkCount(size); for(unsigned i=0;i(PEAK_CHUNK_POOL_NAME+istring(i),false)); peakChunkAccessers[i]->clear(); peakChunkAccessers[i]->append(peakCount); for(sample_pos_t t=0;t(createTempAudioPoolName(tempAudioPoolKey,channel))); } void CSound::removeAllTempAudioPools() { for(size_t t=0;tMAX_LENGTH) throw(runtime_error(string(__func__)+" -- invalid maxLength: "+istring(maxLength))); */ // get the max size of all the audio pools sample_pos_t maxAudioPoolSize=0; for(unsigned t=0;tmaxAudioPoolSize) removeSpaceFromChannel(t,maxAudioPoolSize,channelSize-maxAudioPoolSize); else if(channelSizegetSize()); } const string CSound::getCueName(size_t index) const { if(index>=getCueCount()) throw runtime_error(string(__func__)+" -- index is out of bounds: "+istring(index)); return((*cueAccesser)[index].name); } const sample_pos_t CSound::getCueTime(size_t index) const { if(index>=getCueCount()) throw runtime_error(string(__func__)+" -- index is out of bounds: "+istring(index)); return((*cueAccesser)[index].time); } void CSound::setCueTime(size_t index,sample_pos_t newTime) { if(index>=getCueCount()) throw runtime_error(string(__func__)+" -- index is out of bounds: "+istring(index)); (*cueAccesser)[index].time=newTime; // update cueIndex rebuildCueIndex(); } const bool CSound::isCueAnchored(size_t index) const { if(index>=getCueCount()) throw runtime_error(string(__func__)+" -- index is out of bounds: "+istring(index)); return((*cueAccesser)[index].isAnchored); } void CSound::addCue(const string &name,const sample_pos_t time,const bool isAnchored) { if(name.size()>=MAX_SOUND_CUE_NAME_LENGTH-1) throw(runtime_error(string(__func__)+" -- cue name too long")); cueAccesser->append(1); (*cueAccesser)[cueAccesser->getSize()-1]=RCue(name.c_str(),time,isAnchored); // update cueIndex cueIndex.insert(map::value_type(time,cueAccesser->getSize()-1)); } void CSound::insertCue(size_t index,const string &name,const sample_pos_t time,const bool isAnchored) { if(name.size()>=MAX_SOUND_CUE_NAME_LENGTH-1) throw(runtime_error(string(__func__)+" -- cue name too long")); if(index>cueAccesser->getSize()) throw(runtime_error(string(__func__)+" -- invalid index: "+istring(index))); cueAccesser->insert(index,1); (*cueAccesser)[index]=RCue(name.c_str(),time,isAnchored); // update cueIndex rebuildCueIndex(); } void CSound::removeCue(size_t index) { if(index>=getCueCount()) throw runtime_error(string(__func__)+" -- index is out of bounds: "+istring(index)); cueAccesser->remove(index,1); // update cueIndex rebuildCueIndex(); } size_t CSound::__default_cue_index; bool CSound::containsCue(const string &name,size_t &index) const { for(size_t t=0;tgetSize();t++) { if(strncmp((*cueAccesser)[t].name,name.c_str(),MAX_SOUND_CUE_NAME_LENGTH)==0) { index=t; return true; } } return false; } bool CSound::findCue(const sample_pos_t time,size_t &index) const { const map::const_iterator i=cueIndex.find(time); if(i!=cueIndex.end()) { index=i->second; return true; } else return false; } bool CSound::findNearestCue(const sample_pos_t time,size_t &index,sample_pos_t &distance) const { if(cueIndex.empty()) return false; map::const_iterator i=cueIndex.lower_bound(time); if(i!=cueIndex.begin()) { if(i==cueIndex.end()) { i--; // go back one index=i->second; distance=(sample_pos_t)sample_fpos_fabs((sample_fpos_t)time-(sample_fpos_t)i->first); } else { map::const_iterator pi=i; pi--; const sample_pos_t d1=(sample_pos_t)sample_fpos_fabs((sample_fpos_t)time-(sample_fpos_t)pi->first); const sample_pos_t d2=(sample_pos_t)sample_fpos_fabs((sample_fpos_t)time-(sample_fpos_t)i->first); if(d1second; distance=d1; } else { index=i->second; distance=d2; } } } else { index=i->second; distance=(sample_pos_t)sample_fpos_fabs((sample_fpos_t)time-(sample_fpos_t)i->first); } return true; } bool CSound::findPrevCue(const sample_pos_t time,size_t &index) const { map::const_iterator i=cueIndex.find(time); if(i==cueIndex.end()) return false; if(i!=cueIndex.begin()) i--; index=i->second; return true; } bool CSound::findNextCue(const sample_pos_t time,size_t &index) const { map::const_iterator i=cueIndex.find(time); if(i==cueIndex.end()) return false; i++; if(i==cueIndex.end()) return false; index=i->second; return true; } bool CSound::findPrevCueInTime(const sample_pos_t time,size_t &index) const { if(cueIndex.empty()) return false; // upper_bound returns the element that is greater than the position where 'time' would be inserted if it were map::const_iterator i=cueIndex.upper_bound(time); if(i==cueIndex.end()) { i--; index=i->second; return true; // ok return the last cue in time } else { if(i==cueIndex.begin()) return false; // ok, so time is are prior to all cues i--; index=i->second; return true; // ok we're at the cue prior to the one upper_bound found } } bool CSound::findNextCueInTime(const sample_pos_t time,size_t &index) const { // upper_bound returns the element that is greater than the position where 'time' would be inserted if it were map::const_iterator i=cueIndex.upper_bound(time); if(i!=cueIndex.end()) { index=i->second; return true; } else return false; } const string CSound::getUnusedCueName(const string &prefix) const { // ??? containsCue is not the most efficient, but we're not talking about huge amounts of data here for(unsigned t=1;t<200;t++) { if(!containsCue(prefix+istring(t))) return prefix+istring(t); } return ""; } void CSound::clearCues() { cueAccesser->clear(); // update cueIndex cueIndex.clear(); } void CSound::enableCueAdjustmentsOnSpaceChanges(bool enabled) { adjustCuesOnSpaceChanges=enabled; } /* * This method handles the adjustment of cues * pos1 can be less than pos2 indicating an addition of space at pos1 for pos2-pos1 samples * or pos2 can be less then pos1 indicating a removal of space as pos2 for pos1-pos2 samples */ void CSound::adjustCues(const sample_pos_t pos1,const sample_pos_t pos2) { if(!adjustCuesOnSpaceChanges) return; if(pos1=pos1) setCueTime(t,getCueTime(t)+addedLength); } } else // if(pos2<=pos1) { // removed data sample_pos_t removedLength=pos1-pos2; for(size_t t=0;tpos2 && getCueTime(t)=pos1) setCueTime(t,getCueTime(t)-removedLength); } } // update cueIndex rebuildCueIndex(); } void CSound::createCueAccesser() { if(poolFile.isOpen()) { poolFile.createPool(CUES_POOL_NAME,false); cueAccesser=new CCuePoolAccesser(poolFile.getPoolAccesser(CUES_POOL_NAME)); // update cueIndex rebuildCueIndex(); } } void CSound::deleteCueAccesser() { if(poolFile.isOpen()) { delete cueAccesser; cueAccesser=NULL; // update cueIndex cueIndex.clear(); } } void CSound::rebuildCueIndex() { cueIndex.clear(); for(size_t t=0;tgetSize();t++) cueIndex.insert(map::value_type(getCueTime(t),t)); } // ----------------------------------------------------- // ----------------------------------------------------- // ----------------------------------------------------- const string CSound::getUserNotes() const { if(poolFile.containsPool(NOTES_POOL_NAME)) { const TStaticPoolAccesser a=poolFile.getPoolAccesser(NOTES_POOL_NAME); string s; char buffer[101]; for(size_t t=0;t a=poolFile.containsPool(NOTES_POOL_NAME) ? poolFile.getPoolAccesser(NOTES_POOL_NAME) : poolFile.createPool(NOTES_POOL_NAME); a.clear(); a.write(notes.c_str(),notes.size()); } // this is the explicit instantiation of the TPoolFile for CSound's purposes // #include template class TPoolFile; // Some explicit template method instantiations (not sure why some are necessary and some aren't) /* I'm not sure why, but when I enable float instead of int16_t as the native audio type, it complains that these methods weren't instantiated anywhere when linking .. fine, but I'm not explicitly instantiating them when the type if int16_t either */ template TStaticPoolAccesser > TPoolFile::createPool(const string, const bool); template TStaticPoolAccesser > const TPoolFile::getPoolAccesser(const string) const; #include #include // I fairly certain that these are from calls to getGeneralDataAccesser template void TPoolFile::addAccesser(TStaticPoolAccesser > const *); template void TPoolFile::unreferenceCachedBlock(TStaticPoolAccesser > const *); template void TPoolFile::removeAccesser(TStaticPoolAccesser > const *); template void TStaticPoolAccesser >::cacheBlock(const sample_pos_t) const; template void TPoolFile::cacheBlock(sample_pos_t, TStaticPoolAccesser > const *); template TStaticPoolAccesser > TPoolFile::getPoolAccesser(const string); template TStaticPoolAccesser > const TPoolFile::getPoolAccesser(const string) const; template TStaticPoolAccesser > TPoolFile::createPool(const string, const bool); template TStaticPoolAccesser > TPoolFile::getPoolAccesser(const string); template TStaticPoolAccesser > const TPoolFile::getPoolAccesser(const string) const; template TStaticPoolAccesser > TPoolFile::createPool(const string, const bool);