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