1 /*
2 * Copyright (C) 2002 - David W. Durham
3 *
4 * This file is part of ReZound, an audio editing application.
5 *
6 * ReZound is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published
8 * by the Free Software Foundation; either version 2 of the License,
9 * or (at your option) any later version.
10 *
11 * ReZound is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
19 */
20
21 #include "CSound.h"
22 #include "AStatusComm.h"
23
24 #include <math.h>
25 #include <stdio.h> // ??? just for console info printfs
26
27 #include <stdexcept>
28
29 #include <CPath.h>
30
31 #include <istring>
32
33 #include "settings.h"
TStaticPoolAccesser(pool_file_t * _poolFile,poolId_t _poolId)34 #include "unit_conv.h" // for seconds_to_string()
35
36
37 /* TODO:
38 - add try and catch around space modifier methods that call matchUpChannelLengths even upon exception
39 - create MAX_LENGTH for an arbitrary maximum length incase I need to enforce such a limit
40 - 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
41 - make some macros for doing this since I do it in several places
42 - I have already fixed some of the methods
43
44 - the peak chunk mantainance is not absolutly perfect... if I delete a portion of the data and thus remove some of the
45 entries in the peak chunk data, the min and maxes aren't exactly correct now because the PEAK_CHUNK_SIZE samples which
46 were calculated for teh originaly data is now scewed a little on top of the new data... normally not enough to worry
47 about in just a frontend rendering of it... However overtime this error in the peak chunk data accumulates which is
48 why I have a refresh button which recalculates all the peak data. Perhaps there is some other method which could better
49 reduce or eliminate this error without completely recaclulating the peak chunk data anytime an operation is done on a
50 channel... perhaps with undo, I could store the peak chunk data in temp pools just as I did the audio data and could
51 just as readily restore it on undo
52 */
53
54 // ??? I could just set peakChunk data for space that I know I just silenced to min=max=0 and not bother setting it dirty
55
56
57 #define PEAK_CHUNK_SIZE 500
58
59 // ??? probably check a static variable that doesn't require a lock if the application is not threaded
60 #define ASSERT_RESIZE_LOCK \
61 if(!poolFile.isExclusiveLocked()) \
62 throw(runtime_error(string(__func__)+" -- this CSound object has not been locked for resize"));
63
64 #define ASSERT_SIZE_LOCK \
65 if(!poolFile.isExclusiveLocked() && poolFile.getSharedLockCount()<=0) \
~TStaticPoolAccesser()66 throw(runtime_error(string(__func__)+" -- this CSound object's size has not been locked"));
67
68
69 // current pool names for the current version
70 #define FORMAT_INFO_POOL_NAME "Format Info"
71 #define AUDIO_POOL_NAME "Channel "
72 #define PEAK_CHUNK_POOL_NAME "PeakChunk "
73 #define TEMP_AUDIO_POOL_NAME "TempAudioPool_"
74 #define CUES_POOL_NAME "Cues"
75 #define NOTES_POOL_NAME "UserNotes"
76
77 #define GET_WORKING_FILENAME(workDir,filename) (workDir+CPath::dirDelim+CPath(filename).baseName()+".pf$")
78
79 CSound::CSound() :
80 poolFile(REZOUND_POOLFILE_BLOCKSIZE,REZOUND_WORKING_POOLFILE_SIGNATURE),
81
82 size(0),
operator =(const TStaticPoolAccesser<pool_element_t,pool_file_t> & rhs)83 sampleRate(0),
84 channelCount(0),
85
86 metaInfoPoolID(0),
87
88 tempAudioPoolKeyCounter(1),
89
90 _isModified(true),
91
92 cueAccesser(NULL),
93 adjustCuesOnSpaceChanges(true)
94 {
95 for(unsigned t=0;t<MAX_CHANNELS;t++)
96 peakChunkAccessers[t]=NULL;
97 }
98
99 CSound::CSound(const string &_filename,const unsigned _sampleRate,const unsigned _channelCount,const sample_pos_t _size) :
read(pool_element_t buffer[],l_addr_t count) const100 poolFile(REZOUND_POOLFILE_BLOCKSIZE,REZOUND_WORKING_POOLFILE_SIGNATURE),
101
102 size(0),
103 sampleRate(0),
104 channelCount(0),
105
106 metaInfoPoolID(0),
107
108 tempAudioPoolKeyCounter(1),
109
110 _isModified(true),
111
112 cueAccesser(NULL)
113 {
114 for(unsigned t=0;t<MAX_CHANNELS;t++)
115 peakChunkAccessers[t]=NULL;
116
117 try
118 {
119 createWorkingPoolFile(_filename,_sampleRate,_channelCount,_size);
120 }
121 catch(...)
122 {
123 try
124 {
125 poolFile.closeFile(false,true);
126 } catch(...) {}
127 throw;
128 }
129 }
130
overflowWrite(const pool_element_t buffer[],l_addr_t count,bool append)131 CSound::~CSound()
132 {
133 // going away, so release all read locks
134 // ??? add back, but implement correctly
135 /*
136 while(poolFile.getSharedLockCount()>0)
137 unlockSize();
138 */
139
140 if(poolFile.isOpen())
141 {
142 deletePeakChunkAccessers();
143 deleteCueAccesser();
144 poolFile.closeFile(false,false);
145 }
146 }
147
148 void CSound::changeWorkingFilename(const string newOriginalFilename)
149 {
150 const string workDir=CPath(poolFile.getFilename()).dirName();
151 poolFile.rename(GET_WORKING_FILENAME(workDir,newOriginalFilename));
152 }
153
154 void CSound::closeSound()
155 {
156 // ??? probably should get a lock?
157 deletePeakChunkAccessers();
158 deleteCueAccesser();
159 poolFile.closeFile(false,true);
160 }
161
162 // locks to keep the size from changing (multiple locks can be obtained of this type)
163 void CSound::lockSize() const
164 {
165 poolFile.sharedLock();
copyData(l_addr_t destWhere,const TStaticPoolAccesser<pool_element_t,pool_file_t> & src,l_addr_t srcWhere,l_addr_t length)166 }
167
168 bool CSound::trylockSize() const
169 {
170 return(poolFile.sharedTrylock());
171 }
172
173 void CSound::unlockSize() const
174 {
175 poolFile.sharedUnlock();
176 }
177
178 // locks to be able to change the size (only one lock can be obtained of this type)
179 void CSound::lockForResize() const
180 {
181 poolFile.exclusiveLock();
182 }
183
184 bool CSound::trylockForResize() const
185 {
zeroData(l_addr_t where,l_addr_t length)186 return(poolFile.exclusiveTrylock());
187 }
188
189 void CSound::unlockForResize() const
190 {
191 poolFile.exclusiveUnlock();
192 }
193
194 CSound::CInternalRezPoolAccesser CSound::getAudioInternal(unsigned channel)
195 {
196 return(poolFile.getPoolAccesser<sample_t>(channelPoolIDs[channel]));
197 }
cacheBlock(l_addr_t where) const198
199 CSound::CInternalRezPoolAccesser CSound::getTempDataInternal(unsigned tempAudioPoolKey,unsigned channel)
200 {
201 return(poolFile.getPoolAccesser<sample_t>(createTempAudioPoolName(tempAudioPoolKey,channel)));
202 }
203
204 CRezPoolAccesser CSound::getAudio(unsigned channel)
205 {
206 if(channel>=channelCount)
207 throw(runtime_error(string(__func__)+" -- invalid channel: "+istring(channel)));
208
209 return(poolFile.getPoolAccesser<sample_t>(channelPoolIDs[channel]));
210 }
211
212 const CRezPoolAccesser CSound::getAudio(unsigned channel) const
213 {
214 if(channel>=channelCount)
215 throw(runtime_error(string(__func__)+" -- invalid channel: "+istring(channel)));
216
217 return(poolFile.getPoolAccesser<sample_t>(channelPoolIDs[channel]));
218 }
219
220
221
222 CRezPoolAccesser CSound::getTempAudio(unsigned tempAudioPoolKey,unsigned channel)
223 {
224 return(poolFile.getPoolAccesser<sample_t>(createTempAudioPoolName(tempAudioPoolKey,channel)));
225 }
226
227 const CRezPoolAccesser CSound::getTempAudio(unsigned tempAudioPoolKey,unsigned channel) const
228 {
229 return(poolFile.getPoolAccesser<sample_t>(createTempAudioPoolName(tempAudioPoolKey,channel)));
230 }
231
232 /*
233 * - This method gets the peak chunk information between [dataPos,nextDataPos)
234 * where dataPos and nextDataPos are positions in the sample data.
235 * - That is, the min and max sample value between those data positions.
236 * - This information is used to render the waveform data on screen.
237 *
238 * - This information is stored in a pool of RPeakChunk struct objects in the
239 * pool file for this sound object. And a min and max is stored for every
240 * PEAK_CHUNK_SIZE samples.
241 * - The RPeakChunk struct has a bool flag, 'dirty', that get's set to true
242 * for every chunk that we need to re-calculate the min and max for. This
243 * is necessary when perhaps an action modifies the data, so the view on
244 * screen also needs to change.
245 *
246 * - Sometimes it reads this precalculated peak chunk information, and sometimes
247 * it reads the data directly. If the nextDataPos-dataPos < PEAK_CHUNK_SIZE, then
248 * we should just read the data normally and find the min and max on the fly.
249 * Otherwise, we can use the precalculated information.
250 *
251 * - When using the precalculated information:
252 * - I could simply return peakChunks[floor(dataPos/PEAK_CHUNK_SIZE)], but I
253 * don't since the next time getPeakValue is called, it may have skipped over
254 * more than a chunk size, so the min and max would not necessarily be accurate.
255 * - So, I also have the caller pass in the dataPos of the next time this method
256 * will be called and I combine the mins and maxes of all the chunks that will
257 * be skipped over by the next call.
258 *
259 * - Also, for practical reasons, it is okay if nextDataPos is >= getLength(), it's really
260 * 'what WOULD be the next data position' not than it will be necessarily called with that
261 * position
262 */
263 RPeakChunk CSound::getPeakData(unsigned channel,sample_pos_t dataPos,sample_pos_t nextDataPos,const CRezPoolAccesser &dataAccesser) const
264 {
265 if(channel>=channelCount)
266 throw(runtime_error(string(__func__)+" -- channel parameter is out of change: "+istring(channel)));
267
268 /*
269 if(dataPos==getLength()-1)
270 {
271 RPeakChunk p;
272 p.min=MIN_SAMPLE;
273 p.max=MAX_SAMPLE;
274
275 return(p);
276 }
277 */
278
279 // I could check the bounds in dataPos and nextDataPos, but I'll just let the caller have already done that
280 if((nextDataPos-dataPos)<PEAK_CHUNK_SIZE)
281 { // just find min and max on the fly
282 //printf("NOT using precalculated data\n");
283 sample_t _min=dataAccesser[dataPos];
284 sample_t _max=dataAccesser[dataPos];
285
286 // don't attempt to read past the end of the data
287 if(nextDataPos>dataAccesser.getSize())
288 nextDataPos=dataAccesser.getSize();
289
290 for(sample_pos_t t=dataPos+1;t<nextDataPos;t++)
291 {
292 sample_t s=dataAccesser[t];
293 _min=min(_min,s);
294 _max=max(_max,s);
295 }
296
297 RPeakChunk p;
298 p.min=_min;
299 p.max=_max;
300 return(p);
301 }
302 else
303 {
304 // scale down and truncate the data positions to offsets into the peak chunk information
305 // which only ranges [0,size/PEAK_CHUNK_SIZE]
306 sample_pos_t firstChunk=dataPos/PEAK_CHUNK_SIZE;
307 sample_pos_t lastChunk=nextDataPos/PEAK_CHUNK_SIZE;
308
309 RPeakChunk ret;
310
311 #warning see about using a const CPeakChunkRezPoolAccesser here
312 CPeakChunkRezPoolAccesser &peakChunkAccesser=*(peakChunkAccessers[channel]);
313
314 // don't attempt to read from skipped chunks that don't exist
315 if(lastChunk>peakChunkAccesser.getSize())
316 lastChunk=peakChunkAccesser.getSize();
317
318 // - we combine all the mins and maxes of the peak chunk information between [firstChunk and lastChunk)
319 // - Also, if any chunk is dirty along the way, we recalculate it
320 for(sample_pos_t t=firstChunk;t<lastChunk;t++)
321 {
322 RPeakChunk &p=peakChunkAccesser[t];
323
324 // if dirty, then recalculate for this chunk
325 if(p.dirty)
326 {
327 //printf("recalculating peak chunk data for: %lld\n",(long long)t);
328 sample_pos_t start=t*PEAK_CHUNK_SIZE;
329 sample_pos_t end=start+PEAK_CHUNK_SIZE;
330 if(start<getLength())
331 {
332 if(end>getLength())
333 end=getLength();
334
335 sample_t _min=dataAccesser[start];
336 sample_t _max=dataAccesser[start];
337
338 for(sample_pos_t i=start+1;i<end;i++)
339 {
340 sample_t s=dataAccesser[i];
341 _min=min(_min,s);
342 _max=max(_max,s);
343 }
344
345 p.min=_min;
346 p.max=_max;
347 }
348 p.dirty=false;
349 }
350
351 if(t==firstChunk)
352 {
353 ret.min=p.min;
354 ret.max=p.max;
355 }
356 else
357 {
358 ret.min=min(ret.min,p.min);
359 ret.max=max(ret.max,p.max);
360 }
361 }
362
363 return(ret);
364 }
365 }
366
367 void CSound::invalidatePeakData(unsigned channel,sample_pos_t start,sample_pos_t stop)
368 {
369 if(channel>=channelCount)
370 throw(runtime_error(string(__func__)+" -- channel parameter is out of change: "+istring(channel)));
371
372 // ??? check ranges of start and stop
373
374 CPeakChunkRezPoolAccesser &peakChunkAccesser=*(peakChunkAccessers[channel]);
375
376 // fudge one peak chunk size longer
377 if(stop<MAX_LENGTH-PEAK_CHUNK_SIZE)
378 {
379 stop+=PEAK_CHUNK_SIZE;
380 if(stop>getLength()-1)
381 stop=getLength()-1;
382 }
383
384 const sample_pos_t firstChunk=start/PEAK_CHUNK_SIZE;
385 const sample_pos_t lastChunk=stop/PEAK_CHUNK_SIZE;
386
387 for(sample_pos_t t=firstChunk;t<=lastChunk;t++)
388 peakChunkAccesser[t].dirty=true;
389 }
390
391 void CSound::invalidatePeakData(const bool doChannel[MAX_CHANNELS],sample_pos_t start,sample_pos_t stop)
392 {
393 for(sample_pos_t t=0;t<channelCount;t++)
394 {
395 if(doChannel[t])
396 invalidatePeakData(t,start,stop);
397 }
398 }
399
400 void CSound::invalidateAllPeakData()
401 {
402 deletePeakChunkAccessers();
403 createPeakChunkAccessers();
404 }
405
406
407 void CSound::addChannel(bool doZeroData)
408 {
409 prvAddChannel(true,doZeroData);
410 }
411
412 void CSound::prvAddChannel(bool addAudioSpaceForNewChannel,bool doZeroData)
413 {
414 ASSERT_RESIZE_LOCK
415
416 if((getChannelCount()+1)>MAX_CHANNELS)
417 throw(runtime_error(string(__func__)+" -- adding another channel would exceed the maximum of "+istring(MAX_CHANNELS)+" channels"));
418
419 const string audioPoolName=AUDIO_POOL_NAME+istring(channelCount+1);
420 const string peakChunkPoolName=PEAK_CHUNK_POOL_NAME+istring(channelCount);
421
422 peakChunkAccessers[channelCount]=NULL;
423 bool addedToChannelCount=false;
424 try
425 {
426 CInternalRezPoolAccesser audioPool=poolFile.createPool<sample_t>(audioPoolName);
427 channelPoolIDs[channelCount]=poolFile.getPoolIdByName(audioPoolName);
428
429 CPeakChunkRezPoolAccesser peakChunkPool=poolFile.createPool<RPeakChunk>(peakChunkPoolName);
430 peakChunkAccessers[channelCount]=new CPeakChunkRezPoolAccesser(peakChunkPool);
431
432 channelCount++;
433 addedToChannelCount=true;
434
435 if(addAudioSpaceForNewChannel)
436 matchUpChannelLengths(getLength(),doZeroData);
437 }
438 catch(...)
439 { // attempt to recover
440 if(addedToChannelCount)
441 channelCount--;
442
443 poolFile.removePool(audioPoolName,false);
444 delete peakChunkAccessers[channelCount];
445 poolFile.removePool(peakChunkPoolName,false);
446
447 throw;
448 }
449
450 saveMetaInfo();
451 }
452
453 void CSound::addChannels(unsigned where,unsigned count,bool doZeroData)
454 {
455 ASSERT_RESIZE_LOCK
456
457 if(where>getChannelCount())
458 throw(runtime_error(string(__func__)+" -- where out of range: "+istring(where)+">"+istring(getChannelCount())));
459 if((count+getChannelCount())>MAX_CHANNELS)
460 throw(runtime_error(string(__func__)+" -- adding "+istring(count)+" channels would exceed the maximum of "+istring(MAX_CHANNELS)+" channels"));
461
462 /*
463 * This way may seem a little obtuse, but it's the easiest way without
464 * renaming pools, regetting poolIds, invalidating any outstanding
465 * accessors (because now their poolIds are invalid) and stuff.. simply
466 * add a channel to the end and move it where it needs to be and do this
467 * for each channel to add
468 */
469
470 size_t swapCount=getChannelCount()-where;
471 for(unsigned t=0;t<count;t++)
472 {
473 // add a channel to the end
474 addChannel(doZeroData);
475
476 // through a series of swaps move that channel to where+t
477 for(unsigned i=0;i<swapCount;i++)
478 swapChannels(channelCount-i-1,channelCount-i-2,0,getLength());
479 }
480 }
481
482 void CSound::removeChannel()
483 {
484 ASSERT_RESIZE_LOCK
485
486 if(getChannelCount()<=1)
487 throw(runtime_error(string(__func__)+" -- removing a channel would cause channel count to go to zero"));
488
489 const string audioPoolName=AUDIO_POOL_NAME+istring(channelCount);
490 const string peakChunkPoolName=PEAK_CHUNK_POOL_NAME+istring(channelCount-1);
491
492 poolFile.removePool(audioPoolName);
493 poolFile.removePool(peakChunkPoolName);
494
495 channelCount--;
496 saveMetaInfo();
497 }
498
499 void CSound::removeChannels(unsigned where,unsigned count)
500 {
501 ASSERT_RESIZE_LOCK
502
503 if(where>getChannelCount())
504 throw(runtime_error(string(__func__)+" -- where out of range: "+istring(where)+">"+istring(getChannelCount())));
505 if(count>(getChannelCount()-where))
506 throw(runtime_error(string(__func__)+" -- where/count out of range: "+istring(where)+"/"+istring(count)));
507 if(where==0 && count>=getChannelCount())
508 throw(runtime_error(string(__func__)+" -- removing "+istring(count)+" channels at "+istring(where)+" would cause the channel count to go to zero"));
509
510 unsigned swapCount=channelCount-where-1;
511 for(unsigned t=0;t<count;t++)
512 {
513 // through a series of swaps move the channel at where to the end
514 if(swapCount>0)
515 {
516 for(unsigned i=0;i<swapCount;i++)
517 swapChannels(where+i,where+i+1,0,getLength());
518 swapCount--;
519 }
520
521 // remove the last channel
522 removeChannel();
523 }
524 }
525
526 int CSound::moveChannelsToTemp(const bool whichChannels[MAX_CHANNELS])
527 {
528 ASSERT_RESIZE_LOCK
529
530 unsigned removeCount=0;
531 for(unsigned t=0;t<MAX_CHANNELS;t++)
532 {
533 removeCount+=whichChannels[t] ? 1 : 0;
534 if(whichChannels[t] && t>=getChannelCount())
535 throw(runtime_error(string(__func__)+" -- whichChannels specifies to remove channel "+istring(t)+" which does not exist"));
536 }
537 if(removeCount>=getChannelCount())
538 throw(runtime_error(string(__func__)+" -- whichChannels specifies all the channels, which would cause the channel count to go to zero"));
539
540 // through a series of swaps move all the channels to be removed to become the last channels in the sound
541 removeCount=0;
542 for(unsigned _t=getChannelCount();_t>0;_t--)
543 {
544 unsigned t=_t-1;
545 if(whichChannels[t])
546 {
547 for(unsigned i=t;i<getChannelCount()-1-removeCount;i++)
548 swapChannels(i,i+1,0,getLength());
549 removeCount++;
550 }
551 }
552
553 const int tempAudioPoolKey=tempAudioPoolKeyCounter++;
554
555 // move the last 'removeCount' channels to a temp pool
556 for(unsigned t=getChannelCount()-removeCount;t<getChannelCount();t++)
557 moveDataOutOfChannel(tempAudioPoolKey,t,0,getLength());
558
559 // remove the last 'removeCount' channels
560 for(unsigned t=0;t<removeCount;t++)
561 removeChannel();
562
563 return(tempAudioPoolKey);
564 }
565
566 /*
567 * - tempPoolKey is the value returned by the previous call to moveChannelsToTemp()
568 * - whichChannels MUST match the whichChannels that was given to moveChannelsToTemp and
569 * this sound MUST have the channel layout that it had after the return from. The
570 * sound MUST also be the same length as it was when moveChannelsToTemp() was called
571 * the previous call to moveChannelsToTemp()
572 */
573 void CSound::moveChannelsFromTemp(int tempAudioPoolKey,const bool whichChannels[MAX_CHANNELS])
574 {
575 ASSERT_RESIZE_LOCK
576
577 // just make sure that we will be able to add the channels without exceeding MAX_CHANNELS
578 unsigned addCount=0;
579 for(unsigned t=0;t<MAX_CHANNELS;t++)
580 addCount+=whichChannels[t] ? 1 : 0;
581 if((addCount+getChannelCount())>MAX_CHANNELS)
582 throw(runtime_error(string(__func__)+" -- re-adding the channels specified by whichChannels would exceed the maximum of "+istring(MAX_CHANNELS)+" channels"));
583
584 // make sure all the temp audio pools exist for the channels specified by whichChannels and that they have the correct length
585 // 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
586 int k=getChannelCount();
587 for(unsigned t=0;t<MAX_CHANNELS;t++)
588 {
589 if(whichChannels[t])
590 {
591 if(!poolFile.containsPool(createTempAudioPoolName(tempAudioPoolKey,k)))
592 throw(runtime_error(string(__func__)+" -- whichChannels specifies to add channel "+istring(t)+" from temp which does not exist as a temp audio pool"));
593 if(getTempAudio(tempAudioPoolKey,k).getSize()!=getLength())
594 throw(runtime_error(string(__func__)+" -- the length of the audio in this sound object is not the same now as it was when moveChannelsToTemp() was called"));
595 k++;
596 }
597 }
598
599
600 // move the temp audio pools back as channels at the end of the sound
601 for(unsigned t=0;t<MAX_CHANNELS;t++)
602 {
603 if(whichChannels[t])
604 {
605 prvAddChannel(false,false);
606 moveDataIntoChannel(tempAudioPoolKey,getChannelCount()-1,getChannelCount()-1,0,getLength(),true);
607 }
608 }
609
610 // through a series of swaps put the channels back in their original positions
611 unsigned movedChannelIndex=getChannelCount()-addCount;
612 for(unsigned t=0;t<getChannelCount();t++)
613 {
614 if(whichChannels[t])
615 {
616 for(unsigned i=movedChannelIndex;i>t;i--)
617 swapChannels(i-1,i,0,getLength());
618 movedChannelIndex++;
619 }
620 }
621 }
622
623
624 static const bool isAllChannels(CSound *sound,const bool whichChannels[MAX_CHANNELS])
625 {
626 unsigned count=0;
627 for(unsigned t=0;t<sound->getChannelCount();t++)
628 if(whichChannels[t])
629 count++;
630 return(count==sound->getChannelCount());
631 }
632
633
634 void CSound::addSpace(sample_pos_t where,sample_pos_t length,bool doZeroData)
635 {
636 ASSERT_RESIZE_LOCK
637
638 bool whichChannels[MAX_CHANNELS];
639 for(unsigned t=0;t<MAX_CHANNELS;t++)
640 whichChannels[t]=t<getChannelCount() ? true : false;
641
642 addSpace(whichChannels,where,length,doZeroData);
643 }
644
645 void CSound::addSpace(const bool whichChannels[MAX_CHANNELS],sample_pos_t where,sample_pos_t length,bool doZeroData,sample_pos_t maxLength)
646 {
647 ASSERT_RESIZE_LOCK
648
649 if(where>size)
650 throw(runtime_error(string(__func__)+" -- where parameter out of range: "+istring(where)));
651 /*
652 if(length>MAX_LENGTH)
653 throw(runtime_error(string(__func__)+" -- length parameter out of range: "+istring(length)));
654 */
655
656 for(unsigned t=0;t<getChannelCount();t++)
657 {
658 if(whichChannels[t])
659 addSpaceToChannel(t,where,length,doZeroData);
660 }
661
662 if(isAllChannels(this,whichChannels))
663 adjustCues(where,where+length);
664
665 matchUpChannelLengths(maxLength);
666 }
667
668 void CSound::removeSpace(sample_pos_t where,sample_pos_t length)
669 {
670 ASSERT_RESIZE_LOCK
671
672 bool whichChannels[MAX_CHANNELS];
673 for(unsigned t=0;t<MAX_CHANNELS;t++)
674 whichChannels[t]=t<getChannelCount() ? true : false;
675
676 removeSpace(whichChannels,where,length);
677 }
678
679 void CSound::removeSpace(const bool whichChannels[MAX_CHANNELS],sample_pos_t where,sample_pos_t length,sample_pos_t maxLength)
680 {
681 ASSERT_RESIZE_LOCK
682
683 if(where>size)
684 throw(runtime_error(string(__func__)+" -- where parameter out of range: "+istring(where)));
685 if(length>(size-where))
686 throw(runtime_error(string(__func__)+" -- length parameter out of range: "+istring(length)));
687
688 for(unsigned t=0;t<channelCount;t++)
689 {
690 if(whichChannels[t])
691 removeSpaceFromChannel(t,where,length);
692 }
693
694 if(isAllChannels(this,whichChannels))
695 adjustCues(where+length,where);
696
697 matchUpChannelLengths(maxLength);
698 }
699
700 unsigned CSound::moveDataToTemp(const bool whichChannels[MAX_CHANNELS],sample_pos_t where,sample_pos_t length,sample_pos_t fudgeFactor,sample_pos_t maxLength)
701 {
702 ASSERT_RESIZE_LOCK
703
704 return(moveDataToTempAndReplaceSpace(whichChannels,where,length,0,fudgeFactor));
705 }
706
707 unsigned CSound::copyDataToTemp(const bool whichChannels[MAX_CHANNELS],sample_pos_t where,sample_pos_t length)
708 {
709 ASSERT_SIZE_LOCK
710
711 if(where>size)
712 throw(runtime_error(string(__func__)+" -- where parameter out of range: "+istring(where)));
713 if(length>(size-where))
714 throw(runtime_error(string(__func__)+" -- length parameter out of range: "+istring(length)));
715
716 const unsigned tempAudioPoolKey=tempAudioPoolKeyCounter++;
717
718 for(unsigned t=0;t<channelCount;t++)
719 {
720 if(whichChannels[t])
721 copyDataFromChannel(tempAudioPoolKey,t,where,length);
722 }
723
724 return(tempAudioPoolKey);
725 }
726
727 unsigned CSound::moveDataToTempAndReplaceSpace(const bool whichChannels[MAX_CHANNELS],sample_pos_t where,sample_pos_t length,sample_pos_t replaceLength,sample_pos_t fudgeFactor,sample_pos_t maxLength)
728 {
729 ASSERT_RESIZE_LOCK
730
731 if(where>size)
732 throw(runtime_error(string(__func__)+" -- where parameter out of range: "+istring(where)));
733 if(length>(size-where))
734 throw(runtime_error(string(__func__)+" -- length parameter out of range: "+istring(length)));
735 /*
736 if(replaceLength>MAX_LENGTH)
737 throw(runtime_error(string(__func__)+" -- replaceLength parameter out of range: "+istring(replaceLength)));
738 */
739
740 const unsigned tempAudioPoolKey=tempAudioPoolKeyCounter++;
741
742 for(unsigned t=0;t<channelCount;t++)
743 {
744 if(whichChannels[t])
745 {
746 // move data
747 moveDataOutOfChannel(tempAudioPoolKey,t,where,length);
748
749 // handle fudgeFactor
750 appendForFudgeFactor(getTempDataInternal(tempAudioPoolKey,t),getAudioInternal(t),where,fudgeFactor);
751
752 // replace space
753 addSpaceToChannel(t,where,replaceLength,false);
754 }
755 }
756
757 if(isAllChannels(this,whichChannels))
758 adjustCues(where+length,where+replaceLength);
759
760 matchUpChannelLengths(maxLength);
761
762 return(tempAudioPoolKey);
763 }
764
765 void CSound::moveDataFromTemp(const bool whichChannels[MAX_CHANNELS],unsigned tempAudioPoolKey,sample_pos_t moveWhere,sample_pos_t moveLength,bool removeTempAudioPools,sample_pos_t maxLength)
766 {
767 ASSERT_RESIZE_LOCK
768
769 removeSpaceAndMoveDataFromTemp(whichChannels,0,0,tempAudioPoolKey,moveWhere,moveLength,removeTempAudioPools,maxLength);
770 }
771
772 void CSound::removeSpaceAndMoveDataFromTemp(const bool whichChannels[MAX_CHANNELS],sample_pos_t removeWhere,sample_pos_t removeLength,unsigned tempAudioPoolKey,sample_pos_t moveWhere,sample_pos_t moveLength,bool removeTempAudioPools,sample_pos_t maxLength)
773 {
774 ASSERT_RESIZE_LOCK
775
776 if(removeLength!=0 && (removeWhere>size || removeLength>(size-removeWhere)))
777 throw(runtime_error(string(__func__)+" -- removeWhere/removeLength parameter out of range: "+istring(removeWhere)+"/"+istring(removeLength)));
778 if(moveWhere>(size-removeLength))
779 throw(runtime_error(string(__func__)+" -- moveWhere parameter out of range: "+istring(moveWhere)+" for removeWhere "+istring(removeLength)));
780
781 for(unsigned t=0;t<channelCount;t++)
782 {
783 if(whichChannels[t])
784 { // remove then add the space (by moving from temp pool)
785 removeSpaceFromChannel(t,removeWhere,removeLength);
786 moveDataIntoChannel(tempAudioPoolKey,t,t,moveWhere,moveLength,removeTempAudioPools);
787 }
788 }
789
790 /* ???
791 if(isAllChannels(this,whichChannels))
792 // I may need to handle this.... but as of now I don't because it's use only
793 // for undo (and AAction takes care of restoring the cues) and it's a little
794 // complicated since we can remove from one place and add to another
795 */
796
797 matchUpChannelLengths(maxLength);
798 }
799
800 void CSound::removeTempAudioPools(unsigned tempAudioPoolKey)
801 {
802 for(unsigned t=0;t<MAX_CHANNELS;t++)
803 {
804 const string tempAudioPoolName=createTempAudioPoolName(tempAudioPoolKey,t);
805 if(poolFile.containsPool(tempAudioPoolName))
806 poolFile.removePool(tempAudioPoolName);
807 }
808 }
809
810 void CSound::appendForFudgeFactor(CInternalRezPoolAccesser dest,const CInternalRezPoolAccesser src,sample_pos_t srcWhere,sample_pos_t fudgeFactor)
811 {
812 if(fudgeFactor==0)
813 return;
814
815 sample_pos_t srcLength=src.getSize();
816
817 const sample_pos_t l1=dest.getSize();
818 dest.append(fudgeFactor);
819
820 sample_pos_t t;
821 for(t=0;t<fudgeFactor && (t+srcWhere)<srcLength;t++)
822 dest[t+l1]=src[t+srcWhere];
823
824 for(;t<fudgeFactor;t++) // 000
825 dest[t+l1]=0;
826 }
827
828
829 void CSound::rotateLeft(const bool whichChannels[MAX_CHANNELS],const sample_pos_t start,const sample_pos_t stop,const sample_pos_t amount)
830 {
831 ASSERT_RESIZE_LOCK
832
833 if(stop<start)
834 throw(runtime_error(string(__func__)+" -- stop is less than start"));
835 if(amount>(stop-start))
836 throw(runtime_error(string(__func__)+" -- amount is greater than the distance between start and stop"));
837
838 for(unsigned int i=0;i<channelCount;i++)
839 {
840 if(whichChannels[i])
841 {
842 CInternalRezPoolAccesser accesser=getAudioInternal(i);
843
844 accesser.moveData(stop-amount+1,accesser,start,amount);
845 invalidatePeakData(i,start,stop); // for want of a more efficient way (I already tried using move data on the peak accessers with the same parametesr as the previous call except /PEAK_CHUNK_SIZE, but error after many many operations was unacceptable) ??? perhaps I need a flag which means 'how dirty' and it would absolutely recalculate it if it was just so dirty)
846 }
847 }
848
849 }
850
851 void CSound::rotateRight(const bool whichChannels[MAX_CHANNELS],const sample_pos_t start,const sample_pos_t stop,const sample_pos_t amount)
852 {
853 ASSERT_RESIZE_LOCK
854
855 if(stop<start)
856 throw(runtime_error(string(__func__)+" -- stop is less than start"));
857 if(amount>(stop-start))
858 throw(runtime_error(string(__func__)+" -- amount is greater than the distance between start and stop"));
859
860 for(unsigned int i=0;i<channelCount;i++)
861 {
862 if(whichChannels[i])
863 {
864 CInternalRezPoolAccesser accesser=getAudioInternal(i);
865
866 accesser.moveData(start,accesser,stop-amount+1,amount);
867 invalidatePeakData(i,start,stop); // for want of a more efficient way
868 }
869 }
870 }
871
872
873 void CSound::swapChannels(unsigned channelA,unsigned channelB,const sample_pos_t where,const sample_pos_t length)
874 {
875 ASSERT_RESIZE_LOCK
876
877 if(channelA>=getChannelCount())
878 throw(runtime_error(string(__func__)+" -- channelA is out of range: "+istring(channelA)+">="+istring(getChannelCount())));
879 if(channelB>=getChannelCount())
880 throw(runtime_error(string(__func__)+" -- channelB is out of range: "+istring(channelB)+">="+istring(getChannelCount())));
881
882 if(where>size)
883 throw(runtime_error(string(__func__)+" -- where parameter out of range: "+istring(where)));
884 if(length>(size-where))
885 throw(runtime_error(string(__func__)+" -- where/length parameter out of range: "+istring(where)+"/"+istring(length)));
886
887 if(channelA==channelB)
888 return;
889 if(length<=0)
890 return;
891
892
893 const unsigned tempAudioPoolKeyA=tempAudioPoolKeyCounter++;
894 const unsigned tempAudioPoolKeyB=tempAudioPoolKeyCounter++;
895
896 // move data from each channel to its temp pool
897 moveDataOutOfChannel(tempAudioPoolKeyA,channelA,where,length);
898 moveDataOutOfChannel(tempAudioPoolKeyB,channelB,where,length);
899
900 // move the data back into the channel from each temp pool but swapped (and pass true to remove the temp pool)
901 moveDataIntoChannel(tempAudioPoolKeyA,channelA,channelB,where,length,true);
902 moveDataIntoChannel(tempAudioPoolKeyB,channelB,channelA,where,length,true);
903 }
904
905
906 void CSound::addSpaceToChannel(unsigned channel,sample_pos_t where,sample_pos_t length,bool doZeroData)
907 {
908 if(length==0)
909 return;
910
911 CInternalRezPoolAccesser accesser=getAudioInternal(channel);
912
913 const sample_pos_t peakChunkCountHave=peakChunkAccessers[channel]==NULL ? 0 : peakChunkAccessers[channel]->getSize();
914 const sample_pos_t peakChunkCountNeeded=calcPeakChunkCount(accesser.getSize()+length);
915
916 // modify the audio data pools
917 accesser.insert(where,length);
918
919 if(doZeroData)
920 accesser.zeroData(where,length);
921
922 if(peakChunkAccessers[channel]!=NULL)
923 {
924 // add more peak chunks if the needed size is more than we have
925 if(peakChunkCountNeeded>peakChunkCountHave)
926 {
927 const sample_pos_t insertWhere=where/PEAK_CHUNK_SIZE;
928 const sample_pos_t insertCount=peakChunkCountNeeded-peakChunkCountHave;
929
930 peakChunkAccessers[channel]->insert(insertWhere,insertCount);
931 for(sample_pos_t t=0;t<insertCount;t++)
932 (*(peakChunkAccessers[channel]))[insertWhere+t].dirty=true;
933 }
934 else // just mark the one we inserted into as dirty
935 (*(peakChunkAccessers[channel]))[where/PEAK_CHUNK_SIZE].dirty=true;
936 }
937 }
938
939 void CSound::removeSpaceFromChannel(unsigned channel,sample_pos_t where,sample_pos_t length)
940 {
941 if(length==0)
942 return;
943
944 CInternalRezPoolAccesser accesser=getAudioInternal(channel);
945
946 const sample_pos_t peakChunkCountHave=peakChunkAccessers[channel]==NULL ? 0 : peakChunkAccessers[channel]->getSize();
947 const sample_pos_t peakChunkCountNeeded=calcPeakChunkCount(accesser.getSize()-length);
948
949 accesser.remove(where,length);
950
951 if(peakChunkAccessers[channel]!=NULL)
952 {
953 // modify the peak data pools if the size is dropping below the required size
954 if(peakChunkCountHave>peakChunkCountNeeded)
955 {
956 const sample_pos_t removeWhere=where/PEAK_CHUNK_SIZE;
957 const sample_pos_t removeCount=peakChunkCountHave-peakChunkCountNeeded;
958
959 peakChunkAccessers[channel]->remove(removeWhere,removeCount);
960 }
961
962 // make the a the peak chunk at where recalculate
963 if((where/PEAK_CHUNK_SIZE)<peakChunkAccessers[channel]->getSize())
964 (*(peakChunkAccessers[channel]))[where/PEAK_CHUNK_SIZE].dirty=true;
965 }
966 }
967
968 void CSound::copyDataFromChannel(unsigned tempAudioPoolKey,unsigned channel,sample_pos_t where,sample_pos_t length)
969 {
970 CInternalRezPoolAccesser destAccesser=createTempAudioPool(tempAudioPoolKey,channel);
971
972 if(length==0)
973 return;
974
975 CInternalRezPoolAccesser srcAccesser=getAudioInternal(channel);
976
977 destAccesser.append(length);
978 destAccesser.copyData(0,srcAccesser,where,length);
979 }
980
981 void CSound::moveDataOutOfChannel(unsigned tempAudioPoolKey,unsigned channel,sample_pos_t where,sample_pos_t length)
982 {
983 CInternalRezPoolAccesser destAccesser=createTempAudioPool(tempAudioPoolKey,channel);
984
985 if(length==0)
986 return;
987
988 CInternalRezPoolAccesser srcAccesser=getAudioInternal(channel);
989
990 const sample_pos_t peakChunkCountHave=peakChunkAccessers[channel]==NULL ? 0 : peakChunkAccessers[channel]->getSize();
991 const sample_pos_t peakChunkCountNeeded=calcPeakChunkCount(srcAccesser.getSize()-length);
992
993 destAccesser.moveData(0,srcAccesser,where,length);
994
995 if(peakChunkAccessers[channel]!=NULL)
996 {
997 // modify the peak data pools if the size is dropping below the required size
998 if(peakChunkCountHave>peakChunkCountNeeded)
999 {
1000 const sample_pos_t removeWhere=where/PEAK_CHUNK_SIZE;
1001 const sample_pos_t removeCount=peakChunkCountHave-peakChunkCountNeeded;
1002
1003 peakChunkAccessers[channel]->remove(removeWhere,removeCount);
1004 }
1005
1006 // make the a the peak chunk at where recalculate
1007 if((where/PEAK_CHUNK_SIZE)<peakChunkAccessers[channel]->getSize())
1008 (*(peakChunkAccessers[channel]))[where/PEAK_CHUNK_SIZE].dirty=true;
1009 }
1010 }
1011
1012 void CSound::moveDataIntoChannel(unsigned tempAudioPoolKey,unsigned channelInTempPool,unsigned channelInAudio,sample_pos_t where,sample_pos_t length,bool removeTempAudioPool)
1013 {
1014 if(length==0)
1015 return;
1016
1017 CInternalRezPoolAccesser destAccesser=getAudioInternal(channelInAudio);
1018
1019 const sample_pos_t peakChunkCountHave=peakChunkAccessers[channelInAudio]==NULL ? 0 : peakChunkAccessers[channelInAudio]->getSize();
1020 const sample_pos_t peakChunkCountNeeded=calcPeakChunkCount(destAccesser.getSize()+length);
1021
1022 CInternalRezPoolAccesser srcAccesser=getTempDataInternal(tempAudioPoolKey,channelInTempPool);
1023 if(length>srcAccesser.getSize())
1024 throw(runtime_error(string(__func__)+" -- length parameter out of range: "+istring(length)));
1025
1026 destAccesser.moveData(where,srcAccesser,0,length);
1027
1028 if(removeTempAudioPool)
1029 poolFile.removePool(createTempAudioPoolName(tempAudioPoolKey,channelInTempPool));
1030
1031 if(peakChunkAccessers[channelInAudio]!=NULL)
1032 {
1033 // add more peak chunks if the needed size is more than we have
1034 if(peakChunkCountNeeded>peakChunkCountHave)
1035 {
1036 const sample_pos_t insertWhere=where/PEAK_CHUNK_SIZE;
1037 const sample_pos_t insertCount=peakChunkCountNeeded-peakChunkCountHave;
1038
1039 peakChunkAccessers[channelInAudio]->insert(insertWhere,insertCount);
1040 for(sample_pos_t t=0;t<insertCount;t++)
1041 (*(peakChunkAccessers[channelInAudio]))[insertWhere+t].dirty=true;
1042 }
1043 else // just mark the one we inserted into as dirty
1044 (*(peakChunkAccessers[channelInAudio]))[where/PEAK_CHUNK_SIZE].dirty=true;
1045 }
1046
1047 }
1048
1049 void CSound::silenceSound(unsigned channel,sample_pos_t where,sample_pos_t length,bool doInvalidatePeakData,bool showProgressBar)
1050 {
1051 ASSERT_SIZE_LOCK
1052
1053 // ??? need a progress bar
1054 getAudio(channel).zeroData(where,length);
1055 if(doInvalidatePeakData)
1056 invalidatePeakData(channel,where,where+length);
1057 }
1058
1059 #include "DSP/TSoundStretcher.h"
1060
1061 void CSound::mixSound(unsigned channel,sample_pos_t where,const CRezPoolAccesser src,sample_pos_t srcWhere,unsigned srcSampleRate,sample_pos_t length,MixMethods mixMethod,SourceFitTypes fitSrc,bool doInvalidatePeakData,bool showProgressBar)
1062 {
1063 ASSERT_SIZE_LOCK
1064
1065 if(srcSampleRate==0)
1066 throw(runtime_error(string(__func__)+" -- srcSampleRate is 0"));
1067
1068 if(length==0)
1069 return;
1070
1071 CRezPoolAccesser dest=getAudio(channel);
1072 const sample_pos_t destOffset=where;
1073 const unsigned destSampleRate=getSampleRate();
1074
1075 #warning implement using sftChangeTempo now that I have a TTempoChanger DSP block
1076
1077 switch(mixMethod)
1078 {
1079 case mmOverwrite:
1080 if(fitSrc!=sftNone)
1081 {
1082 if(fitSrc==sftChangeRate)
1083 {
1084 TSoundStretcher<const CRezPoolAccesser> srcStretcher(src,srcWhere,src.getSize()-srcWhere,length);
1085 const sample_pos_t last=where+length;
1086 if(showProgressBar)
1087 {
1088 CStatusBar statusBar(_("Copying/Fitting Data -- Channel ")+istring(channel),where,last);
1089 for(sample_pos_t t=where;t<last;t++)
1090 {
1091 dest[t]=srcStretcher.getSample();
1092 statusBar.update(t);
1093 }
1094 }
1095 else
1096 {
1097 for(sample_pos_t t=where;t<last;t++)
1098 dest[t]=srcStretcher.getSample();
1099 }
1100 }
1101 else
1102 throw runtime_error(string(__func__)+" -- unimplemented fitSrc type: "+istring(fitSrc));
1103 }
1104 else if(srcSampleRate!=destSampleRate)
1105 { // do sample rate conversion
1106 TSoundStretcher<const CRezPoolAccesser> srcStretcher(src,srcWhere,(sample_pos_t)((sample_fpos_t)length/destSampleRate*srcSampleRate),length);
1107 const sample_pos_t last=where+length;
1108 if(showProgressBar)
1109 {
1110 CStatusBar statusBar(_("Copying Data -- Channel ")+istring(channel),where,last);
1111 for(sample_pos_t t=where;t<last;t++)
1112 {
1113 dest[t]=srcStretcher.getSample();
1114 statusBar.update(t);
1115 }
1116 }
1117 else
1118 {
1119 for(sample_pos_t t=where;t<last;t++)
1120 dest[t]=srcStretcher.getSample();
1121 }
1122 }
1123 else
1124 {
1125 if((length/100)>0)
1126 {
1127 CStatusBar statusBar(_("Copying Data -- Channel ")+istring(channel),0,100,false);
1128 for(sample_pos_t t=0;t<100;t++)
1129 {
1130 dest.copyData(destOffset+(t*(length/100)),src,srcWhere+(t*(length/100)),length/100);
1131 statusBar.update(t);
1132 }
1133 }
1134 dest.copyData(destOffset+(100*(length/100)),src,srcWhere+(100*(length/100)),length%100);
1135 }
1136
1137 break;
1138
1139 case mmAdd:
1140 if(fitSrc!=sftNone)
1141 {
1142 if(fitSrc==sftChangeRate)
1143 {
1144 TSoundStretcher<const CRezPoolAccesser> srcStretcher(src,srcWhere,src.getSize()-srcWhere,length);
1145 const sample_pos_t last=where+length;
1146 if(showProgressBar)
1147 {
1148 CStatusBar statusBar(_("Mixing/Fitting Data (add) -- Channel ")+istring(channel),where,last);
1149 for(sample_pos_t t=where;t<last;t++)
1150 {
1151 dest[t]=ClipSample((mix_sample_t)dest[t]+(mix_sample_t)srcStretcher.getSample());
1152 statusBar.update(t);
1153 }
1154 }
1155 else
1156 {
1157 for(sample_pos_t t=where;t<last;t++)
1158 dest[t]=ClipSample((mix_sample_t)dest[t]+(mix_sample_t)srcStretcher.getSample());
1159 }
1160 }
1161 else
1162 throw runtime_error(string(__func__)+" -- unimplemented fitSrc type: "+istring(fitSrc));
1163 }
1164 else if(srcSampleRate!=destSampleRate)
1165 {
1166 TSoundStretcher<const CRezPoolAccesser> srcStretcher(src,srcWhere,(sample_pos_t)((sample_fpos_t)length/destSampleRate*srcSampleRate),length);
1167 const sample_pos_t last=where+length;
1168 if(showProgressBar)
1169 {
1170 CStatusBar statusBar(_("Mixing Data (add) -- Channel ")+istring(channel),where,last);
1171 for(sample_pos_t t=where;t<last;t++)
1172 {
1173 dest[t]=ClipSample((mix_sample_t)dest[t]+(mix_sample_t)srcStretcher.getSample());
1174 statusBar.update(t);
1175 }
1176 }
1177 else
1178 {
1179 for(sample_pos_t t=where;t<last;t++)
1180 dest[t]=ClipSample((mix_sample_t)dest[t]+(mix_sample_t)srcStretcher.getSample());
1181 }
1182 }
1183 else
1184 { // not fiting src and sample rates match
1185 const sample_pos_t last=where+length;
1186 if(showProgressBar)
1187 {
1188 CStatusBar statusBar(_("Mixing Data (add) -- Channel ")+istring(channel),where,last);
1189 for(sample_pos_t t=where;t<last;t++)
1190 {
1191 dest[t]=ClipSample((mix_sample_t)dest[t]+(mix_sample_t)src[srcWhere++]);
1192 statusBar.update(t);
1193 }
1194 }
1195 else
1196 {
1197 for(sample_pos_t t=where;t<last;t++)
1198 dest[t]=ClipSample((mix_sample_t)dest[t]+(mix_sample_t)src[srcWhere++]);
1199 }
1200 }
1201
1202 break;
1203
1204 case mmSubtract:
1205 if(fitSrc!=sftNone)
1206 {
1207 if(fitSrc==sftChangeRate)
1208 {
1209 TSoundStretcher<const CRezPoolAccesser> srcStretcher(src,srcWhere,src.getSize()-srcWhere,length);
1210 const sample_pos_t last=where+length;
1211 if(showProgressBar)
1212 {
1213 CStatusBar statusBar(_("Mixing/Fitting Data (subtract) -- Channel ")+istring(channel),where,last);
1214 for(sample_pos_t t=where;t<last;t++)
1215 {
1216 dest[t]=ClipSample((mix_sample_t)dest[t]-(mix_sample_t)srcStretcher.getSample());
1217 statusBar.update(t);
1218 }
1219 }
1220 else
1221 {
1222 for(sample_pos_t t=where;t<last;t++)
1223 dest[t]=ClipSample((mix_sample_t)dest[t]-(mix_sample_t)srcStretcher.getSample());
1224 }
1225 }
1226 else
1227 throw runtime_error(string(__func__)+" -- unimplemented fitSrc type: "+istring(fitSrc));
1228 }
1229 else if(srcSampleRate!=destSampleRate)
1230 {
1231 TSoundStretcher<const CRezPoolAccesser> srcStretcher(src,srcWhere,(sample_pos_t)((sample_fpos_t)length/destSampleRate*srcSampleRate),length);
1232 const sample_pos_t last=where+length;
1233 if(showProgressBar)
1234 {
1235 CStatusBar statusBar(_("Mixing Data (subtract) -- Channel ")+istring(channel),where,last);
1236 for(sample_pos_t t=where;t<last;t++)
1237 {
1238 dest[t]=ClipSample((mix_sample_t)dest[t]-(mix_sample_t)srcStretcher.getSample());
1239 statusBar.update(t);
1240 }
1241 }
1242 else
1243 {
1244 for(sample_pos_t t=where;t<last;t++)
1245 dest[t]=ClipSample((mix_sample_t)dest[t]-(mix_sample_t)srcStretcher.getSample());
1246 }
1247 }
1248 else
1249 { // not fiting src and sample rates match
1250 const sample_pos_t last=where+length;
1251 if(showProgressBar)
1252 {
1253 CStatusBar statusBar(_("Mixing Data (subtract) -- Channel ")+istring(channel),where,last);
1254 for(sample_pos_t t=where;t<last;t++)
1255 {
1256 dest[t]=ClipSample((mix_sample_t)dest[t]-(mix_sample_t)src[srcWhere++]);
1257 statusBar.update(t);
1258 }
1259 }
1260 else
1261 {
1262 for(sample_pos_t t=where;t<last;t++)
1263 dest[t]=ClipSample((mix_sample_t)dest[t]-(mix_sample_t)src[srcWhere++]);
1264 }
1265 }
1266
1267 break;
1268
1269 case mmMultiply:
1270 if(fitSrc!=sftNone)
1271 {
1272 if(fitSrc==sftChangeRate)
1273 {
1274 TSoundStretcher<const CRezPoolAccesser> srcStretcher(src,srcWhere,src.getSize()-srcWhere,length);
1275 const sample_pos_t last=where+length;
1276 if(showProgressBar)
1277 {
1278 CStatusBar statusBar(_("Mixing/Fitting Data (multiply) -- Channel ")+istring(channel),where,last);
1279 for(sample_pos_t t=where;t<last;t++)
1280 {
1281 dest[t]=ClipSample((mix_sample_t)dest[t]*(mix_sample_t)srcStretcher.getSample()/MAX_SAMPLE);
1282 statusBar.update(t);
1283 }
1284 }
1285 else
1286 {
1287 for(sample_pos_t t=where;t<last;t++)
1288 dest[t]=ClipSample((mix_sample_t)dest[t]*(mix_sample_t)srcStretcher.getSample()/MAX_SAMPLE);
1289 }
1290 }
1291 else
1292 throw runtime_error(string(__func__)+" -- unimplemented fitSrc type: "+istring(fitSrc));
1293 }
1294 else if(srcSampleRate!=destSampleRate)
1295 {
1296 TSoundStretcher<const CRezPoolAccesser> srcStretcher(src,srcWhere,(sample_pos_t)((sample_fpos_t)length/destSampleRate*srcSampleRate),length);
1297 const sample_pos_t last=where+length;
1298 if(showProgressBar)
1299 {
1300 CStatusBar statusBar(_("Mixing Data (multiply) -- Channel ")+istring(channel),where,last);
1301 for(sample_pos_t t=where;t<last;t++)
1302 {
1303 dest[t]=ClipSample((mix_sample_t)dest[t]*(mix_sample_t)srcStretcher.getSample()/MAX_SAMPLE);
1304 statusBar.update(t);
1305 }
1306 }
1307 else
1308 {
1309 for(sample_pos_t t=where;t<last;t++)
1310 dest[t]=ClipSample((mix_sample_t)dest[t]*(mix_sample_t)srcStretcher.getSample()/MAX_SAMPLE);
1311 }
1312 }
1313 else
1314 { // not fiting src and sample rates match
1315 const sample_pos_t last=where+length;
1316 if(showProgressBar)
1317 {
1318 CStatusBar statusBar(_("Mixing Data (multiply) -- Channel ")+istring(channel),where,last);
1319 for(sample_pos_t t=where;t<last;t++)
1320 {
1321 dest[t]=ClipSample((mix_sample_t)dest[t]*(mix_sample_t)src[srcWhere++]/MAX_SAMPLE);
1322 statusBar.update(t);
1323 }
1324 }
1325 else
1326 {
1327 for(sample_pos_t t=where;t<last;t++)
1328 dest[t]=ClipSample((mix_sample_t)dest[t]*(mix_sample_t)src[srcWhere++]/MAX_SAMPLE);
1329 }
1330 }
1331
1332 break;
1333
1334 case mmAverage:
1335 if(fitSrc!=sftNone)
1336 {
1337 if(fitSrc==sftChangeRate)
1338 {
1339 TSoundStretcher<const CRezPoolAccesser> srcStretcher(src,srcWhere,src.getSize()-srcWhere,length);
1340 const sample_pos_t last=where+length;
1341 if(showProgressBar)
1342 {
1343 CStatusBar statusBar(_("Mixing/Fitting Data (average) -- Channel ")+istring(channel),where,last);
1344 for(sample_pos_t t=where;t<last;t++)
1345 {
1346 dest[t]=((mix_sample_t)dest[t]+(mix_sample_t)srcStretcher.getSample())/2;
1347 statusBar.update(t);
1348 }
1349 }
1350 else
1351 {
1352 for(sample_pos_t t=where;t<last;t++)
1353 dest[t]=((mix_sample_t)dest[t]+(mix_sample_t)srcStretcher.getSample())/2;
1354 }
1355 }
1356 else
1357 throw runtime_error(string(__func__)+" -- unimplemented fitSrc type: "+istring(fitSrc));
1358 }
1359 else if(srcSampleRate!=destSampleRate)
1360 {
1361 TSoundStretcher<const CRezPoolAccesser> srcStretcher(src,srcWhere,(sample_pos_t)((sample_fpos_t)length/destSampleRate*srcSampleRate),length);
1362 const sample_pos_t last=where+length;
1363 if(showProgressBar)
1364 {
1365 CStatusBar statusBar(_("Mixing Data (average) -- Channel ")+istring(channel),where,last);
1366 for(sample_pos_t t=where;t<last;t++)
1367 {
1368 dest[t]=((mix_sample_t)dest[t]+(mix_sample_t)srcStretcher.getSample())/2;
1369 statusBar.update(t);
1370 }
1371 }
1372 else
1373 {
1374 for(sample_pos_t t=where;t<last;t++)
1375 dest[t]=((mix_sample_t)dest[t]+(mix_sample_t)srcStretcher.getSample())/2;
1376 }
1377 }
1378 else
1379 { // not fiting src and sample rates match
1380 const sample_pos_t last=where+length;
1381 if(showProgressBar)
1382 {
1383 CStatusBar statusBar(_("Mixing Data (average) -- Channel ")+istring(channel),where,last);
1384 for(sample_pos_t t=where;t<last;t++)
1385 {
1386 dest[t]=((mix_sample_t)dest[t]+(mix_sample_t)src[srcWhere++])/2;
1387 statusBar.update(t);
1388 }
1389 }
1390 else
1391 {
1392 for(sample_pos_t t=where;t<last;t++)
1393 dest[t]=((mix_sample_t)dest[t]+(mix_sample_t)src[srcWhere++])/2;
1394 }
1395 }
1396
1397 break;
1398
1399 default:
1400 throw(runtime_error(string(__func__)+" -- unhandled mixMethod: "+istring(mixMethod)));
1401 }
1402
1403 if(doInvalidatePeakData)
1404 invalidatePeakData(channel,where,where+length);
1405 }
1406
1407
1408
1409
1410
1411
1412 const string CSound::getTimePosition(sample_pos_t samplePos,int secondsDecimalPlaces,bool includeUnits) const
1413 {
1414 const sample_fpos_t sampleRate=getSampleRate();
1415 const sample_fpos_t sTime=samplePos/sampleRate;
1416
1417 return seconds_to_string(sTime,secondsDecimalPlaces,includeUnits);
1418 }
1419
1420 #include <stdio.h> // for sscanf
1421 const sample_pos_t CSound::getPositionFromTime(const string time,bool &wasInvalid) const
1422 {
1423 wasInvalid=false;
1424 sample_pos_t samplePos=0;
1425
1426 if(istring(time).count(':')==2)
1427 { // supposedly HH:MM:SS.sssss
1428 unsigned h=0,m=0;
1429 double s=0.0;
1430 // ??? this may be a potential porting issue
1431 sscanf(time.c_str()," %u:%u:%lf ",&h,&m,&s);
1432 samplePos=(sample_pos_t)(((sample_fpos_t)h*3600.0+(sample_fpos_t)m*60.0+(sample_fpos_t)s)*(sample_fpos_t)getSampleRate());
1433 }
1434 else if(istring(time).count(':')==1)
1435 { // supposedly MM:SS.sssss
1436 unsigned m=0;
1437 double s=0.0;
1438 // ??? this may be a potential porting issue
1439 sscanf(time.c_str()," %u:%lf ",&m,&s);
1440 samplePos=(sample_pos_t)(((sample_fpos_t)m*60.0+(sample_fpos_t)s)*(sample_fpos_t)getSampleRate());
1441 }
1442 else if(istring(time).count(':')==0)
1443 { // supposedly SSSS.sssss
1444 double s=0.0;
1445 // ??? this may be a potential porting issue
1446 sscanf(time.c_str()," %lf ",&s);
1447 samplePos=(sample_pos_t)(((sample_fpos_t)s)*(sample_fpos_t)getSampleRate());
1448 }
1449 else
1450 {
1451 wasInvalid=true;
1452 samplePos=0;
1453 }
1454
1455 return(samplePos);
1456 }
1457
1458 const string CSound::getAudioDataSize(const sample_pos_t sampleCount) const
1459 {
1460 sample_fpos_t audioDataSize=sampleCount*sizeof(sample_t)*channelCount;
1461 if(audioDataSize>=1024*1024*1024)
1462 { // return as gb
1463 return(istring(audioDataSize/1024.0/1024.0/1024.0,5,3)+"gb");
1464 }
1465 else if(audioDataSize>=1024*1024)
1466 { // return as mb
1467 return(istring(audioDataSize/1024.0/1024.0,5,2)+"mb");
1468 }
1469 else if(audioDataSize>=1024)
1470 { // return as kb
1471 return(istring(audioDataSize/1024.0,5,1)+"kb");
1472 }
1473 else
1474 { // return as b
1475 return(istring((sample_pos_t)audioDataSize)+"b");
1476 }
1477 }
1478
1479 const string CSound::getAudioDataSize() const
1480 {
1481 return(getAudioDataSize(isEmpty() ? 0 : getAudio(0).getSize()));
1482 }
1483
1484 const string CSound::getPoolFileSize() const
1485 {
1486 uint64_t iPoolFileSize=poolFile.getFileSize();
1487 if(iPoolFileSize>=1024*1024*1024)
1488 { // return as gb
1489 return(istring((long double)iPoolFileSize/1024.0/1024.0/1024.0,5,3)+"gb");
1490 }
1491 else if(iPoolFileSize>=1024*1024)
1492 { // return as mb
1493 return(istring((long double)iPoolFileSize/1024.0/1024.0,5,2)+"mb");
1494 }
1495 else if(iPoolFileSize>=1024)
1496 { // return as kb
1497 return(istring((long double)iPoolFileSize/1024.0,5,1)+"kb");
1498 }
1499 else
1500 { // return as b
1501 return(istring(iPoolFileSize)+"b");
1502 }
1503 }
1504
1505 void CSound::defragPoolFile()
1506 {
1507 lockForResize();
1508 try
1509 {
1510 poolFile.defrag();
1511 unlockForResize();
1512 }
1513 catch(...)
1514 {
1515 unlockForResize();
1516 throw;
1517 }
1518 }
1519
1520 void CSound::printSAT()
1521 {
1522 poolFile.printSAT();
1523 }
1524
1525 void CSound::verifySAT()
1526 {
1527 poolFile.verifyAllBlockInfo(false);
1528 }
1529
1530
1531 void CSound::flush()
1532 {
1533 poolFile.flushData();
1534 }
1535
1536
1537 /*
1538 Finds a working directory for a working file
1539 for the given filename. A suitable working
1540 directory should be writable and have free
1541 space of 110% of the given file's size
1542 */
1543 #if defined(rez_OS_SOLARIS)
1544 #include <sys/statvfs.h>
1545 #define statfs statvfs
1546 #elif defined(rez_OS_BSD)
1547 #include <sys/param.h> /* 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 */
1548 #include <sys/mount.h>
1549 #elif defined(rez_OS_LINUX)
1550 #include <sys/statfs.h>
1551 #else
1552 #error complier error imminent no xxxfs.h has been included
1553 #endif
1554
1555 #include <stdio.h>
1556 #include <string.h>
1557 #include <errno.h>
1558 const string findWorkDir(const string filename)
1559 {
1560 vector<string> workingDirs;
1561 if(gPrimaryWorkDir!="" && CPath(gPrimaryWorkDir).isDirectory()) {
1562 workingDirs.push_back(CPath(gPrimaryWorkDir).realPath());
1563 }
1564 workingDirs.push_back(CPath(filename).dirName()); // first try the dirname of the given filename
1565 workingDirs.push_back(gFallbackWorkDir); // next try to fallback working dir (in the future, this may be a list)
1566
1567 for(size_t t=0;t<workingDirs.size();t++)
1568 {
1569 const string workDir=workingDirs[t];
1570 const string testFilename=workDir+CPath::dirDelim+"rezrez842rezrez";
1571 FILE *f=fopen(testFilename.c_str(),"wb");
1572 if(f!=NULL)
1573 { // directry is writable
1574 fclose(f);
1575 remove(testFilename.c_str());
1576
1577 struct statfs s;
1578 if(statfs(workDir.c_str(),&s)!=0)
1579 {
1580 int e=errno;
1581 fprintf(stderr,"error getting free space on working directory candidate: %s -- %s\n",workDir.c_str(),strerror(e));
1582 continue; // couldn't stat the fs for some reason
1583 }
1584
1585 // ??? this would only be true if it's an uncompressed format
1586 // I really need a way to know how big the working file will be after loading
1587 const int64_t fsSize= (int64_t)s.f_bsize * (int64_t)s.f_bfree;
1588 const int64_t fileSize=CPath(filename).getSize(false);
1589
1590 if(fsSize<(fileSize+(fileSize/10))) // ??? 10% overhead
1591 {
1592 fprintf(stderr,"insufficient free space in working directory candidate: %s\n",workDir.c_str());
1593 continue; // not enough free space on that partition
1594 }
1595
1596 return(workDir);
1597 }
1598 else
1599 {
1600 int e=errno;
1601 fprintf(stderr,"cannot write to working directory candidate: %s -- %s\n",workDir.c_str(),strerror(e));
1602 }
1603 }
1604
1605 throw(runtime_error(string(__func__)+" -- no suitable working directory available to load the file: "+filename));
1606 }
1607
1608 void CSound::createWorkingPoolFile(const string originalFilename,const unsigned _sampleRate,const unsigned _channelCount,const sample_pos_t _size)
1609 {
1610 if(poolFile.isOpen())
1611 throw(runtime_error(string(__func__)+" -- poolFile is already opened"));
1612
1613 if(_channelCount>MAX_CHANNELS)
1614 throw(runtime_error(string(__func__)+" -- invalid number of channels: "+istring(_channelCount)));
1615
1616 channelCount=_channelCount;
1617 sampleRate=_sampleRate;
1618 size=_size;
1619
1620 // determine a suitable place for the working file
1621 const string workDir=findWorkDir(originalFilename);
1622
1623 const string workingFilename=GET_WORKING_FILENAME(workDir,originalFilename);
1624 PoolFile_t::removeFile(workingFilename);
1625 poolFile.openFile(workingFilename,true);
1626 removeAllTempAudioPools();
1627
1628 CFormatInfoPoolAccesser a=poolFile.createPool<RFormatInfo>(FORMAT_INFO_POOL_NAME);
1629 metaInfoPoolID=poolFile.getPoolIdByName(FORMAT_INFO_POOL_NAME);
1630 a.append(1);
1631
1632 // create an audio pool for each channel
1633 for(unsigned t=0;t<channelCount;t++)
1634 {
1635 string poolName=AUDIO_POOL_NAME+istring(t+1);
1636 CInternalRezPoolAccesser a1=poolFile.createPool<sample_t>(poolName);
1637 channelPoolIDs[t]=poolFile.getPoolIdByName(poolName);
1638 a1.append(size);
1639 }
1640
1641 // create all dirty peakChunks after we know the data size
1642 createPeakChunkAccessers();
1643
1644 createCueAccesser();
1645
1646 /*??? maybe could speed things up by not zeroing the data here */
1647 matchUpChannelLengths(NIL_SAMPLE_POS);
1648
1649 saveMetaInfo();
1650 }
1651
1652 bool CSound::createFromWorkingPoolFileIfExists(const string originalFilename,bool promptIfFound)
1653 {
1654 if(poolFile.isOpen())
1655 throw(runtime_error(string(__func__)+" -- poolFile is already opened"));
1656
1657 try
1658 {
1659
1660 vector<string> workingDirs;
1661 workingDirs.push_back(CPath(originalFilename).dirName());
1662 workingDirs.push_back(gFallbackWorkDir);
1663
1664 // look in all possible working spaces
1665 string workingFilename="";
1666 for(size_t t=0;t<workingDirs.size();t++)
1667 {
1668 const string f=GET_WORKING_FILENAME(workingDirs[t],originalFilename);
1669 // ??? and we need to know that if this file is being used by any other loaded file.. then this is not the file and we need to alter the working filename at that point some how.. or just refuse to load the file at all
1670 if(CPath(f).exists())
1671 {
1672 workingFilename=f;
1673 break;
1674 }
1675 }
1676
1677 if(workingFilename=="")
1678 return(false); // wasn't found
1679
1680 if(promptIfFound)
1681 {
1682 // ??? probably have a cancel button to avoid loaded the sound at all.. probably throw an exception of a different type which is an ESkipLoadingFile
1683 if(Question(_("File: ")+workingFilename+"\n\n"+_("A temporary file was found indicating that this file was previously being edited when a crash occurred or the process was killed.\n\nDo you wish to attempt to recover from this temporary file (otherwise the file will be deleted)?"),yesnoQues)==noAns)
1684 {
1685 // ??? doesn't remove other files in the set of files if it was a multi file set >2gb
1686 PoolFile_t::removeFile(workingFilename);
1687 return(false);
1688 }
1689 }
1690
1691 poolFile.openFile(workingFilename,false);
1692 _isModified=true;
1693
1694 removeAllTempAudioPools();
1695 deletePeakChunkAccessers();
1696
1697 deleteCueAccesser();
1698
1699 // now that file is successfully opened, ask user if they want
1700 // to try to pick up where they left off or forget all edit
1701 // history
1702
1703 metaInfoPoolID=poolFile.getPoolIdByName(FORMAT_INFO_POOL_NAME);
1704
1705 // check version at the beginning of RFormat and perhaps handle things differently
1706 uint32_t version=0xffffffff;
1707 poolFile.readPoolRaw(metaInfoPoolID,&version,sizeof(version));
1708 if(version==1)
1709 {
1710 const CFormatInfoPoolAccesser a=poolFile.getPoolAccesser<RFormatInfo>(metaInfoPoolID);
1711 RFormatInfo r;
1712 r=a[0];
1713
1714 if(r.size>MAX_LENGTH)
1715 {
1716 // ??? what should I do? truncate the sound or just error out?
1717 }
1718
1719 size=r.size; // actually overwritten by matchUpChannelLengths
1720 sampleRate=r.sampleRate;
1721 channelCount=r.channelCount;
1722 }
1723 else
1724 throw(runtime_error(string(__func__)+" -- unhandled format version: "+istring(version)+" in found working file: "+workingFilename));
1725
1726 if(channelCount<0 || channelCount>MAX_CHANNELS)
1727 {
1728 deletePeakChunkAccessers();
1729 deleteCueAccesser();
1730 poolFile.closeFile(false,false);
1731 throw(runtime_error(string(__func__)+" -- invalid number of channels: "+istring(channelCount)+" in found working file: "+workingFilename));
1732 }
1733
1734 for(unsigned t=0;t<channelCount;t++)
1735 channelPoolIDs[t]=poolFile.getPoolIdByName(AUDIO_POOL_NAME+istring(t+1));
1736
1737
1738 // just in case the channels have different lengths
1739 matchUpChannelLengths(NIL_SAMPLE_POS);
1740
1741 // create all dirty peakChunks after we know the data size
1742 createPeakChunkAccessers();
1743
1744 createCueAccesser();
1745
1746 saveMetaInfo();
1747
1748 return(true);
1749 }
1750 catch(...)
1751 {
1752 // remove the file if it was corrupt and just has a problem opening
1753 deletePeakChunkAccessers();
1754 deleteCueAccesser();
1755 poolFile.closeFile(false,true);
1756 return(false);
1757 }
1758 }
1759
1760 void CSound::setSampleRate(unsigned newSampleRate)
1761 {
1762 sampleRate=newSampleRate;
1763 saveMetaInfo();
1764 }
1765
1766 void CSound::saveMetaInfo()
1767 {
1768 if(!poolFile.isOpen())
1769 throw(runtime_error(string(__func__)+" -- poolFile is not opened"));
1770
1771 CFormatInfoPoolAccesser b=poolFile.getPoolAccesser<RFormatInfo>(metaInfoPoolID);
1772 if(b.getSize()==1)
1773 {
1774 RFormatInfo &r=b[0];
1775
1776 // always write the newest format
1777 r.version=1;
1778 r.size=size;
1779 r.sampleRate=sampleRate;
1780 r.channelCount=channelCount;
1781 }
1782 else
1783 {
1784 RFormatInfo r;
1785
1786 // always write the newest format
1787 r.version=1;
1788 r.size=size;
1789 r.sampleRate=sampleRate;
1790 r.channelCount=channelCount;
1791
1792 poolFile.clearPool(metaInfoPoolID);
1793 poolFile.setPoolAlignment(metaInfoPoolID,sizeof(RFormatInfo));
1794
1795 CFormatInfoPoolAccesser b=poolFile.getPoolAccesser<RFormatInfo>(metaInfoPoolID);
1796 b.append(1);
1797 b[0]=r;
1798 }
1799
1800 // really slows things down especially for recording
1801 // flush();
1802 }
1803
1804 void CSound::createPeakChunkAccessers()
1805 {
1806 if(poolFile.isOpen())
1807 {
1808 sample_pos_t peakCount=calcPeakChunkCount(size);
1809 for(unsigned i=0;i<channelCount;i++)
1810 {
1811 peakChunkAccessers[i]=new CPeakChunkRezPoolAccesser(poolFile.createPool<RPeakChunk>(PEAK_CHUNK_POOL_NAME+istring(i),false));
1812 peakChunkAccessers[i]->clear();
1813 peakChunkAccessers[i]->append(peakCount);
1814 for(sample_pos_t t=0;t<peakCount;t++)
1815 (*(peakChunkAccessers[i]))[t].dirty=true;
1816 }
1817 }
1818 }
1819
1820 void CSound::deletePeakChunkAccessers()
1821 {
1822 if(poolFile.isOpen())
1823 {
1824 for(unsigned t=0;t<MAX_CHANNELS;t++)
1825 {
1826 delete peakChunkAccessers[t];
1827 peakChunkAccessers[t]=NULL;
1828 }
1829 }
1830 }
1831
1832 // returns the number of peak chunks that there needs to be for the given size
1833 sample_pos_t CSound::calcPeakChunkCount(sample_pos_t givenSize)
1834 {
1835 sample_pos_t v=((sample_pos_t)ceil(((sample_fpos_t)givenSize)/((sample_fpos_t)PEAK_CHUNK_SIZE)));
1836 if(v<=0)
1837 v=1;
1838 return(v);
1839 }
1840
1841
1842 const string CSound::createTempAudioPoolName(unsigned tempAudioPoolKey,unsigned channel)
1843 {
1844 return(TEMP_AUDIO_POOL_NAME+istring(tempAudioPoolKey)+"_"+istring(channel));
1845 }
1846
1847 CSound::CInternalRezPoolAccesser CSound::createTempAudioPool(unsigned tempAudioPoolKey,unsigned channel)
1848 {
1849 return(poolFile.createPool<sample_t>(createTempAudioPoolName(tempAudioPoolKey,channel)));
1850 }
1851
1852 void CSound::removeAllTempAudioPools()
1853 {
1854 for(size_t t=0;t<poolFile.getPoolIndexCount();t++)
1855 {
1856 const PoolFile_t::poolId_t poolId=poolFile.getPoolIdByIndex(t);
1857 const string _poolName=poolFile.getPoolNameById(poolId);
1858 const char *poolName=_poolName.c_str();
1859 // ??? since I'm using strstr, string probably needs/has some string searching methods
1860 if(strstr(poolName,TEMP_AUDIO_POOL_NAME)==poolName)
1861 {
1862 poolFile.removePool(poolId);
1863 t--;
1864 }
1865 }
1866 }
1867
1868
1869 // appends silence to the end of any channel that is shorter than the longest one
1870 void CSound::matchUpChannelLengths(sample_pos_t maxLength,bool doZeroData)
1871 {
1872 /*
1873 if(maxLength>MAX_LENGTH)
1874 throw(runtime_error(string(__func__)+" -- invalid maxLength: "+istring(maxLength)));
1875 */
1876
1877 // get the max size of all the audio pools
1878 sample_pos_t maxAudioPoolSize=0;
1879 for(unsigned t=0;t<channelCount;t++)
1880 maxAudioPoolSize=max(maxAudioPoolSize,getAudioInternal(t).getSize());
1881
1882 if(maxLength==NIL_SAMPLE_POS)
1883 {
1884 // add space to the end of any audio pool that is shorter
1885 for(unsigned t=0;t<channelCount;t++)
1886 {
1887 const sample_pos_t channelSize=getAudioInternal(t).getSize();
1888 if(channelSize<maxAudioPoolSize)
1889 addSpaceToChannel(t,channelSize,maxAudioPoolSize-channelSize,doZeroData);
1890 }
1891 }
1892 else
1893 {
1894 maxAudioPoolSize=min(maxAudioPoolSize,maxLength);
1895
1896 // add space to the end of any audio pool that is shorter and truncate ones that are longer
1897 for(unsigned t=0;t<channelCount;t++)
1898 {
1899 const sample_pos_t channelSize=getAudioInternal(t).getSize();
1900 if(channelSize>maxAudioPoolSize)
1901 removeSpaceFromChannel(t,maxAudioPoolSize,channelSize-maxAudioPoolSize);
1902 else if(channelSize<maxAudioPoolSize)
1903 addSpaceToChannel(t,channelSize,maxAudioPoolSize-channelSize,doZeroData);
1904 }
1905 }
1906
1907 size=maxAudioPoolSize;
1908 ensureNonZeroLength();
1909 saveMetaInfo();
1910 }
1911
1912 void CSound::ensureNonZeroLength()
1913 {
1914 if(size<=0)
1915 {
1916 for(unsigned t=0;t<channelCount;t++)
1917 {
1918 CInternalRezPoolAccesser a=getAudioInternal(t);
1919 a.clear();
1920 a.append(1);
1921 a[a.getSize()-1]=0;
1922
1923 // I would also do it for peakChunkAccessers, but I handled that
1924 // by always insisting on at least 1 chunk in calcPeakChunkCount
1925 }
1926 size=1;
1927 }
1928 }
1929
1930 void CSound::setIsModified(bool v)
1931 {
1932 _isModified=v;
1933 }
1934
1935 const bool CSound::isModified() const
1936 {
1937 return(_isModified);
1938 }
1939
1940
1941
1942
1943 // -----------------------------------------------------
1944 // --- Cue Methods -------------------------------------
1945 // -----------------------------------------------------
1946
1947 const size_t CSound::getCueCount() const
1948 {
1949 return(cueAccesser->getSize());
1950 }
1951
1952 const string CSound::getCueName(size_t index) const
1953 {
1954 if(index>=getCueCount())
1955 throw runtime_error(string(__func__)+" -- index is out of bounds: "+istring(index));
1956 return((*cueAccesser)[index].name);
1957 }
1958
1959 const sample_pos_t CSound::getCueTime(size_t index) const
1960 {
1961 if(index>=getCueCount())
1962 throw runtime_error(string(__func__)+" -- index is out of bounds: "+istring(index));
1963 return((*cueAccesser)[index].time);
1964 }
1965
1966 void CSound::setCueTime(size_t index,sample_pos_t newTime)
1967 {
1968 if(index>=getCueCount())
1969 throw runtime_error(string(__func__)+" -- index is out of bounds: "+istring(index));
1970 (*cueAccesser)[index].time=newTime;
1971
1972 // update cueIndex
1973 rebuildCueIndex();
1974 }
1975
1976 const bool CSound::isCueAnchored(size_t index) const
1977 {
1978 if(index>=getCueCount())
1979 throw runtime_error(string(__func__)+" -- index is out of bounds: "+istring(index));
1980 return((*cueAccesser)[index].isAnchored);
1981 }
1982
1983 void CSound::addCue(const string &name,const sample_pos_t time,const bool isAnchored)
1984 {
1985 if(name.size()>=MAX_SOUND_CUE_NAME_LENGTH-1)
1986 throw(runtime_error(string(__func__)+" -- cue name too long"));
1987
1988 cueAccesser->append(1);
1989 (*cueAccesser)[cueAccesser->getSize()-1]=RCue(name.c_str(),time,isAnchored);
1990
1991 // update cueIndex
1992 cueIndex.insert(map<sample_pos_t,size_t>::value_type(time,cueAccesser->getSize()-1));
1993 }
1994
1995 void CSound::insertCue(size_t index,const string &name,const sample_pos_t time,const bool isAnchored)
1996 {
1997 if(name.size()>=MAX_SOUND_CUE_NAME_LENGTH-1)
1998 throw(runtime_error(string(__func__)+" -- cue name too long"));
1999 if(index>cueAccesser->getSize())
2000 throw(runtime_error(string(__func__)+" -- invalid index: "+istring(index)));
2001
2002 cueAccesser->insert(index,1);
2003 (*cueAccesser)[index]=RCue(name.c_str(),time,isAnchored);
2004
2005 // update cueIndex
2006 rebuildCueIndex();
2007 }
2008
2009 void CSound::removeCue(size_t index)
2010 {
2011 if(index>=getCueCount())
2012 throw runtime_error(string(__func__)+" -- index is out of bounds: "+istring(index));
2013 cueAccesser->remove(index,1);
2014
2015 // update cueIndex
2016 rebuildCueIndex();
2017 }
2018
2019 size_t CSound::__default_cue_index;
2020 bool CSound::containsCue(const string &name,size_t &index) const
2021 {
2022 for(size_t t=0;t<cueAccesser->getSize();t++)
2023 {
2024 if(strncmp((*cueAccesser)[t].name,name.c_str(),MAX_SOUND_CUE_NAME_LENGTH)==0)
2025 {
2026 index=t;
2027 return true;
2028 }
2029 }
2030 return false;
2031 }
2032
2033 bool CSound::findCue(const sample_pos_t time,size_t &index) const
2034 {
2035 const map<sample_pos_t,size_t>::const_iterator i=cueIndex.find(time);
2036
2037 if(i!=cueIndex.end())
2038 {
2039 index=i->second;
2040 return true;
2041 }
2042 else
2043 return false;
2044 }
2045
2046 bool CSound::findNearestCue(const sample_pos_t time,size_t &index,sample_pos_t &distance) const
2047 {
2048 if(cueIndex.empty())
2049 return false;
2050
2051 map<sample_pos_t,size_t>::const_iterator i=cueIndex.lower_bound(time);
2052
2053 if(i!=cueIndex.begin())
2054 {
2055 if(i==cueIndex.end())
2056 {
2057 i--; // go back one
2058 index=i->second;
2059 distance=(sample_pos_t)sample_fpos_fabs((sample_fpos_t)time-(sample_fpos_t)i->first);
2060 }
2061 else
2062 {
2063 map<sample_pos_t,size_t>::const_iterator pi=i;
2064 pi--;
2065
2066 const sample_pos_t d1=(sample_pos_t)sample_fpos_fabs((sample_fpos_t)time-(sample_fpos_t)pi->first);
2067 const sample_pos_t d2=(sample_pos_t)sample_fpos_fabs((sample_fpos_t)time-(sample_fpos_t)i->first);
2068
2069 if(d1<d2)
2070 {
2071 index=pi->second;
2072 distance=d1;
2073 }
2074 else
2075 {
2076 index=i->second;
2077 distance=d2;
2078 }
2079 }
2080 }
2081 else
2082 {
2083 index=i->second;
2084 distance=(sample_pos_t)sample_fpos_fabs((sample_fpos_t)time-(sample_fpos_t)i->first);
2085 }
2086
2087 return true;
2088 }
2089
2090 bool CSound::findPrevCue(const sample_pos_t time,size_t &index) const
2091 {
2092 map<sample_pos_t,size_t>::const_iterator i=cueIndex.find(time);
2093 if(i==cueIndex.end())
2094 return false;
2095
2096 if(i!=cueIndex.begin())
2097 i--;
2098 index=i->second;
2099 return true;
2100 }
2101
2102 bool CSound::findNextCue(const sample_pos_t time,size_t &index) const
2103 {
2104 map<sample_pos_t,size_t>::const_iterator i=cueIndex.find(time);
2105 if(i==cueIndex.end())
2106 return false;
2107
2108 i++;
2109 if(i==cueIndex.end())
2110 return false;
2111 index=i->second;
2112 return true;
2113 }
2114
2115 bool CSound::findPrevCueInTime(const sample_pos_t time,size_t &index) const
2116 {
2117 if(cueIndex.empty())
2118 return false;
2119
2120 // upper_bound returns the element that is greater than the position where 'time' would be inserted if it were
2121 map<sample_pos_t,size_t>::const_iterator i=cueIndex.upper_bound(time);
2122
2123 if(i==cueIndex.end())
2124 {
2125 i--;
2126 index=i->second;
2127 return true; // ok return the last cue in time
2128 }
2129 else
2130 {
2131 if(i==cueIndex.begin())
2132 return false; // ok, so time is are prior to all cues
2133 i--;
2134 index=i->second;
2135 return true; // ok we're at the cue prior to the one upper_bound found
2136 }
2137 }
2138
2139 bool CSound::findNextCueInTime(const sample_pos_t time,size_t &index) const
2140 {
2141 // upper_bound returns the element that is greater than the position where 'time' would be inserted if it were
2142 map<sample_pos_t,size_t>::const_iterator i=cueIndex.upper_bound(time);
2143
2144 if(i!=cueIndex.end())
2145 {
2146 index=i->second;
2147 return true;
2148 }
2149 else
2150 return false;
2151 }
2152
2153 const string CSound::getUnusedCueName(const string &prefix) const
2154 {
2155 // ??? containsCue is not the most efficient, but we're not talking about huge amounts of data here
2156 for(unsigned t=1;t<200;t++)
2157 {
2158 if(!containsCue(prefix+istring(t)))
2159 return prefix+istring(t);
2160 }
2161 return "";
2162 }
2163
2164 void CSound::clearCues()
2165 {
2166 cueAccesser->clear();
2167
2168 // update cueIndex
2169 cueIndex.clear();
2170 }
2171
2172 void CSound::enableCueAdjustmentsOnSpaceChanges(bool enabled)
2173 {
2174 adjustCuesOnSpaceChanges=enabled;
2175 }
2176
2177 /*
2178 * This method handles the adjustment of cues
2179 * pos1 can be less than pos2 indicating an addition of space at pos1 for pos2-pos1 samples
2180 * or pos2 can be less then pos1 indicating a removal of space as pos2 for pos1-pos2 samples
2181 */
2182 void CSound::adjustCues(const sample_pos_t pos1,const sample_pos_t pos2)
2183 {
2184 if(!adjustCuesOnSpaceChanges)
2185 return;
2186
2187 if(pos1<pos2)
2188 { // added data
2189 sample_pos_t addedLength=pos2-pos1;
2190 for(size_t t=0;t<getCueCount();t++)
2191 {
2192 if(isCueAnchored(t))
2193 continue; // ignore
2194
2195 if(getCueTime(t)>=pos1)
2196 setCueTime(t,getCueTime(t)+addedLength);
2197 }
2198 }
2199 else // if(pos2<=pos1)
2200 { // removed data
2201 sample_pos_t removedLength=pos1-pos2;
2202 for(size_t t=0;t<getCueCount();t++)
2203 {
2204 if(isCueAnchored(t))
2205 continue; // ignore
2206
2207 if(getCueTime(t)>pos2 && getCueTime(t)<pos1)
2208 removeCue(t--);
2209 else if(getCueTime(t)>=pos1)
2210 setCueTime(t,getCueTime(t)-removedLength);
2211 }
2212
2213 }
2214
2215 // update cueIndex
2216 rebuildCueIndex();
2217 }
2218
2219 void CSound::createCueAccesser()
2220 {
2221 if(poolFile.isOpen())
2222 {
2223 poolFile.createPool<RCue>(CUES_POOL_NAME,false);
2224 cueAccesser=new CCuePoolAccesser(poolFile.getPoolAccesser<RCue>(CUES_POOL_NAME));
2225
2226 // update cueIndex
2227 rebuildCueIndex();
2228 }
2229 }
2230
2231 void CSound::deleteCueAccesser()
2232 {
2233 if(poolFile.isOpen())
2234 {
2235 delete cueAccesser;
2236 cueAccesser=NULL;
2237
2238 // update cueIndex
2239 cueIndex.clear();
2240 }
2241 }
2242
2243 void CSound::rebuildCueIndex()
2244 {
2245 cueIndex.clear();
2246
2247 for(size_t t=0;t<cueAccesser->getSize();t++)
2248 cueIndex.insert(map<sample_pos_t,size_t>::value_type(getCueTime(t),t));
2249 }
2250
2251 // -----------------------------------------------------
2252 // -----------------------------------------------------
2253 // -----------------------------------------------------
2254
2255
2256 const string CSound::getUserNotes() const
2257 {
2258 if(poolFile.containsPool(NOTES_POOL_NAME))
2259 {
2260 const TStaticPoolAccesser<char,PoolFile_t> a=poolFile.getPoolAccesser<char>(NOTES_POOL_NAME);
2261
2262 string s;
2263
2264 char buffer[101];
2265 for(size_t t=0;t<a.getSize()/100;t++)
2266 {
2267 a.read(buffer,100);
2268 buffer[100]=0;
2269 s+=buffer;
2270 }
2271
2272 a.read(buffer,a.getSize()%100);
2273 buffer[a.getSize()%100]=0;
2274 s+=buffer;
2275
2276 return(s);
2277 }
2278 else
2279 return("");
2280 }
2281
2282 void CSound::setUserNotes(const string ¬es)
2283 {
2284 TPoolAccesser<char,PoolFile_t> a=poolFile.containsPool(NOTES_POOL_NAME) ? poolFile.getPoolAccesser<char>(NOTES_POOL_NAME) : poolFile.createPool<char>(NOTES_POOL_NAME);
2285
2286 a.clear();
2287 a.write(notes.c_str(),notes.size());
2288 }
2289
2290
2291 // this is the explicit instantiation of the TPoolFile for CSound's purposes
2292 // #include <TPoolFile.cpp>
2293 template class TPoolFile<sample_pos_t,uint64_t>;
2294
2295 // Some explicit template method instantiations (not sure why some are necessary and some aren't)
2296
2297 /* 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 */
2298 template TStaticPoolAccesser<int16_t,TPoolFile<sample_pos_t,uint64_t> > TPoolFile<sample_pos_t,uint64_t>::createPool<int16_t>(const string, const bool);
2299 template TStaticPoolAccesser<int16_t,TPoolFile<sample_pos_t,uint64_t> > const TPoolFile<sample_pos_t,uint64_t>::getPoolAccesser<int16_t>(const string) const;
2300
2301
2302 #include <TStaticPoolAccesser.h>
2303 #include <TPoolAccesser.h>
2304
2305 // I fairly certain that these are from calls to getGeneralDataAccesser<int>
2306 template void TPoolFile<sample_pos_t,uint64_t>::addAccesser<int>(TStaticPoolAccesser<int, TPoolFile<sample_pos_t,uint64_t> > const *);
2307 template void TPoolFile<sample_pos_t,uint64_t>::unreferenceCachedBlock<int>(TStaticPoolAccesser<int, TPoolFile<sample_pos_t,uint64_t> > const *);
2308 template void TPoolFile<sample_pos_t,uint64_t>::removeAccesser<int>(TStaticPoolAccesser<int, TPoolFile<sample_pos_t,uint64_t> > const *);
2309 template void TStaticPoolAccesser<int, TPoolFile<sample_pos_t,uint64_t> >::cacheBlock(const sample_pos_t) const;
2310 template void TPoolFile<sample_pos_t,uint64_t>::cacheBlock<int>(sample_pos_t, TStaticPoolAccesser<int, TPoolFile<sample_pos_t,uint64_t> > const *);
2311
2312 template TStaticPoolAccesser<int, TPoolFile<sample_pos_t,uint64_t> > TPoolFile<sample_pos_t,uint64_t>::getPoolAccesser<int>(const string);
2313 template TStaticPoolAccesser<int, TPoolFile<sample_pos_t,uint64_t> > const TPoolFile<sample_pos_t,uint64_t>::getPoolAccesser<int>(const string) const;
2314 template TStaticPoolAccesser<int, TPoolFile<sample_pos_t,uint64_t> > TPoolFile<sample_pos_t,uint64_t>::createPool<int>(const string, const bool);
2315
2316 template TStaticPoolAccesser<uint8_t, TPoolFile<sample_pos_t,uint64_t> > TPoolFile<sample_pos_t,uint64_t>::getPoolAccesser<uint8_t>(const string);
2317 template TStaticPoolAccesser<uint8_t, TPoolFile<sample_pos_t,uint64_t> > const TPoolFile<sample_pos_t,uint64_t>::getPoolAccesser<uint8_t>(const string) const;
2318 template TStaticPoolAccesser<uint8_t, TPoolFile<sample_pos_t,uint64_t> > TPoolFile<sample_pos_t,uint64_t>::createPool<uint8_t>(const string, const bool);
2319
2320