Audacity 3.2.0
Sequence.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 Sequence.cpp
6
7 Dominic Mazzoni
8
9*******************************************************************//****************************************************************//****************************************************************//*******************************************************************/
29
30
31
32#include "Sequence.h"
33
34#include <algorithm>
35#include <optional>
36#include <float.h>
37#include <math.h>
38
39#include <wx/intl.h>
40#include <wx/filefn.h>
41#include <wx/ffile.h>
42#include <wx/log.h>
43
44#include "BasicUI.h"
45#include "SampleBlock.h"
47
48size_t Sequence::sMaxDiskBlockSize = 1048576;
49
50// Sequence methods
53: mpFactory(pFactory),
54 mSampleFormat(format),
55 mMinSamples(sMaxDiskBlockSize / SAMPLE_SIZE(mSampleFormat) / 2),
56 mMaxSamples(mMinSamples * 2)
57{
58}
59
60// essentially a copy constructor - but you must pass in the
61// current project, because we might be copying from one
62// project to another
64 const Sequence &orig, const SampleBlockFactoryPtr &pFactory)
65: mpFactory(pFactory),
66 mSampleFormat(orig.mSampleFormat),
67 mMinSamples(orig.mMinSamples),
68 mMaxSamples(orig.mMaxSamples)
69{
70 Paste(0, &orig);
71}
72
74{
75}
76
78{
79 return mMaxSamples;
80}
81
83{
84 return mMaxSamples;
85}
86
88{
89 for (unsigned int i = 0; i < mBlock.size(); i++)
90 mBlock[i].sb->CloseLock();
91
92 return true;
93}
94
96{
97 return mSampleFormat;
98}
99
100/*
101bool Sequence::SetSampleFormat(sampleFormat format)
102{
103 if (mBlock.size() > 0 || mNumSamples > 0)
104 return false;
105
106 mSampleFormat = format;
107 return true;
108}
109*/
110
111namespace {
113 size_t &size, size_t required,
114 SampleBuffer *pSecondBuffer = nullptr)
115 {
116 // This should normally do nothing, but it is a defense against corrupt
117 // projects than might have inconsistent block files bigger than the
118 // expected maximum size.
119 if (size < required) {
120 // reallocate
121 buffer.Allocate(required, format);
122 if (pSecondBuffer && pSecondBuffer->ptr())
123 pSecondBuffer->Allocate(required, format);
124 if (!buffer.ptr() || (pSecondBuffer && !pSecondBuffer->ptr())) {
125 // malloc failed
126 // Perhaps required is a really crazy value,
127 // and perhaps we should throw an AudacityException, but that is
128 // a second-order concern
130 }
131 size = required;
132 }
133 }
134}
135
138 const std::function<void(size_t)> & progressReport)
139{
140 if (format == mSampleFormat)
141 // no change
142 return false;
143
144 if (mBlock.size() == 0)
145 {
147 return true;
148 }
149
150 const sampleFormat oldFormat = mSampleFormat;
152
153 const auto oldMinSamples = mMinSamples, oldMaxSamples = mMaxSamples;
154 // These are the same calculations as in the constructor.
157
158 bool bSuccess = false;
159 auto cleanup = finally( [&] {
160 if (!bSuccess) {
161 // Conversion failed. Revert these member vars.
162 mSampleFormat = oldFormat;
163 mMaxSamples = oldMaxSamples;
164 mMinSamples = oldMinSamples;
165 }
166 } );
167
168 BlockArray newBlockArray;
169 // Use the ratio of old to NEW mMaxSamples to make a reasonable guess
170 // at allocation.
171 newBlockArray.reserve
172 (1 + mBlock.size() * ((float)oldMaxSamples / (float)mMaxSamples));
173
174 {
175 size_t oldSize = oldMaxSamples;
176 SampleBuffer bufferOld(oldSize, oldFormat);
177 size_t newSize = oldMaxSamples;
178 SampleBuffer bufferNew(newSize, format);
179
180 for (size_t i = 0, nn = mBlock.size(); i < nn; i++)
181 {
182 SeqBlock &oldSeqBlock = mBlock[i];
183 const auto &oldBlockFile = oldSeqBlock.sb;
184 const auto len = oldBlockFile->GetSampleCount();
185 ensureSampleBufferSize(bufferOld, oldFormat, oldSize, len);
186 Read(bufferOld.ptr(), oldFormat, oldSeqBlock, 0, len, true);
187
188 ensureSampleBufferSize(bufferNew, format, newSize, len);
189 CopySamples(bufferOld.ptr(), oldFormat, bufferNew.ptr(), format, len);
190
191 // Note this fix for http://bugzilla.audacityteam.org/show_bug.cgi?id=451,
192 // using Blockify, allows (len < mMinSamples).
193 // This will happen consistently when going from more bytes per sample to fewer...
194 // This will create a block that's smaller than mMinSamples, which
195 // shouldn't be allowed, but we agreed it's okay for now.
196 //vvv ANSWER-ME: Does this cause any bugs, or failures on write, elsewhere?
197 // If so, need to special-case (len < mMinSamples) and start combining data
198 // from the old blocks... Oh no!
199
200 // Using Blockify will handle the cases where len > the NEW mMaxSamples. Previous code did not.
201 const auto blockstart = oldSeqBlock.start;
203 newBlockArray, blockstart, bufferNew.ptr(), len);
204
205 if (progressReport)
206 progressReport(len);
207 }
208 }
209
210 // Invalidate all the old, non-aliased block files.
211 // Aliased files will be converted at save, per comment above.
212
213 // Commit the changes to block file array
215 (newBlockArray, mNumSamples, wxT("Sequence::ConvertToSampleFormat()"));
216
217 // Commit the other changes
218 bSuccess = true;
219
220 return true;
221}
222
223std::pair<float, float> Sequence::GetMinMax(
224 sampleCount start, sampleCount len, bool mayThrow) const
225{
226 if (len == 0 || mBlock.size() == 0) {
227 return {
228 0.f,
229 // FLT_MAX? So it doesn't look like a spurious '0' to a caller?
230
231 0.f
232 // -FLT_MAX? So it doesn't look like a spurious '0' to a caller?
233 };
234 }
235
236 float min = FLT_MAX;
237 float max = -FLT_MAX;
238
239 unsigned int block0 = FindBlock(start);
240 unsigned int block1 = FindBlock(start + len - 1);
241
242 // First calculate the min/max of the blocks in the middle of this region;
243 // this is very fast because we have the min/max of every entire block
244 // already in memory.
245
246 for (unsigned b = block0 + 1; b < block1; ++b) {
247 auto results = mBlock[b].sb->GetMinMaxRMS(mayThrow);
248
249 if (results.min < min)
250 min = results.min;
251 if (results.max > max)
252 max = results.max;
253 }
254
255 // Now we take the first and last blocks into account, noting that the
256 // selection may only partly overlap these blocks. If the overall min/max
257 // of either of these blocks is within min...max, then we can ignore them.
258 // If not, we need read some samples and summaries from disk.
259 {
260 const SeqBlock &theBlock = mBlock[block0];
261 const auto &theFile = theBlock.sb;
262 auto results = theFile->GetMinMaxRMS(mayThrow);
263
264 if (results.min < min || results.max > max) {
265 // start lies within theBlock:
266 auto s0 = ( start - theBlock.start ).as_size_t();
267 const auto maxl0 = (
268 // start lies within theBlock:
269 theBlock.start + theFile->GetSampleCount() - start
270 ).as_size_t();
271 wxASSERT(maxl0 <= mMaxSamples); // Vaughan, 2011-10-19
272 const auto l0 = limitSampleBufferSize ( maxl0, len );
273
274 results = theFile->GetMinMaxRMS(s0, l0, mayThrow);
275 if (results.min < min)
276 min = results.min;
277 if (results.max > max)
278 max = results.max;
279 }
280 }
281
282 if (block1 > block0)
283 {
284 const SeqBlock &theBlock = mBlock[block1];
285 const auto &theFile = theBlock.sb;
286 auto results = theFile->GetMinMaxRMS(mayThrow);
287
288 if (results.min < min || results.max > max) {
289
290 // start + len - 1 lies in theBlock:
291 const auto l0 = ( start + len - theBlock.start ).as_size_t();
292 wxASSERT(l0 <= mMaxSamples); // Vaughan, 2011-10-19
293
294 results = theFile->GetMinMaxRMS(0, l0, mayThrow);
295 if (results.min < min)
296 min = results.min;
297 if (results.max > max)
298 max = results.max;
299 }
300 }
301
302 return { min, max };
303}
304
305float Sequence::GetRMS(sampleCount start, sampleCount len, bool mayThrow) const
306{
307 // len is the number of samples that we want the rms of.
308 // it may be longer than a block, and the code is carefully set up to handle that.
309 if (len == 0 || mBlock.size() == 0)
310 return 0.f;
311
312 double sumsq = 0.0;
313 sampleCount length = 0; // this is the cumulative length of the bits we have the ms of so far, and should end up == len
314
315 unsigned int block0 = FindBlock(start);
316 unsigned int block1 = FindBlock(start + len - 1);
317
318 // First calculate the rms of the blocks in the middle of this region;
319 // this is very fast because we have the rms of every entire block
320 // already in memory.
321 for (unsigned b = block0 + 1; b < block1; b++) {
322 const SeqBlock &theBlock = mBlock[b];
323 const auto &sb = theBlock.sb;
324 auto results = sb->GetMinMaxRMS(mayThrow);
325
326 const auto fileLen = sb->GetSampleCount();
327 const auto blockRMS = results.RMS;
328 sumsq += blockRMS * blockRMS * fileLen;
329 length += fileLen;
330 }
331
332 // Now we take the first and last blocks into account, noting that the
333 // selection may only partly overlap these blocks.
334 // If not, we need read some samples and summaries from disk.
335 {
336 const SeqBlock &theBlock = mBlock[block0];
337 const auto &sb = theBlock.sb;
338 // start lies within theBlock
339 auto s0 = ( start - theBlock.start ).as_size_t();
340 // start lies within theBlock
341 const auto maxl0 =
342 (theBlock.start + sb->GetSampleCount() - start).as_size_t();
343 wxASSERT(maxl0 <= mMaxSamples); // Vaughan, 2011-10-19
344 const auto l0 = limitSampleBufferSize( maxl0, len );
345
346 auto results = sb->GetMinMaxRMS(s0, l0, mayThrow);
347 const auto partialRMS = results.RMS;
348 sumsq += partialRMS * partialRMS * l0;
349 length += l0;
350 }
351
352 if (block1 > block0) {
353 const SeqBlock &theBlock = mBlock[block1];
354 const auto &sb = theBlock.sb;
355
356 // start + len - 1 lies within theBlock
357 const auto l0 = ( start + len - theBlock.start ).as_size_t();
358 wxASSERT(l0 <= mMaxSamples); // PRL: I think Vaughan missed this
359
360 auto results = sb->GetMinMaxRMS(0, l0, mayThrow);
361 const auto partialRMS = results.RMS;
362 sumsq += partialRMS * partialRMS * l0;
363 length += l0;
364 }
365
366 // PRL: catch bugs like 1320:
367 wxASSERT(length == len);
368
369 return sqrt(sumsq / length.as_double() );
370}
371
372// Must pass in the correct factory for the result. If it's not the same
373// as in this, then block contents must be copied.
374std::unique_ptr<Sequence> Sequence::Copy( const SampleBlockFactoryPtr &pFactory,
375 sampleCount s0, sampleCount s1) const
376{
377 // Make a new Sequence object for the specified factory:
378 auto dest = std::make_unique<Sequence>(pFactory, mSampleFormat);
379 if (s0 >= s1 || s0 >= mNumSamples || s1 < 0) {
380 return dest;
381 }
382
383 // Decide whether to share sample blocks or make new copies, when whole block
384 // contents are used -- must copy if factories are different:
385 auto pUseFactory = (pFactory == mpFactory) ? nullptr : pFactory.get();
386
387 int numBlocks = mBlock.size();
388
389 int b0 = FindBlock(s0);
390 const int b1 = FindBlock(s1 - 1);
391 wxASSERT(b0 >= 0);
392 wxASSERT(b0 < numBlocks);
393 wxASSERT(b1 < numBlocks);
394 wxUnusedVar(numBlocks);
395 wxASSERT(b0 <= b1);
396
397 dest->mBlock.reserve(b1 - b0 + 1);
398
399 auto bufferSize = mMaxSamples;
400 SampleBuffer buffer(bufferSize, mSampleFormat);
401
402 int blocklen;
403
404 // Do any initial partial block
405
406 const SeqBlock &block0 = mBlock[b0];
407 if (s0 != block0.start) {
408 const auto &sb = block0.sb;
409 // Nonnegative result is length of block0 or less:
410 blocklen =
411 ( std::min(s1, block0.start + sb->GetSampleCount()) - s0 ).as_size_t();
412 wxASSERT(blocklen <= (int)mMaxSamples); // Vaughan, 2012-02-29
413 ensureSampleBufferSize(buffer, mSampleFormat, bufferSize, blocklen);
414 Get(b0, buffer.ptr(), mSampleFormat, s0, blocklen, true);
415
416 dest->Append(buffer.ptr(), mSampleFormat, blocklen);
417 }
418 else
419 --b0;
420
421 // If there are blocks in the middle, use the blocks whole
422 for (int bb = b0 + 1; bb < b1; ++bb)
423 AppendBlock(pUseFactory, mSampleFormat,
424 dest->mBlock, dest->mNumSamples, mBlock[bb]);
425 // Increase ref count or duplicate file
426
427 // Do the last block
428 if (b1 > b0) {
429 // Probable case of a partial block
430 const SeqBlock &block = mBlock[b1];
431 const auto &sb = block.sb;
432 // s1 is within block:
433 blocklen = (s1 - block.start).as_size_t();
434 wxASSERT(blocklen <= (int)mMaxSamples); // Vaughan, 2012-02-29
435 if (blocklen < (int)sb->GetSampleCount()) {
436 ensureSampleBufferSize(buffer, mSampleFormat, bufferSize, blocklen);
437 Get(b1, buffer.ptr(), mSampleFormat, block.start, blocklen, true);
438 dest->Append(buffer.ptr(), mSampleFormat, blocklen);
439 }
440 else
441 // Special case of a whole block
442 AppendBlock(pUseFactory, mSampleFormat,
443 dest->mBlock, dest->mNumSamples, block);
444 // Increase ref count or duplicate file
445 }
446
447 dest->ConsistencyCheck(wxT("Sequence::Copy()"));
448
449 return dest;
450}
451
452namespace {
453 inline bool Overflows(double numSamples)
454 {
455 return numSamples > wxLL(9223372036854775807);
456 }
457
460 {
461 if ( pFactory ) {
462 // must copy contents to a fresh SampleBlock object in another database
463 auto sampleCount = sb->GetSampleCount();
465 sb->GetSamples( buffer.ptr(), format, 0, sampleCount );
466 sb = pFactory->Create( buffer.ptr(), sampleCount, format );
467 }
468 else
469 // Can just share
470 ;
471 return sb;
472 }
473}
474
477{
478 if ((s < 0) || (s > mNumSamples))
479 {
480 wxLogError(
481 wxT("Sequence::Paste: sampleCount s %s is < 0 or > mNumSamples %s)."),
482 // PRL: Why bother with Internat when the above is just wxT?
486 }
487
488 // Quick check to make sure that it doesn't overflow
490 {
491 wxLogError(
492 wxT("Sequence::Paste: mNumSamples %s + src->mNumSamples %s would overflow."),
493 // PRL: Why bother with Internat when the above is just wxT?
497 }
498
499 if (src->mSampleFormat != mSampleFormat)
500 {
501 wxLogError(
502 wxT("Sequence::Paste: Sample format to be pasted, %s, does not match destination format, %s."),
506 }
507
508 const BlockArray &srcBlock = src->mBlock;
509 auto addedLen = src->mNumSamples;
510 const unsigned int srcNumBlocks = srcBlock.size();
511 auto sampleSize = SAMPLE_SIZE(mSampleFormat);
512
513 if (addedLen == 0 || srcNumBlocks == 0)
514 return;
515
516 const size_t numBlocks = mBlock.size();
517
518 // Decide whether to share sample blocks or make new copies, when whole block
519 // contents are used -- must copy if factories are different:
520 auto pUseFactory =
521 (src->mpFactory == mpFactory) ? nullptr : mpFactory.get();
522
523 if (numBlocks == 0 ||
524 (s == mNumSamples && mBlock.back().sb->GetSampleCount() >= mMinSamples)) {
525 // Special case: this track is currently empty, or it's safe to append
526 // onto the end because the current last block is longer than the
527 // minimum size
528
529 // Build and swap a copy so there is a strong exception safety guarantee
530 BlockArray newBlock{ mBlock };
531 sampleCount samples = mNumSamples;
532 for (unsigned int i = 0; i < srcNumBlocks; i++)
533 // AppendBlock may throw for limited disk space, if pasting from
534 // one project into another.
535 AppendBlock(pUseFactory, mSampleFormat,
536 newBlock, samples, srcBlock[i]);
537
539 (newBlock, samples, wxT("Paste branch one"));
540 return;
541 }
542
543 const int b = (s == mNumSamples) ? mBlock.size() - 1 : FindBlock(s);
544 wxASSERT((b >= 0) && (b < (int)numBlocks));
545 SeqBlock *const pBlock = &mBlock[b];
546 const auto length = pBlock->sb->GetSampleCount();
547 const auto largerBlockLen = addedLen + length;
548 // PRL: when insertion point is the first sample of a block,
549 // and the following test fails, perhaps we could test
550 // whether coalescence with the previous block is possible.
551 if (largerBlockLen <= mMaxSamples) {
552 // Special case: we can fit all of the NEW samples inside of
553 // one block!
554
555 SeqBlock &block = *pBlock;
556 // largerBlockLen is not more than mMaxSamples...
557 SampleBuffer buffer(largerBlockLen.as_size_t(), mSampleFormat);
558
559 // ...and addedLen is not more than largerBlockLen
560 auto sAddedLen = addedLen.as_size_t();
561 // s lies within block:
562 auto splitPoint = ( s - block.start ).as_size_t();
563 Read(buffer.ptr(), mSampleFormat, block, 0, splitPoint, true);
564 src->Get(0, buffer.ptr() + splitPoint*sampleSize,
565 mSampleFormat, 0, sAddedLen, true);
566 Read(buffer.ptr() + (splitPoint + sAddedLen) * sampleSize,
567 mSampleFormat, block,
568 splitPoint, length - splitPoint, true);
569
570 // largerBlockLen is not more than mMaxSamples...
571 block.sb = mpFactory->Create(
572 buffer.ptr(),
573 largerBlockLen.as_size_t(),
575
576 // Don't make a duplicate array. We can still give Strong-guarantee
577 // if we modify only one block in place.
578
579 // use No-fail-guarantee in remaining steps
580 for (unsigned int i = b + 1; i < numBlocks; i++)
581 mBlock[i].start += addedLen;
582
583 mNumSamples += addedLen;
584
585 // This consistency check won't throw, it asserts.
586 // Proof that we kept consistency is not hard.
587 ConsistencyCheck(wxT("Paste branch two"), false);
588 return;
589 }
590
591 // Case three: if we are inserting four or fewer blocks,
592 // it's simplest to just lump all the data together
593 // into one big block along with the split block,
594 // then resplit it all
595 BlockArray newBlock;
596 newBlock.reserve(numBlocks + srcNumBlocks + 2);
597 newBlock.insert(newBlock.end(), mBlock.begin(), mBlock.begin() + b);
598
599 SeqBlock &splitBlock = mBlock[b];
600 auto splitLen = splitBlock.sb->GetSampleCount();
601 // s lies within splitBlock
602 auto splitPoint = ( s - splitBlock.start ).as_size_t();
603
604 unsigned int i;
605 if (srcNumBlocks <= 4) {
606
607 // addedLen is at most four times maximum block size
608 auto sAddedLen = addedLen.as_size_t();
609 const auto sum = splitLen + sAddedLen;
610
611 SampleBuffer sumBuffer(sum, mSampleFormat);
612 Read(sumBuffer.ptr(), mSampleFormat, splitBlock, 0, splitPoint, true);
613 src->Get(0, sumBuffer.ptr() + splitPoint * sampleSize,
615 0, sAddedLen, true);
616 Read(sumBuffer.ptr() + (splitPoint + sAddedLen) * sampleSize, mSampleFormat,
617 splitBlock, splitPoint,
618 splitLen - splitPoint, true);
619
621 newBlock, splitBlock.start, sumBuffer.ptr(), sum);
622 } else {
623
624 // The final case is that we're inserting at least five blocks.
625 // We divide these into three groups: the first two get merged
626 // with the first half of the split block, the middle ones get
627 // used whole, and the last two get merged with the last
628 // half of the split block.
629
630 const auto srcFirstTwoLen =
631 srcBlock[0].sb->GetSampleCount() + srcBlock[1].sb->GetSampleCount();
632 const auto leftLen = splitPoint + srcFirstTwoLen;
633
634 const SeqBlock &penultimate = srcBlock[srcNumBlocks - 2];
635 const auto srcLastTwoLen =
636 penultimate.sb->GetSampleCount() +
637 srcBlock[srcNumBlocks - 1].sb->GetSampleCount();
638 const auto rightSplit = splitBlock.sb->GetSampleCount() - splitPoint;
639 const auto rightLen = rightSplit + srcLastTwoLen;
640
641 SampleBuffer sampleBuffer(std::max(leftLen, rightLen), mSampleFormat);
642
643 Read(sampleBuffer.ptr(), mSampleFormat, splitBlock, 0, splitPoint, true);
644 src->Get(0, sampleBuffer.ptr() + splitPoint*sampleSize,
645 mSampleFormat, 0, srcFirstTwoLen, true);
646
648 newBlock, splitBlock.start, sampleBuffer.ptr(), leftLen);
649
650 for (i = 2; i < srcNumBlocks - 2; i++) {
651 const SeqBlock &block = srcBlock[i];
652 auto sb = ShareOrCopySampleBlock(
653 pUseFactory, mSampleFormat, block.sb );
654 newBlock.push_back(SeqBlock(sb, block.start + s));
655 }
656
657 auto lastStart = penultimate.start;
658 src->Get(srcNumBlocks - 2, sampleBuffer.ptr(), mSampleFormat,
659 lastStart, srcLastTwoLen, true);
660 Read(sampleBuffer.ptr() + srcLastTwoLen * sampleSize, mSampleFormat,
661 splitBlock, splitPoint, rightSplit, true);
662
664 newBlock, s + lastStart, sampleBuffer.ptr(), rightLen);
665 }
666
667 // Copy remaining blocks to NEW block array and
668 // swap the NEW block array in for the old
669 for (i = b + 1; i < numBlocks; i++)
670 newBlock.push_back(mBlock[i].Plus(addedLen));
671
673 (newBlock, mNumSamples + addedLen, wxT("Paste branch three"));
674}
675
678{
679 SetSamples(NULL, mSampleFormat, s0, len);
680}
681
684{
685 auto &factory = *mpFactory;
686
687 // Quick check to make sure that it doesn't overflow
688 if (Overflows((mNumSamples.as_double()) + (len.as_double())))
690
691 if (len <= 0)
692 return;
693
694 // Create a NEW track containing as much silence as we
695 // need to insert, and then call Paste to do the insertion.
696
698
699 auto idealSamples = GetIdealBlockSize();
700
701 sampleCount pos = 0;
702
703 // Could nBlocks overflow a size_t? Not very likely. You need perhaps
704 // 2 ^ 52 samples which is over 3000 years at 44.1 kHz.
705 auto nBlocks = (len + idealSamples - 1) / idealSamples;
706 sTrack.mBlock.reserve(nBlocks.as_size_t());
707
708 if (len >= idealSamples) {
709 auto silentFile = factory.CreateSilent(
710 idealSamples,
712 while (len >= idealSamples) {
713 sTrack.mBlock.push_back(SeqBlock(silentFile, pos));
714
715 pos += idealSamples;
716 len -= idealSamples;
717 }
718 }
719 if (len != 0) {
720 // len is not more than idealSamples:
721 sTrack.mBlock.push_back(SeqBlock(
722 factory.CreateSilent(len.as_size_t(), mSampleFormat), pos));
723 pos += len;
724 }
725
726 sTrack.mNumSamples = pos;
727
728 // use Strong-guarantee
729 Paste(s0, &sTrack);
730}
731
733 BlockArray &mBlock, sampleCount &mNumSamples, const SeqBlock &b)
734{
735 // Quick check to make sure that it doesn't overflow
736 if (Overflows((mNumSamples.as_double()) + ((double)b.sb->GetSampleCount())))
738
739 auto sb = ShareOrCopySampleBlock( pFactory, format, b.sb );
740 SeqBlock newBlock(sb, mNumSamples);
741
742 // We can assume newBlock.sb is not null
743
744 mBlock.push_back(newBlock);
745 mNumSamples += newBlock.sb->GetSampleCount();
746
747 // Don't do a consistency check here because this
748 // function gets called in an inner loop.
749}
750
752{
753 int b = FindBlock(position);
754 return mBlock[b].start;
755}
756
758{
759 // This method returns a nice number of samples you should try to grab in
760 // one big chunk in order to land on a block boundary, based on the starting
761 // sample. The value returned will always be nonzero and will be no larger
762 // than the value of GetMaxBlockSize()
763
764 if (start < 0 || start >= mNumSamples)
765 return mMaxSamples;
766
767 int b = FindBlock(start);
768 int numBlocks = mBlock.size();
769
770 const SeqBlock &block = mBlock[b];
771 // start is in block:
772 auto result = (block.start + block.sb->GetSampleCount() - start).as_size_t();
773
774 decltype(result) length;
775 while(result < mMinSamples && b+1<numBlocks &&
776 ((length = mBlock[b+1].sb->GetSampleCount()) + result) <= mMaxSamples) {
777 b++;
778 result += length;
779 }
780
781 wxASSERT(result > 0 && result <= mMaxSamples);
782
783 return result;
784}
785
786bool Sequence::HandleXMLTag(const std::string_view& tag, const AttributesList &attrs)
787{
788 auto &factory = *mpFactory;
789
790 /* handle waveblock tag and its attributes */
791 if (tag == "waveblock")
792 {
793 SeqBlock wb;
794
795 // Give SampleBlock a go at the attributes first
796 wb.sb = factory.CreateFromXML(mSampleFormat, attrs);
797 if (wb.sb == nullptr)
798 {
799 mErrorOpening = true;
800 return false;
801 }
802
803 // loop through attrs, which is a null-terminated list of attribute-value pairs
804 for (auto pair : attrs)
805 {
806 auto attr = pair.first;
807 auto value = pair.second;
808
809 if (attr == "start")
810 {
811 // This attribute is a sample offset, so can be 64bit
812 sampleCount::type start;
813
814 if (!value.TryGet(start))
815 {
816 mErrorOpening = true;
817 return false;
818 }
819
820 wb.start = start;
821 }
822 }
823
824 mBlock.push_back(wb);
825
826 return true;
827 }
828
829 /* handle sequence tag and its attributes */
830 if (tag == "sequence")
831 {
832 for (auto pair : attrs)
833 {
834 auto attr = pair.first;
835 auto value = pair.second;
836
837 long long nValue = 0;
838
839 if (attr == "maxsamples")
840 {
841 // This attribute is a sample count, so can be 64bit
842 if (!value.TryGet(nValue))
843 {
844 mErrorOpening = true;
845 return false;
846 }
847
848 // Dominic, 12/10/2006:
849 // Let's check that maxsamples is >= 1024 and <= 64 * 1024 * 1024
850 // - that's a pretty wide range of reasonable values.
851 if ((nValue < 1024) || (nValue > 64 * 1024 * 1024))
852 {
853 mErrorOpening = true;
854 return false;
855 }
856
857 // nValue is now safe for size_t
858 mMaxSamples = nValue;
859 }
860 else if (attr == "sampleformat")
861 {
862 // This attribute is a sample format, normal int
863 long fValue;
864
865 if (!value.TryGet(fValue) || !IsValidSampleFormat(fValue))
866 {
867 mErrorOpening = true;
868 return false;
869 }
870
871 mSampleFormat = (sampleFormat)fValue;
872 }
873 else if (attr == "numsamples")
874 {
875 // This attribute is a sample count, so can be 64bit
876 if (!value.TryGet(nValue) || (nValue < 0))
877 {
878 mErrorOpening = true;
879 return false;
880 }
881 mNumSamples = nValue;
882 }
883 } // for
884
885 return true;
886 }
887
888 return false;
889}
890
891void Sequence::HandleXMLEndTag(const std::string_view& tag)
892{
893 if (tag != "sequence" != 0)
894 {
895 return;
896 }
897
898 // Make sure that the sequence is valid.
899
900 // Make sure that start times and lengths are consistent
901 sampleCount numSamples = 0;
902 for (unsigned b = 0, nn = mBlock.size(); b < nn; b++)
903 {
904 SeqBlock &block = mBlock[b];
905 if (block.start != numSamples)
906 {
907 wxLogWarning(
908 wxT("Gap detected in project file.\n")
909 wxT(" Start (%s) for block file %lld is not one sample past end of previous block (%s).\n")
910 wxT(" Moving start so blocks are contiguous."),
911 // PRL: Why bother with Internat when the above is just wxT?
913 block.sb->GetBlockID(),
914 Internat::ToString(numSamples.as_double(), 0));
915 block.start = numSamples;
916 mErrorOpening = true;
917 }
918 numSamples += block.sb->GetSampleCount();
919 }
920
921 if (mNumSamples != numSamples)
922 {
923 wxLogWarning(
924 wxT("Gap detected in project file. Correcting sequence sample count from %s to %s."),
925 // PRL: Why bother with Internat when the above is just wxT?
927 Internat::ToString(numSamples.as_double(), 0));
928 mNumSamples = numSamples;
929 mErrorOpening = true;
930 }
931}
932
933XMLTagHandler *Sequence::HandleXMLChild(const std::string_view& tag)
934{
935 if (tag == "waveblock")
936 {
937 return this;
938 }
939
940 return nullptr;
941}
942
943// Throws exceptions rather than reporting errors.
944void Sequence::WriteXML(XMLWriter &xmlFile) const
945// may throw
946{
947 unsigned int b;
948
949 xmlFile.StartTag(wxT("sequence"));
950
951 xmlFile.WriteAttr(wxT("maxsamples"), mMaxSamples);
952 xmlFile.WriteAttr(wxT("sampleformat"), (size_t)mSampleFormat);
953 xmlFile.WriteAttr(wxT("numsamples"), mNumSamples.as_long_long() );
954
955 for (b = 0; b < mBlock.size(); b++) {
956 const SeqBlock &bb = mBlock[b];
957
958 // See http://bugzilla.audacityteam.org/show_bug.cgi?id=451.
959 if (bb.sb->GetSampleCount() > mMaxSamples)
960 {
961 // PRL: Bill observed this error. Not sure how it was caused.
962 // I have added code in ConsistencyCheck that should abort the
963 // editing operation that caused this, not fixing
964 // the problem but moving the point of detection earlier if we
965 // find a reproducible case.
966 using namespace BasicUI;
967 auto sMsg =
968 XO("Sequence has block file exceeding maximum %s samples per block.\nTruncating to this maximum length.")
969 .Format( Internat::ToString(((wxLongLong)mMaxSamples).ToDouble(), 0) );
971 sMsg,
973 .Caption(XO("Warning - Truncating Overlong Block File"))
974 .IconStyle(Icon::Warning)
975 .ButtonStyle(Button::Ok));
976 wxLogWarning(sMsg.Translation()); //Debug?
977// bb.sb->SetLength(mMaxSamples);
978 }
979
980 xmlFile.StartTag(wxT("waveblock"));
981 xmlFile.WriteAttr(wxT("start"), bb.start.as_long_long() );
982
983 bb.sb->SaveXML(xmlFile);
984
985 xmlFile.EndTag(wxT("waveblock"));
986 }
987
988 xmlFile.EndTag(wxT("sequence"));
989}
990
992{
993 wxASSERT(pos >= 0 && pos < mNumSamples);
994
995 if (pos == 0)
996 return 0;
997
998 int numBlocks = mBlock.size();
999
1000 size_t lo = 0, hi = numBlocks, guess;
1001 sampleCount loSamples = 0, hiSamples = mNumSamples;
1002
1003 while (true) {
1004 //this is not a binary search, but a
1005 //dictionary search where we guess something smarter than the binary division
1006 //of the unsearched area, since samples are usually proportional to block file number.
1007 const double frac = (pos - loSamples).as_double() /
1008 (hiSamples - loSamples).as_double();
1009 guess = std::min(hi - 1, lo + size_t(frac * (hi - lo)));
1010 const SeqBlock &block = mBlock[guess];
1011
1012 wxASSERT(block.sb->GetSampleCount() > 0);
1013 wxASSERT(lo <= guess && guess < hi && lo < hi);
1014
1015 if (pos < block.start) {
1016 wxASSERT(lo != guess);
1017 hi = guess;
1018 hiSamples = block.start;
1019 }
1020 else {
1021 const sampleCount nextStart = block.start + block.sb->GetSampleCount();
1022 if (pos < nextStart)
1023 break;
1024 else {
1025 wxASSERT(guess < hi - 1);
1026 lo = guess + 1;
1027 loSamples = nextStart;
1028 }
1029 }
1030 }
1031
1032 const int rval = guess;
1033 wxASSERT(rval >= 0 && rval < numBlocks &&
1034 pos >= mBlock[rval].start &&
1035 pos < mBlock[rval].start + mBlock[rval].sb->GetSampleCount());
1036
1037 return rval;
1038}
1039
1040//static
1042 const SeqBlock &b, size_t blockRelativeStart, size_t len,
1043 bool mayThrow)
1044{
1045 const auto &sb = b.sb;
1046
1047 wxASSERT(blockRelativeStart + len <= sb->GetSampleCount());
1048
1049 // Either throws, or of !mayThrow, tells how many were really read
1050 auto result = sb->GetSamples(buffer, format, blockRelativeStart, len, mayThrow);
1051
1052 if (result != len)
1053 {
1054 wxLogWarning(wxT("Expected to read %ld samples, got %ld samples."),
1055 len, result);
1056 return false;
1057 }
1058
1059 return true;
1060}
1061
1063 sampleCount start, size_t len, bool mayThrow) const
1064{
1065 if (start == mNumSamples) {
1066 return len == 0;
1067 }
1068
1069 if (start < 0 || start + len > mNumSamples) {
1070 if (mayThrow)
1072 ClearSamples( buffer, floatSample, 0, len );
1073 return false;
1074 }
1075 int b = FindBlock(start);
1076
1077 return Get(b, buffer, format, start, len, mayThrow);
1078}
1079
1081 sampleCount start, size_t len, bool mayThrow) const
1082{
1083 bool result = true;
1084 while (len) {
1085 const SeqBlock &block = mBlock[b];
1086 // start is in block
1087 const auto bstart = (start - block.start).as_size_t();
1088 // bstart is not more than block length
1089 const auto blen = std::min(len, block.sb->GetSampleCount() - bstart);
1090
1091 if (! Read(buffer, format, block, bstart, blen, mayThrow) )
1092 result = false;
1093
1094 len -= blen;
1095 buffer += (blen * SAMPLE_SIZE(format));
1096 b++;
1097 start += blen;
1098 }
1099 return result;
1100}
1101
1102// Pass NULL to set silence
1105 sampleCount start, sampleCount len)
1106{
1107 auto &factory = *mpFactory;
1108
1109 const auto size = mBlock.size();
1110
1111 if (start < 0 || start + len > mNumSamples)
1113
1114 size_t tempSize = mMaxSamples;
1115 // to do: allocate this only on demand
1116 SampleBuffer scratch(tempSize, mSampleFormat);
1117
1118 SampleBuffer temp;
1119 if (buffer && format != mSampleFormat) {
1120 temp.Allocate(tempSize, mSampleFormat);
1121 }
1122
1123 int b = FindBlock(start);
1124 BlockArray newBlock;
1125 std::copy( mBlock.begin(), mBlock.begin() + b, std::back_inserter(newBlock) );
1126
1127 while (len > 0
1128 // Redundant termination condition,
1129 // but it guards against infinite loop in case of inconsistencies
1130 // (too-small files, not yet seen?)
1131 // that cause the loop to make no progress because blen == 0
1132 && b < (int)size
1133 ) {
1134 newBlock.push_back( mBlock[b] );
1135 SeqBlock &block = newBlock.back();
1136 // start is within block
1137 const auto bstart = ( start - block.start ).as_size_t();
1138 const auto fileLength = block.sb->GetSampleCount();
1139
1140 // the std::min is a guard against inconsistent Sequence
1141 const auto blen =
1142 limitSampleBufferSize( fileLength - std::min( bstart, fileLength ),
1143 len );
1144 wxASSERT(blen == 0 || bstart + blen <= fileLength);
1145
1146#if 0
1147 // PRL: This inconsistency (too-big file) has been seen in "the wild"
1148 // in 2.2.0. It is the least problematic kind of inconsistency.
1149 // We will tolerate it for 2.2.1.
1150 // Not known whether it is only in projects saved in earlier versions.
1151 // After 2.2.1, we should detect and correct it at file loading time.
1152 if (fileLength > mMaxSamples) {
1154 }
1155#endif
1156
1157 ensureSampleBufferSize(scratch, mSampleFormat, tempSize, fileLength,
1158 &temp);
1159
1160 auto useBuffer = buffer;
1161 if (buffer && format != mSampleFormat)
1162 {
1163 // To do: remove the extra movement.
1164 // Note: we ensured temp can hold fileLength. blen is not more
1165 CopySamples(buffer, format, temp.ptr(), mSampleFormat, blen);
1166 useBuffer = temp.ptr();
1167 }
1168
1169 // We don't ever write to an existing block; to support Undo,
1170 // we copy the old block entirely into memory, dereference it,
1171 // make the change, and then write the NEW block to disk.
1172
1173 if ( bstart > 0 || blen < fileLength ) {
1174 // First or last block is only partially overwritten
1175 Read(scratch.ptr(), mSampleFormat, block, 0, fileLength, true);
1176
1177 if (useBuffer) {
1178 auto sampleSize = SAMPLE_SIZE(mSampleFormat);
1179 memcpy(scratch.ptr() +
1180 bstart * sampleSize, useBuffer, blen * sampleSize);
1181 }
1182 else
1183 ClearSamples(scratch.ptr(), mSampleFormat, bstart, blen);
1184
1185 block.sb = factory.Create(
1186 scratch.ptr(),
1187 fileLength,
1189 }
1190 else {
1191 // Avoid reading the disk when the replacement is total
1192 if (useBuffer)
1193 block.sb = factory.Create(useBuffer, fileLength, mSampleFormat);
1194 else
1195 block.sb = factory.CreateSilent(fileLength, mSampleFormat);
1196 }
1197
1198 // blen might be zero for inconsistent Sequence...
1199 if( buffer )
1200 buffer += (blen * SAMPLE_SIZE(format));
1201
1202 len -= blen;
1203 start += blen;
1204
1205 // ... but this, at least, always guarantees some loop progress:
1206 b++;
1207 }
1208
1209 std::copy( mBlock.begin() + b, mBlock.end(), std::back_inserter(newBlock) );
1210
1211 CommitChangesIfConsistent( newBlock, mNumSamples, wxT("SetSamples") );
1212}
1213
1215{
1216 int numBlocks = mBlock.size();
1217 const auto max = GetMaxBlockSize();
1218
1219 if (numBlocks == 0)
1220 return max;
1221
1222 const auto lastBlockLen = mBlock.back().sb->GetSampleCount();
1223 if (lastBlockLen >= max)
1224 return max;
1225 else
1226 return max - lastBlockLen;
1227}
1228
1231 constSamplePtr buffer, sampleFormat format, size_t len)
1232{
1233 return DoAppend( buffer, format, len, false );
1234}
1235
1238{
1239 auto len = pBlock->GetSampleCount();
1240
1241 // Quick check to make sure that it doesn't overflow
1242 if (Overflows(mNumSamples.as_double() + ((double)len)))
1244
1245 BlockArray newBlock;
1246 newBlock.emplace_back( pBlock, mNumSamples );
1247 auto newNumSamples = mNumSamples + len;
1248
1249 AppendBlocksIfConsistent(newBlock, false,
1250 newNumSamples, wxT("Append"));
1251
1252// JKC: During generate we use Append again and again.
1253// If generating a long sequence this test would give O(n^2)
1254// performance - not good!
1255#ifdef VERY_SLOW_CHECKING
1256 ConsistencyCheck(wxT("Append"));
1257#endif
1258}
1259
1262{
1263 DoAppend(buffer, format, len, true);
1264}
1265
1268 constSamplePtr buffer, sampleFormat format, size_t len, bool coalesce)
1269{
1271
1272 if (len == 0)
1273 return result;
1274
1275 auto &factory = *mpFactory;
1276
1277 // Quick check to make sure that it doesn't overflow
1278 if (Overflows(mNumSamples.as_double() + ((double)len)))
1280
1281 BlockArray newBlock;
1282 sampleCount newNumSamples = mNumSamples;
1283
1284 // If the last block is not full, we need to add samples to it
1285 int numBlocks = mBlock.size();
1286 SeqBlock *pLastBlock;
1287 decltype(pLastBlock->sb->GetSampleCount()) length;
1288 size_t bufferSize = mMaxSamples;
1289 SampleBuffer buffer2(bufferSize, mSampleFormat);
1290 bool replaceLast = false;
1291 if (coalesce &&
1292 numBlocks > 0 &&
1293 (length =
1294 (pLastBlock = &mBlock.back())->sb->GetSampleCount()) < mMinSamples) {
1295 // Enlarge a sub-minimum block at the end
1296 const SeqBlock &lastBlock = *pLastBlock;
1297 const auto addLen = std::min(mMaxSamples - length, len);
1298
1299 Read(buffer2.ptr(), mSampleFormat, lastBlock, 0, length, true);
1300
1301 CopySamples(buffer,
1302 format,
1303 buffer2.ptr() + length * SAMPLE_SIZE(mSampleFormat),
1305 addLen);
1306
1307 const auto newLastBlockLen = length + addLen;
1308 SampleBlockPtr pBlock = factory.Create(
1309 buffer2.ptr(),
1310 newLastBlockLen,
1312 SeqBlock newLastBlock(pBlock, lastBlock.start);
1313
1314 newBlock.push_back( newLastBlock );
1315
1316 len -= addLen;
1317 newNumSamples += addLen;
1318 buffer += addLen * SAMPLE_SIZE(format);
1319
1320 replaceLast = true;
1321 }
1322 // Append the rest as NEW blocks
1323 while (len) {
1324 const auto idealSamples = GetIdealBlockSize();
1325 const auto addedLen = std::min(idealSamples, len);
1326 SampleBlockPtr pBlock;
1327 if (format == mSampleFormat) {
1328 pBlock = factory.Create(buffer, addedLen, mSampleFormat);
1329 // It's expected that when not requesting coalescence, the
1330 // data should fit in one block
1331 wxASSERT( coalesce || !result );
1332 result = pBlock;
1333 }
1334 else {
1335 CopySamples(buffer, format, buffer2.ptr(), mSampleFormat, addedLen);
1336 pBlock = factory.Create(buffer2.ptr(), addedLen, mSampleFormat);
1337 }
1338
1339 newBlock.push_back(SeqBlock(pBlock, newNumSamples));
1340
1341 buffer += addedLen * SAMPLE_SIZE(format);
1342 newNumSamples += addedLen;
1343 len -= addedLen;
1344 }
1345
1346 AppendBlocksIfConsistent(newBlock, replaceLast,
1347 newNumSamples, wxT("Append"));
1348
1349// JKC: During generate we use Append again and again.
1350// If generating a long sequence this test would give O(n^2)
1351// performance - not good!
1352#ifdef VERY_SLOW_CHECKING
1353 ConsistencyCheck(wxT("Append"));
1354#endif
1355
1356 return result;
1357}
1358
1360 size_t mMaxSamples, sampleFormat mSampleFormat,
1361 BlockArray &list, sampleCount start,
1362 constSamplePtr buffer, size_t len)
1363{
1364 if (len <= 0)
1365 return;
1366
1367 auto num = (len + (mMaxSamples - 1)) / mMaxSamples;
1368 list.reserve(list.size() + num);
1369
1370 for (decltype(num) i = 0; i < num; i++) {
1371 SeqBlock b;
1372
1373 const auto offset = i * len / num;
1374 b.start = start + offset;
1375 int newLen = ((i + 1) * len / num) - offset;
1376 auto bufStart = buffer + (offset * SAMPLE_SIZE(mSampleFormat));
1377
1378 b.sb = factory.Create(bufStart, newLen, mSampleFormat);
1379
1380 list.push_back(b);
1381 }
1382}
1383
1386{
1387 if (len == 0)
1388 return;
1389
1390 if (len < 0 || start < 0 || start + len > mNumSamples)
1392
1393 auto &factory = *mpFactory;
1394
1395 const unsigned int numBlocks = mBlock.size();
1396
1397 const unsigned int b0 = FindBlock(start);
1398 unsigned int b1 = FindBlock(start + len - 1);
1399
1400 auto sampleSize = SAMPLE_SIZE(mSampleFormat);
1401
1402 SeqBlock *pBlock;
1403 decltype(pBlock->sb->GetSampleCount()) length;
1404
1405 // One buffer for reuse in various branches here
1406 SampleBuffer scratch;
1407 // The maximum size that should ever be needed
1408 auto scratchSize = mMaxSamples + mMinSamples;
1409
1410 // Special case: if the samples to DELETE are all within a single
1411 // block and the resulting length is not too small, perform the
1412 // deletion within this block:
1413 if (b0 == b1 &&
1414 (length = (pBlock = &mBlock[b0])->sb->GetSampleCount()) - len >= mMinSamples) {
1415 SeqBlock &b = *pBlock;
1416 // start is within block
1417 auto pos = ( start - b.start ).as_size_t();
1418
1419 // Guard against failure of this anyway below with limitSampleBufferSize
1420 wxASSERT(len < length);
1421
1422 // len must be less than length
1423 // because start + len - 1 is also in the block...
1424 auto newLen = ( length - limitSampleBufferSize( length, len ) );
1425
1426 scratch.Allocate(scratchSize, mSampleFormat);
1427 ensureSampleBufferSize(scratch, mSampleFormat, scratchSize, newLen);
1428
1429 Read(scratch.ptr(), mSampleFormat, b, 0, pos, true);
1430 Read(scratch.ptr() + (pos * sampleSize), mSampleFormat,
1431 b,
1432 // ... and therefore pos + len
1433 // is not more than the length of the block
1434 ( pos + len ).as_size_t(), newLen - pos, true);
1435
1436 b.sb = factory.Create(scratch.ptr(), newLen, mSampleFormat);
1437
1438 // Don't make a duplicate array. We can still give Strong-guarantee
1439 // if we modify only one block in place.
1440
1441 // use No-fail-guarantee in remaining steps
1442
1443 for (unsigned int j = b0 + 1; j < numBlocks; j++)
1444 mBlock[j].start -= len;
1445
1446 mNumSamples -= len;
1447
1448 // This consistency check won't throw, it asserts.
1449 // Proof that we kept consistency is not hard.
1450 ConsistencyCheck(wxT("Delete - branch one"), false);
1451 return;
1452 }
1453
1454 // Create a NEW array of blocks
1455 BlockArray newBlock;
1456 newBlock.reserve(numBlocks - (b1 - b0) + 2);
1457
1458 // Copy the blocks before the deletion point over to
1459 // the NEW array
1460 newBlock.insert(newBlock.end(), mBlock.begin(), mBlock.begin() + b0);
1461 unsigned int i;
1462
1463 // First grab the samples in block b0 before the deletion point
1464 // into preBuffer. If this is enough samples for its own block,
1465 // or if this would be the first block in the array, write it out.
1466 // Otherwise combine it with the previous block (splitting them
1467 // 50/50 if necessary).
1468 const SeqBlock &preBlock = mBlock[b0];
1469 // start is within preBlock
1470 auto preBufferLen = ( start - preBlock.start ).as_size_t();
1471 if (preBufferLen) {
1472 if (preBufferLen >= mMinSamples || b0 == 0) {
1473 if (!scratch.ptr())
1474 scratch.Allocate(scratchSize, mSampleFormat);
1475 ensureSampleBufferSize(scratch, mSampleFormat, scratchSize, preBufferLen);
1476 Read(scratch.ptr(), mSampleFormat, preBlock, 0, preBufferLen, true);
1477 auto pFile =
1478 factory.Create(scratch.ptr(), preBufferLen, mSampleFormat);
1479
1480 newBlock.push_back(SeqBlock(pFile, preBlock.start));
1481 } else {
1482 const SeqBlock &prepreBlock = mBlock[b0 - 1];
1483 const auto prepreLen = prepreBlock.sb->GetSampleCount();
1484 const auto sum = prepreLen + preBufferLen;
1485
1486 if (!scratch.ptr())
1487 scratch.Allocate(scratchSize, mSampleFormat);
1488 ensureSampleBufferSize(scratch, mSampleFormat, scratchSize,
1489 sum);
1490
1491 Read(scratch.ptr(), mSampleFormat, prepreBlock, 0, prepreLen, true);
1492 Read(scratch.ptr() + prepreLen*sampleSize, mSampleFormat,
1493 preBlock, 0, preBufferLen, true);
1494
1495 newBlock.pop_back();
1497 newBlock, prepreBlock.start, scratch.ptr(), sum);
1498 }
1499 }
1500 else {
1501 // The sample where we begin deletion happens to fall
1502 // right on the beginning of a block.
1503 }
1504
1505 // Now, symmetrically, grab the samples in block b1 after the
1506 // deletion point into postBuffer. If this is enough samples
1507 // for its own block, or if this would be the last block in
1508 // the array, write it out. Otherwise combine it with the
1509 // subsequent block (splitting them 50/50 if necessary).
1510 const SeqBlock &postBlock = mBlock[b1];
1511 // start + len - 1 lies within postBlock
1512 const auto postBufferLen = (
1513 (postBlock.start + postBlock.sb->GetSampleCount()) - (start + len)
1514 ).as_size_t();
1515 if (postBufferLen) {
1516 if (postBufferLen >= mMinSamples || b1 == numBlocks - 1) {
1517 if (!scratch.ptr())
1518 // Last use of scratch, can ask for smaller
1519 scratch.Allocate(postBufferLen, mSampleFormat);
1520 // start + len - 1 lies within postBlock
1521 auto pos = (start + len - postBlock.start).as_size_t();
1522 Read(scratch.ptr(), mSampleFormat, postBlock, pos, postBufferLen, true);
1523 auto file =
1524 factory.Create(scratch.ptr(), postBufferLen, mSampleFormat);
1525
1526 newBlock.push_back(SeqBlock(file, start));
1527 } else {
1528 SeqBlock &postpostBlock = mBlock[b1 + 1];
1529 const auto postpostLen = postpostBlock.sb->GetSampleCount();
1530 const auto sum = postpostLen + postBufferLen;
1531
1532 if (!scratch.ptr())
1533 // Last use of scratch, can ask for smaller
1534 scratch.Allocate(sum, mSampleFormat);
1535 // start + len - 1 lies within postBlock
1536 auto pos = (start + len - postBlock.start).as_size_t();
1537 Read(scratch.ptr(), mSampleFormat, postBlock, pos, postBufferLen, true);
1538 Read(scratch.ptr() + (postBufferLen * sampleSize), mSampleFormat,
1539 postpostBlock, 0, postpostLen, true);
1540
1542 newBlock, start, scratch.ptr(), sum);
1543 b1++;
1544 }
1545 }
1546 else {
1547 // The sample where we begin deletion happens to fall
1548 // right on the end of a block.
1549 }
1550
1551 // Copy the remaining blocks over from the old array
1552 for (i = b1 + 1; i < numBlocks; i++)
1553 newBlock.push_back(mBlock[i].Plus(-len));
1554
1556 (newBlock, mNumSamples - len, wxT("Delete - branch two"));
1557}
1558
1559void Sequence::ConsistencyCheck(const wxChar *whereStr, bool mayThrow) const
1560{
1561 ConsistencyCheck(mBlock, mMaxSamples, 0, mNumSamples, whereStr, mayThrow);
1562}
1563
1565 (const BlockArray &mBlock, size_t maxSamples, size_t from,
1566 sampleCount mNumSamples, const wxChar *whereStr,
1567 bool WXUNUSED(mayThrow))
1568{
1569 // Construction of the exception at the appropriate line of the function
1570 // gives a little more discrimination
1571 std::optional<InconsistencyException> ex;
1572
1573 unsigned int numBlocks = mBlock.size();
1574
1575 unsigned int i;
1576 sampleCount pos = from < numBlocks ? mBlock[from].start : mNumSamples;
1577 if ( from == 0 && pos != 0 )
1579
1580 for (i = from; !ex && i < numBlocks; i++) {
1581 const SeqBlock &seqBlock = mBlock[i];
1582 if (pos != seqBlock.start)
1584
1585 if ( seqBlock.sb ) {
1586 const auto length = seqBlock.sb->GetSampleCount();
1587 if (length > maxSamples)
1589 pos += length;
1590 }
1591 else
1593 }
1594 if ( !ex && pos != mNumSamples )
1596
1597 if ( ex )
1598 {
1599 wxLogError(wxT("*** Consistency check failed at %d after %s. ***"),
1600 ex->GetLine(), whereStr);
1601 wxString str;
1603 wxLogError(wxT("%s"), str);
1604 wxLogError(wxT("*** Please report this error to https://forum.audacityteam.org/. ***\n\n")
1605 wxT("Recommended course of action:\n")
1606 wxT("Undo the failed operation(s), then export or save your work and quit."));
1607
1608 //if (mayThrow)
1609 //throw *ex;
1610 //else
1611 wxASSERT(false);
1612 }
1613}
1614
1616 (BlockArray &newBlock, sampleCount numSamples, const wxChar *whereStr)
1617{
1618 ConsistencyCheck( newBlock, mMaxSamples, 0, numSamples, whereStr ); // may throw
1619
1620 // now commit
1621 // use No-fail-guarantee
1622
1623 mBlock.swap(newBlock);
1624 mNumSamples = numSamples;
1625}
1626
1628(BlockArray &additionalBlocks, bool replaceLast,
1629 sampleCount numSamples, const wxChar *whereStr)
1630{
1631 // Any additional blocks are meant to be appended,
1632 // replacing the final block if there was one.
1633
1634 if (additionalBlocks.empty())
1635 return;
1636
1637 bool tmpValid = false;
1638 SeqBlock tmp;
1639
1640 if ( replaceLast && ! mBlock.empty() ) {
1641 tmp = mBlock.back(), tmpValid = true;
1642 mBlock.pop_back();
1643 }
1644
1645 auto prevSize = mBlock.size();
1646
1647 bool consistent = false;
1648 auto cleanup = finally( [&] {
1649 if ( !consistent ) {
1650 mBlock.resize( prevSize );
1651 if ( tmpValid )
1652 mBlock.push_back( tmp );
1653 }
1654 } );
1655
1656 std::copy( additionalBlocks.begin(), additionalBlocks.end(),
1657 std::back_inserter( mBlock ) );
1658
1659 // Check consistency only of the blocks that were added,
1660 // avoiding quadratic time for repeated checking of repeating appends
1661 ConsistencyCheck( mBlock, mMaxSamples, prevSize, numSamples, whereStr ); // may throw
1662
1663 // now commit
1664 // use No-fail-guarantee
1665
1666 mNumSamples = numSamples;
1667 consistent = true;
1668}
1669
1671 (const BlockArray &mBlock, sampleCount mNumSamples, wxString *dest)
1672{
1673 unsigned int i;
1674 decltype(mNumSamples) pos = 0;
1675
1676 for (i = 0; i < mBlock.size(); i++) {
1677 const SeqBlock &seqBlock = mBlock[i];
1678 *dest += wxString::Format
1679 (wxT(" Block %3u: start %8lld, len %8lld, refs %ld, id %lld"),
1680 i,
1681 seqBlock.start.as_long_long(),
1682 seqBlock.sb ? (long long) seqBlock.sb->GetSampleCount() : 0,
1683 seqBlock.sb ? seqBlock.sb.use_count() : 0,
1684 seqBlock.sb ? (long long) seqBlock.sb->GetBlockID() : 0);
1685
1686 if ((pos != seqBlock.start) || !seqBlock.sb)
1687 *dest += wxT(" ERROR\n");
1688 else
1689 *dest += wxT("\n");
1690
1691 if (seqBlock.sb)
1692 pos += seqBlock.sb->GetSampleCount();
1693 }
1694 if (pos != mNumSamples)
1695 *dest += wxString::Format
1696 (wxT("ERROR mNumSamples = %lld\n"), mNumSamples.as_long_long());
1697}
1698
1699// static
1701{
1702 sMaxDiskBlockSize = bytes;
1703}
1704
1706{
1707 return sMaxDiskBlockSize;
1708}
1709
1710bool Sequence::IsValidSampleFormat(const int nValue)
1711{
1712 return (nValue == int16Sample) || (nValue == int24Sample) || (nValue == floatSample);
1713}
wxT("CloseDown"))
Toolkit-neutral facade for basic user interface services.
int min(int a, int b)
#define str(a)
int format
Definition: ExportPCM.cpp:56
MessageBoxException for violation of preconditions or assertions.
#define CONSTRUCT_INCONSISTENCY_EXCEPTION
Construct InconsistencyException, using C++ preprocessor to identify the source code location.
#define THROW_INCONSISTENCY_EXCEPTION
Throw InconsistencyException, using C++ preprocessor to identify the source code location.
#define XO(s)
Definition: Internat.h:31
std::shared_ptr< SampleBlock > SampleBlockPtr
Definition: SampleBlock.h:26
std::shared_ptr< SampleBlockFactory > SampleBlockFactoryPtr
Definition: SampleBlock.h:28
size_t limitSampleBufferSize(size_t bufferSize, sampleCount limit)
Definition: SampleCount.cpp:22
TranslatableString GetSampleFormatStr(sampleFormat format)
void ClearSamples(samplePtr dst, sampleFormat format, size_t start, size_t len)
void CopySamples(constSamplePtr src, sampleFormat srcFormat, samplePtr dst, sampleFormat dstFormat, size_t len, DitherType ditherType, unsigned int srcStride, unsigned int dstStride)
Copy samples from any format to any other format; apply dithering only if narrowing the format.
sampleFormat
Definition: SampleFormat.h:29
@ floatSample
Definition: SampleFormat.h:34
@ int16Sample
Definition: SampleFormat.h:32
@ int24Sample
Definition: SampleFormat.h:33
char * samplePtr
Definition: SampleFormat.h:49
#define SAMPLE_SIZE(SampleFormat)
Definition: SampleFormat.h:44
const char * constSamplePtr
Definition: SampleFormat.h:50
std::vector< Attribute > AttributesList
Definition: XMLTagHandler.h:40
static wxString ToString(double numberToConvert, int digitsAfterDecimalPoint=-1)
Convert a number to a string, always uses the dot as decimal separator.
Definition: Internat.cpp:151
abstract base class with methods to produce SampleBlock objects
Definition: SampleBlock.h:110
SampleBlockPtr Create(constSamplePtr src, size_t numsamples, sampleFormat srcformat)
Definition: SampleBlock.cpp:26
SampleBuffer & Allocate(size_t count, sampleFormat format)
Definition: SampleFormat.h:96
samplePtr ptr() const
Definition: SampleFormat.h:110
Data structure containing pointer to a sample block and a start time. Element of a BlockArray.
Definition: Sequence.h:28
sampleCount start
the sample in the global wavetrack that this block starts at.
Definition: Sequence.h:33
SampleBlockPtr sb
Definition: Sequence.h:31
std::shared_ptr< SampleBlock > SampleBlockPtr
Definition: Sequence.h:30
A WaveTrack contains WaveClip(s). A WaveClip contains a Sequence. A Sequence is primarily an interfac...
Definition: Sequence.h:61
static void SetMaxDiskBlockSize(size_t bytes)
Definition: Sequence.cpp:1700
void AppendBlocksIfConsistent(BlockArray &additionalBlocks, bool replaceLast, sampleCount numSamples, const wxChar *whereStr)
Definition: Sequence.cpp:1628
void AppendSharedBlock(const SeqBlock::SampleBlockPtr &pBlock)
Append a complete block, not coalescing.
Definition: Sequence.cpp:1237
size_t GetMaxBlockSize() const
Definition: Sequence.cpp:77
size_t GetBestBlockSize(sampleCount start) const
Definition: Sequence.cpp:757
sampleFormat mSampleFormat
Definition: Sequence.h:194
bool ConvertToSampleFormat(sampleFormat format, const std::function< void(size_t)> &progressReport={})
Definition: Sequence.cpp:137
void HandleXMLEndTag(const std::string_view &tag) override
Definition: Sequence.cpp:891
SeqBlock::SampleBlockPtr AppendNewBlock(constSamplePtr buffer, sampleFormat format, size_t len)
Append data, not coalescing blocks, returning a pointer to the new block.
Definition: Sequence.cpp:1230
BlockArray mBlock
Definition: Sequence.h:193
void Delete(sampleCount start, sampleCount len)
Definition: Sequence.cpp:1385
void Paste(sampleCount s0, const Sequence *src)
Definition: Sequence.cpp:476
XMLTagHandler * HandleXMLChild(const std::string_view &tag) override
Definition: Sequence.cpp:933
std::unique_ptr< Sequence > Copy(const SampleBlockFactoryPtr &pFactory, sampleCount s0, sampleCount s1) const
Definition: Sequence.cpp:374
void CommitChangesIfConsistent(BlockArray &newBlock, sampleCount numSamples, const wxChar *whereStr)
Definition: Sequence.cpp:1616
size_t GetIdealBlockSize() const
Definition: Sequence.cpp:82
void ConsistencyCheck(const wxChar *whereStr, bool mayThrow=true) const
Definition: Sequence.cpp:1559
bool Get(samplePtr buffer, sampleFormat format, sampleCount start, size_t len, bool mayThrow) const
Definition: Sequence.cpp:1062
SampleBlockFactoryPtr mpFactory
Definition: Sequence.h:191
static size_t sMaxDiskBlockSize
Definition: Sequence.h:185
void InsertSilence(sampleCount s0, sampleCount len)
Definition: Sequence.cpp:683
void SetSilence(sampleCount s0, sampleCount len)
Definition: Sequence.cpp:677
static void AppendBlock(SampleBlockFactory *pFactory, sampleFormat format, BlockArray &blocks, sampleCount &numSamples, const SeqBlock &b)
Definition: Sequence.cpp:732
static size_t GetMaxDiskBlockSize()
Definition: Sequence.cpp:1705
sampleCount GetBlockStart(sampleCount position) const
Definition: Sequence.cpp:751
sampleFormat GetSampleFormat() const
Definition: Sequence.cpp:95
bool HandleXMLTag(const std::string_view &tag, const AttributesList &attrs) override
Definition: Sequence.cpp:786
static bool IsValidSampleFormat(const int nValue)
true if nValue is one of the sampleFormat enum values
Definition: Sequence.cpp:1710
size_t mMinSamples
Definition: Sequence.h:199
void SetSamples(constSamplePtr buffer, sampleFormat format, sampleCount start, sampleCount len)
Definition: Sequence.cpp:1104
Sequence(const SampleBlockFactoryPtr &pFactory, sampleFormat format)
Definition: Sequence.cpp:51
static bool Read(samplePtr buffer, sampleFormat format, const SeqBlock &b, size_t blockRelativeStart, size_t len, bool mayThrow)
Definition: Sequence.cpp:1041
float GetRMS(sampleCount start, sampleCount len, bool mayThrow) const
Definition: Sequence.cpp:305
static void DebugPrintf(const BlockArray &block, sampleCount numSamples, wxString *dest)
Definition: Sequence.cpp:1671
size_t mMaxSamples
Definition: Sequence.h:200
void Append(constSamplePtr buffer, sampleFormat format, size_t len)
Definition: Sequence.cpp:1261
bool mErrorOpening
Definition: Sequence.h:202
SeqBlock::SampleBlockPtr DoAppend(constSamplePtr buffer, sampleFormat format, size_t len, bool coalesce)
Definition: Sequence.cpp:1267
int FindBlock(sampleCount pos) const
Definition: Sequence.cpp:991
static void Blockify(SampleBlockFactory &factory, size_t maxSamples, sampleFormat format, BlockArray &list, sampleCount start, constSamplePtr buffer, size_t len)
Definition: Sequence.cpp:1359
bool CloseLock()
Definition: Sequence.cpp:87
sampleCount mNumSamples
Definition: Sequence.h:197
void WriteXML(XMLWriter &xmlFile) const
Definition: Sequence.cpp:944
std::pair< float, float > GetMinMax(sampleCount start, sampleCount len, bool mayThrow) const
Definition: Sequence.cpp:223
~Sequence()
Definition: Sequence.cpp:73
size_t GetIdealAppendLen() const
Definition: Sequence.cpp:1214
wxString Debug() const
Format as an English string for debugging logs and developers' eyes, not for end users.
This class is an interface which should be implemented by classes which wish to be able to load and s...
Definition: XMLTagHandler.h:42
Base class for XMLFileWriter and XMLStringWriter that provides the general functionality for creating...
Definition: XMLWriter.h:26
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:19
long long as_long_long() const
Definition: SampleCount.h:48
long long type
Definition: SampleCount.h:21
size_t as_size_t() const
Definition: SampleCount.cpp:16
double as_double() const
Definition: SampleCount.h:46
MessageBoxResult ShowMessageBox(const TranslatableString &message, MessageBoxOptions options={})
Show a modal message box with either Ok or Yes and No, and optionally Cancel.
Definition: BasicUI.h:269
void ensureSampleBufferSize(SampleBuffer &buffer, sampleFormat format, size_t &size, size_t required, SampleBuffer *pSecondBuffer=nullptr)
Definition: Sequence.cpp:112
bool Overflows(double numSamples)
Definition: Sequence.cpp:453
SampleBlockPtr ShareOrCopySampleBlock(SampleBlockFactory *pFactory, sampleFormat format, SampleBlockPtr sb)
Definition: Sequence.cpp:458
static RegisteredToolbarFactory factory
MessageBoxOptions && Caption(TranslatableString caption_) &&
Definition: BasicUI.h:98